Skip to content

Commit e88f9ff

Browse files
committed
Added improved Pi Revision Code detection
1 parent 2b3b7ef commit e88f9ff

File tree

5 files changed

+411
-3
lines changed

5 files changed

+411
-3
lines changed

adafruit_platformdetect/board.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,16 @@ def _pi_id(self) -> Optional[str]:
201201
# Check for Pi boards:
202202
pi_rev_code = self._pi_rev_code()
203203
if pi_rev_code:
204-
for model, codes in boards._PI_REV_CODES.items():
205-
if pi_rev_code in codes:
206-
return model
204+
from adafruit_platformdetect.revcodes import PiDecoder
207205

206+
try:
207+
decoder = PiDecoder(pi_rev_code)
208+
model = boards._PI_MODELS[decoder.type_raw]
209+
if isinstance(model, dict):
210+
model = model[decoder.revision]
211+
return model
212+
except ValueError:
213+
pass
208214
# We may be on a non-Raspbian OS, so try to lazily determine
209215
# the version based on `get_device_model`
210216
else:

adafruit_platformdetect/constants/boards.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
RASPBERRY_PI_AVNET_IIOT_GW = "RASPBERY_PI_AVNET_IIOT_GW"
124124
RASPBERRY_PI_400 = "RASPBERRY_PI_400"
125125
RASPBERRY_PI_CM4 = "RASPBERRY_PI_CM4"
126+
RASPBERRY_PI_CM4S = "RASPBERRY_PI_CM4S"
126127

127128
ODROID_C1 = "ODROID_C1"
128129
ODROID_C1_PLUS = "ODROID_C1_PLUS"
@@ -341,6 +342,7 @@
341342
RASPBERRY_PI_CM3,
342343
RASPBERRY_PI_CM3_PLUS,
343344
RASPBERRY_PI_CM4,
345+
RASPBERRY_PI_CM4S,
344346
)
345347

346348
_ODROID_40_PIN_IDS = (
@@ -564,6 +566,31 @@
564566
RASPBERRY_PI_ZERO_2_W: ("902120", "2902120"),
565567
}
566568

