common/recipes-core/psu-update/files/srec.py (107 lines of code) (raw):
#!/usr/bin/env python3
"""
Parses S-Records into an Image object. Data in an Image can be referenced by slicing it:
>>> import srec
>>> img = srec.parse("file.S")
>>> first_16_bytes = img[0x0000:0x0010]
Slices always take *byte* addresses even if the image was
parsed with the address_scale option.
"""
import binascii
import pickle
import pickletools
def hx(bs):
return binascii.b2a_hex(bs).decode("ascii")
def schecksum(bs):
return (sum(bs, 0) & 0xFF) ^ 0xFF
class Section:
def __init__(self, offset: int) -> None:
self.start = offset
self.data = bytearray()
@property
def end(self):
return self.start + len(self.data)
def contains(self, addr):
return addr >= self.start and addr < self.end
def __getitem__(self, k):
if not isinstance(k, slice):
raise Exception("must use a slice")
return self.data.__getitem__(slice(k.start - self.start, k.stop - self.start))
class SRecord:
__slots__ = ["stype", "addr", "data"]
def __init__(self, stype, addr, data):
self.stype = stype
self.addr = addr
self.data = data
@property
def end(self):
return self.addr + len(self.data)
class Image:
def __init__(self):
self.sections = []
def dump(self, path):
with open(path, "wb") as of:
for s in self.sections:
of.seek(s.start)
of.write(s.data)
def pickle(self, path):
with open(path, "wb") as of:
of.write(pickletools.optimize(pickle.dumps(self)))
def __getitem__(self, k):
if not isinstance(k, slice):
raise Exception("must use slices")
if k.step is not None:
raise Exception("cannot step")
result = bytearray()
rlen = k.stop - k.start
while len(result) < rlen:
pos = k.start + len(result)
section = next((s for s in self.sections if s.contains(pos)), None)
if not section:
next_section = next((s for s in self.sections if s.start > pos), None)
if next_section:
fill = min(next_section.start, k.stop) - pos
# print("filling region", fill)
result.extend(b"\xff" * fill)
continue
raise Exception(
"requested range {:x}, {:x} past end of image".format(pos, k.stop)
)
add = section[pos : k.stop]
result.extend(add)
return result
@classmethod
def from_recs(cls, recs):
sections = []
section = None
for sr in recs:
if section:
if sr.addr == section.end:
section.data.extend(sr.data)
continue
if sr.addr > section.start and sr.addr < section.end:
raise Exception("SRec records overlap", sr)
else:
sections.append(section)
# print("{:x} bytes @{:x}".format(len(section.data), section.start))
section = None
section = Section(sr.addr)
section.data.extend(sr.data)
if section:
# print("{:x} bytes @{:x}".format(len(section.data), section.start))
sections.append(section)
image = cls()
image.sections = sections
return image
def __str__(self):
return "Image<{} sections>".format(len(self.sections))
def parse(path, address_scale=1):
with open(path) as srf:
for line in srf:
line = line.strip()
if line.startswith("S"):
stype = int(line[1])
recdata = binascii.a2b_hex(line[2:])
reclen = recdata[0]
rec = recdata[1 : 1 + reclen]
if stype == 0:
# Header
continue
if stype in (1, 2, 3):
# Data record
addrlen = 1 + stype
addr = int.from_bytes(rec[0:addrlen], byteorder="big")
addr *= address_scale
payload = rec[addrlen:-1]
checksum = rec[-1]
if schecksum(recdata[:-1]) != checksum:
raise Exception("Bad SREC Checksum")
yield SRecord(stype, addr, payload)
continue
raise Exception("Unhandled record: {}".format(line))