raspi_revcode.py
81634fc5
 #!/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()