In a few cases a license was added: Stuff coming from Linux is "GPL-2.0" (not GPL-2.0-only!), build-release is by me and got the usual GPL-2.0-only treatment. uio_usbdebug and spkmodem had their licenses propagate to all their files. Change-Id: Ia5712bbaa417cb9e937834512351fcc0acfa16be Signed-off-by: Patrick Georgi <pgeorgi@google.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/41202 Reviewed-by: Angel Pons <th3fanbus@gmail.com> Reviewed-by: HAOUAS Elyes <ehaouas@noos.fr> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
		
			
				
	
	
		
			224 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| # spdtool - Tool for partial deblobbing of UEFI firmware images
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| #
 | |
| # 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
 |