#!/usr/bin/env python3 ## Help """ usage: raspi-revcode [<code>] raspi-revcode -h|--help raspi-revcode --version Parse Raspberry Pi revision codes. positional arguments: <code> A hexadecimal revision code to parse. If not given, '/proc/cpuinfo' will be used to get the revision code of the current device. optional arguments: -h, --help Show this help message and exit. --version Show program's version number and exit. """ ## Imports import sys import re import docopt ## Constants VERSION = '1.0.0' # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes CPUINFO_PATH = '/proc/cpuinfo' CPUINFO_RE = r'^Revision\s*:\s*(.*)$' FIELD_RE = r'((.)\2*)' FIELDS = [ # Old-style. # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#old-style-revision-codes # https://forums.raspberrypi.com/viewtopic.php?p=176865#p177014 'uuuuuuuWFuuuuuuuIIIIIIIIIIIIIIII', # New-style. # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes 'NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR', ] SYMBOL_UNUSED = 'u' SYMBOL_NEW_FLAG = 'F' SYMBOLS = { 'I': [["Type", "Revision", "Memory size", "Manufacturer"], { 0x0002: ["B", "1.0", "256MB", "Egoman" ], 0x0003: ["B", "1.0", "256MB", "Egoman" ], 0x0004: ["B", "2.0", "256MB", "Sony UK" ], 0x0005: ["B", "2.0", "256MB", "Qisda" ], 0x0006: ["B", "2.0", "256MB", "Egoman" ], 0x0007: ["A", "2.0", "256MB", "Egoman" ], 0x0008: ["A", "2.0", "256MB", "Sony UK" ], 0x0009: ["A", "2.0", "256MB", "Qisda" ], 0x000d: ["B", "2.0", "512MB", "Egoman" ], 0x000e: ["B", "2.0", "512MB", "Sony UK" ], 0x000f: ["B", "2.0", "512MB", "Egoman" ], 0x0010: ["B+", "1.2", "512MB", "Sony UK" ], 0x0011: ["CM1", "1.0", "512MB", "Sony UK" ], 0x0012: ["A+", "1.1", "256MB", "Sony UK" ], 0x0013: ["B+", "1.2", "512MB", "Embest" ], 0x0014: ["CM1", "1.0", "512MB", "Embest" ], 0x0015: ["A+", "1.1", "256MB/512MB", "Embest" ], }], 'R': [["Revision"], { rev: [f"1.{rev}"] for rev in range(2**len('RRRR')) }], 'T': [["Type"], { 0x00: ["A"], 0x01: ["B"], 0x02: ["A+"], 0x03: ["B+"], 0x04: ["2B"], 0x05: ["Alpha (early prototype)"], 0x06: ["CM1"], 0x08: ["3B"], 0x09: ["Zero"], 0x0a: ["CM3"], 0x0c: ["Zero W"], 0x0d: ["3B+"], 0x0e: ["3A+"], 0x0f: ["Internal use only"], 0x10: ["CM3+"], 0x11: ["4B"], 0x12: ["Zero 2 W"], 0x13: ["400"], 0x14: ["CM4"], 0x15: ["CM4S"], 0x16: ["Internal use only"], 0x17: ["5"], 0x18: ["CM5"], 0x19: ["500"], 0x1a: ["CM5 Lite"], }], 'P': [["Processor"], { 0x0: ["BCM2835"], 0x1: ["BCM2836"], 0x2: ["BCM2837"], 0x3: ["BCM2711"], 0x4: ["BCM2712"], }], 'C': [["Manufacturer"], { 0x0: ["Sony UK"], 0x1: ["Egoman"], 0x2: ["Embest"], 0x3: ["Sony Japan"], 0x4: ["Embest"], 0x5: ["Stadium"], }], 'M': [["Memory size"], { 0x0: ["256MB"], 0x1: ["512MB"], 0x2: ["1GB"], 0x3: ["2GB"], 0x4: ["4GB"], 0x5: ["8GB"], 0x6: ["16GB"], }], 'F': [["New flag"], { 0x0: ["Old-style revision"], 0x1: ["New-style revision"], }], 'W': [["Warranty bit"], { 0x0: ["Warranty is intact"], 0x1: ["Warranty has been voided by overclocking"], }], 'Q': [["OTP Read"], { 0x0: ["OTP reading allowed"], 0x1: ["OTP reading disallowed"], }], 'O': [["OTP Program"], { 0x0: ["OTP programming allowed"], 0x1: ["OTP programming disallowed"], }], 'N': [["Overvoltage"], { 0x0: ["Overvoltage allowed"], 0x1: ["Overvoltage disallowed"], }], } ## Helpers def error(msg): print(msg, file=sys.stderr) exit(1) def unexpected(name, value): return name, f"<UNEXPECTED 0x{value:x}>" def parse(code): new_flag = (code >> FIELDS[0][::-1].index(SYMBOL_NEW_FLAG)) & 1 fields = FIELDS[new_flag] for field, symbol in re.findall(FIELD_RE, fields[::-1]): length = len(field) value = code & ((1<<length)-1) code = code >> length if symbol == SYMBOL_UNUSED: if value != 0: yield unexpected("Unused", value) continue names, values = SYMBOLS[symbol] descs = values.get(value) if not descs: yield unexpected(names[0], value) continue yield from zip(names, descs) ## Main def main(): # Parse arguments. args = docopt.docopt(__doc__, version=VERSION) # Get code string from argument. code = args['<code>'] # Get code string from system. if not code: try: with open(CPUINFO_PATH) as file: match = re.search(CPUINFO_RE, file.read(), re.MULTILINE) except IOError: error(f"Could not open '{CPUINFO_PATH}'.") if not match: error(f"Could not find revision code in '{CPUINFO_PATH}'.") code = match.group(1) # Parse code string. try: code = int(code, 16) except ValueError: error(f"Could not parse revision code {code} as a hexadecimal number.") # Parse code. names_descs = list(parse(code)) # Print. width = max(len(name) for name, _ in names_descs) for name, desc in names_descs: print(f"{name:{width}} : {desc}") if __name__ == '__main__': main()