# # Copyright (c) 2013-2015 The Linux Foundation. All rights reserved. # """ Script to create a U-Boot flashable multi-image blob. This script creates a multi-image blob, from a bunch of images, and adds a U-Boot shell script to the blob, that can flash the images from within U-Boot. The procedure to use this script is listed below. 1. Create an images folder. Ex: my-pack 2. Copy all the images to be flashed into the folder. 3. Copy the partition MBN file into the folder. The file should be named 'partition.mbn'. This is used to determine the offsets for each of the named partitions. 4. Create a flash configuration file, specifying the images be flashed, and the partition in which the images is to be flashed. The flash configuration file can be specified using the -f option, default is flash.conf. 5. Invoke 'pack' with the folder name as argument, pass flash parameters as arguments if required. A single image file will be created, out side the images folder, with .img suffix. Ex: my-pack.img 6. Transfer the file into a valid SDRAM address and invoke the following U-Boot command to flash the images. Replace 0x41000000, with address location where the image has been loaded. The script expects the variable 'imgaddr' to be set. u-boot> imgaddr=0x88000000 source $imgaddr:script Host-side Pre-req * Python >= 2.6 * ordereddict >= 1.1 (for Python 2.6) * mkimage >= 2012.07 * dtc >= 1.2.0 Target-side Pre-req The following U-Boot config macros should be enabled, for the generated flashing script to work. * CONFIG_FIT -- FIT image format support * CONFIG_SYS_HUSH_PARSER -- bash style scripting support * CONFIG_SYS_NULLDEV -- redirecting command output support * CONFIG_CMD_XIMG -- extracting sub-images support * CONFIG_CMD_NAND -- NAND Flash commands support * CONFIG_CMD_NAND_YAFFS -- NAND YAFFS2 write support * CONFIG_CMD_SF -- SPI Flash commands support """ from configparser import ConfigParser from configparser import Error as ConfigParserError from os.path import getsize from getopt import getopt from getopt import GetoptError from collections import namedtuple from string import Template from unittest import TestCase from tempfile import mkdtemp from shutil import rmtree import os import sys import os.path import subprocess import struct import re import hashlib version = "1.1" # # Python 2.6 and earlier did not have OrderedDict use the backport # from ordereddict package. If that is not available report error. # try: from collections import OrderedDict except ImportError: try: from ordereddict import OrderedDict except ImportError: print("error: this script requires the 'ordereddict' class.") print("Try 'pip install --user ordereddict'") print("Or 'easy_install --user ordereddict'") sys.exit(1) __all__ = [] KB = 1024 MB = 1024 * KB def error(msg, ex=None): """Print an error message and exit. msg -- string, the message to print ex -- exception, the associate exception, if any """ sys.stderr.write("pack: %s" % msg) if ex != None: sys.stderr.write(": %s" % str(ex)) sys.stderr.write("\n") sys.exit(1) FlashInfo = namedtuple("FlashInfo", "type pagesize blocksize chipsize") ImageInfo = namedtuple("ProgInfo", "name filename type primary") PartInfo = namedtuple("PartInfo", "name offset length which_flash") def roundup(value, roundto): """Return the next largest multiple of 'roundto'.""" return ((value + roundto - 1) // roundto) * roundto class GPT(object): GPTheader = namedtuple("GPTheader", "signature revision header_size" " crc32 current_lba backup_lba first_usable_lba" " last_usable_lba disk_guid start_lba_part_entry" " num_part_entry part_entry_size part_crc32") GPT_SIGNATURE = 'EFI PART' GPT_REVISION = '\x00\x00\x01\x00' GPT_HEADER_SIZE = 0x5C GPT_HEADER_FMT = "<8s4sLL4xQQQQ16sQLLL" GPTtable = namedtuple("GPTtable", "part_type unique_guid first_lba" " last_lba attribute_flag part_name") GPT_TABLE_FMT = "<16s16sQQQ72s" def __init__(self, filename, pagesize, blocksize, chipsize): self.filename = filename self.pagesize = pagesize self.blocksize = blocksize self.chipsize = chipsize self.__partitions = OrderedDict() def __validate_and_read_parts(self, part_fp): """Validate the GPT and read the partition""" part_fp.seek(self.blocksize, os.SEEK_SET) gptheader_str = part_fp.read(struct.calcsize(GPT.GPT_HEADER_FMT)) gptheader = struct.unpack(GPT.GPT_HEADER_FMT, gptheader_str) gptheader = GPT.GPTheader._make(gptheader) if gptheader.signature != GPT.GPT_SIGNATURE: error("Invalid signature") if gptheader.revision != GPT.GPT_REVISION: error("Unsupported GPT Revision") if gptheader.header_size != GPT.GPT_HEADER_SIZE: error("Invalid Header size") # Adding GPT partition info. This has to be flashed first. # GPT Header starts at LBA1 so (current_lba -1) will give the # starting of primary GPT. # blocksize will equal to gptheader.first_usuable_lba - current_lba + 1 name = "0:GPT" block_start = gptheader.current_lba - 1 block_count = gptheader.first_usable_lba - gptheader.current_lba + 1 which_flash = 0 part_info = PartInfo(name, block_start, block_count, which_flash) self.__partitions[name] = part_info part_fp.seek(2 * self.blocksize, os.SEEK_SET) for i in range(gptheader.num_part_entry): gpt_table_str = part_fp.read(struct.calcsize(GPT.GPT_TABLE_FMT)) gpt_table = struct.unpack(GPT.GPT_TABLE_FMT, gpt_table_str) gpt_table = GPT.GPTtable._make(gpt_table) block_start = gpt_table.first_lba block_count = gpt_table.last_lba - gpt_table.first_lba + 1 part_name = gpt_table.part_name.strip(chr(0)) name = part_name.replace('\0','') part_info = PartInfo(name, block_start, block_count, which_flash) self.__partitions[name] = part_info # Adding the GPT Backup partition. # GPT header backup_lba gives block number where the GPT backup header will be. # GPT Backup header will start from offset of 32 blocks before # the GPTheader.backup_lba. Backup GPT size is 33 blocks. name = "0:GPTBACKUP" block_start = gptheader.backup_lba - 32 block_count = 33 part_info = PartInfo(name, block_start, block_count, which_flash) self.__partitions[name] = part_info def get_parts(self): """Returns a list of partitions present in the GPT.""" try: with open(self.filename, "r") as part_fp: self.__validate_and_read_parts(part_fp) except IOError as e: error("error opening %s" % self.filename, e) return self.__partitions class MIBIB(object): Header = namedtuple("Header", "magic1 magic2 version age") HEADER_FMT = " 0: if yaffs: self.append("nand write.yaffs $fileaddr 0x%08x 0x%08x" % (offset, size)) else: size = roundup(size, self.pagesize) self.append("nand write $fileaddr 0x%08x 0x%08x" % (offset, size)) def switch_layout(self, layout): """Generate code, to switch between sbl/linux layouts.""" self.append("ipq_nand %s" % layout) def probe(self): if self.spi_nand == "true": self.append("nand device 1") else: self.append("nand device 0") class NorScript(FlashScript): """Class for creating NAND flash scripts.""" def __init__(self, flinfo, spi_nand): FlashScript.__init__(self, flinfo) self.spi_nand = spi_nand def erase(self, offset, size): """Generate code, to erase the specified partition.""" size = roundup(size, self.blocksize) self.append("sf erase 0x%08x +0x%08x" % (offset, size)) def write(self, offset, size, yaffs): """Generate code, to write to a partition.""" if size > 0: self.append("sf write $fileaddr 0x%08x 0x%08x" % (offset, size)) def nand_write(self, offset, part_size, img_size): """Handle the NOR + NAND case All binaries upto HLOS will go to NOR and Root FS will go to NAND Assumed all nand page sizes are less than are equal to 8KB """ if self.spi_nand == "true": self.append("nand device 1 && nand erase 0x%08x 0x%08x" % (offset, part_size)) else: self.append("nand device 0 && nand erase 0x%08x 0x%08x" % (offset, part_size)) if img_size > 0: self.append("nand write $fileaddr 0x%08x 0x%08x" % (offset, img_size)) def probe(self): self.append("sf probe") def switch_layout(self, layout): pass class EmmcScript(FlashScript): """Class for creating EMMC scripts.""" def __init__(self, flinfo): FlashScript.__init__(self, flinfo) def erase(self, offset, size): """Generate code, to erase the specified partition.""" self.append("mmc erase 0x%08x %x" % (offset, size)) def write(self, offset, size, yaffs): """Generate code, to write to a partition.""" if size > 0: size = roundup(size, self.blocksize) blk_cnt = size / self.blocksize self.append("mmc write $fileaddr 0x%08x %x" % (offset, blk_cnt)) def switch_layout(self, layout): pass def probe(self): pass its_tmpl = Template(""" /dts-v1/; / { description = "${desc}"; images { ${images} }; }; """) its_image_tmpl = Template(""" ${name} { description = "${desc}"; data = /incbin/("${fname}"); type = "${imtype}"; arch = "arm"; compression = "none"; hash@1 { algo = "crc32"; }; }; """) def sha1(message): """Returns SHA1 digest in hex format of the message.""" m = hashlib.sha1() m.update(message.encode()) return m.hexdigest() class Pack(object): """Class to create a flashable, multi-image blob. Combine multiple images present in a directory, and generate a U-Boot script to flash the images. """ # The maximum rootfs size is 64MB norplusnand_rootfs_img_size = (64 * 1024 * 1024) def __init__(self): self.flinfo = None self.images_dname = None self.ipq_nand = None self.spi_nand = "false" self.partitions = {} self.fconf_fname = None self.scr_fname = None self.its_fname = None self.img_fname = None def __get_yaffs(self, info, section): """Get the yaffs flag for a section. info -- ConfigParser object, containing image flashing info section -- section to check if yaffs flag is set """ try: yaffs = info.get(section, "yaffs") if yaffs.lower() in ["0", "no"]: yaffs = False elif yaffs.lower() in ["1", "yes"]: yaffs = True else: error("invalid value for 'yaffs' in '%s'" % section) except ConfigParserError as e: yaffs = False if self.flinfo.type == "nor" and yaffs == True: error("yaffs cannot be used with NOR flash type") return yaffs def __get_layout(self, info, section): """Get the layout for a section. info -- ConfigParser object, containing image flashing info section - section to retreive the layout from """ try: layout = info.get(section, "layout") except ConfigParserError as e: layout = None if self.ipq_nand and layout == None: error("layout not specified for IPQ device") if not self.ipq_nand and layout != None: error("layout specified for a non IPQ device") if layout not in ("sbl", "linux", None): error("invalid layout in '%s'" % section) return layout def __get_machid(self, info, section): """Get the machid for a section. info -- ConfigParser object, containing image flashing info section -- section to retreive the machid from """ try: machid = info.get(section, "if_machid") machid = int(machid, 0) machid = "%x" % machid except ConfigParserError as e: machid = None except ValueError as e: error("invalid value for machid, should be integer") return machid def __get_img_size(self, filename): """Get the size of the image to be flashed filaneme -- string, filename of the image to be flashed """ if filename.lower() == "none": return 0 try: return getsize(os.path.join(self.images_dname, filename)) except OSError as e: error("error getting image size '%s'" % filename, e) def __get_part_info(self, partition): """Return partition info for the specified partition. partition -- string, partition name """ try: return self.partitions[partition] except KeyError as e: return None def __gen_flash_script(self, info, script, flinfo): """Generate the script to flash the images. info -- ConfigParser object, containing image flashing info script -- Script object, to append commands to """ count = 0 for section in info.sections(): try: filename = info.get(section, "filename") partition = info.get(section, "partition") include = info.get(section, "include") except ConfigParserError as e: error("error getting image info in section '%s'" % section, e) try: primary = info.get(section, "primary") except ConfigParserError as e: primary = "0" if include.lower() in ["0", "no"]: continue machid = self.__get_machid(info, section) layout = self.__get_layout(info, section) yaffs = self.__get_yaffs(info, section) img_size = self.__get_img_size(filename) part_info = self.__get_part_info(partition) if self.flinfo.type == 'nand': size = roundup(img_size, flinfo.pagesize) tr = ' | tr \"\\000\" \"\\377\"' if self.flinfo.type == 'emmc': size = roundup(img_size, flinfo.blocksize) tr = '' if ((self.flinfo.type == 'nand' or self.flinfo.type == 'emmc') and (size != img_size)): pad_size = size - img_size filename_abs = os.path.join(self.images_dname, filename) filename_abs_pad = filename_abs + ".padded" cmd = 'cat %s > %s' % (filename_abs, filename_abs_pad) ret = subprocess.call(cmd, shell=True) if ret != 0: error("failed to copy image") cmd = 'dd if=/dev/zero count=1 bs=%s %s >> %s' % (pad_size, tr, filename_abs_pad) cmd = '(' + cmd + ') 1>/dev/null 2>/dev/null' ret = subprocess.call(cmd, shell=True) if ret != 0: error("failed to create padded image from script") if self.flinfo.type != "emmc": if part_info == None: if self.flinfo.type == 'norplusnand': if count > 2: error("More than 2 NAND images for NOR+NAND is not allowed") elif img_size > part_info.length: error("img size is larger than part. len in '%s'" % section) else: if part_info != None: if (img_size > 0): if img_size > (part_info.length * self.flinfo.blocksize): error("img size is larger than part. len in '%s'" % section) if part_info == None and self.flinfo.type != 'norplusnand': continue if machid: script.start_if("machid", machid) script.start_activity("Flashing %s:" % section) if self.ipq_nand: script.switch_layout(layout) if img_size > 0: if primary != "0": section = primary if ((self.flinfo.type == 'nand' or self.flinfo.type == 'emmc') and (size != img_size)): filename_pad = filename + ".padded" script.imxtract(section + "-" + sha1(filename_pad)) else: script.imxtract(section + "-" + sha1(filename)) if part_info == None: if self.flinfo.type == 'norplusnand': offset = count * Pack.norplusnand_rootfs_img_size part_size = Pack.norplusnand_rootfs_img_size script.nand_write(offset, part_size, img_size) count = count + 1 else: if part_info.which_flash == 0: offset = part_info.offset script.erase(offset, part_info.length) script.write(offset, img_size, yaffs) else: offset = part_info.offset script.nand_write(offset, part_info.length, img_size) script.finish_activity() if machid: script.end_if() if part_info == None and self.flinfo.type != 'norplusnand': print("Flash type is norplusemmc") else: script.end() def __gen_script(self, script_fp, info_fp, script, images, flinfo): """Generate the script to flash the multi-image blob. script_fp -- file object, to write script to info_fp -- file object, to read flashing information from script -- Script object, to append the commands to images -- list of ImageInfo, appended to, based on images in config """ try: info = ConfigParser({"include": "yes"}) info.read_file(info_fp) except ConfigParserError as e: error("error parsing info file '%s'" % self.fconf_fname, e) self.__gen_flash_script(info, script, flinfo) for section in info.sections(): if info.get(section, "include").lower() in ["0", "no"]: continue partition = info.get(section, "partition") part_info = self.__get_part_info(partition) if part_info == None and self.flinfo.type != 'norplusnand': continue filename = info.get(section, "filename") if self.flinfo.type == 'nand': img_size = self.__get_img_size(filename) size = roundup(img_size, flinfo.pagesize) if ( size != img_size ): filename = filename + ".padded" if self.flinfo.type == 'emmc': img_size = self.__get_img_size(filename) size = roundup(img_size, flinfo.blocksize) if ( size != img_size ): filename = filename + ".padded" try: primary = info.get(section, "primary") except ConfigParserError as e: primary = "0" if primary != "0": print("primary: %s" % primary) image_info = ImageInfo(section + "-" + sha1(filename), filename, "firmware", primary) if filename.lower() != "none": if image_info not in images: images.append(image_info) def __its_escape(self, string): """Return string with ITS special characters escaped. string -- string to be escape. String in ITS files, consider 'slash' as special character. Escape them by prefixing them with a slash. """ return string.replace("\\", "\\\\") def __mkimage(self, images): """Create the multi-image blob. images -- list of ImageInfo, containing images to be part of the blob """ try: its_fp = open(self.its_fname, "w") except IOError as e: error("error opening its file '%s'" % self.its_fname, e) desc = "Flashing %s %x %x" desc = desc % (self.flinfo.type, self.flinfo.pagesize, self.flinfo.blocksize) image_data = [] for (section, fname, imtype, primary) in images: if primary != "0": continue fname = self.__its_escape(fname) subs = dict(name=section, desc=fname, fname=fname, imtype=imtype) image_data.append(its_image_tmpl.substitute(subs)) image_data = "".join(image_data) its_data = its_tmpl.substitute(desc=desc, images=image_data) its_fp.write(its_data) its_fp.close() try: cmd = ["mkimage", "-f", self.its_fname, self.img_fname] ret = subprocess.call(cmd) if ret != 0: error("failed to create u-boot image from script") except OSError as e: error("error executing mkimage", e) def __create_fnames(self): """Populate the filenames.""" self.scr_fname = os.path.join(self.images_dname, "flash.scr") self.its_fname = os.path.join(self.images_dname, "flash.its") def __gen_board_script(self, board_section, machid, flinfo, part_fname, fconf_fname, images): """Generate the flashing script for one board. board_section -- string, board section in board config file machid -- string, board machine ID in hex format flinfo -- FlashInfo object, contains board specific flash params part_fname -- string, partition file specific to the board fconf_fname -- string, flash config file specific to the board images -- list of ImageInfo, append images used by the board here """ script_fp = open(self.scr_fname, "a") try: fconf_fp = open(fconf_fname) except IOError as e: error("error opening flash config file '%s'" % fconf_fname, e) if flinfo.type != "emmc": nand_pagesize = int(self.bconf.get(board_section, "nand_pagesize")) nand_pages_per_block = int(self.bconf.get(board_section, "nand_pages_per_block")) nand_blocks_per_chip = int(self.bconf.get(board_section, "nand_total_blocks")) nand_blocksize = nand_pages_per_block * nand_pagesize nand_chipsize = nand_blocks_per_chip * nand_blocksize mibib = MIBIB(part_fname, flinfo.pagesize, flinfo.blocksize, flinfo.chipsize, nand_blocksize, nand_chipsize) self.partitions = mibib.get_parts() else: gpt = GPT(part_fname, flinfo.pagesize, flinfo.blocksize, flinfo.chipsize) self.partitions = gpt.get_parts() self.flinfo = flinfo if flinfo.type == "nand": self.ipq_nand = False script = NandScript(flinfo, self.ipq_nand, self.spi_nand) elif flinfo.type == "nor" or flinfo.type == "norplusnand": self.spi_nand = self.bconf.get(board_section, "spi_nand_available") self.ipq_nand = False script = NorScript(flinfo, self.spi_nand) elif flinfo.type == "emmc": self.ipq_nand = False script = EmmcScript(flinfo) else: error("error, flash type unspecified.") script.start_if("machid", machid) script.probe() self.__gen_script(script_fp, fconf_fp, script, images, flinfo) script.end_if() try: script_fp.write(script.dumps()) except IOError as e: error("error writing to script '%s'" % script_fp.name, e) script_fp.close() def __process_board_flash_emmc(self, ftype, board_section, machid, images): """Extract board info from config and generate the flash script. ftype -- string, flash type 'emmc' board_section -- string, board section in config file machid -- string, board machine ID in hex format images -- list of ImageInfo, append images used by the board here """ pagesize_param = "%s_pagesize" % ftype blocksize_param = "%s_blocksize" % "emmc" blocks_per_chip_param = "%s_total_blocks" % "emmc" part_fname_param = "%s_partition_mbn" % ftype fconf_fname_param = "%s_flash_conf" % ftype if ftype == "norplusemmc": part_fname_param = "%s_gpt_bin" % ftype try: pagesize = int(self.bconf.get(board_section, pagesize_param)) blocksize = int(self.bconf.get(board_section, blocksize_param)) chipsize = int(self.bconf.get(board_section, blocks_per_chip_param)) except ConfigParserError as e: error("missing flash info in section '%s'" % board_section, e) except ValueError as e: error("invalid flash info in section '%s'" % board_section, e) if ftype == "norplusemmc": ftype = "emmc" flinfo = FlashInfo(ftype, pagesize, blocksize, chipsize) try: part_fname = self.bconf.get(board_section, part_fname_param) if not os.path.isabs(part_fname): part_fname = os.path.join(self.images_dname, part_fname) except ConfigParserError as e: error("missing partition file in section '%s'" % board_section, e) try: fconf_fname = self.bconf.get(board_section, fconf_fname_param) if not os.path.isabs(fconf_fname): fconf_fname = os.path.join(self.images_dname, fconf_fname) except ConfigParserError as e: error("missing NAND config in section '%s'" % board_section, e) self.__gen_board_script(board_section, machid, flinfo, part_fname, fconf_fname, images) def __process_board_flash(self, ftype, board_section, machid, images): """Extract board info from config and generate the flash script. ftype -- string, flash type 'nand' or 'nor' or 'emmc' or 'norplusnand' board_section -- string, board section in config file machid -- string, board machine ID in hex format images -- list of ImageInfo, append images used by the board here """ pagesize_param = "%s_pagesize" % ftype pages_per_block_param = "%s_pages_per_block" % ftype blocks_per_chip_param = "%s_total_blocks" % ftype part_fname_param = "%s_partition_mbn" % ftype fconf_fname_param = "%s_flash_conf" % ftype if ftype == "norplusnand" or ftype == "norplusemmc": pagesize_param = "%s_pagesize" % "nor" pages_per_block_param = "%s_pages_per_block" % "nor" blocks_per_chip_param = "%s_total_blocks" % "nor" try: pagesize = int(self.bconf.get(board_section, pagesize_param)) pages_per_block = int(self.bconf.get(board_section, pages_per_block_param)) blocks_per_chip = int(self.bconf.get(board_section, blocks_per_chip_param)) except ConfigParserError as e: error("missing flash info in section '%s'" % board_section, e) except ValueError as e: error("invalid flash info in section '%s'" % board_section, e) blocksize = pages_per_block * pagesize chipsize = blocks_per_chip * blocksize if ftype == "norplusemmc": ftype = "nor" flinfo = FlashInfo(ftype, pagesize, blocksize, chipsize) try: part_fname = self.bconf.get(board_section, part_fname_param) if not os.path.isabs(part_fname): part_fname = os.path.join(self.images_dname, part_fname) except ConfigParserError as e: error("missing partition file in section '%s'" % board_section, e) try: fconf_fname = self.bconf.get(board_section, fconf_fname_param) if not os.path.isabs(fconf_fname): fconf_fname = os.path.join(self.images_dname, fconf_fname) except ConfigParserError as e: error("missing NAND config in section '%s'" % board_section, e) self.__gen_board_script(board_section, machid, flinfo, part_fname, fconf_fname, images) def __process_board(self, board_section, images): try: machid = int(self.bconf.get(board_section, "machid"), 0) machid = "%x" % machid except ConfigParserError as e: error("missing machid in section '%s'" % board_section, e) except ValueError as e: error("invalid machid in section '%s'" % board_section, e) try: available = self.bconf.get(board_section, "nand_available") if available == "true" and self.flash_type == "nand": self.__process_board_flash("nand", board_section, machid, images) except ConfigParserError as e: error("error getting board info in section '%s'" % board_section, e) try: available = self.bconf.get(board_section, "nor_available") if available == "true" and self.flash_type == "nor": self.__process_board_flash("nor", board_section, machid, images) except ConfigParserError as e: error("error getting board info in section '%s'" % board_section, e) try: available = self.bconf.get(board_section, "emmc_available") if available == "true" and self.flash_type == "emmc": self.__process_board_flash_emmc("emmc", board_section, machid, images) except ConfigParserError as e: error("error getting board info in section '%s'" % board_section, e) try: available = self.bconf.get(board_section, "norplusnand_available") if available == "true" and self.flash_type == "norplusnand": self.__process_board_flash("norplusnand", board_section, machid, images) except ConfigParserError as e: error("error getting board info in section '%s'" % board_section, e) try: available = self.bconf.get(board_section, "norplusemmc_available") if available == "true" and self.flash_type == "norplusemmc": self.__process_board_flash("norplusemmc", board_section, machid, images) self.__process_board_flash_emmc("norplusemmc", board_section, machid, images) except ConfigParserError as e: error("error getting board info in section '%s'" % board_section, e) def main_bconf(self, flash_type, images_dname, out_fname, brdconfig): """Start the packing process, using board config. flash_type -- string, indicates flash type, 'nand' or 'nor' or 'emmc' or 'norplusnand' images_dname -- string, name of images directory out_fname -- string, output file path """ self.flash_type = flash_type self.images_dname = images_dname self.img_fname = out_fname self.__create_fnames() try: os.unlink(self.scr_fname) except OSError as e: pass try: bconf_fname = os.path.join(images_dname, brdconfig) bconf_fp = open(bconf_fname) self.bconf = ConfigParser() self.bconf.read_file(bconf_fp) bconf_fp.close() except IOError as e: error("error reading board configuration file", e) except ConfigParserError as e: error("error parsing board configuration file", e) images = [] for section in self.bconf.sections(): self.__process_board(section, images) images.insert(0, ImageInfo("script", "flash.scr", "script","0")) self.__mkimage(images) def main(self, flinfo, images_dname, out_fname, part_fname, fconf_fname, ipq_nand): """Start the packing process. flinfo -- FlashInfo object, containing flash parameters images_dname -- string, name of images directory out_fname -- string, output file path part_fname -- string, partition file path fconf_fname -- string, flash confing file path ipq_nand -- bool, indicates whether this is an IPQ device """ self.flinfo = flinfo self.images_dname = images_dname self.img_fname = out_fname self.ipq_nand = ipq_nand self.__create_fnames() if self.flinfo.type == "nand": script = NandScript(self.flinfo, self.ipq_nand) elif self.flinfo.type == "nor" or self.flinfo.type == "norplusnand": script = NorScript(self.flinfo) elif self.flinfo.type == "emmc": script = EmmcScript(self.flinfo) else: error("Invalid flash type specified. It should be 'nand' or 'nor' or 'norplusnand'") if not os.path.isabs(part_fname): part_fname = os.path.join(self.images_dname, part_fname) if not os.path.isabs(fconf_fname): self.fconf_fname = os.path.join(self.images_dname, fconf_fname) nand_pagesize = int(self.bconf.get(board_section, "nand_pagesize")) nand_pages_per_block = int(self.bconf.get(board_section, "nand_pages_per_block")) nand_blocks_per_chip = int(self.bconf.get(board_section, "nand_total_blocks")) nand_blocksize = nand_pages_per_block * nand_pagesize nand_chipsize = nand_blocks_per_chip * nand_blocksize mibib = MIBIB(part_fname, self.flinfo.pagesize, self.flinfo.blocksize, self.flinfo.chipsize, nand_blocksize, nand_chipsize) self.partitions = mibib.get_parts() script.echo("", verbose=True) script.start_activity("Check environment:") script.check_isset("imgaddr") script.check_isset("machid") script.finish_activity() try: info_fp = open(self.fconf_fname) except IOError as e: error("error opening info file '%s'" % self.fconf_fname, e) try: scr_fp = open(self.scr_fname, "wb") except IOError as e: error("error opening script file '%s'" % self.scr_fname, e) images = [] self.__gen_script(scr_fp, info_fp, script, images) try: scr_fp.write(script.dumps()) except IOError as e: error("error writing to script '%s'" % script_fp.name, e) scr_fp.close() # Flush out all written commands images.insert(0, ImageInfo("script", "flash.scr", "script", "0")) self.__mkimage(images) class UsageError(Exception): """Indicates error in command arguments.""" pass class ArgParser(object): """Class to parse command-line arguments.""" DEFAULT_PAGESIZE = 4096 DEFAULT_PAGES_PER_BLOCK = 64 DEFAULT_BLOCKS_PER_CHIP = 1024 DEFAULT_TYPE = "nand" DEFAULT_PART_FNAME = "partition.mbn" DEFAULT_FCONF_FNAME = "flash.conf" def __init__(self): self.__pagesize = None self.__pages_per_block = None self.__blocksize = None self.__chipsize = None self.__flash_type = None self.flash_info = None self.images_dname = None self.ipq_nand = False self.bconf = False self.bconf_fname = "boardconfig" self.part_fname = None self.fconf_fname = None def __init_pagesize(self, pagesize): """Set the pagesize, from the command line argument. pagesize -- string, flash page size Raise UsageError, if pagesize is invalid """ if pagesize == None: self.__pagesize = ArgParser.DEFAULT_PAGESIZE else: try: self.__pagesize = int(pagesize) except ValueError: raise UsageError("invalid page size '%s'" % pagesize) def __init_blocksize(self, pages_per_block): """Set the blocksize, from the command line argument. pages_per_block -- string, no. of pages in a flash block Raise UsageError, if pages_per_block is invalid """ if pages_per_block == None: self.__blocksize = (self.__pagesize * ArgParser.DEFAULT_PAGES_PER_BLOCK) else: try: self.__blocksize = self.__pagesize * int(pages_per_block) except ValueError: raise UsageError("invalid block size '%s'" % self.__blocksize) def __init_chipsize(self, blocks_per_chip): """Set the chipsize, from the command line argument. blocks_per_chip -- string, no. of blocks in a flash chip Raise UsageError, if chips_per_block is invalid """ if blocks_per_chip == None: self.__chipsize = (self.__blocksize * ArgParser.DEFAULT_BLOCKS_PER_CHIP) else: try: self.__chipsize = self.__blocksize * int(blocks_per_chip) except ValueError: raise UsageError("invalid chip size '%s'" % self.__chipsize) def __init_flash_info(self): """Set flash_info from the parsed flash paramaters.""" self.flash_info = FlashInfo(self.__flash_type, self.__pagesize, self.__blocksize, self.__chipsize) def __init_flash_type(self, flash_type): """Set the flash_type, from the command line argument. flash_type -- string, nand or nor Raise UsageError, if flash_type is invalid """ if flash_type == None: self.__flash_type = ArgParser.DEFAULT_TYPE elif flash_type in [ "nand", "nor", "emmc", "norplusnand", "norplusemmc" ]: self.__flash_type = flash_type else: raise UsageError("invalid flash type '%s'" % flash_type) def __init_part_fname(self, part_fname): """Set the partition filename from command line argument part_fname -- string, the partition filename """ if part_fname == None: self.part_fname = ArgParser.DEFAULT_PART_FNAME else: self.part_fname = part_fname def __init_fconf_fname(self, fconf_fname): """Set the flash configuration filename from command line argument fconf_fname -- string, the flash configuration filename """ if fconf_fname == None: self.fconf_fname = ArgParser.DEFAULT_FCONF_FNAME else: self.fconf_fname = fconf_fname def __init_out_fname(self, out_fname, images_dname, flash_type, pagesize, pages_per_block, blocks_per_chip, bconf): """Set the out_fname from the command line argument. out_fname -- string, the output filename images_dname -- string, the images dirname flash_type -- string, the flash type pagesize -- int, the flash page size in bytes pages_per_block -- int, the flash pages per block blocks_per_chip -- int, the flash blocks per chip bconf -- bool, whether is board config mode """ if out_fname == None: images_dname_norm = os.path.abspath(images_dname) if bconf: fmt = "%s-%s%simg" self.out_fname = fmt % (images_dname_norm, flash_type, os.path.extsep) else: fmt = "%s-%s-%d-%d-%d%simg" self.out_fname = fmt % (images_dname_norm, flash_type, pagesize, pages_per_block, blocks_per_chip, os.path.extsep) else: if os.path.isabs(out_fname): self.out_fname = out_fname else: images_dname_parent = os.path.dirname(images_dname) self.out_fname = os.path.join(images_dname_parent, out_fname) def __init_images_dname(self, args): """Set the images_dname from the command line argument. args -- list of string, command line args after stripping options """ self.images_dname = args[0] def parse(self, argv): """Start the parsing process, and populate members with parsed value. argv -- list of string, the command line arguments """ flash_type = None pagesize = None pages_per_block = None blocks_per_chip = None ipq_nand = False out_fname = None part_fname = None fconf_fname = None bconf = False bconf_fname = None try: opts, args = getopt(argv[1:], "Bib:hp:t:o:c:m:f:F:") except GetoptError as e: raise UsageError(e.msg) for option, value in opts: if option == "-t": flash_type = value elif option == "-i": ipq_nand = True elif option == "-p": pagesize = value elif option == "-b": pages_per_block = value elif option == '-c': blocks_per_chip = value elif option == "-o": out_fname = value elif option == "-m": part_fname = value elif option == "-f": fconf_fname = value elif option == "-B": bconf = True elif option == "-F": bconf_fname = value if len(args) != 1: raise UsageError("insufficient arguments") self.__init_flash_type(flash_type) self.__init_pagesize(pagesize) self.__init_blocksize(pages_per_block) self.__init_chipsize(blocks_per_chip) self.__init_flash_info() self.__init_images_dname(args) self.__init_out_fname(out_fname, self.images_dname, self.__flash_type, self.__pagesize, self.__blocksize / self.__pagesize, self.__chipsize / self.__blocksize, bconf) self.__init_part_fname(part_fname) self.__init_fconf_fname(fconf_fname) self.ipq_nand = ipq_nand self.bconf = bconf if bconf_fname != None: self.bconf_fname = bconf_fname def usage(self, msg): """Print error message and command usage information. msg -- string, the error message """ print("pack: %s" % msg) print() print("Usage: pack [options] IDIR") print() print("where IDIR is the path containing the images.") print() print(" -t TYPE specifies partition type, 'nand' or 'nor',") print(" default is '%s'." % ArgParser.DEFAULT_TYPE) print(" -p SIZE specifies the page size in bytes,") print(" default is %d." % ArgParser.DEFAULT_PAGESIZE) print(" -b COUNT specifies the pages per block,") print(" default is %d." % ArgParser.DEFAULT_PAGES_PER_BLOCK) print(" -c COUNT specifies the no. of blocks per chip") print(" default is %d." % ArgParser.DEFAULT_BLOCKS_PER_CHIP) print(" -i specifies IPQ processor specific NAND layout") print(" switch, default disabled.") print(" -m FILE specifies the partition filename") print(" default is '%s'." % ArgParser.DEFAULT_PART_FNAME) print(" -f FILE specifies the flash configuration filename") print(" default is '%s'." % ArgParser.DEFAULT_FCONF_FNAME) print(" -o FILE specifies the output filename") print(" default is IDIR-TYPE-SIZE-COUNT.img") print(" if the filename is relative, it is relative") print(" to the parent of IDIR.") print() print("NOTE: The above invocation method of pack is deprecated.") print("The new pack invocation uses a board config file to retrieve") print("the board specific parameters. The new invocation is provided") print("below.") print() print("Usage: pack [options] IDIR") print() print("where IDIR is the path containing the images.") print() print(" -t TYPE specifies partition type, 'nand' or 'nor', or 'emmc'") print(" default is '%s'." % ArgParser.DEFAULT_TYPE) print(" -B specifies board config should be used, for") print(" flash parameters.") print(" -F specifies board config file name should be used, for") print(" flash parameters. Defaults to 'boardconfig'") print(" -o FILE specifies the output filename") print(" default is IDIR-TYPE.img") print(" if the filename is relative, it is relative") print(" to the parent of IDIR.") print() print("Pack Version: %s" % version) def main(): """Main script entry point. Created to avoid polluting the global namespace. """ try: parser = ArgParser() parser.parse(sys.argv) except UsageError as e: parser.usage(e.args[0]) sys.exit(1) pack = Pack() if parser.bconf: pack.main_bconf(parser.flash_info.type, parser.images_dname, parser.out_fname, parser.bconf_fname) else: pack.main(parser.flash_info, parser.images_dname, parser.out_fname, parser.part_fname, parser.fconf_fname, parser.ipq_nand) class ArgParserTestCase(TestCase): def setUp(self): self.parser = ArgParser() def test_defaults(self): self.parser.parse(["pack.py", "itest"]) self.assertEqual(self.parser.images_dname, "itest") fmt = "itest-%s-%d-%d-%d.img" expected_fname = fmt % (ArgParser.DEFAULT_TYPE, ArgParser.DEFAULT_PAGESIZE, ArgParser.DEFAULT_PAGES_PER_BLOCK, ArgParser.DEFAULT_BLOCKS_PER_CHIP) self.assertEqual(self.parser.out_fname, expected_fname) self.assertEqual(self.parser.ipq_nand, False) self.assertEqual(self.parser.flash_info.type, ArgParser.DEFAULT_TYPE) self.assertEqual(self.parser.flash_info.pagesize, ArgParser.DEFAULT_PAGESIZE) self.assertEqual(self.parser.flash_info.blocksize, ArgParser.DEFAULT_PAGES_PER_BLOCK * ArgParser.DEFAULT_PAGESIZE) self.assertEqual(self.parser.flash_info.chipsize, ArgParser.DEFAULT_BLOCKS_PER_CHIP * ArgParser.DEFAULT_PAGES_PER_BLOCK * ArgParser.DEFAULT_PAGESIZE) def test_ipq_flag(self): self.parser.parse(["pack.py", "-i", "itest"]) self.assertEqual(self.parser.ipq_nand, True) def test_invalid_flag(self): self.assertRaises(UsageError, self.parser.parse, ["pack.py", "-x", "itest"]) def test_type_option(self): self.parser.parse(["pack.py", "-t", "nor", "itest"]) self.assertEqual(self.parser.flash_info.type, "nor") def test_invalid_type_option(self): self.assertRaises(UsageError, self.parser.parse, ["pack.py", "-t", "abcd", "itest"]) def test_pagesize_option(self): self.parser.parse(["pack.py", "-p", "2048", "itest"]) self.assertEqual(self.parser.flash_info.pagesize, 2048) def test_invalid_pagesize_option(self): self.assertRaises(UsageError, self.parser.parse, ["pack.py", "-p", "abcd", "itest"]) def test_pages_per_block_option(self): self.parser.parse(["pack.py", "-b", "32", "itest"]) self.assertEqual(self.parser.flash_info.blocksize, ArgParser.DEFAULT_PAGESIZE * 32) def test_invalid_pages_per_block_option(self): self.assertRaises(UsageError, self.parser.parse, ["pack.py", "-b", "abcd", "itest"]) def test_blocks_per_chip_option(self): self.parser.parse(["pack.py", "-c", "512", "itest"]) self.assertEqual(self.parser.flash_info.chipsize, ArgParser.DEFAULT_PAGESIZE * ArgParser.DEFAULT_PAGES_PER_BLOCK * 512) def test_out_fname_rel_option(self): self.parser.parse(["pack.py", "-o", "abcd", "/tmp/test/itest"]) self.assertEqual(self.parser.out_fname, "/tmp/test/abcd") def test_out_fname_abs_option(self): self.parser.parse(["pack.py", "-o", "/tmp/abcd", "/tmp/test/itest"]) self.assertEqual(self.parser.out_fname, "/tmp/abcd") def test_partition_option(self): self.parser.parse(["pack.py", "-m", "abcd", "/tmp/test/itest"]) self.assertEqual(self.parser.part_fname, "abcd") def test_flash_conf_option(self): self.parser.parse(["pack.py", "-f", "abcd", "/tmp/test/itest"]) self.assertEqual(self.parser.fconf_fname, "abcd") class PackTestCase(TestCase): def setUp(self): self.pack = Pack() blocksize = (ArgParser.DEFAULT_PAGESIZE * ArgParser.DEFAULT_PAGES_PER_BLOCK) chipsize = blocksize * ArgParser.DEFAULT_BLOCKS_PER_CHIP self.flinfo = FlashInfo(ArgParser.DEFAULT_TYPE, ArgParser.DEFAULT_PAGESIZE, blocksize, chipsize) self.img_dname = mkdtemp() self.img_fname = self.img_dname + ".img" self.part_fname = "partition.mbn" self.fconf_fname = "flash.conf" sbl1_fp = open(os.path.join(self.img_dname, "sbl1.mbn"), "w") sbl1_fp.write("#" * blocksize * 2) sbl1_fp.close() self.__create_partition_mbn(blocksize, chipsize) def __create_partition_mbn(self, blocksize, chipsize): part_fname = os.path.join(self.img_dname, self.part_fname) mibib = MIBIB(part_fname, ArgParser.DEFAULT_PAGESIZE, blocksize, chipsize) offset = 0 part_size = 2 * blocksize mibib.add_part(PartInfo("0:SBL1", offset, part_size)) offset += part_size part_size = 2 * blocksize mibib.add_part(PartInfo("0:MIBIB", offset, part_size)) offset += part_size part_size = 1 * blocksize mibib.add_part(PartInfo("0:SBL2", offset, part_size)) offset += part_size part_size = None mibib.add_part(PartInfo("0:FS", offset, part_size)) mibib.write() def __mkconf(self, conf_str): conf_fname = os.path.join(self.img_dname, self.fconf_fname) conf_fp = open(conf_fname, "w") conf_fp.write(conf_str) conf_fp.close() def tearDown(self): rmtree(self.img_dname) try: os.remove(self.img_fname) except OSError: pass def test_simple(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) self.assertEqual(os.path.exists(self.img_fname), True) def test_missing_conf(self): self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_nand_layout(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn layout = sbl """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=True) self.assertEqual(os.path.exists(self.img_fname), True) def test_invalid_layout(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn layout = abcd """) self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=True) def test_inconsistent_layout(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn layout = sbl """) self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_invalid_filename(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl10.mbn """) self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_special_chars_in_filename(self): self.__mkconf(""" [slash] partition = 0:SBL1 filename = sb\\l1.mbn """) sbl1_fp = open(os.path.join(self.img_dname, "sb\\l1.mbn"), "w") sbl1_fp.write("abcdef") sbl1_fp.close() self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) self.assertEqual(os.path.exists(self.img_fname), True) def __get_images(self): mkimage_output = subprocess.check_output(["mkimage", "-l", self.img_fname]) return re.findall(r"Image \d+ \((.*)\)", mkimage_output) def test_multi_image(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn [sbl2] partition = 0:MIBIB filename = partition.mbn """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) count = len(self.__get_images()) self.assertEqual(count, 3) def test_include(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn include = no [sbl2] partition = 0:MIBIB filename = partition.mbn """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) images = self.__get_images() print(images) self.assertTrue("sbl2" in images) self.assertTrue("sbl1" not in images) def test_yaffs_yes(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn yaffs = yes """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_yaffs_no(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn yaffs = no """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_yaffs_invalid(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn yaffs = abcd """) self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_invalid_partition(self): self.__mkconf(""" [sbl1] partition = 0:SBL5 filename = sbl1.mbn """) self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_img_larger_than_partition(self): self.__mkconf(""" [sbl2] partition = 0:SBL2 filename = sbl1.mbn """) self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) def test_machid_in_hex(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn if_machid = 0x152 """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) self.assertEqual(os.path.exists(self.img_fname), True) def test_machid_in_dec(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn if_machid = 256 """) self.pack.main(self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) self.assertEqual(os.path.exists(self.img_fname), True) def test_machid_invalid(self): self.__mkconf(""" [sbl1] partition = 0:SBL1 filename = sbl1.mbn if_machid = xyz """) self.assertRaises(SystemExit, self.pack.main, self.flinfo, self.img_dname, self.img_fname, self.part_fname, self.fconf_fname, ipq_nand=False) if __name__ == "__main__": main()