Browse code

Add implementation

Robert Cranston authored on 29/10/2023 05:38:08
Showing 3 changed files

... ...
@@ -5,6 +5,71 @@ Parse [Raspberry Pi revision codes][].
5 5
 [`raspi-revcode`]: https://git.rcrnstn.net/rcrnstn/raspi-revcode
6 6
 [Raspberry Pi revision codes]: https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes
7 7
 
8
+## Usage
9
+
10
+`raspi-revcode --help`:
11
+
12
+```
13
+usage:
14
+  raspi-revcode [<code>]
15
+  raspi-revcode -h|--help
16
+  raspi-revcode --version
17
+
18
+Parse Raspberry Pi revision codes.
19
+
20
+positional arguments:
21
+  <code>
22
+    A hexadecimal revision code to parse. If not given, '/proc/cpuinfo' will be
23
+    used to get the revision code of the current device.
24
+
25
+optional arguments:
26
+  -h, --help
27
+    Show this help message and exit.
28
+  --version
29
+    Show program's version number and exit.
30
+```
31
+
32
+## Examples
33
+
34
+The following example shows calling `raspi-revcode` from the shell, and the
35
+produced output:
36
+
37
+```
38
+$ raspi-revcode 0012
39
+Type         : A+
40
+Revision     : 1.1
41
+Memory size  : 256MB
42
+Manufacturer : Sony UK
43
+New flag     : Old-style revision
44
+Warranty bit : Warranty is intact
45
+
46
+$ raspi-revcode 900021
47
+Revision     : 1.1
48
+Type         : A+
49
+Processor    : BCM2835
50
+Manufacturer : Sony UK
51
+Memory size  : 512MB
52
+New flag     : New-style revision
53
+Warranty bit : Warranty is intact
54
+OTP Read     : OTP reading allowed
55
+OTP Program  : OTP programming allowed
56
+Overvoltage  : Overvoltage allowed
57
+
58
+$ raspi-revcode ffffffff
59
+Revision     : 1.15
60
+Type         : <UNEXPECTED 0xff>
61
+Processor    : <UNEXPECTED 0xf>
62
+Manufacturer : <UNEXPECTED 0xf>
63
+Memory size  : <UNEXPECTED 0x7>
64
+New flag     : New-style revision
65
+Unused       : <UNEXPECTED 0x1>
66
+Warranty bit : Warranty has been voided by overclocking
67
+Unused       : <UNEXPECTED 0x7>
68
+OTP Read     : OTP reading disallowed
69
+OTP Program  : OTP programming disallowed
70
+Overvoltage  : Overvoltage disallowed
71
+```
72
+
8 73
 ## Install
9 74
 
10 75
 ### Prerequisites
