2 # spdtool - Tool for partial deblobbing of UEFI firmware images
3 # SPDX-License-Identifier: GPL-3.0-or-later
5 # Parse a blob and search for SPD files.
6 # First it is searched for a possible SPD header.
8 # For each candidate the function verify_match is invoked to check
9 # additional fields (known bits, reserved bits, CRC, ...)
11 # Dumps the found SPDs into the current folder.
23 def __init__(self
, blob
, verbose
=False, ignorecrc
=False):
25 self
.ignorecrc
= ignorecrc
26 self
.verbose
= verbose
30 """Return the first byte to look for"""
31 raise Exception("Function not implemented")
33 def verify_match(self
, header
, offset
):
34 """Return true if it looks like a SPD"""
35 raise Exception("Function not implemented")
37 def get_len(self
, header
, offset
):
38 """Return the length of the SPD"""
39 raise Exception("Function not implemented")
41 def get_part_number(self
, offset
):
42 """Return the part number in SPD"""
45 def get_manufacturer_id(self
, offset
):
46 """Return the manufacturer ID in SPD"""
49 def get_mtransfers(self
, offset
):
50 """Return the number of MT/s"""
53 def get_manufacturer(self
, offset
):
54 """Return manufacturer as string"""
55 id = self
.get_manufacturer_id(offset
)
59 0x2c80: "Crucial/Micron",
66 0xad80: "Hynix/Hyundai",
67 0xb502: "SuperTalent",
77 def blob_as_ord(self
, offset
):
78 """Helper for python2/python3 compatibility"""
79 return self
.blob
[offset
] if type(self
.blob
[offset
]) is int \
80 else ord(self
.blob
[offset
])
82 def search(self
, start
):
83 """Search for SPD at start. Returns -1 on error or offset
86 for i
in self
.get_matches():
87 for offset
in range(start
, len(self
.blob
)):
88 if self
.blob_as_ord(offset
) == i
and \
89 self
.verify_match(i
, offset
):
90 return offset
, self
.get_len(i
, offset
)
94 class SPD4Parser(Parser
):
97 """Return DDR4 possible header candidates"""
99 for i
in [1, 2, 3, 4]:
101 ret
.append(i
+ j
* 16)
104 def verify_match(self
, header
, offset
):
105 """Verify DDR4 specific bit fields."""
106 # offset 0 is a candidate, no need to validate
107 if self
.blob_as_ord(offset
+ 1) == 0xff:
109 if self
.blob_as_ord(offset
+ 2) != 0x0c:
111 if self
.blob_as_ord(offset
+ 5) & 0xc0 > 0:
113 if self
.blob_as_ord(offset
+ 6) & 0xc > 0:
115 if self
.blob_as_ord(offset
+ 7) & 0xc0 > 0:
117 if self
.blob_as_ord(offset
+ 8) != 0:
119 if self
.blob_as_ord(offset
+ 9) & 0xf > 0:
122 print("%x: Looks like DDR4 SPD" % offset
)
124 crc
= crc16
.crc16xmodem(self
.blob
[offset
:offset
+ 0x7d + 1])
125 # Vendors ignore the endianness...
126 crc_spd1
= self
.blob_as_ord(offset
+ 0x7f)
127 crc_spd1 |
= (self
.blob_as_ord(offset
+ 0x7e) << 8)
128 crc_spd2
= self
.blob_as_ord(offset
+ 0x7e)
129 crc_spd2 |
= (self
.blob_as_ord(offset
+ 0x7f) << 8)
130 if crc
!= crc_spd1
and crc
!= crc_spd2
:
132 print("%x: CRC16 doesn't match" % offset
)
133 if not self
.ignorecrc
:
138 def get_len(self
, header
, offset
):
139 """Return the length of the SPD found."""
140 if (header
>> 4) & 7 == 1:
142 if (header
>> 4) & 7 == 2:
146 def get_part_number(self
, offset
):
147 """Return part number as string"""
148 if offset
+ 0x15c >= len(self
.blob
):
150 tmp
= self
.blob
[offset
+ 0x149:offset
+ 0x15c + 1]
151 return tmp
.decode('utf-8').rstrip()
153 def get_manufacturer_id(self
, offset
):
154 """Return manufacturer ID"""
155 if offset
+ 0x141 >= len(self
.blob
):
157 tmp
= self
.blob
[offset
+ 0x140:offset
+ 0x141 + 1]
158 return struct
.unpack('H', tmp
)[0]
160 def get_mtransfers(self
, offset
):
161 """Return MT/s as specified by MTB and FTB"""
162 if offset
+ 0x7d >= len(self
.blob
):
165 if self
.blob_as_ord(offset
+ 0x11) != 0:
169 tmp
= self
.blob
[offset
+ 0x12:offset
+ 0x12 + 1]
170 tckm
= struct
.unpack('B', tmp
)[0]
171 tmp
= self
.blob
[offset
+ 0x7d:offset
+ 0x7d + 1]
172 tckf
= struct
.unpack('b', tmp
)[0]
173 return int(2000 / (tckm
/ mtb
+ tckf
/ ftb
))
176 if __name__
== "__main__":
177 parser
= argparse
.ArgumentParser(description
='SPD rom dumper')
178 parser
.add_argument('--blob', required
=True,
179 help='The ROM to search SPDs in.')
180 parser
.add_argument('--spd4', action
='store_true', default
=False,
181 help='Search for DDR4 SPDs.')
182 parser
.add_argument('--hex', action
='store_true', default
=False,
183 help='Store SPD in hex format otherwise binary.')
184 parser
.add_argument('-v', '--verbose', help='increase output verbosity',
186 parser
.add_argument('--ignorecrc', help='Ignore CRC mismatch',
187 action
='store_true', default
=False)
188 args
= parser
.parse_args()
190 blob
= open(args
.blob
, "rb").read()
193 p
= SPD4Parser(blob
, args
.verbose
, args
.ignorecrc
)
195 raise Exception("Must specify one of the following arguments:\n--spd4")
200 offset
, length
= p
.search(offset
)
203 print("Found SPD at 0x%x" % offset
)
204 print(" '%s', size %d, manufacturer %s (0x%04x) %d MT/s\n" %
205 (p
.get_part_number(offset
), length
, p
.get_manufacturer(offset
),
206 p
.get_manufacturer_id(offset
), p
.get_mtransfers(offset
)))
207 filename
= "spd-%d-%s-%s.bin" % (cnt
, p
.get_part_number(offset
),
208 p
.get_manufacturer(offset
))
209 filename
= filename
.replace("/", "_")
210 filename
= "".join([c
for c
in filename
if c
.isalpha() or c
.isdigit()
211 or c
== '-' or c
== '.' or c
== '_']).rstrip()
213 open(filename
, "wb").write(blob
[offset
:offset
+ length
])
216 with
open(filename
, "w") as fn
:
218 for i
in blob
[offset
:offset
+ length
]:
219 fn
.write("%02X" % struct
.unpack('B', i
)[0])
220 fn
.write(" " if j
< 15 else "\n")