diff options
Diffstat (limited to 'util')
-rw-r--r-- | util/spdtool/description.md | 3 | ||||
-rw-r--r-- | util/spdtool/spdtool.py | 235 |
2 files changed, 238 insertions, 0 deletions
diff --git a/util/spdtool/description.md b/util/spdtool/description.md new file mode 100644 index 0000000000..3d58c39ec9 --- /dev/null +++ b/util/spdtool/description.md @@ -0,0 +1,3 @@ +Dumps SPD ROMs from a given blob to separate files using known patterns +and reserved bits. Useful for analysing firmware that holds SPDs on boards +that have soldered down DRAM. `python` diff --git a/util/spdtool/spdtool.py b/util/spdtool/spdtool.py new file mode 100644 index 0000000000..f6f9f85122 --- /dev/null +++ b/util/spdtool/spdtool.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python + +# spdtool - Tool for partial deblobbing of UEFI firmware images +# Copyright (C) 2019 9elements Agency GmbH <patrick.rudolph@9elements.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# +# Parse a blob and search for SPD files. +# First it is searched for a possible SPD header. +# +# For each candidate the function verify_match is invoked to check +# additional fields (known bits, reserved bits, CRC, ...) +# +# Dumps the found SPDs into the current folder. +# +# Implemented: +# DDR4 SPDs +# + +import argparse +import crc16 +import struct + + +class Parser(object): + def __init__(self, blob, verbose=False, ignorecrc=False): + self.blob = blob + self.ignorecrc = ignorecrc + self.verbose = verbose + + @staticmethod + def get_matches(): + """Return the first byte to look for""" + raise Exception("Function not implemented") + + def verify_match(self, header, offset): + """Return true if it looks like a SPD""" + raise Exception("Function not implemented") + + def get_len(self, header, offset): + """Return the length of the SPD""" + raise Exception("Function not implemented") + + def get_part_number(self, offset): + """Return the part number in SPD""" + return "" + + def get_manufacturer_id(self, offset): + """Return the manufacturer ID in SPD""" + return 0xffff + + def get_mtransfers(self, offset): + """Return the number of MT/s""" + return 0 + + def get_manufacturer(self, offset): + """Return manufacturer as string""" + id = self.get_manufacturer_id(offset) + if id == 0xffff: + return "Unknown" + ids = { + 0x2c80: "Crucial/Micron", + 0x4304: "Ramaxel", + 0x4f01: "Transcend", + 0x9801: "Kingston", + 0x987f: "Hynix", + 0x9e02: "Corsair", + 0xb004: "OCZ", + 0xad80: "Hynix/Hyundai", + 0xb502: "SuperTalent", + 0xcd04: "GSkill", + 0xce80: "Samsung", + 0xfe02: "Elpida", + 0xff2c: "Micron", + } + if id in ids: + return ids[id] + return "Unknown" + + def blob_as_ord(self, offset): + """Helper for python2/python3 compatibility""" + return self.blob[offset] if type(self.blob[offset]) is int \ + else ord(self.blob[offset]) + + def search(self, start): + """Search for SPD at start. Returns -1 on error or offset + if found. + """ + for i in self.get_matches(): + for offset in range(start, len(self.blob)): + if self.blob_as_ord(offset) == i and \ + self.verify_match(i, offset): + return offset, self.get_len(i, offset) + return -1, 0 + + +class SPD4Parser(Parser): + @staticmethod + def get_matches(): + """Return DDR4 possible header candidates""" + ret = [] + for i in [1, 2, 3, 4]: + for j in [1, 2]: + ret.append(i + j * 16) + return ret + + def verify_match(self, header, offset): + """Verify DDR4 specific bit fields.""" + # offset 0 is a candidate, no need to validate + if self.blob_as_ord(offset + 1) == 0xff: + return False + if self.blob_as_ord(offset + 2) != 0x0c: + return False + if self.blob_as_ord(offset + 5) & 0xc0 > 0: + return False + if self.blob_as_ord(offset + 6) & 0xc > 0: + return False + if self.blob_as_ord(offset + 7) & 0xc0 > 0: + return False + if self.blob_as_ord(offset + 8) != 0: + return False + if self.blob_as_ord(offset + 9) & 0xf > 0: + return False + if self.verbose: + print("%x: Looks like DDR4 SPD" % offset) + + crc = crc16.crc16xmodem(self.blob[offset:offset + 0x7d + 1]) + # Vendors ignore the endianness... + crc_spd1 = self.blob_as_ord(offset + 0x7f) + crc_spd1 |= (self.blob_as_ord(offset + 0x7e) << 8) + crc_spd2 = self.blob_as_ord(offset + 0x7e) + crc_spd2 |= (self.blob_as_ord(offset + 0x7f) << 8) + if crc != crc_spd1 and crc != crc_spd2: + if self.verbose: + print("%x: CRC16 doesn't match" % offset) + if not self.ignorecrc: + return False + + return True + + def get_len(self, header, offset): + """Return the length of the SPD found.""" + if (header >> 4) & 7 == 1: + return 256 + if (header >> 4) & 7 == 2: + return 512 + return 0 + + def get_part_number(self, offset): + """Return part number as string""" + if offset + 0x15c >= len(self.blob): + return "" + tmp = self.blob[offset + 0x149:offset + 0x15c + 1] + return tmp.decode('utf-8').rstrip() + + def get_manufacturer_id(self, offset): + """Return manufacturer ID""" + if offset + 0x141 >= len(self.blob): + return 0xffff + tmp = self.blob[offset + 0x140:offset + 0x141 + 1] + return struct.unpack('H', tmp)[0] + + def get_mtransfers(self, offset): + """Return MT/s as specified by MTB and FTB""" + if offset + 0x7d >= len(self.blob): + return 0 + + if self.blob_as_ord(offset + 0x11) != 0: + return 0 + mtb = 8.0 + ftb = 1000.0 + tmp = self.blob[offset + 0x12:offset + 0x12 + 1] + tckm = struct.unpack('B', tmp)[0] + tmp = self.blob[offset + 0x7d:offset + 0x7d + 1] + tckf = struct.unpack('b', tmp)[0] + return int(2000 / (tckm / mtb + tckf / ftb)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='SPD rom dumper') + parser.add_argument('--blob', required=True, + help='The ROM to search SPDs in.') + parser.add_argument('--spd4', action='store_true', default=False, + help='Search for DDR4 SPDs.') + parser.add_argument('--hex', action='store_true', default=False, + help='Store SPD in hex format otherwise binary.') + parser.add_argument('-v', '--verbose', help='increase output verbosity', + action='store_true') + parser.add_argument('--ignorecrc', help='Ignore CRC mismatch', + action='store_true', default=False) + args = parser.parse_args() + + blob = open(args.blob, "rb").read() + + if args.spd4: + p = SPD4Parser(blob, args.verbose, args.ignorecrc) + else: + raise Exception("Must specify one of the following arguments:\n--spd4") + + offset = 0 + cnt = 0 + while True: + offset, length = p.search(offset) + if length == 0: + break + print("Found SPD at 0x%x" % offset) + print(" '%s', size %d, manufacturer %s (0x%04x) %d MT/s\n" % + (p.get_part_number(offset), length, p.get_manufacturer(offset), + p.get_manufacturer_id(offset), p.get_mtransfers(offset))) + filename = "spd-%d-%s-%s.bin" % (cnt, p.get_part_number(offset), + p.get_manufacturer(offset)) + filename = filename.replace("/", "_") + filename = "".join([c for c in filename if c.isalpha() or c.isdigit() + or c == '-' or c == '.' or c == '_']).rstrip() + if not args.hex: + open(filename, "wb").write(blob[offset:offset + length]) + else: + filename += ".hex" + with open(filename, "w") as fn: + j = 0 + for i in blob[offset:offset + length]: + fn.write("%02X" % struct.unpack('B', i)[0]) + fn.write(" " if j < 15 else "\n") + j = (j + 1) % 16 + offset += 1 + cnt += 1 |