1#!/usr/bin/env python3 2 3# Script for editing APCB_V3 binaries, such as injecting SPDs. 4 5import sys 6import re 7import argparse 8from collections import namedtuple 9from struct import * 10import binascii 11import os 12 13# SPD_MAGIC matches the expected SPD header: 14# Byte 0 = 0x23 = 512 bytes total / 384 bytes used 15# Byte 1 = 0x11 = Revision 1.1 16# Byte 2 = 0x11 = LPDDR4X SDRAM 17# = 0x13 = LP5 SDRAM 18# = 0x15 = LP5X SDRAM 19# Byte 3 = 0x0E = Non-DIMM Solution 20LP4_SPD_MAGIC = bytes.fromhex('2311110E') 21LP5_SPD_MAGIC = bytes.fromhex('2311130E') 22LP5X_SPD_MAGIC = bytes.fromhex('2311150E') 23EMPTY_SPD = b'\x00' * 512 24 25spd_ssp_struct_fmt = '??B?IIBBBxIIBBBx' 26spd_ssp_struct = namedtuple( 27 'spd_ssp_struct', 'SpdValid, DimmPresent, \ 28 PageAddress, NvDimmPresent, \ 29 DramManufacturersIDCode, Address, \ 30 SpdMuxPresent, MuxI2CAddress, MuxChannel, \ 31 Technology, Package, SocketNumber, \ 32 ChannelNumber, DimmNumber') 33 34apcb_v3_header_fmt = 'HHHHBBBBBBH' 35apcb_v3_header = namedtuple( 36 'apcb_v3_header', 'GroupId, TypeId, SizeOfType, \ 37 InstanceId, ContextType, ContextFormat, UnitSize, \ 38 PriorityMask, KeySize, KeyPos, BoardMask') 39 40def parseargs(): 41 parser = argparse.ArgumentParser(description='Inject SPDs into APCB binaries') 42 parser.add_argument( 43 'apcb_in', 44 type=str, 45 help='APCB input file') 46 parser.add_argument( 47 'apcb_out', 48 type=str, 49 help='APCB output file') 50 parser.add_argument( 51 '--spd_sources', 52 nargs='+', 53 help='List of SPD sources') 54 parser.add_argument( 55 '--mem_type', 56 type=str, 57 default='lp4', 58 help='Memory type [lp4|lp5|lp5x]. Default = lp4') 59 return parser.parse_args() 60 61 62def chksum(data): 63 sum = 0 64 for b in data[:16] + data[17:]: 65 sum = (sum + b) & 0xff 66 return (0x100 - sum) & 0xff 67 68 69def inject(orig, insert, offset): 70 return b''.join([orig[:offset], insert, orig[offset + len(insert):]]) 71 72 73def main(): 74 spd_magic = LP4_SPD_MAGIC 75 76 args = parseargs() 77 78 print(f'Reading input APCB from {args.apcb_in}') 79 80 with open(args.apcb_in, 'rb') as f: 81 apcb = f.read() 82 83 orig_apcb_len = len(apcb) 84 85 assert chksum(apcb) == apcb[16], f'ERROR: {args.apcb_in} checksum is invalid' 86 87 print(f'Using SPD Sources = {args.spd_sources}') 88 89 if args.mem_type == 'lp5': 90 spd_magic = LP5_SPD_MAGIC 91 elif args.mem_type == 'lp5x': 92 spd_magic = LP5X_SPD_MAGIC 93 94 spds = [] 95 for spd_source in args.spd_sources: 96 with open(spd_source, 'rb') as f: 97 spd_data = bytes.fromhex(re.sub(r'\s+', '', f.read().decode())) 98 assert(len(spd_data) == 512), f'ERROR: {spd_source} not 512 bytes' 99 spds.append(spd_data) 100 101 spd_offset = 0 102 instance = 0 103 while True: 104 spd_offset = apcb.find(spd_magic, spd_offset) 105 if spd_offset < 0: 106 print('No more SPD magic numbers in APCB') 107 break 108 109 spd_ssp_offset = spd_offset - calcsize(spd_ssp_struct_fmt) 110 spd_ssp_bytes = apcb[spd_ssp_offset:spd_offset] 111 spd_ssp = spd_ssp_struct._make( 112 unpack(spd_ssp_struct_fmt, spd_ssp_bytes)) 113 114 assert spd_ssp.DimmNumber >= 0 and spd_ssp.DimmNumber <= 1, \ 115 'ERROR: Unexpected dimm number found in APCB' 116 assert spd_ssp.ChannelNumber >= 0 and spd_ssp.ChannelNumber <= 1, \ 117 'ERROR: Unexpected channel number found in APCB' 118 119 print(f'Found SPD instance {instance} with channel {spd_ssp.ChannelNumber} ' 120 f'and dimm {spd_ssp.DimmNumber} at offset {spd_offset}') 121 122 # APCB V3 header is above first channel 0 entry 123 if spd_ssp.ChannelNumber == 0: 124 apcb_v3_header_offset = spd_ssp_offset - \ 125 calcsize(apcb_v3_header_fmt) - 4 126 apcb_v3_header_bytes = apcb[apcb_v3_header_offset: 127 apcb_v3_header_offset + calcsize(apcb_v3_header_fmt)] 128 apcb_v3 = apcb_v3_header._make( 129 unpack(apcb_v3_header_fmt, apcb_v3_header_bytes)) 130 apcb_v3 = apcb_v3._replace(BoardMask=(1 << instance)) 131 132 if instance < len(spds): 133 print(f'Enabling channel {spd_ssp.ChannelNumber}, ' 134 f'dimm {spd_ssp.DimmNumber} and injecting SPD') 135 spd_ssp = spd_ssp._replace(SpdValid=True, DimmPresent=True) 136 spd = spds[instance] 137 else: 138 print(f'Disabling channel {spd_ssp.ChannelNumber}, ' 139 f'dimm {spd_ssp.DimmNumber} and clearing SPD') 140 spd_ssp = spd_ssp._replace(SpdValid=False, DimmPresent=False) 141 spd = EMPTY_SPD 142 143 assert len(spd) == 512, f'ERROR: Expected SPD to be 512 bytes, got {len(spd)}' 144 145 apcb = inject(apcb, pack(spd_ssp_struct_fmt, *spd_ssp), spd_ssp_offset) 146 apcb = inject(apcb, spd, spd_offset) 147 if spd_ssp.ChannelNumber == 0: 148 apcb = inject(apcb, pack(apcb_v3_header_fmt, *apcb_v3), apcb_v3_header_offset) 149 else: 150 instance += 1 151 152 spd_offset += 512 153 154 assert instance >= len(spds), \ 155 f'ERROR: Not enough SPD slots in APCB, found {instance}, need {len(spds)}' 156 157 print(f'Fixing checksum and writing to {args.apcb_out}') 158 159 apcb = inject(apcb, bytes([chksum(apcb)]), 16) 160 161 assert chksum(apcb) == apcb[16], 'ERROR: Final checksum is invalid' 162 assert orig_apcb_len == len(apcb), \ 163 'ERROR: The size of the APCB binary changed.' 164 165 print(f'Writing {len(apcb)} bytes to {args.apcb_out}') 166 167 with open(args.apcb_out, 'wb') as f: 168 f.write(apcb) 169 170 171if __name__ == "__main__": 172 main() 173