11 76
new file mode 100755
... ...
@@ -0,0 +1,205 @@
1
+#!/usr/bin/env python3
2
+
3
+## Help
4
+
5
+"""
6
+usage:
7
+  raspi-revcode [<code>]
8
+  raspi-revcode -h|--help
9
+  raspi-revcode --version
10
+
11
+Parse Raspberry Pi revision codes.
12
+
13
+positional arguments:
14
+  <code>
15
+    A hexadecimal revision code to parse. If not given, '/proc/cpuinfo' will be
16
+    used to get the revision code of the current device.
17
+
18
+optional arguments:
19
+  -h, --help
20
+    Show this help message and exit.
21
+  --version
22
+    Show program's version number and exit.
23
+"""
24
+
25
+## Imports
26
+
27
+import sys
28
+import re
29
+import docopt
30
+
31
+## Constants
32
+
33
+VERSION = '1.0.0'
34
+
35
+# https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes
36
+CPUINFO_PATH = '/proc/cpuinfo'
37
+CPUINFO_RE   = r'^Revision\s*:\s*(.*)$'
38
+
39
+FIELD_RE = r'((.)\2*)'
40
+FIELDS = [
41
+    # Old-style.
42
+    # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#old-style-revision-codes
43
+    # https://forums.raspberrypi.com/viewtopic.php?p=176865#p177014
44
+    'uuuuuuuWFuuuuuuuIIIIIIIIIIIIIIII',
45
+    # New-style.
46
+    # https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes
47
+    'NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR',
48
+]
49
+
50
+SYMBOL_UNUSED   = 'u'
51
+SYMBOL_NEW_FLAG = 'F'
52
+SYMBOLS = {
53
+    'I':       [["Type", "Revision", "Memory size", "Manufacturer"], {
54
+        0x0002: ["B",    "1.0",      "256MB",       "Egoman"      ],
55
+        0x0003: ["B",    "1.0",      "256MB",       "Egoman"      ],
56
+        0x0004: ["B",    "2.0",      "256MB",       "Sony UK"     ],
57
+        0x0005: ["B",    "2.0",      "256MB",       "Qisda"       ],
58
+        0x0006: ["B",    "2.0",      "256MB",       "Egoman"      ],
59
+        0x0007: ["A",    "2.0",      "256MB",       "Egoman"      ],
60
+        0x0008: ["A",    "2.0",      "256MB",       "Sony UK"     ],
61
+        0x0009: ["A",    "2.0",      "256MB",       "Qisda"       ],
62
+        0x000d: ["B",    "2.0",      "512MB",       "Egoman"      ],
63
+        0x000e: ["B",    "2.0",      "512MB",       "Sony UK"     ],
64
+        0x000f: ["B",    "2.0",      "512MB",       "Egoman"      ],
65
+        0x0010: ["B+",   "1.2",      "512MB",       "Sony UK"     ],
66
+        0x0011: ["CM1",  "1.0",      "512MB",       "Sony UK"     ],
67
+        0x0012: ["A+",   "1.1",      "256MB",       "Sony UK"     ],
68
+        0x0013: ["B+",   "1.2",      "512MB",       "Embest"      ],
69
+        0x0014: ["CM1",  "1.0",      "512MB",       "Embest"      ],
70
+        0x0015: ["A+",   "1.1",      "256MB/512MB", "Embest"      ],
71
+    }],
72
+    'R':       [["Revision"], {
73
+        rev:    [f"1.{rev}"] for rev in range(2**len('RRRR'))
74
+    }],
75
+    'T':       [["Type"], {
76
+        0x00:   ["A"],
77
+        0x01:   ["B"],
78
+        0x02:   ["A+"],
79
+        0x03:   ["B+"],
80
+        0x04:   ["2B"],
81
+        0x05:   ["Alpha (early prototype)"],
82
+        0x06:   ["CM1"],
83
+        0x08:   ["3B"],
84
+        0x09:   ["Zero"],
85
+        0x0a:   ["CM3"],
86
+        0x0c:   ["Zero W"],
87
+        0x0d:   ["3B+"],
88
+        0x0e:   ["3A+"],
89
+        0x0f:   ["Internal use only"],
90
+        0x10:   ["CM3+"],
91
+        0x11:   ["4B"],
92
+        0x12:   ["Zero 2 W"],
93
+        0x13:   ["400"],
94
+        0x14:   ["CM4"],
95
+        0x15:   ["CM4S"],
96
+        0x16:   ["Internal use only"],
97
+        0x17:   ["5"],
98
+        0x18:   ["CM5"],
99
+        0x19:   ["500"],
100
+        0x1a:   ["CM5 Lite"],
101
+    }],
102
+    'P':       [["Processor"], {
103
+        0x0:    ["BCM2835"],
104
+        0x1:    ["BCM2836"],
105
+        0x2:    ["BCM2837"],
106
+        0x3:    ["BCM2711"],
107
+        0x4:    ["BCM2712"],
108
+    }],
109
+    'C':       [["Manufacturer"], {
110
+        0x0:    ["Sony UK"],
111
+        0x1:    ["Egoman"],
112
+        0x2:    ["Embest"],
113
+        0x3:    ["Sony Japan"],
114
+        0x4:    ["Embest"],
115
+        0x5:    ["Stadium"],
116
+    }],
117
+    'M':       [["Memory size"], {
118
+        0x0:    ["256MB"],
119
+        0x1:    ["512MB"],
120
+        0x2:    ["1GB"],
121
+        0x3:    ["2GB"],
122
+        0x4:    ["4GB"],
123
+        0x5:    ["8GB"],
124
+        0x6:    ["16GB"],
125
+    }],
126
+    'F':       [["New flag"], {
127
+        0x0:    ["Old-style revision"],
128
+        0x1:    ["New-style revision"],
129
+    }],
130
+    'W':       [["Warranty bit"], {
131
+        0x0:    ["Warranty is intact"],
132
+        0x1:    ["Warranty has been voided by overclocking"],
133
+    }],
134
+    'Q':       [["OTP Read"], {
135
+        0x0:    ["OTP reading allowed"],
136
+        0x1:    ["OTP reading disallowed"],
137
+    }],
138
+    'O':      [["OTP Program"], {
139
+        0x0:   ["OTP programming allowed"],
140
+        0x1:   ["OTP programming disallowed"],
141
+    }],
142
+    'N':      [["Overvoltage"], {
143
+        0x0:   ["Overvoltage allowed"],
144
+        0x1:   ["Overvoltage disallowed"],
145
+    }],
146
+}
147
+
148
+## Helpers
149
+
150
+def error(msg):
151
+    print(msg, file=sys.stderr)
152
+    exit(1)
153
+
154
+def unexpected(name, value):
155
+    return name, f"<UNEXPECTED 0x{value:x}>"
156
+
157
+def parse(code):
158
+    new_flag = (code >> FIELDS[0][::-1].index(SYMBOL_NEW_FLAG)) & 1
159
+    fields   = FIELDS[new_flag]
160
+    for field, symbol in re.findall(FIELD_RE, fields[::-1]):
161
+        length = len(field)
162
+        value  = code & ((1<<length)-1)
163
+        code   = code >> length
164
+        if symbol == SYMBOL_UNUSED:
165
+            if value != 0:
166
+                yield unexpected("Unused", value)
167
+            continue
168
+        names, values = SYMBOLS[symbol]
169
+        descs = values.get(value)
170
+        if not descs:
171
+            yield unexpected(names[0], value)
172
+            continue
173
+        yield from zip(names, descs)
174
+
175
+## Main
176
+
177
+def main():
178
+    # Parse arguments.
179
+    args = docopt.docopt(__doc__, version=VERSION)
180
+    # Get code string from argument.
181
+    code = args['<code>']
182
+    # Get code string from system.
183
+    if not code:
184
+        try:
185
+            with open(CPUINFO_PATH) as file:
186
+                match = re.search(CPUINFO_RE, file.read(), re.MULTILINE)
187
+        except IOError:
188
+            error(f"Could not open '{CPUINFO_PATH}'.")
189
+        if not match:
190
+            error(f"Could not find revision code in '{CPUINFO_PATH}'.")
191
+        code = match.group(1)
192
+    # Parse code string.
193
+    try:
194
+        code = int(code, 16)
195
+    except ValueError:
196
+        error(f"Could not parse revision code {code} as a hexadecimal number.")
197
+    # Parse code.
198
+    names_descs = list(parse(code))
199
+    # Print.
200
+    width = max(len(name) for name, _ in names_descs)
201
+    for name, desc in names_descs:
202
+        print(f"{name:{width}} : {desc}")
203
+
204
+if __name__ == '__main__':
205
+    main()
... ...
@@ -19,6 +19,7 @@ setup(
19 19
     ],
20 20
     python_requires='>=3, <4',
21 21
     install_requires=[
22
+        'docopt',
22 23
     ],
23 24
     py_modules=['raspi_revcode'],
24 25
     entry_points={