diff --git a/adafruit_platformdetect/board.py b/adafruit_platformdetect/board.py index 7fbd8222..77b27379 100644 --- a/adafruit_platformdetect/board.py +++ b/adafruit_platformdetect/board.py @@ -201,10 +201,16 @@ def _pi_id(self) -> Optional[str]: # Check for Pi boards: pi_rev_code = self._pi_rev_code() if pi_rev_code: - for model, codes in boards._PI_REV_CODES.items(): - if pi_rev_code in codes: - return model + from adafruit_platformdetect.revcodes import PiDecoder + try: + decoder = PiDecoder(pi_rev_code) + model = boards._PI_MODELS[decoder.type_raw] + if isinstance(model, dict): + model = model[decoder.revision] + return model + except ValueError: + pass # We may be on a non-Raspbian OS, so try to lazily determine # the version based on `get_device_model` else: diff --git a/adafruit_platformdetect/constants/boards.py b/adafruit_platformdetect/constants/boards.py index 0176ede4..058e11a2 100644 --- a/adafruit_platformdetect/constants/boards.py +++ b/adafruit_platformdetect/constants/boards.py @@ -123,6 +123,7 @@ RASPBERRY_PI_AVNET_IIOT_GW = "RASPBERY_PI_AVNET_IIOT_GW" RASPBERRY_PI_400 = "RASPBERRY_PI_400" RASPBERRY_PI_CM4 = "RASPBERRY_PI_CM4" +RASPBERRY_PI_CM4S = "RASPBERRY_PI_CM4S" ODROID_C1 = "ODROID_C1" ODROID_C1_PLUS = "ODROID_C1_PLUS" @@ -341,6 +342,7 @@ RASPBERRY_PI_CM3, RASPBERRY_PI_CM3_PLUS, RASPBERRY_PI_CM4, + RASPBERRY_PI_CM4S, ) _ODROID_40_PIN_IDS = ( @@ -564,6 +566,31 @@ RASPBERRY_PI_ZERO_2_W: ("902120", "2902120"), } +_PI_MODELS = { + 0x00: RASPBERRY_PI_A, + 0x01: { + 1.0: RASPBERRY_PI_B_REV1, + 2.0: RASPBERRY_PI_B_REV2, + }, + 0x02: RASPBERRY_PI_A_PLUS, + 0x03: RASPBERRY_PI_B_PLUS, + 0x04: RASPBERRY_PI_2B, + 0x06: RASPBERRY_PI_CM1, + 0x08: RASPBERRY_PI_3B, + 0x09: RASPBERRY_PI_ZERO, + 0x0A: RASPBERRY_PI_CM3, + 0x0B: RASPBERRY_PI_AVNET_IIOT_GW, + 0x0C: RASPBERRY_PI_ZERO_W, + 0x0D: RASPBERRY_PI_3B_PLUS, + 0x0E: RASPBERRY_PI_3A_PLUS, + 0x10: RASPBERRY_PI_CM3_PLUS, + 0x11: RASPBERRY_PI_4B, + 0x12: RASPBERRY_PI_ZERO_2_W, + 0x13: RASPBERRY_PI_400, + 0x14: RASPBERRY_PI_CM4, + 0x15: RASPBERRY_PI_CM4S, +} + # Onion omega boards _ONION_OMEGA_BOARD_IDS = (ONION_OMEGA, ONION_OMEGA2) diff --git a/adafruit_platformdetect/revcodes.py b/adafruit_platformdetect/revcodes.py new file mode 100644 index 00000000..d8db8b6d --- /dev/null +++ b/adafruit_platformdetect/revcodes.py @@ -0,0 +1,296 @@ +# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_platformdetect.revcodes` +================================================================================ + +Class to help with Raspberry Pi Rev Codes + +* Author(s): Melissa LeBlanc-Williams + +Implementation Notes +-------------------- + +**Software and Dependencies:** + +* Linux and Python 3.7 or Higher + +Data values from https://github.com/raspberrypi/documentation/blob/develop/ +documentation/asciidoc/computers/raspberry-pi/revision-codes.adoc#new-style-revision-codes + +""" + +NEW_OVERVOLTAGE = ( + "Overvoltage allowed", + "Overvoltage disallowed", +) + +NEW_OTP_PROGRAM = ( + "OTP programming is allowed", + "OTP programming is disallowed", +) + +NEW_OTP_READ = ( + "OTP reading is allowed", + "OTP reading is disallowed", +) + +NEW_WARRANTY_BIT = ( + "Warranty is intact", + "Warranty has been voided by overclocking", +) + +NEW_REV_STYLE = ( + "Old-style revision", + "New-style revision", +) + +NEW_MEMORY_SIZE = ( + "256MB", + "512MB", + "1GB", + "2GB", + "4GB", + "8GB", +) + +NEW_MANUFACTURER = ( + "Sony UK", + "Egoman", + "Embest", + "Sony Japan", + "Embest", + "Stadium", +) + +NEW_PROCESSOR = ( + "BCM2835", + "BCM2836", + "BCM2837", + "BCM2711", +) + +PI_TYPE = { + 0x00: "A", + 0x01: "B", + 0x02: "A+", + 0x03: "B+", + 0x04: "2B", + 0x05: "Alpha (early prototype)", + 0x06: "CM1", + 0x08: "3B", + 0x09: "Zero", + 0x0A: "CM3", + 0x0B: "Custom", + 0x0C: "Zero W", + 0x0D: "3B+", + 0x0E: "3A+", + 0x0F: "Internal use only", + 0x10: "CM3+", + 0x11: "4B", + 0x12: "Zero 2 W", + 0x13: "400", + 0x14: "CM4", + 0x15: "CM4S", +} + +OLD_MANUFACTURER = ( + "Sony UK", + "Egoman", + "Embest", + "Qisda", +) + +OLD_MEMORY_SIZE = ("256MB", "512MB", "256MB/512MB") + +NEW_REV_STRUCTURE = { + "overvoltage": (31, 1, NEW_OVERVOLTAGE), + "otp_program": (30, 1, NEW_OTP_PROGRAM), + "otp_read": (29, 1, NEW_OTP_READ), + "warranty": (25, 1, NEW_WARRANTY_BIT), + "rev_style": (23, 1, NEW_REV_STYLE), + "memory_size": (20, 3, NEW_MEMORY_SIZE), + "manufacturer": (16, 4, NEW_MANUFACTURER), + "processor": (12, 4, NEW_PROCESSOR), + "type": (4, 8, PI_TYPE), + "revision": (0, 4, int), +} + +OLD_REV_STRUCTURE = { + "type": (0, PI_TYPE), + "revision": (1, float), + "memory_size": (2, OLD_MEMORY_SIZE), + "manufacturer": (3, OLD_MANUFACTURER), +} + +OLD_REV_EXTRA_PROPS = { + "warranty": (24, 1, NEW_WARRANTY_BIT), +} + +OLD_REV_LUT = { + 0x02: (1, 1.0, 0, 1), + 0x03: (1, 1.0, 0, 1), + 0x04: (1, 2.0, 0, 0), + 0x05: (1, 2.0, 0, 3), + 0x06: (1, 2.0, 0, 1), + 0x07: (0, 2.0, 0, 1), + 0x08: (0, 2.0, 0, 0), + 0x09: (0, 2.0, 0, 3), + 0x0D: (1, 2.0, 1, 1), + 0x0E: (1, 2.0, 1, 0), + 0x0F: (1, 2.0, 1, 1), + 0x10: (3, 1.2, 1, 0), + 0x11: (6, 1.0, 1, 0), + 0x12: (2, 1.1, 0, 0), + 0x13: (3, 1.2, 1, 2), + 0x14: (6, 1.0, 1, 2), + 0x15: (2, 1.1, 2, 2), +} + + +class PiDecoder: + """Raspberry Pi Revision Code Decoder""" + + def __init__(self, rev_code): + try: + self.rev_code = int(rev_code, 16) & 0xFFFFFFFF + except ValueError: + print("Invalid revision code. It should be a hexadecimal value.") + + def is_valid_code(self): + """Quickly check the validity of a code""" + if self.is_new_format(): + for code_format in NEW_REV_STRUCTURE.values(): + lower_bit, bit_size, values = code_format + prop_value = (self.rev_code >> lower_bit) & ((1 << bit_size) - 1) + if not self._valid_value(prop_value, values): + return False + else: + if (self.rev_code & 0xFFFF) not in OLD_REV_LUT.keys(): + return False + for code_format in OLD_REV_STRUCTURE.values(): + index, values = code_format + code_format = OLD_REV_LUT[self.rev_code & 0xFFFF] + if index >= len(code_format): + return False + if not self._valid_value(code_format[index], values): + return False + return True + + def _get_rev_prop_value(self, name, structure=None, raw=False): + if structure is None: + structure = NEW_REV_STRUCTURE + if name not in structure.keys(): + raise ValueError(f"Unknown property {name}") + lower_bit, bit_size, values = structure[name] + prop_value = self._get_bits_value(lower_bit, bit_size) + if not self._valid_value(prop_value, values): + raise ValueError(f"Invalid value {prop_value} for property {name}") + if raw: + return prop_value + return self._format_value(prop_value, values) + + def _get_bits_value(self, lower_bit, bit_size): + return (self.rev_code >> lower_bit) & ((1 << bit_size) - 1) + + def _get_old_rev_prop_value(self, name, raw=False): + if name not in OLD_REV_STRUCTURE.keys(): + raise ValueError(f"Unknown property {name}") + index, values = OLD_REV_STRUCTURE[name] + data = OLD_REV_LUT[self.rev_code & 0xFFFF] + if index >= len(data): + raise IndexError(f"Index {index} out of range for property {name}") + if not self._valid_value(data[index], values): + raise ValueError(f"Invalid value {data[index]} for property {name}") + if raw: + return data[index] + return self._format_value(data[index], values) + + @staticmethod + def _format_value(value, valid_values): + if valid_values is float or valid_values is int: + return valid_values(value) + return valid_values[value] + + @staticmethod + def _valid_value(value, valid_values): + if valid_values is float or valid_values is int: + return isinstance(value, valid_values) + if isinstance(valid_values, (tuple, list)) and 0 <= value < len(valid_values): + return True + if isinstance(valid_values, dict) and value in valid_values.keys(): + return True + return False + + def _get_property(self, name, raw=False): + if name not in NEW_REV_STRUCTURE: + raise ValueError(f"Unknown property {name}") + if self.is_new_format(): + return self._get_rev_prop_value(name, raw=raw) + if name in OLD_REV_EXTRA_PROPS: + return self._get_rev_prop_value( + name, structure=OLD_REV_EXTRA_PROPS, raw=raw + ) + return self._get_old_rev_prop_value(name, raw=raw) + + def is_new_format(self): + """Check if the code is in the new format""" + return self._get_rev_prop_value("rev_style", raw=True) == 1 + + @property + def overvoltage(self): + """Overvoltage allowed/disallowed""" + return self._get_property("overvoltage") + + @property + def warranty_bit(self): + """Warranty bit""" + return self._get_property("warranty") + + @property + def otp_program(self): + """OTP programming allowed/disallowed""" + return self._get_property("otp_program") + + @property + def otp_read(self): + """OTP reading allowed/disallowed""" + return self._get_property("otp_read") + + @property + def rev_style(self): + """Revision Code style""" + # Force new style for Rev Style + return self._get_rev_prop_value("rev_style") + + @property + def memory_size(self): + """Memory size""" + return self._get_property("memory_size") + + @property + def manufacturer(self): + """Manufacturer""" + return self._get_property("manufacturer") + + @property + def processor(self): + """Processor""" + return self._get_property("processor") + + @property + def type(self): + """Specific Model""" + return self._get_property("type") + + @property + def type_raw(self): + """Raw Value of Specific Model""" + return self._get_property("type", raw=True) + + @property + def revision(self): + """Revision Number""" + return self._get_property("revision") diff --git a/bin/rev_code_tester.py b/bin/rev_code_tester.py new file mode 100644 index 00000000..1e5e5dd1 --- /dev/null +++ b/bin/rev_code_tester.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`bin.rev_code_tester` +================================================================================ + +Tests that all existing rev codes in the boards constant file + match what the decoder finds. + +* Author(s): Melissa LeBlanc-Williams + +Implementation Notes +-------------------- + +**Software and Dependencies:** + +* Linux and Python 3.7 or Higher + +""" + +import adafruit_platformdetect +import adafruit_platformdetect.constants.boards as ap_board +from adafruit_platformdetect.revcodes import PiDecoder + +detector = adafruit_platformdetect.Detector() + + +def print_property(label, value): + "Format and print a property" + print(f"{label}: {value}") + + +def print_info(pi_decoder): + "Print the info for the board" + if pi_decoder.is_new_format(): + print_property("Overvoltage", pi_decoder.overvoltage) + print_property("OTP Program", pi_decoder.otp_program) + print_property("OTP Read", pi_decoder.otp_read) + print_property("Warranty bit", pi_decoder.warranty_bit) + print_property("New flag", pi_decoder.rev_style) + print_property("Memory size", pi_decoder.memory_size) + print_property("Manufacturer", pi_decoder.manufacturer) + print_property("Processor", pi_decoder.processor) + print_property("Type", pi_decoder.type) + print_property("Revision", pi_decoder.revision) + else: + print_property("Warranty bit", pi_decoder.warranty_bit) + print_property("Model", pi_decoder.type) + print_property("Revision", pi_decoder.revision) + print_property("RAM", pi_decoder.memory_size) + print_property("Manufacturer", pi_decoder.manufacturer) + + +# Iterate through the _PI_REV_CODES dictionary to find the model +# Run the code through the decoder to check that: +# - It is a valid code +# - It matches the model +for model, codes in ap_board._PI_REV_CODES.items(): # pylint: disable=protected-access + for pi_rev_code in codes: + try: + decoder = PiDecoder(pi_rev_code) + except ValueError as e: + print("Invalid revision code. It should be a hexadecimal value.") + decoded_model = ap_board._PI_MODELS[ # pylint: disable=protected-access + decoder.type_raw + ] + if isinstance(decoded_model, dict): + decoded_model = decoded_model[decoder.revision] + if decoded_model == model: + print(f"Decoded model matches expected model: {model}") + else: + print(f"Decoded model does not match expected model: {model}") + print(f"Decoded model: {decoded_model}") + print(f"Expected model: {model}") + print_info(decoder) diff --git a/bin/rpi_info.py b/bin/rpi_info.py new file mode 100644 index 00000000..5b018813 --- /dev/null +++ b/bin/rpi_info.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`bin.rpi_info` +================================================================================ + +Interactive mode will prompt for the revision code +Otherwise it will be detected automatically + +* Author(s): Melissa LeBlanc-Williams + +Implementation Notes +-------------------- + +**Software and Dependencies:** + +* Linux and Python 3.7 or Higher + +""" + +import sys +import adafruit_platformdetect +from adafruit_platformdetect.revcodes import PiDecoder + +pi_rev_code = None + +detector = adafruit_platformdetect.Detector() +pi_rev_code = detector.board._pi_rev_code() # pylint: disable=protected-access + +if pi_rev_code is None: + print("Raspberry Pi not detected. Using interactive mode") + pi_rev_code = input("Enter a Raspberry Pi revision code (e.g. d03114 or 000f): ") + +try: + decoder = PiDecoder(pi_rev_code) +except ValueError as e: + print("Invalid revision code. It should be a hexadecimal value.") + sys.exit(1) + +if not decoder.is_valid_code(): + print( + "Code is invalid. This rev code includes at least one " + "value that is outside of the expected range." + ) + sys.exit(1) + + +def print_property(label, value): + "Format and print a property" + print(f"{label}: {value}") + + +if decoder.is_new_format(): + print_property("Overvoltage", decoder.overvoltage) + print_property("OTP Program", decoder.otp_program) + print_property("OTP Read", decoder.otp_read) + print_property("Warranty bit", decoder.warranty_bit) + print_property("New flag", decoder.rev_style) + print_property("Memory size", decoder.memory_size) + print_property("Manufacturer", decoder.manufacturer) + print_property("Processor", decoder.processor) + print_property("Type", decoder.type) + print_property("Revision", decoder.revision) +else: + print_property("Warranty bit", decoder.warranty_bit) + print_property("Model", decoder.type) + print_property("Revision", decoder.revision) + print_property("RAM", decoder.memory_size) + print_property("Manufacturer", decoder.manufacturer)