... | ... |
@@ -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() |