/ util / apcb / apcb_v3_edit.py
apcb_v3_edit.py
  1  #!/usr/bin/env python3
  2  
  3  # Script for editing APCB_V3 binaries, such as injecting SPDs.
  4  
  5  import sys
  6  import re
  7  import argparse
  8  from collections import namedtuple
  9  from struct import *
 10  import binascii
 11  import 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
 20  LP4_SPD_MAGIC = bytes.fromhex('2311110E')
 21  LP5_SPD_MAGIC = bytes.fromhex('2311130E')
 22  LP5X_SPD_MAGIC = bytes.fromhex('2311150E')
 23  EMPTY_SPD = b'\x00' * 512
 24  
 25  spd_ssp_struct_fmt = '??B?IIBBBxIIBBBx'
 26  spd_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  
 34  apcb_v3_header_fmt = 'HHHHBBBBBBH'
 35  apcb_v3_header = namedtuple(
 36      'apcb_v3_header', 'GroupId, TypeId, SizeOfType, \
 37      InstanceId, ContextType, ContextFormat, UnitSize, \
 38      PriorityMask, KeySize, KeyPos, BoardMask')
 39  
 40  def 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  
 62  def 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  
 69  def inject(orig, insert, offset):
 70      return b''.join([orig[:offset], insert, orig[offset + len(insert):]])
 71  
 72  
 73  def 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  
171  if __name__ == "__main__":
172      main()