569+
_PI_MODELS = {
570+
0x00: RASPBERRY_PI_A,
571+
0x01: {
572+
1.0: RASPBERRY_PI_B_REV1,
573+
2.0: RASPBERRY_PI_B_REV2,
574+
},
575+
0x02: RASPBERRY_PI_A_PLUS,
576+
0x03: RASPBERRY_PI_B_PLUS,
577+
0x04: RASPBERRY_PI_2B,
578+
0x06: RASPBERRY_PI_CM1,
579+
0x08: RASPBERRY_PI_3B,
580+
0x09: RASPBERRY_PI_ZERO,
581+
0x0A: RASPBERRY_PI_CM3,
582+
0x0B: RASPBERRY_PI_AVNET_IIOT_GW,
583+
0x0C: RASPBERRY_PI_ZERO_W,
584+
0x0D: RASPBERRY_PI_3B_PLUS,
585+
0x0E: RASPBERRY_PI_3A_PLUS,
586+
0x10: RASPBERRY_PI_CM3_PLUS,
587+
0x11: RASPBERRY_PI_4B,
588+
0x12: RASPBERRY_PI_ZERO_2_W,
589+
0x13: RASPBERRY_PI_400,
590+
0x14: RASPBERRY_PI_CM4,
591+
0x15: RASPBERRY_PI_CM4S,
592+
}
593+
567594
# Onion omega boards
568595
_ONION_OMEGA_BOARD_IDS = (ONION_OMEGA, ONION_OMEGA2)
569596

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
# Class to help with Raspberry Pi Rev Codes
6+
# Written by Melissa LeBlanc-Williams for Adafruit Industries
7+
#
8+
# Data values from https://github.com/raspberrypi/documentation/blob/develop/
9+
# documentation/asciidoc/computers/raspberry-pi/revision-codes.adoc#new-style-revision-codes
10+
11+
NEW_OVERVOLTAGE = (
12+
"Overvoltage allowed",
13+
"Overvoltage disallowed",
14+
)
15+
16+
NEW_OTP_PROGRAM = (
17+
"OTP programming is allowed",
18+
"OTP programming is disallowed",
19+
)
20+
21+
NEW_OTP_READ = (
22+
"OTP reading is allowed",
23+
"OTP reading is disallowed",
24+
)
25+
26+
NEW_WARRANTY_BIT = (
27+
"Warranty is intact",
28+
"Warranty has been voided by overclocking",
29+
)
30+
31+
NEW_REV_STYLE = (
32+
"Old-style revision",
33+
"New-style revision",
34+
)
35+
36+
NEW_MEMORY_SIZE = (
37+
"256MB",
38+
"512MB",
39+
"1GB",
40+
"2GB",
41+
"4GB",
42+
"8GB",
43+
)
44+
45+
NEW_MANUFACTURER = (
46+
"Sony UK",
47+
"Egoman",
48+
"Embest",
49+
"Sony Japan",
50+
"Embest",
51+
"Stadium",
52+
)
53+
54+
NEW_PROCESSOR = (
55+
"BCM2835",
56+
"BCM2836",
57+
"BCM2837",
58+
"BCM2711",
59+
)
60+
61+
PI_TYPE = {
62+
0x00: "A",
63+
0x01: "B",
64+
0x02: "A+",
65+
0x03: "B+",
66+
0x04: "2B",
67+
0x05: "Alpha (early prototype)",
68+
0x06: "CM1",
69+
0x08: "3B",
70+
0x09: "Zero",
71+
0x0A: "CM3",
72+
0x0B: "Custom",
73+
0x0C: "Zero W",
74+
0x0D: "3B+",
75+
0x0E: "3A+",
76+
0x0F: "Internal use only",
77+
0x10: "CM3+",
78+
0x11: "4B",
79+
0x12: "Zero 2 W",
80+
0x13: "400",
81+
0x14: "CM4",
82+
0x15: "CM4S",
83+
}
84+
85+
OLD_MANUFACTURER = (
86+
"Sony UK",
87+
"Egoman",
88+
"Embest",
89+
"Qisda",
90+
)
91+
92+
OLD_MEMORY_SIZE = ("256MB", "512MB", "256MB/512MB")
93+
94+
NEW_REV_STRUCTURE = {
95+
"overvoltage": (31, 1, NEW_OVERVOLTAGE),
96+
"otp_program": (30, 1, NEW_OTP_PROGRAM),
97+
"otp_read": (29, 1, NEW_OTP_READ),
98+
"warranty": (25, 1, NEW_WARRANTY_BIT),
99+
"rev_style": (23, 1, NEW_REV_STYLE),
100+
"memory_size": (20, 3, NEW_MEMORY_SIZE),
101+
"manufacturer": (16, 4, NEW_MANUFACTURER),
102+
"processor": (12, 4, NEW_PROCESSOR),
103+
"type": (4, 8, PI_TYPE),
104+
"revision": (0, 4, int),
105+
}
106+
107+
OLD_REV_STRUCTURE = {
108+
"type": (0, PI_TYPE),
109+
"revision": (1, float),
110+
"memory_size": (2, OLD_MEMORY_SIZE),
111+
"manufacturer": (3, OLD_MANUFACTURER),
112+
}
113+
114+
OLD_REV_EXTRA_PROPS = {
115+
"warranty": (24, 1, NEW_WARRANTY_BIT),
116+
}
117+
118+
OLD_REV_LUT = {
119+
0x02: (1, 1.0, 0, 1),
120+
0x03: (1, 1.0, 0, 1),
121+
0x04: (1, 2.0, 0, 0),
122+
0x05: (1, 2.0, 0, 3),
123+
0x06: (1, 2.0, 0, 1),
124+
0x07: (0, 2.0, 0, 1),
125+
0x08: (0, 2.0, 0, 0),
126+
0x09: (0, 2.0, 0, 3),
127+
0x0D: (1, 2.0, 1, 1),
128+
0x0E: (1, 2.0, 1, 0),
129+
0x0F: (1, 2.0, 1, 1),
130+
0x10: (3, 1.2, 1, 0),
131+
0x11: (6, 1.0, 1, 0),
132+
0x12: (2, 1.1, 0, 0),
133+
0x13: (3, 1.2, 1, 2),
134+
0x14: (6, 1.0, 1, 2),
135+
0x15: (2, 1.1, 2, 2),
136+
}
137+
138+
class PiDecoder:
139+
def __init__(self, rev_code):
140+
try:
141+
self.rev_code = int(rev_code, 16) & 0xFFFFFFFF
142+
except ValueError:
143+
print("Invalid revision code. It should be a hexadecimal value.")
144+
145+
def is_valid_code(self):
146+
# This is a check intended to quickly check the validity of a code
147+
if self.is_new_format():
148+
for format in NEW_REV_STRUCTURE.values():
149+
lower_bit, bit_size, values = format
150+
prop_value = (self.rev_code >> lower_bit) & ((1 << bit_size) - 1)
151+
if not self._valid_value(prop_value, values):
152+
return False
153+
else:
154+
if (self.rev_code & 0xFFFF) not in OLD_REV_LUT.keys():
155+
return False
156+
for format in OLD_REV_STRUCTURE.values():
157+
index, values = format
158+
format = OLD_REV_LUT[self.rev_code & 0xFFFF]
159+
if index >= len(format):
160+
return False
161+
if not self._valid_value(format[index], values):
162+
return False
163+
return True
164+
165+
def _get_rev_prop_value(self, name, structure=NEW_REV_STRUCTURE, raw=False):
166+
if name not in structure.keys():
167+
raise ValueError(f"Unknown property {name}")
168+
lower_bit, bit_size, values = structure[name]
169+
prop_value = self._get_bits_value(lower_bit, bit_size)
170+
if not self._valid_value(prop_value, values):
171+
raise ValueError(f"Invalid value {prop_value} for property {name}")
172+
if raw:
173+
return prop_value
174+
return self._format_value(prop_value, values)
175+
176+
def _get_bits_value(self, lower_bit, bit_size):
177+
return (self.rev_code >> lower_bit) & ((1 << bit_size) - 1)
178+
179+
def _get_old_rev_prop_value(self, name, raw=False):
180+
if name not in OLD_REV_STRUCTURE.keys():
181+
raise ValueError(f"Unknown property {name}")
182+
index, values = OLD_REV_STRUCTURE[name]
183+
data = OLD_REV_LUT[self.rev_code & 0xFFFF]
184+
if index >= len(data):
185+
raise IndexError(f"Index {index} out of range for property {name}")
186+
if not self._valid_value(data[index], values):
187+
raise ValueError(f"Invalid value {data[index]} for property {name}")
188+
if raw:
189+
return data[index]
190+
return self._format_value(data[index], values)
191+
192+
def _format_value(self, value, valid_values):
193+
if valid_values is float or valid_values is int:
194+
return valid_values(value)
195+
return valid_values[value]
196+
197+
def _valid_value(self, value, valid_values):
198+
if valid_values is float or valid_values is int:
199+
return isinstance(value, valid_values)
200+
if isinstance(valid_values, (tuple, list)) and 0 <= value < len(valid_values):
201+
return True
202+
if isinstance(valid_values, dict) and value in valid_values.keys():
203+
return True
204+
return False
205+
206+
def _get_property(self, name, raw=False):
207+
if name not in NEW_REV_STRUCTURE:
208+
raise ValueError(f"Unknown property {name}")
209+
if self.is_new_format():
210+
return self._get_rev_prop_value(name, raw=raw)
211+
elif name in OLD_REV_EXTRA_PROPS:
212+
return self._get_rev_prop_value(
213+
name, structure=OLD_REV_EXTRA_PROPS, raw=raw
214+
)
215+
else:
216+
return self._get_old_rev_prop_value(name, raw=raw)
217+
218+
def is_new_format(self):
219+
return self._get_rev_prop_value("rev_style", raw=True) == 1
220+
221+
@property
222+
def overvoltage(self):
223+
return self._get_property("overvoltage")
224+
225+
@property
226+
def warranty_bit(self):
227+
return self._get_property("warranty")
228+
229+
@property
230+
def otp_program(self):
231+
return self._get_property("otp_program")
232+
233+
@property
234+
def otp_read(self):
235+
return self._get_property("otp_read")
236+
237+
@property
238+
def rev_style(self):
239+
# Force new style for Rev Style
240+
return self._get_rev_prop_value("rev_style")
241+
242+
@property
243+
def memory_size(self):
244+
return self._get_property("memory_size")
245+
246+
@property
247+
def manufacturer(self):
248+
return self._get_property("manufacturer")
249+
250+
@property
251+
def processor(self):
252+
return self._get_property("processor")
253+
254+
@property
255+
def type(self):
256+
return self._get_property("type")
257+
258+
@property
259+
def type_raw(self):
260+
return self._get_property("type", raw = True)
261+
262+
@property
263+
def revision(self):
264+
return self._get_property("revision")

