#!/usr/bin/env python # (RTL8195AM/RTL8711AM/RTL8711AF/RTL8710AF) RtlImages Utility # pvvx from __future__ import print_function, division from operator import attrgetter import argparse import os import struct import sys __version__ = "22.01.18" PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3 HM_IS_HDRR = 1 HM_IS_HDRD = 2 HM_IS_BOOT = 4 HM_IS_OTA = 8 HM_IS_SRAM = 16 HM_IS_SDRAM = 32 # Function to return nth byte of a bitstring # Different behaviour on Python 2 vs 3 if PYTHON2: def byte(bitstr, index): return ord(bitstr[index]) else: def byte(bitstr, index): return bitstr[index] class FatalError(RuntimeError): """ Wrapper class for runtime errors that aren't caused by internal bugs, but by xchip responses or input content. """ def __init__(self, message): RuntimeError.__init__(self, message) @staticmethod def WithResult(message, result): """ Return a fatal error object that appends the hex values of 'result' as a string formatted argument. """ message += " (result was %s)" % hexify(result) return FatalError(message) class ImageSegment(object): """ Wrapper class for a segment in an RTL image (very similar to a section in an ELFImage also) """ def __init__(self, addr, data, file_offs = None): self.addr = addr # pad all ImageSegments to at least 4 bytes length pad_mod = len(data) % 4 if pad_mod != 0: data += b"\x00" * (4 - pad_mod) self.data = data self.file_offs = file_offs def copy_with_new_addr(self, new_addr): """ Return a new ImageSegment with same data, but mapped at a new address. """ return ImageSegment(new_addr, self.data, 0) def __repr__(self): r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr) if self.file_offs is not None: r += " file_offs 0x%08x" % (self.file_offs) return r class ELFSection(ImageSegment): """ Wrapper class for a section in an ELF image, has a section name as well as the common properties of an ImageSegment. """ def __init__(self, name, addr, data, size): super(ELFSection, self).__init__(addr, data) self.name = name.decode("utf-8") self.size = size def __repr__(self): return "%s %s" % (self.name, super(ELFSection, self).__repr__()) class ELFFile(object): SEC_TYPE_PROGBITS = 0x01 SEC_TYPE_STRTAB = 0x03 def __init__(self, name): # Load sections from the ELF file self.name = name try: with open(self.name, 'rb') as f: self._read_elf_file(f) except: print('Error: Not open input ELF file <%s>!' % self.name) sys.exit(-1) def get_section(self, section_name): for s in self.sections: if s.name == section_name: return s raise ValueError("No section %s in ELF file" % section_name) def _read_elf_file(self, f): # read the ELF file header LEN_FILE_HEADER = 0x34 try: (ident,_type,machine,_version, self.entrypoint,_phoff,shoff,_flags, _ehsize, _phentsize,_phnum,_shentsize, _shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER)) except struct.error as e: raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e)) if byte(ident, 0) != 0x7f or ident[1:4] != b'ELF': raise FatalError("%s has invalid ELF magic header" % self.name) if machine != 0x28: raise FatalError("%s does not appear to be an RTL ELF file. e_machine=%04x" % (self.name, machine)) self._read_sections(f, shoff, shstrndx, _shnum) def _read_sections(self, f, section_header_offs, shstrndx, shnum): LEN_SEC_HEADER = 0x28 f.seek(section_header_offs) # print("%d sections found in ELF file" % shnum) if shnum == 0 or shnum > 100: raise FatalError("%d sections found in ELF file!" % shnum) section_header = f.read(shnum *LEN_SEC_HEADER) if len(section_header) == 0: raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs) if len(section_header) != shnum *LEN_SEC_HEADER: print('WARNING: Unexpected ELF section header length %04x is not mod-%02x' % (len(section_header), LEN_SEC_HEADER)) # walk through the section header and extract all sections section_header_offsets = range(0, len(section_header), LEN_SEC_HEADER) def read_section_header(offs): name_offs,sec_type,_flags,lma,sec_offs,size = struct.unpack_from(" 1: sorted(imgsegs, key = attrgetter('addr')) elif len(imgsegs[0].data) == 0: # file size == 0 self.addr = imgsegs[0].addr # print('Segment at 0x%08x,' % self.addr, 'size 0x00000000 copy to image', self.fname) sections.remove(imgsegs[0]) return self.addr = imgsegs[0].addr s = imgsegs[len(imgsegs) - 1] self.size = s.addr + len(s.data) - self.addr # print('Segment at 0x%08x,' % self.addr, 'size 0x%08x' % self.size, 'copy to image', self.fname) if self.addr < self.addrl and self.addr > self.addrh: print('Segment Address Error!') return if self.size: if self.hm & HM_IS_HDRR: self.data = struct.pack(b'!' % self.fname) sys.exit(-1) def save_ota(self, f, fn, chks): if self.size: # print('Segment at 0x%08x,' % self.addr, 'size 0x%08x' % self.size, 'save to %s' % fn) try: f.write(self.data) i = 0 while i < len(self.data): chks += byte(self.data, i) i += 1 except: print('Error: Not write file %s!' % fn) sys.exit(-1) return chks def save_sram(self, f, fn): if self.size: # print('Segment at 0x%08x,' % self.addr, 'size 0x%08x' % self.size, 'save to %s' % fn) try: if self.hm & HM_IS_BOOT: gap = 0x0b000 i = len(self.data) if i > gap: print('Error: Segment %s is big!' % self.name) f.write(self.data) while i < gap: f.write('\xff') i += 1 else: f.write(self.data) except: print('Error: Not write file %s!' % fn) sys.exit(-1) def elf2image(args): e = ELFFile(args.elffile) # ELFSection is a subclass of ImageSegment # print(e.sections) image = [xFirmwareImage('ram_1', ['.ram.start.table', '.ram_image1.text', '.ram_image1.data'], HM_IS_BOOT + HM_IS_SRAM, 0x10000bc8, 0x10070000), xFirmwareImage('ram_2', ['.image2.start.table', '.ram_image2.text', '.ram_image2.rodata', '.ram.data'], HM_IS_OTA + HM_IS_HDRR + HM_IS_SRAM, 0x10006000, 0x10070000), xFirmwareImage('sdram', ['.sdr_text', '.sdr_rodata', '.sdr_data'], HM_IS_OTA + HM_IS_HDRD + HM_IS_SDRAM, 0x30000000, 0x30200000)] img_sram_use = 0 img_sdram_use = 0 for s in image: s.load(e.sections) if s.hm & HM_IS_SRAM: img_sram_use += s.size elif s.hm & HM_IS_SDRAM: img_sdram_use += s.size for s in image: if s.size: print('Segment at 0x%08x,' % s.addr, 'size 0x%08x' % s.size, '(%s)' % s.fname) s.save(args.outdir, None); s.save(args.outdir, 1); if args.ota: fn = args.outdir + 'ota.bin' try: chks = 0 with open(fn, "wb") as f: for s in image: if s.hm & HM_IS_OTA: chks = s.save_ota(f, fn, chks) f.write(struct.pack(b'!' % fn) sys.exit(-1) if args.ram_all: if image[0].size == 0 and image[1].size == 0: print('Error: Ram_all = 0!') sys.exit(-1) fn = args.outdir + 'ram_all.bin' try: with open(fn, "wb") as f: for s in image: s.save_sram(f, fn) f.write(struct.pack(b'!' % fn) sys.exit(-1) print('Images size: SRAM %u bytes, SDRAM %u bytes [%u]' % (img_sram_use, img_sdram_use, img_sram_use + img_sdram_use)) def main(): parser = argparse.ArgumentParser(description='RtlAImages Utility version %s' % __version__, prog='rtlimage') parser.add_argument('--ram_all', '-r', action='store_true', help='Generate ram_all files') parser.add_argument('--ota', '-a', action='store_true', help='Generate OTA files') parser.add_argument('elffile', type=str, help='Input ELF file',) parser.add_argument('--outdir', '-o', type=str, default='', help='Outpyt directory') args = parser.parse_args() print('RtlAImages Utility version %s' % __version__) if args.elffile is None: parser.print_help() sys.exit(1) elf2image(args) sys.exit(0) if __name__ == '__main__': try: main() except FatalError as e: print('\nA fatal error occurred: %s' % e) sys.exit(2)