| #!/usr/bin/env python3 |
| |
| # Script for injecting SPDs into APCB_v3a binaries. |
| |
| import re |
| import argparse |
| from collections import namedtuple |
| from struct import * |
| |
| APCB_CHECKSUM_OFFSET = 16 |
| SPD_ENTRY_MAGIC = bytes.fromhex('0200480000000000') |
| SPD_SIZE = 512 |
| EMPTY_SPD = b'\x00' * SPD_SIZE |
| ZERO_BLOCKS = (2, 4, 6, 7) |
| SPD_BLOCK_SIZE = 64 |
| SPD_BLOCK_HEADER_FMT = '<HHHH' |
| SPD_BLOCK_HEADER_SIZE = calcsize(SPD_BLOCK_HEADER_FMT) |
| spd_block_header = namedtuple( |
| 'spd_block_header', 'Type, Length, Key, Reserved') |
| |
| def parseargs(): |
| parser = argparse.ArgumentParser(description='Inject SPDs into APCB binaries') |
| parser.add_argument( |
| 'apcb_in', |
| type=str, |
| help='APCB input file') |
| parser.add_argument( |
| 'apcb_out', |
| type=str, |
| help='APCB output file') |
| parser.add_argument( |
| '--spd_sources', |
| nargs='+', |
| help='List of SPD sources') |
| return parser.parse_args() |
| |
| |
| # Calculate checksum of APCB binary |
| def chksum(data): |
| sum = 0 |
| for i, v in enumerate(data): |
| if i == APCB_CHECKSUM_OFFSET: continue |
| sum = (sum + v) & 0xff |
| return (0x100 - sum) & 0xff |
| |
| |
| # Inject bytes into binary blob by overwriting |
| def inject(orig, insert, offset): |
| return b''.join([orig[:offset], insert, orig[offset + len(insert):]]) |
| |
| |
| def main(): |
| args = parseargs() |
| |
| # Load input APCB |
| print(f'Reading input APCB from {args.apcb_in}') |
| with open(args.apcb_in, 'rb') as f: |
| apcb = f.read() |
| assert chksum(apcb) == apcb[APCB_CHECKSUM_OFFSET], 'Initial checksum is invalid' |
| orig_apcb_len = len(apcb) |
| |
| # Load SPDs |
| print(f'Using SPD Sources = {", ".join(args.spd_sources)}') |
| spds = [] |
| for spd_source in args.spd_sources: |
| with open(spd_source, 'rb') as f: |
| spd_data = bytes.fromhex(re.sub(r'\s+', '', f.read().decode())) |
| assert len(spd_data) == SPD_SIZE, f'{spd_source} is not {SPD_SIZE} bytes' |
| # Verify ZERO_BLOCKS are zero |
| for b in ZERO_BLOCKS: |
| assert all(v==0 for v in spd_data[b*SPD_BLOCK_SIZE:(b+1)*SPD_BLOCK_SIZE]), f'SPD block #{b} is not zero' |
| spds.append(spd_data) |
| assert len(spds) > 0, "No SPDs provided" |
| |
| # Inject SPDs into APCB |
| apcb_offset = 0 |
| spd_idx = 0 |
| while True: |
| apcb_offset = apcb.find(SPD_ENTRY_MAGIC, apcb_offset) |
| if apcb_offset < 0: |
| print(f'No more SPD entries found') |
| assert spd_idx >= len(spds), f'Not enough SPD entries in APCB. Need {len(spds)}, found {spd_idx}' |
| break |
| |
| if spd_idx < len(spds): |
| print(f'Injecting SPD instance {spd_idx}') |
| spd = spds[spd_idx] |
| else: |
| print(f'Injecting empty SPD for instance {spd_idx}') |
| spd = EMPTY_SPD |
| |
| # Inject SPD blocks |
| for b in range(int(SPD_SIZE/SPD_BLOCK_SIZE)): |
| if b in ZERO_BLOCKS: continue |
| header_data = apcb[apcb_offset:apcb_offset + SPD_BLOCK_HEADER_SIZE] |
| header = spd_block_header._make(unpack(SPD_BLOCK_HEADER_FMT, header_data)) |
| socket = (header.Key >> 12) & 0xF |
| channel = (header.Key >> 8) & 0xF |
| dimm = (header.Key >> 4) & 0xF |
| block_id = (header.Key >> 0) & 0xF |
| |
| assert header.Type == 2 |
| assert header.Length == SPD_BLOCK_HEADER_SIZE + SPD_BLOCK_SIZE |
| assert socket == 0 |
| assert channel == 0 |
| assert block_id == b |
| assert dimm == 0 |
| assert header.Reserved == 0 |
| |
| spd_block = spd[b*SPD_BLOCK_SIZE:(b+1)*SPD_BLOCK_SIZE] |
| apcb_offset += SPD_BLOCK_HEADER_SIZE |
| apcb = inject(apcb, spd_block, apcb_offset) |
| apcb_offset += SPD_BLOCK_SIZE |
| |
| spd_idx += 1 |
| |
| # Fix APCB checksum |
| print(f'Fixing APCB checksum') |
| apcb = inject(apcb, bytes([chksum(apcb)]), APCB_CHECKSUM_OFFSET) |
| assert chksum(apcb) == apcb[APCB_CHECKSUM_OFFSET], 'Final checksum is invalid' |
| assert orig_apcb_len == len(apcb), 'The size of the APCB changed.' |
| |
| # Write APCB to file |
| print(f'Writing {len(apcb)} byte APCB to {args.apcb_out}') |
| with open(args.apcb_out, 'wb') as f: |
| f.write(apcb) |
| |
| |
| if __name__ == "__main__": |
| main() |