#!/usr/bin/env python3 ## Help """ raspi-revcode 1.0 Parse Raspberry Pi revision codes. Usage: raspi-revcode [<code>] raspi-revcode -h|--help 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. """ ## Imports import sys import re import docopt ## Constants # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes CPUINFO_PATH = '/proc/cpuinfo' CPUINFO_RE = r'^Revision\s*:\s*(.*)$' # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#old-style-revision-codes # https://forums.raspberrypi.com//viewtopic.php?p=176865#p177014 OLD_STYLE_WARRANTY_BIT = 24 OLD_STYLE_WARRANTY_FIELD = 'W' OLD_STYLE = { 'names': ['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' ], } # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes NEW_STYLE_BIT = 23 NEW_STYLE_BITS = 'NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR' NEW_STYLE = { 'N': ['Overvoltage', { 0x0: 'Overvoltage allowed', 0x1: 'Overvoltage disallowed', }], 'O': ['OTP Program', { 0x0: 'OTP programming allowed', 0x1: 'OTP programming disallowed', }], 'Q': ['OTP Read', { 0x0: 'OTP reading allowed', 0x1: 'OTP reading disallowed', }], # 'uuu': ['Unused', { # u: 'Unused' for u in range(2**len('uuu')) # }], 'W': ['Warranty bit', { 0x0: 'Warranty is intact', 0x1: 'Warranty has been voided by overclocking', }], # 'u': ['Unused', { # u: 'Unused' for u in range(2**len('u')) # }], 'F': ['New flag', { 0x1: 'New-style revision', 0x0: 'Old-style revision', }], 'MMM': ['Memory size', { 0x0: '256MB', 0x1: '512MB', 0x2: '1GB', 0x3: '2GB', 0x4: '4GB', 0x5: '8GB', }], 'CCCC': ['Manufacturer', { 0x0: 'Sony UK', 0x1: 'Egoman', 0x2: 'Embest', 0x3: 'Sony Japan', 0x4: 'Embest', 0x5: 'Stadium', }], 'PPPP': ['Processor', { 0x0: 'BCM2835', 0x1: 'BCM2836', 0x2: 'BCM2837', 0x3: 'BCM2711', 0x4: 'BCM2712', }], 'TTTTTTTT': ['Type', { 0x0: 'A', 0x1: 'B', 0x2: 'A+', 0x3: 'B+', 0x4: '2B', 0x5: 'Alpha (early prototype)', 0x6: 'CM1', 0x8: '3B', 0x9: 'Zero', 0xa: 'CM3', 0xc: 'Zero W', 0xd: '3B+', 0xe: '3A+', 0xf: 'Internal use only', 0x10: 'CM3+', 0x11: '4B', 0x12: 'Zero 2 W', 0x13: '400', 0x14: 'CM4', 0x15: 'CM4S', 0x16: 'Internal use only', 0x17: '5', }], 'RRRR': ['Revision', { rev: f'1.{rev}' for rev in range(2**len('RRRR')) }], } ## Error def error(msg): print(msg, file=sys.stderr) exit(1) ## Main def main(): args = docopt.docopt(__doc__) code = args.get('<code>') try: if not code: with open(CPUINFO_PATH) as file: match = re.search(CPUINFO_RE, file.read(), re.MULTILINE) if not match: error(f"Could not find revision code in '{CPUINFO_PATH}'.") code = match.group(1) code = int(code, 16) except IOError: error(f"Could not open '{CPUINFO_PATH}'.") except ValueError: error(f"Code '{code}' is not hexadecimal number.") if (code >> NEW_STYLE_BIT) & 0x1: width = max(1 + len(name) for _, (name, _) in NEW_STYLE.items()) for field, _ in re.findall(r'((.)\2*)', NEW_STYLE_BITS[::-1]): length = len(field) value = code & ((1<<length)-1) code = code >> length info = NEW_STYLE.get(field) if info: name = info[0] desc = info[1].get(value, '<UNKNOWN>') print(f"{name:{width}}: {desc}") else: width = max(1 + len(name) for name in OLD_STYLE['names']) warranty = code & (1<<OLD_STYLE_WARRANTY_BIT) code = code & ~(1<<OLD_STYLE_WARRANTY_BIT) width = max(width, 1 + len(NEW_STYLE[OLD_STYLE_WARRANTY_FIELD][0])) info = OLD_STYLE.get(code) if info: for name, desc in zip(OLD_STYLE['names'], info): print(f"{name:{width}}: {desc}") info = NEW_STYLE[OLD_STYLE_WARRANTY_FIELD] name = info[0] desc = info[1].get(bool(warranty)) print(f"{name:{width}}: {desc}") if __name__ == '__main__': main()