blob: 89976eac5992aade2c8e001a725d69bb4de1ea0c [file] [log] [blame]
Patrick Rudolph9b545df2019-02-12 11:52:41 +01001#!/usr/bin/env python
Patrick Rudolph9b545df2019-02-12 11:52:41 +01002# spdtool - Tool for partial deblobbing of UEFI firmware images
Patrick Georgi1afe2862020-05-10 17:34:15 +02003# SPDX-License-Identifier: GPL-3.0-or-later
Patrick Rudolph9b545df2019-02-12 11:52:41 +01004#
5# Parse a blob and search for SPD files.
6# First it is searched for a possible SPD header.
7#
8# For each candidate the function verify_match is invoked to check
9# additional fields (known bits, reserved bits, CRC, ...)
10#
11# Dumps the found SPDs into the current folder.
12#
13# Implemented:
14# DDR4 SPDs
15#
16
17import argparse
18import crc16
19import struct
20
21
22class Parser(object):
23 def __init__(self, blob, verbose=False, ignorecrc=False):
24 self.blob = blob
25 self.ignorecrc = ignorecrc
26 self.verbose = verbose
27
28 @staticmethod
29 def get_matches():
30 """Return the first byte to look for"""
31 raise Exception("Function not implemented")
32
33 def verify_match(self, header, offset):
34 """Return true if it looks like a SPD"""
35 raise Exception("Function not implemented")
36
37 def get_len(self, header, offset):
38 """Return the length of the SPD"""
39 raise Exception("Function not implemented")
40
41 def get_part_number(self, offset):
42 """Return the part number in SPD"""
43 return ""
44
45 def get_manufacturer_id(self, offset):
46 """Return the manufacturer ID in SPD"""
47 return 0xffff
48
49 def get_mtransfers(self, offset):
50 """Return the number of MT/s"""
51 return 0
52
53 def get_manufacturer(self, offset):
54 """Return manufacturer as string"""
55 id = self.get_manufacturer_id(offset)
56 if id == 0xffff:
57 return "Unknown"
58 ids = {
59 0x2c80: "Crucial/Micron",
60 0x4304: "Ramaxel",
61 0x4f01: "Transcend",
62 0x9801: "Kingston",
63 0x987f: "Hynix",
64 0x9e02: "Corsair",
65 0xb004: "OCZ",
66 0xad80: "Hynix/Hyundai",
67 0xb502: "SuperTalent",
68 0xcd04: "GSkill",
69 0xce80: "Samsung",
70 0xfe02: "Elpida",
71 0xff2c: "Micron",
72 }
73 if id in ids:
74 return ids[id]
75 return "Unknown"
76
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])
81
82 def search(self, start):
83 """Search for SPD at start. Returns -1 on error or offset
84 if found.
85 """
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)
91 return -1, 0
92
93
94class SPD4Parser(Parser):
95 @staticmethod
96 def get_matches():
97 """Return DDR4 possible header candidates"""
98 ret = []
99 for i in [1, 2, 3, 4]:
100 for j in [1, 2]:
101 ret.append(i + j * 16)
102 return ret
103
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:
108 return False
109 if self.blob_as_ord(offset + 2) != 0x0c:
110 return False
111 if self.blob_as_ord(offset + 5) & 0xc0 > 0:
112 return False
113 if self.blob_as_ord(offset + 6) & 0xc > 0:
114 return False
115 if self.blob_as_ord(offset + 7) & 0xc0 > 0:
116 return False
117 if self.blob_as_ord(offset + 8) != 0:
118 return False
119 if self.blob_as_ord(offset + 9) & 0xf > 0:
120 return False
121 if self.verbose:
122 print("%x: Looks like DDR4 SPD" % offset)
123
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:
131 if self.verbose:
132 print("%x: CRC16 doesn't match" % offset)
133 if not self.ignorecrc:
134 return False
135
136 return True
137
138 def get_len(self, header, offset):
139 """Return the length of the SPD found."""
140 if (header >> 4) & 7 == 1:
141 return 256
142 if (header >> 4) & 7 == 2:
143 return 512
144 return 0
145
146 def get_part_number(self, offset):
147 """Return part number as string"""
148 if offset + 0x15c >= len(self.blob):
149 return ""
150 tmp = self.blob[offset + 0x149:offset + 0x15c + 1]
151 return tmp.decode('utf-8').rstrip()
152
153 def get_manufacturer_id(self, offset):
154 """Return manufacturer ID"""
155 if offset + 0x141 >= len(self.blob):
156 return 0xffff
157 tmp = self.blob[offset + 0x140:offset + 0x141 + 1]
158 return struct.unpack('H', tmp)[0]
159
160 def get_mtransfers(self, offset):
161 """Return MT/s as specified by MTB and FTB"""
162 if offset + 0x7d >= len(self.blob):
163 return 0
164
165 if self.blob_as_ord(offset + 0x11) != 0:
166 return 0
167 mtb = 8.0
168 ftb = 1000.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))
174
175
176if __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',
185 action='store_true')
186 parser.add_argument('--ignorecrc', help='Ignore CRC mismatch',
187 action='store_true', default=False)
188 args = parser.parse_args()
189
190 blob = open(args.blob, "rb").read()
191
192 if args.spd4:
193 p = SPD4Parser(blob, args.verbose, args.ignorecrc)
194 else:
195 raise Exception("Must specify one of the following arguments:\n--spd4")
196
197 offset = 0
198 cnt = 0
199 while True:
200 offset, length = p.search(offset)
201 if length == 0:
202 break
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()
212 if not args.hex:
213 open(filename, "wb").write(blob[offset:offset + length])
214 else:
215 filename += ".hex"
216 with open(filename, "w") as fn:
217 j = 0
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")
221 j = (j + 1) % 16
222 offset += 1
223 cnt += 1