... | ... |
@@ -5,6 +5,25 @@ 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 |
+raspi-revcode 1.0 |
|
14 |
+ |
|
15 |
+Parse Raspberry Pi revision codes. |
|
16 |
+ |
|
17 |
+Usage: |
|
18 |
+ raspi-revcode [<code>] |
|
19 |
+ raspi-revcode -h|--help |
|
20 |
+ |
|
21 |
+Arguments: |
|
22 |
+ <code> |
|
23 |
+ A hexadecimal revision code to parse. If not given, '/proc/cpuinfo' will be |
|
24 |
+ used to get the revision code of the current device. |
|
25 |
+``` |
|
26 |
+ |
|
8 | 27 |
## Install |
9 | 28 |
|
10 | 29 |
Make sure [Python is installed][Python download], [`pip`][] is available and |
11 | 30 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,192 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+ |
|
4 |
+## Help |
|
5 |
+ |
|
6 |
+""" |
|
7 |
+raspi-revcode 1.0 |
|
8 |
+ |
|
9 |
+Parse Raspberry Pi revision codes. |
|
10 |
+ |
|
11 |
+Usage: |
|
12 |
+ raspi-revcode [<code>] |
|
13 |
+ raspi-revcode -h|--help |
|
14 |
+ |
|
15 |
+Arguments: |
|
16 |
+ <code> |
|
17 |
+ A hexadecimal revision code to parse. If not given, '/proc/cpuinfo' will be |
|
18 |
+ used to get the revision code of the current device. |
|
19 |
+""" |
|
20 |
+ |
|
21 |
+ |
|
22 |
+## Imports |
|
23 |
+ |
|
24 |
+import sys |
|
25 |
+import re |
|
26 |
+import docopt |
|
27 |
+ |
|
28 |
+ |
|
29 |
+## Constants |
|
30 |
+ |
|
31 |
+# https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-revision-codes |
|
32 |
+CPUINFO_PATH = '/proc/cpuinfo' |
|
33 |
+CPUINFO_RE = r'^Revision\s*:\s*(.*)$' |
|
34 |
+ |
|
35 |
+# https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#old-style-revision-codes |
|
36 |
+# https://forums.raspberrypi.com//viewtopic.php?p=176865#p177014 |
|
37 |
+OLD_STYLE_WARRANTY_BIT = 24 |
|
38 |
+OLD_STYLE_WARRANTY_FIELD = 'W' |
|
39 |
+OLD_STYLE = { |
|
40 |
+ 'names': ['Type', 'Revision', 'Memory size', 'Manufacturer'], |
|
41 |
+ 0x0002: ['B', '1.0', '256MB', 'Egoman' ], |
|
42 |
+ 0x0003: ['B', '1.0', '256MB', 'Egoman' ], |
|
43 |
+ 0x0004: ['B', '2.0', '256MB', 'Sony UK' ], |
|
44 |
+ 0x0005: ['B', '2.0', '256MB', 'Qisda' ], |
|
45 |
+ 0x0006: ['B', '2.0', '256MB', 'Egoman' ], |
|
46 |
+ 0x0007: ['A', '2.0', '256MB', 'Egoman' ], |
|
47 |
+ 0x0008: ['A', '2.0', '256MB', 'Sony UK' ], |
|
48 |
+ 0x0009: ['A', '2.0', '256MB', 'Qisda' ], |
|
49 |
+ 0x000d: ['B', '2.0', '512MB', 'Egoman' ], |
|
50 |
+ 0x000e: ['B', '2.0', '512MB', 'Sony UK' ], |
|
51 |
+ 0x000f: ['B', '2.0', '512MB', 'Egoman' ], |
|
52 |
+ 0x0010: ['B+', '1.2', '512MB', 'Sony UK' ], |
|
53 |
+ 0x0011: ['CM1', '1.0', '512MB', 'Sony UK' ], |
|
54 |
+ 0x0012: ['A+', '1.1', '256MB', 'Sony UK' ], |
|
55 |
+ 0x0013: ['B+', '1.2', '512MB', 'Embest' ], |
|
56 |
+ 0x0014: ['CM1', '1.0', '512MB', 'Embest' ], |
|
57 |
+ 0x0015: ['A+', '1.1', '256MB/512MB', 'Embest' ], |
|
58 |
+} |
|
59 |
+ |
|
60 |
+# https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes |
|
61 |
+NEW_STYLE_BIT = 23 |
|
62 |
+NEW_STYLE_BITS = 'NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR' |
|
63 |
+NEW_STYLE = { |
|
64 |
+ 'N': ['Overvoltage', { |
|
65 |
+ 0x0: 'Overvoltage allowed', |
|
66 |
+ 0x1: 'Overvoltage disallowed', |
|
67 |
+ }], |
|
68 |
+ 'O': ['OTP Program', { |
|
69 |
+ 0x0: 'OTP programming allowed', |
|
70 |
+ 0x1: 'OTP programming disallowed', |
|
71 |
+ }], |
|
72 |
+ 'Q': ['OTP Read', { |
|
73 |
+ 0x0: 'OTP reading allowed', |
|
74 |
+ 0x1: 'OTP reading disallowed', |
|
75 |
+ }], |
|
76 |
+ # 'uuu': ['Unused', { |
|
77 |
+ # u: 'Unused' for u in range(2**len('uuu')) |
|
78 |
+ # }], |
|
79 |
+ 'W': ['Warranty bit', { |
|
80 |
+ 0x0: 'Warranty is intact', |
|
81 |
+ 0x1: 'Warranty has been voided by overclocking', |
|
82 |
+ }], |
|
83 |
+ # 'u': ['Unused', { |
|
84 |
+ # u: 'Unused' for u in range(2**len('u')) |
|
85 |
+ # }], |
|
86 |
+ 'F': ['New flag', { |
|
87 |
+ 0x1: 'New-style revision', |
|
88 |
+ 0x0: 'Old-style revision', |
|
89 |
+ }], |
|
90 |
+ 'MMM': ['Memory size', { |
|
91 |
+ 0x0: '256MB', |
|
92 |
+ 0x1: '512MB', |
|
93 |
+ 0x2: '1GB', |
|
94 |
+ 0x3: '2GB', |
|
95 |
+ 0x4: '4GB', |
|
96 |
+ 0x5: '8GB', |
|
97 |
+ }], |
|
98 |
+ 'CCCC': ['Manufacturer', { |
|
99 |
+ 0x0: 'Sony UK', |
|
100 |
+ 0x1: 'Egoman', |
|
101 |
+ 0x2: 'Embest', |
|
102 |
+ 0x3: 'Sony Japan', |
|
103 |
+ 0x4: 'Embest', |
|
104 |
+ 0x5: 'Stadium', |
|
105 |
+ }], |
|
106 |
+ 'PPPP': ['Processor', { |
|
107 |
+ 0x0: 'BCM2835', |
|
108 |
+ 0x1: 'BCM2836', |
|
109 |
+ 0x2: 'BCM2837', |
|
110 |
+ 0x3: 'BCM2711', |
|
111 |
+ 0x4: 'BCM2712', |
|
112 |
+ }], |
|
113 |
+ 'TTTTTTTT': ['Type', { |
|
114 |
+ 0x0: 'A', |
|
115 |
+ 0x1: 'B', |
|
116 |
+ 0x2: 'A+', |
|
117 |
+ 0x3: 'B+', |
|
118 |
+ 0x4: '2B', |
|
119 |
+ 0x5: 'Alpha (early prototype)', |
|
120 |
+ 0x6: 'CM1', |
|
121 |
+ 0x8: '3B', |
|
122 |
+ 0x9: 'Zero', |
|
123 |
+ 0xa: 'CM3', |
|
124 |
+ 0xc: 'Zero W', |
|
125 |
+ 0xd: '3B+', |
|
126 |
+ 0xe: '3A+', |
|
127 |
+ 0xf: 'Internal use only', |
|
128 |
+ 0x10: 'CM3+', |
|
129 |
+ 0x11: '4B', |
|
130 |
+ 0x12: 'Zero 2 W', |
|
131 |
+ 0x13: '400', |
|
132 |
+ 0x14: 'CM4', |
|
133 |
+ 0x15: 'CM4S', |
|
134 |
+ 0x16: 'Internal use only', |
|
135 |
+ 0x17: '5', |
|
136 |
+ }], |
|
137 |
+ 'RRRR': ['Revision', { |
|
138 |
+ rev: f'1.{rev}' for rev in range(2**len('RRRR')) |
|
139 |
+ }], |
|
140 |
+} |
|
141 |
+ |
|
142 |
+## Error |
|
143 |
+ |
|
144 |
+def error(msg): |
|
145 |
+ print(msg, file=sys.stderr) |
|
146 |
+ exit(1) |
|
147 |
+ |
|
148 |
+## Main |
|
149 |
+ |
|
150 |
+def main(): |
|
151 |
+ args = docopt.docopt(__doc__) |
|
152 |
+ code = args.get('<code>') |
|
153 |
+ try: |
|
154 |
+ if not code: |
|
155 |
+ with open(CPUINFO_PATH) as file: |
|
156 |
+ match = re.search(CPUINFO_RE, file.read(), re.MULTILINE) |
|
157 |
+ if not match: |
|
158 |
+ error(f"Could not find revision code in '{CPUINFO_PATH}'.") |
|
159 |
+ code = match.group(1) |
|
160 |
+ code = int(code, 16) |
|
161 |
+ except IOError: |
|
162 |
+ error(f"Could not open '{CPUINFO_PATH}'.") |
|
163 |
+ except ValueError: |
|
164 |
+ error(f"Code '{code}' is not hexadecimal number.") |
|
165 |
+ if (code >> NEW_STYLE_BIT) & 0x1: |
|
166 |
+ width = max(1 + len(name) for _, (name, _) in NEW_STYLE.items()) |
|
167 |
+ for field, _ in re.findall(r'((.)\2*)', NEW_STYLE_BITS[::-1]): |
|
168 |
+ length = len(field) |
|
169 |
+ value = code & ((1<<length)-1) |
|
170 |
+ code = code >> length |
|
171 |
+ info = NEW_STYLE.get(field) |
|
172 |
+ if info: |
|
173 |
+ name = info[0] |
|
174 |
+ desc = info[1].get(value, '<UNKNOWN>') |
|
175 |
+ print(f"{name:{width}}: {desc}") |
|
176 |
+ else: |
|
177 |
+ width = max(1 + len(name) for name in OLD_STYLE['names']) |
|
178 |
+ warranty = code & (1<<OLD_STYLE_WARRANTY_BIT) |
|
179 |
+ code = code & ~(1<<OLD_STYLE_WARRANTY_BIT) |
|
180 |
+ width = max(width, 1 + len(NEW_STYLE[OLD_STYLE_WARRANTY_FIELD][0])) |
|
181 |
+ info = OLD_STYLE.get(code) |
|
182 |
+ if info: |
|
183 |
+ for name, desc in zip(OLD_STYLE['names'], info): |
|
184 |
+ print(f"{name:{width}}: {desc}") |
|
185 |
+ info = NEW_STYLE[OLD_STYLE_WARRANTY_FIELD] |
|
186 |
+ name = info[0] |
|
187 |
+ desc = info[1].get(bool(warranty)) |
|
188 |
+ print(f"{name:{width}}: {desc}") |
|
189 |
+ |
|
190 |
+ |
|
191 |
+if __name__ == '__main__': |
|
192 |
+ main() |