Patrick Rudolph has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/31385
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
util/spdtool: Add tool to extract SPD from BLOBs
Opens a binary file to extract DDR SPDs using known bits. At the moment only DDR4 SPDs are supported. Dumps the found SPDs into the current folder, as either binary or hex encoded file.
Works with python2 and python3.
Change-Id: I26dd73d43b724ea6891bb5b6e96856c42db8577c Signed-off-by: Patrick Rudolph patrick.rudolph@9elements.com --- A util/spdtool/description.md A util/spdtool/spdtool.py 2 files changed, 221 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/85/31385/1
diff --git a/util/spdtool/description.md b/util/spdtool/description.md new file mode 100644 index 0000000..39d4a5c --- /dev/null +++ b/util/spdtool/description.md @@ -0,0 +1,3 @@ +Dumps SPD ROMs from a given blob to seperate 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 0000000..c20ee57 --- /dev/null +++ b/util/spdtool/spdtool.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python + +# spdtool - Tool for partial deblobbing of Intel ME/TXE 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 + def get_matches(self): + """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 compability""" + 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: + continue + if not self.verify_match(i, offset): + continue + return offset, self.get_len(i, offset) + return -1, 0 + +class SPD4Parser(Parser): + def get_matches(self): + """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) | (self.blob_as_ord(offset + 0x7e) << 8) + crc_spd2 = self.blob_as_ord(offset + 0x7e) | (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 "" + return self.blob[offset + 0x149:offset + 0x15c + 1].decode('utf-8').rstrip() + + def get_manufacturer_id(self, offset): + """Return manufacturer ID""" + if offset + 0x141 > len(self.blob): + return 0xffff + return struct.unpack('H', self.blob[offset + 0x140:offset + 0x141 + 1])[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 + tckm = struct.unpack('B', self.blob[offset + 0x12:offset + 0x12 + 1])[0] + tckf = struct.unpack('b', self.blob[offset + 0x7d:offset + 0x7d + 1])[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 missmatch', + 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 + length = 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" + fn = open(filename, "w") + j = 0 + for i in blob[offset:offset + length]: + fn.write("%02X%s" % (struct.unpack('B',i)[0], " " if j < 15 else "\n")) + j = (j + 1) % 16 + offset += 1 + cnt += 1
build bot (Jenkins) has posted comments on this change. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
Patch Set 1:
(3 comments)
https://review.coreboot.org/#/c/31385/1/util/spdtool/description.md File util/spdtool/description.md:
https://review.coreboot.org/#/c/31385/1/util/spdtool/description.md@1 PS1, Line 1: Dumps SPD ROMs from a given blob to seperate files using known patterns 'seperate' may be misspelled - perhaps 'separate'?
https://review.coreboot.org/#/c/31385/1/util/spdtool/spdtool.py File util/spdtool/spdtool.py:
https://review.coreboot.org/#/c/31385/1/util/spdtool/spdtool.py@82 PS1, Line 82: """Helper for python2/python3 compability""" 'compability' may be misspelled - perhaps 'compatibility'?
https://review.coreboot.org/#/c/31385/1/util/spdtool/spdtool.py@182 PS1, Line 182: parser.add_argument('--ignorecrc', help='Ignore CRC missmatch', 'missmatch' may be misspelled - perhaps 'mismatch'?
Patrick Rudolph has uploaded a new patch set (#2). ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
util/spdtool: Add tool to extract SPD from BLOBs
Opens a binary file to extract DDR SPDs using known bits. At the moment only DDR4 SPDs are supported. Dumps the found SPDs into the current folder, as either binary or hex encoded file.
Works with python2 and python3.
Change-Id: I26dd73d43b724ea6891bb5b6e96856c42db8577c Signed-off-by: Patrick Rudolph patrick.rudolph@9elements.com --- A util/spdtool/description.md A util/spdtool/spdtool.py 2 files changed, 221 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/85/31385/2
Paul Menzel has posted comments on this change. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
Patch Set 2: Code-Review+1
(4 comments)
Do you have an example file?
https://review.coreboot.org/#/c/31385/2//COMMIT_MSG Commit Message:
https://review.coreboot.org/#/c/31385/2//COMMIT_MSG@7 PS2, Line 7: BLOBs I thinks, the archive was changed to use blobs.
https://review.coreboot.org/#/c/31385/2//COMMIT_MSG@9 PS2, Line 9: Opens a binary file to extract DDR SPDs using known bits. : At the moment only DDR4 SPDs are supported. : Dumps the found SPDs into the current folder, as either : binary or hex encoded file. Please format it as a list.
https://review.coreboot.org/#/c/31385/2/util/spdtool/spdtool.py File util/spdtool/spdtool.py:
https://review.coreboot.org/#/c/31385/2/util/spdtool/spdtool.py@3 PS2, Line 3: Tool for partial deblobbing of Intel ME/TXE firmware images Is that accurate?
https://review.coreboot.org/#/c/31385/2/util/spdtool/spdtool.py@17 PS2, Line 17: BLOB blob
Philipp Deppenwiese has uploaded a new patch set (#3) to the change originally created by Patrick Rudolph. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
util/spdtool: Add tool to extract SPD from BLOBs
Opens a binary file to extract DDR SPDs using known bits. At the moment only DDR4 SPDs are supported. Dumps the found SPDs into the current folder, as either binary or hex encoded file.
Works with python2 and python3.
Change-Id: I26dd73d43b724ea6891bb5b6e96856c42db8577c Signed-off-by: Patrick Rudolph patrick.rudolph@9elements.com --- A util/spdtool/description.md A util/spdtool/spdtool.py 2 files changed, 221 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/85/31385/3
Philipp Deppenwiese has posted comments on this change. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
Patch Set 3: Code-Review+2
Will Bradley has posted comments on this change. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
Patch Set 3:
(8 comments)
Hello there! I'm stepping in here with comments having never seen the rest of this codebase. So, I don't really know anything about the social aspects of code review in the context of coreboot. I was asked by a friend to take a look, so please take all of this with a grain of salt. Cheers!
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py File util/spdtool/spdtool.py:
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@1 PS3, Line 1: #!/usr/bin/env python Starting comments.
In general "/usr/bin/env python" means Python 2, so for someone to run this using Python3, they would have to explicitly invoke it with "python3 util/spdtool/spdtool.py ..."
I'm not super familiar with this codebase, but I'll quickly dump out the warnings and errors that flake8 found in this source code. Note that flake8 is a command line tool to check for PEP8 compliance. If you are not familiar with PEP8, it is documented here: https://www.python.org/dev/peps/pep-0008/
spdtool.py:5:1: E302 expected 2 blank lines, found 1 spdtool.py:10:5: E301 expected 1 blank line, found 0 spdtool.py:13:5: E301 expected 1 blank line, found 0 spdtool.py:16:5: E301 expected 1 blank line, found 0 spdtool.py:19:5: E301 expected 1 blank line, found 0 spdtool.py:22:5: E301 expected 1 blank line, found 0 spdtool.py:25:5: E301 expected 1 blank line, found 0 spdtool.py:55:80: E501 line too long (94 > 79 characters) spdtool.py:70:1: E302 expected 2 blank lines, found 1 spdtool.py:101:80: E501 line too long (91 > 79 characters) spdtool.py:102:80: E501 line too long (91 > 79 characters) spdtool.py:123:80: E501 line too long (84 > 79 characters) spdtool.py:126:8: E111 indentation is not a multiple of four spdtool.py:127:8: E111 indentation is not a multiple of four spdtool.py:128:12: E111 indentation is not a multiple of four spdtool.py:129:8: E111 indentation is not a multiple of four spdtool.py:129:80: E501 line too long (81 > 79 characters) spdtool.py:132:8: E111 indentation is not a multiple of four spdtool.py:133:8: E111 indentation is not a multiple of four spdtool.py:134:12: E111 indentation is not a multiple of four spdtool.py:136:8: E111 indentation is not a multiple of four spdtool.py:137:12: E111 indentation is not a multiple of four spdtool.py:138:8: E111 indentation is not a multiple of four spdtool.py:139:8: E111 indentation is not a multiple of four spdtool.py:140:8: E111 indentation is not a multiple of four spdtool.py:141:8: E111 indentation is not a multiple of four spdtool.py:142:8: E111 indentation is not a multiple of four spdtool.py:144:1: E305 expected 2 blank lines after class or function definition, found 1 spdtool.py:177:17: E128 continuation line under-indented for visual indent spdtool.py:179:80: E501 line too long (118 > 79 characters) spdtool.py:179:83: E225 missing whitespace around operator spdtool.py:179:93: E225 missing whitespace around operator spdtool.py:179:103: E225 missing whitespace around operator spdtool.py:187:55: E231 missing whitespace after ',' spdtool.py:187:80: E501 line too long (87 > 79 characters) spdtool.py:190:17: W292 no newline at end of file
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@91 PS3, Line 91: if self.blob_as_ord(offset) != i: : continue : if not self.verify_match(i, offset): : continue nit: The multiple "continue"s followed by the return are a bit harder to read than is necessary. Why not turn these into two anded clauses that return when they are satisfied?
for ...: for ...: if ... and ...: return offset, self....
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@99 PS3, Line 99: def get_matches(self): This function does not use "self". Consider lifting it out of the class into a private module function (leading underscore.)
def _get_ddr4_header_candidates(): ...
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@149 PS3, Line 149: if offset + 0x15c > len(self.blob): This looks like it should be >=, given that you are indexing self.blob[offset + 0x15c] below. If len(self.blob) == 0x15c and offset == 0, then self.blob[offset + 0x15c] will result in an IndexError.
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@155 PS3, Line 155: if offset + 0x141 > len(self.blob): This should also be >=
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@161 PS3, Line 161: if offset + 0x7d > len(self.blob): Again here
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@193 PS3, Line 193: offset = 0 : length = 0 Dead code can be deleted.
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@212 PS3, Line 212: fn = open(filename, "w") idiomatic Python is to use a "with" block here to ensure that all variants of Python will properly flush/close the file prior to the next loop coming along and opening the file again.
with open(filename, "w") as fn: j = 0 for ...
Hello Marcello Sylvester Bauer, Paul Menzel, Philipp Deppenwiese, build bot (Jenkins), Jens Drenhaus,
I'd like you to reexamine a change. Please visit
https://review.coreboot.org/c/coreboot/+/31385
to look at the new patch set (#4).
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
util/spdtool: Add tool to extract SPD from BLOBs
Opens a binary file to extract DDR SPDs using known bits. At the moment only DDR4 SPDs are supported. Dumps the found SPDs into the current folder, as either binary or hex encoded file.
Works with python2 and python3.
Change-Id: I26dd73d43b724ea6891bb5b6e96856c42db8577c Signed-off-by: Patrick Rudolph patrick.rudolph@9elements.com --- A util/spdtool/description.md A util/spdtool/spdtool.py 2 files changed, 238 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/85/31385/4
Patrick Rudolph has posted comments on this change. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
Patch Set 4:
(8 comments)
Fixed most of the PEP8 errors.
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py File util/spdtool/spdtool.py:
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@1 PS3, Line 1: #!/usr/bin/env python
Starting comments. […]
"/usr/bin/env python" as it's compatible to python2 and python3.
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@91 PS3, Line 91: if self.blob_as_ord(offset) != i: : continue : if not self.verify_match(i, offset): : continue
nit: The multiple "continue"s followed by the return are a bit harder to read than is necessary. […]
Done
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@99 PS3, Line 99: def get_matches(self):
This function does not use "self". […]
Done
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@149 PS3, Line 149: if offset + 0x15c > len(self.blob):
This looks like it should be >=, given that you are indexing self.blob[offset + 0x15c] below. […]
Done
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@155 PS3, Line 155: if offset + 0x141 > len(self.blob):
This should also be >=
Done
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@161 PS3, Line 161: if offset + 0x7d > len(self.blob):
Again here
Done
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@193 PS3, Line 193: offset = 0 : length = 0
Dead code can be deleted.
Done
https://review.coreboot.org/#/c/31385/3/util/spdtool/spdtool.py@212 PS3, Line 212: fn = open(filename, "w")
idiomatic Python is to use a "with" block here to ensure that all variants of Python will properly f […]
Done
Philipp Deppenwiese has posted comments on this change. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
Patch Set 4: Code-Review+2
Patrick Georgi has submitted this change and it was merged. ( https://review.coreboot.org/c/coreboot/+/31385 )
Change subject: util/spdtool: Add tool to extract SPD from BLOBs ......................................................................
util/spdtool: Add tool to extract SPD from BLOBs
Opens a binary file to extract DDR SPDs using known bits. At the moment only DDR4 SPDs are supported. Dumps the found SPDs into the current folder, as either binary or hex encoded file.
Works with python2 and python3.
Change-Id: I26dd73d43b724ea6891bb5b6e96856c42db8577c Signed-off-by: Patrick Rudolph patrick.rudolph@9elements.com Reviewed-on: https://review.coreboot.org/c/coreboot/+/31385 Tested-by: build bot (Jenkins) no-reply@coreboot.org Reviewed-by: Philipp Deppenwiese zaolin.daisuki@gmail.com --- A util/spdtool/description.md A util/spdtool/spdtool.py 2 files changed, 238 insertions(+), 0 deletions(-)
Approvals: build bot (Jenkins): Verified Philipp Deppenwiese: Looks good to me, approved
diff --git a/util/spdtool/description.md b/util/spdtool/description.md new file mode 100644 index 0000000..3d58c39 --- /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 0000000..f6f9f85 --- /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