bin/rev_code_tester.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env python3
2+
3+
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
4+
#
5+
# SPDX-License-Identifier: MIT
6+
7+
# This tests that all existing rev codes in the boards constant file
8+
# match what the decoder finds.
9+
10+
import adafruit_platformdetect
11+
import adafruit_platformdetect.constants.boards as ap_board
12+
from adafruit_platformdetect.revcodes import PiDecoder
13+
14+
detector = adafruit_platformdetect.Detector()
15+
16+
def print_property(label, value):
17+
print(f"{label}: {value}")
18+
19+
def print_info(pi_decoder):
20+
if pi_decoder.is_new_format():
21+
print_property("Overvoltage", pi_decoder.overvoltage)
22+
print_property("OTP Program", pi_decoder.otp_program)
23+
print_property("OTP Read", pi_decoder.otp_read)
24+
print_property("Warranty bit", pi_decoder.warranty_bit)
25+
print_property("New flag", pi_decoder.rev_style)
26+
print_property("Memory size", pi_decoder.memory_size)
27+
print_property("Manufacturer", pi_decoder.manufacturer)
28+
print_property("Processor", pi_decoder.processor)
29+
print_property("Type", pi_decoder.type)
30+
print_property("Revision", pi_decoder.revision)
31+
else:
32+
print_property("Warranty bit", pi_decoder.warranty_bit)
33+
print_property("Model", pi_decoder.type)
34+
print_property("Revision", pi_decoder.revision)
35+
print_property("RAM", pi_decoder.memory_size)
36+
print_property("Manufacturer", pi_decoder.manufacturer)
37+
38+
# Iterate through the _PI_REV_CODES dictionary to find the model
39+
# Run the code through the decoder to check that:
40+
# - It is a valid code
41+
# - It matches the model
42+
for model, codes in ap_board._PI_REV_CODES.items():
43+
for pi_rev_code in codes:
44+
try:
45+
decoder = PiDecoder(pi_rev_code)
46+
except ValueError as e:
47+
print("Invalid revision code. It should be a hexadecimal value.")
48+
decoded_model = ap_board._PI_MODELS[decoder.type_raw]
49+
if isinstance(decoded_model, dict):
50+
decoded_model = decoded_model[decoder.revision]
51+
if decoded_model == model:
52+
print(f"Decoded model matches expected model: {model}")
53+
else:
54+
print(f"Decoded model does not match expected model: {model}")
55+
print(f"Decoded model: {decoded_model}")
56+
print(f"Expected model: {model}")
57+
print_info(decoder)

0 commit comments

Comments
 (0)