summaryrefslogtreecommitdiffstats
path: root/util/apcb/apcb_v3_edit.py
blob: 140b23238c9ec3c40f73eba33b76d9bdd0822b72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python3

# Script for editing APCB_V3 binaries, such as injecting SPDs.

import sys
import re
import argparse
from collections import namedtuple
from struct import *
import binascii
import os

# SPD_MAGIC matches the expected SPD header:
# Byte 0 = 0x23 = 512 bytes total / 384 bytes used
# Byte 1 = 0x11 = Revision 1.1
# Byte 2 = 0x11 = LPDDR4X SDRAM
#        = 0x13 = LP5 SDRAM
# Byte 3 = 0x0E = Non-DIMM Solution
LP4_SPD_MAGIC = bytes.fromhex('2311110E')
LP5_SPD_MAGIC = bytes.fromhex('2311130E')
EMPTY_SPD = b'\x00' * 512

spd_ssp_struct_fmt = '??B?IIBBBxIIBBBx'
spd_ssp_struct = namedtuple(
    'spd_ssp_struct', 'SpdValid, DimmPresent, \
                    PageAddress, NvDimmPresent, \
                    DramManufacturersIDCode, Address, \
                    SpdMuxPresent, MuxI2CAddress, MuxChannel, \
                    Technology, Package, SocketNumber, \
                    ChannelNumber, DimmNumber')

apcb_v3_header_fmt = 'HHHHBBBBBBH'
apcb_v3_header = namedtuple(
    'apcb_v3_header', 'GroupId, TypeId, SizeOfType, \
    InstanceId, ContextType, ContextFormat, UnitSize, \
    PriorityMask, KeySize, KeyPos, BoardMask')

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')
    parser.add_argument(
        '--mem_type',
        type=str,
        default='lp4',
        help='Memory type [lp4|lp5]. Default = lp4')
    return parser.parse_args()


def chksum(data):
    sum = 0
    for b in data[:16] + data[17:]:
        sum = (sum + b) & 0xff
    return (0x100 - sum) & 0xff


def inject(orig, insert, offset):
    return b''.join([orig[:offset], insert, orig[offset + len(insert):]])


def main():
    spd_magic = LP4_SPD_MAGIC

    args = parseargs()

    print(f'Reading input APCB from {args.apcb_in}')

    with open(args.apcb_in, 'rb') as f:
        apcb = f.read()

    orig_apcb_len = len(apcb)

    assert chksum(apcb) == apcb[16], f'ERROR: {args.apcb_in} checksum is invalid'

    print(f'Using SPD Sources = {args.spd_sources}')

    if args.mem_type == 'lp5':
        spd_magic = LP5_SPD_MAGIC

    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) == 512), f'ERROR: {spd_source} not 512 bytes'
        spds.append(spd_data)

    spd_offset = 0
    instance = 0
    while True:
        spd_offset = apcb.find(spd_magic, spd_offset)
        if spd_offset < 0:
            print('No more SPD magic numbers in APCB')
            break

        spd_ssp_offset = spd_offset - calcsize(spd_ssp_struct_fmt)
        spd_ssp_bytes = apcb[spd_ssp_offset:spd_offset]
        spd_ssp = spd_ssp_struct._make(
            unpack(spd_ssp_struct_fmt, spd_ssp_bytes))

        assert spd_ssp.DimmNumber >= 0 and spd_ssp.DimmNumber <= 1, \
                'ERROR: Unexpected dimm number found in APCB'
        assert spd_ssp.ChannelNumber >= 0 and spd_ssp.ChannelNumber <= 1, \
                'ERROR: Unexpected channel number found in APCB'

        print(f'Found SPD instance {instance} with channel {spd_ssp.ChannelNumber} '
              f'and dimm {spd_ssp.DimmNumber} at offset {spd_offset}')

        # APCB V3 header is above first channel 0 entry
        if spd_ssp.ChannelNumber == 0:
            apcb_v3_header_offset = spd_ssp_offset - \
                calcsize(apcb_v3_header_fmt) - 4
            apcb_v3_header_bytes = apcb[apcb_v3_header_offset:
                                        apcb_v3_header_offset + calcsize(apcb_v3_header_fmt)]
            apcb_v3 = apcb_v3_header._make(
                unpack(apcb_v3_header_fmt, apcb_v3_header_bytes))
            apcb_v3 = apcb_v3._replace(BoardMask=(1 << instance))

        if instance < len(spds):
            print(f'Enabling channel {spd_ssp.ChannelNumber}, '
                  f'dimm {spd_ssp.DimmNumber} and injecting SPD')
            spd_ssp = spd_ssp._replace(SpdValid=True, DimmPresent=True)
            spd = spds[instance]
        else:
             print(f'Disabling channel {spd_ssp.ChannelNumber}, '
                   f'dimm {spd_ssp.DimmNumber} and clearing SPD')
             spd_ssp = spd_ssp._replace(SpdValid=False, DimmPresent=False)
             spd = EMPTY_SPD

        assert len(spd) == 512, f'ERROR: Expected SPD to be 512 bytes, got {len(spd)}'

        apcb = inject(apcb, pack(spd_ssp_struct_fmt, *spd_ssp), spd_ssp_offset)
        apcb = inject(apcb, spd, spd_offset)
        if spd_ssp.ChannelNumber == 0:
            apcb = inject(apcb, pack(apcb_v3_header_fmt, *apcb_v3), apcb_v3_header_offset)
        else:
            instance += 1

        spd_offset += 512

    assert instance >= len(spds), \
            f'ERROR: Not enough SPD slots in APCB, found {instance}, need {len(spds)}'

    print(f'Fixing checksum and writing to {args.apcb_out}')

    apcb = inject(apcb, bytes([chksum(apcb)]), 16)

    assert chksum(apcb) == apcb[16], 'ERROR: Final checksum is invalid'
    assert orig_apcb_len == len(apcb), \
                'ERROR: The size of the APCB binary changed.'

    print(f'Writing {len(apcb)} bytes to {args.apcb_out}')

    with open(args.apcb_out, 'wb') as f:
        f.write(apcb)


if __name__ == "__main__":
    main()