sdm845: Select VBOOT_MIGRATE_WORKING_DATA, now required
[coreboot.git] / util / spdtool / spdtool.py
blobf6f9f85122e742372df190c746c80a47105d0648
1 #!/usr/bin/env python
3 # spdtool - Tool for partial deblobbing of UEFI firmware images
4 # Copyright (C) 2019 9elements Agency GmbH <patrick.rudolph@9elements.com>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
17 # Parse a blob and search for SPD files.
18 # First it is searched for a possible SPD header.
20 # For each candidate the function verify_match is invoked to check
21 # additional fields (known bits, reserved bits, CRC, ...)
23 # Dumps the found SPDs into the current folder.
25 # Implemented:
26 # DDR4 SPDs
29 import argparse
30 import crc16
31 import struct
34 class Parser(object):
35 def __init__(self, blob, verbose=False, ignorecrc=False):
36 self.blob = blob
37 self.ignorecrc = ignorecrc
38 self.verbose = verbose
40 @staticmethod
41 def get_matches():
42 """Return the first byte to look for"""
43 raise Exception("Function not implemented")
45 def verify_match(self, header, offset):
46 """Return true if it looks like a SPD"""
47 raise Exception("Function not implemented")
49 def get_len(self, header, offset):
50 """Return the length of the SPD"""
51 raise Exception("Function not implemented")
53 def get_part_number(self, offset):
54 """Return the part number in SPD"""
55 return ""
57 def get_manufacturer_id(self, offset):
58 """Return the manufacturer ID in SPD"""
59 return 0xffff
61 def get_mtransfers(self, offset):
62 """Return the number of MT/s"""
63 return 0
65 def get_manufacturer(self, offset):
66 """Return manufacturer as string"""
67 id = self.get_manufacturer_id(offset)
68 if id == 0xffff:
69 return "Unknown"
70 ids = {
71 0x2c80: "Crucial/Micron",
72 0x4304: "Ramaxel",
73 0x4f01: "Transcend",
74 0x9801: "Kingston",
75 0x987f: "Hynix",
76 0x9e02: "Corsair",
77 0xb004: "OCZ",
78 0xad80: "Hynix/Hyundai",
79 0xb502: "SuperTalent",
80 0xcd04: "GSkill",
81 0xce80: "Samsung",
82 0xfe02: "Elpida",
83 0xff2c: "Micron",
85 if id in ids:
86 return ids[id]
87 return "Unknown"
89 def blob_as_ord(self, offset):
90 """Helper for python2/python3 compatibility"""
91 return self.blob[offset] if type(self.blob[offset]) is int \
92 else ord(self.blob[offset])
94 def search(self, start):
95 """Search for SPD at start. Returns -1 on error or offset
96 if found.
97 """
98 for i in self.get_matches():
99 for offset in range(start, len(self.blob)):
100 if self.blob_as_ord(offset) == i and \
101 self.verify_match(i, offset):
102 return offset, self.get_len(i, offset)
103 return -1, 0
106 class SPD4Parser(Parser):
107 @staticmethod
108 def get_matches():
109 """Return DDR4 possible header candidates"""
110 ret = []
111 for i in [1, 2, 3, 4]:
112 for j in [1, 2]:
113 ret.append(i + j * 16)
114 return ret
116 def verify_match(self, header, offset):
117 """Verify DDR4 specific bit fields."""
118 # offset 0 is a candidate, no need to validate
119 if self.blob_as_ord(offset + 1) == 0xff:
120 return False
121 if self.blob_as_ord(offset + 2) != 0x0c:
122 return False
123 if self.blob_as_ord(offset + 5) & 0xc0 > 0:
124 return False
125 if self.blob_as_ord(offset + 6) & 0xc > 0:
126 return False
127 if self.blob_as_ord(offset + 7) & 0xc0 > 0:
128 return False
129 if self.blob_as_ord(offset + 8) != 0:
130 return False
131 if self.blob_as_ord(offset + 9) & 0xf > 0:
132 return False
133 if self.verbose:
134 print("%x: Looks like DDR4 SPD" % offset)
136 crc = crc16.crc16xmodem(self.blob[offset:offset + 0x7d + 1])
137 # Vendors ignore the endianness...
138 crc_spd1 = self.blob_as_ord(offset + 0x7f)
139 crc_spd1 |= (self.blob_as_ord(offset + 0x7e) << 8)
140 crc_spd2 = self.blob_as_ord(offset + 0x7e)
141 crc_spd2 |= (self.blob_as_ord(offset + 0x7f) << 8)
142 if crc != crc_spd1 and crc != crc_spd2:
143 if self.verbose:
144 print("%x: CRC16 doesn't match" % offset)
145 if not self.ignorecrc:
146 return False
148 return True
150 def get_len(self, header, offset):
151 """Return the length of the SPD found."""
152 if (header >> 4) & 7 == 1:
153 return 256
154 if (header >> 4) & 7 == 2:
155 return 512
156 return 0
158 def get_part_number(self, offset):
159 """Return part number as string"""
160 if offset + 0x15c >= len(self.blob):
161 return ""
162 tmp = self.blob[offset + 0x149:offset + 0x15c + 1]
163 return tmp.decode('utf-8').rstrip()
165 def get_manufacturer_id(self, offset):
166 """Return manufacturer ID"""
167 if offset + 0x141 >= len(self.blob):
168 return 0xffff
169 tmp = self.blob[offset + 0x140:offset + 0x141 + 1]
170 return struct.unpack('H', tmp)[0]
172 def get_mtransfers(self, offset):
173 """Return MT/s as specified by MTB and FTB"""
174 if offset + 0x7d >= len(self.blob):
175 return 0
177 if self.blob_as_ord(offset + 0x11) != 0:
178 return 0
179 mtb = 8.0
180 ftb = 1000.0
181 tmp = self.blob[offset + 0x12:offset + 0x12 + 1]
182 tckm = struct.unpack('B', tmp)[0]
183 tmp = self.blob[offset + 0x7d:offset + 0x7d + 1]
184 tckf = struct.unpack('b', tmp)[0]
185 return int(2000 / (tckm / mtb + tckf / ftb))
188 if __name__ == "__main__":
189 parser = argparse.ArgumentParser(description='SPD rom dumper')
190 parser.add_argument('--blob', required=True,
191 help='The ROM to search SPDs in.')
192 parser.add_argument('--spd4', action='store_true', default=False,
193 help='Search for DDR4 SPDs.')
194 parser.add_argument('--hex', action='store_true', default=False,
195 help='Store SPD in hex format otherwise binary.')
196 parser.add_argument('-v', '--verbose', help='increase output verbosity',
197 action='store_true')
198 parser.add_argument('--ignorecrc', help='Ignore CRC mismatch',
199 action='store_true', default=False)
200 args = parser.parse_args()
202 blob = open(args.blob, "rb").read()
204 if args.spd4:
205 p = SPD4Parser(blob, args.verbose, args.ignorecrc)
206 else:
207 raise Exception("Must specify one of the following arguments:\n--spd4")
209 offset = 0
210 cnt = 0
211 while True:
212 offset, length = p.search(offset)
213 if length == 0:
214 break
215 print("Found SPD at 0x%x" % offset)
216 print(" '%s', size %d, manufacturer %s (0x%04x) %d MT/s\n" %
217 (p.get_part_number(offset), length, p.get_manufacturer(offset),
218 p.get_manufacturer_id(offset), p.get_mtransfers(offset)))
219 filename = "spd-%d-%s-%s.bin" % (cnt, p.get_part_number(offset),
220 p.get_manufacturer(offset))
221 filename = filename.replace("/", "_")
222 filename = "".join([c for c in filename if c.isalpha() or c.isdigit()
223 or c == '-' or c == '.' or c == '_']).rstrip()
224 if not args.hex:
225 open(filename, "wb").write(blob[offset:offset + length])
226 else:
227 filename += ".hex"
228 with open(filename, "w") as fn:
229 j = 0
230 for i in blob[offset:offset + length]:
231 fn.write("%02X" % struct.unpack('B', i)[0])
232 fn.write(" " if j < 15 else "\n")
233 j = (j + 1) % 16
234 offset += 1
235 cnt += 1