use std::error;
use std::fmt;
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::result;
const PERIPHERAL_BASE_RPI: u32 = 0x2000_0000;
const PERIPHERAL_BASE_RPI2: u32 = 0x3f00_0000;
const PERIPHERAL_BASE_RPI4: u32 = 0xfe00_0000;
const PERIPHERAL_BASE_RP1: u32 = 0x4000_0000;
const GPIO_OFFSET: u32 = 0x20_0000;
const GPIO_OFFSET_RP1: u32 = 0x0d_0000;
const GPIO_LINES_BCM283X: u8 = 54;
const GPIO_LINES_BCM2711: u8 = 58;
const GPIO_LINES_RP1: u8 = 28;
#[derive(Debug)]
pub enum Error {
UnknownModel,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::UnknownModel => write!(f, "Unknown Raspberry Pi model"),
}
}
}
impl error::Error for Error {}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[non_exhaustive]
pub enum Model {
RaspberryPiA,
RaspberryPiAPlus,
RaspberryPiBRev1,
RaspberryPiBRev2,
RaspberryPiBPlus,
RaspberryPi2B,
RaspberryPi3APlus,
RaspberryPi3B,
RaspberryPi3BPlus,
RaspberryPi4B,
RaspberryPi400,
RaspberryPi5,
RaspberryPiComputeModule,
RaspberryPiComputeModule3,
RaspberryPiComputeModule3Plus,
RaspberryPiComputeModule4,
RaspberryPiComputeModule4S,
RaspberryPiZero,
RaspberryPiZeroW,
RaspberryPiZero2W,
}
impl fmt::Display for Model {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Model::RaspberryPiA => write!(f, "Raspberry Pi A"),
Model::RaspberryPiAPlus => write!(f, "Raspberry Pi A+"),
Model::RaspberryPiBRev1 => write!(f, "Raspberry Pi B Rev 1"),
Model::RaspberryPiBRev2 => write!(f, "Raspberry Pi B Rev 2"),
Model::RaspberryPiBPlus => write!(f, "Raspberry Pi B+"),
Model::RaspberryPi2B => write!(f, "Raspberry Pi 2 B"),
Model::RaspberryPi3B => write!(f, "Raspberry Pi 3 B"),
Model::RaspberryPi3BPlus => write!(f, "Raspberry Pi 3 B+"),
Model::RaspberryPi3APlus => write!(f, "Raspberry Pi 3 A+"),
Model::RaspberryPi4B => write!(f, "Raspberry Pi 4 B"),
Model::RaspberryPi400 => write!(f, "Raspberry Pi 400"),
Model::RaspberryPi5 => write!(f, "Raspberry Pi 5"),
Model::RaspberryPiComputeModule => write!(f, "Raspberry Pi Compute Module"),
Model::RaspberryPiComputeModule3 => write!(f, "Raspberry Pi Compute Module 3"),
Model::RaspberryPiComputeModule3Plus => write!(f, "Raspberry Pi Compute Module 3+"),
Model::RaspberryPiComputeModule4 => write!(f, "Raspberry Pi Compute Module 4"),
Model::RaspberryPiComputeModule4S => write!(f, "Raspberry Pi Compute Module 4S"),
Model::RaspberryPiZero => write!(f, "Raspberry Pi Zero"),
Model::RaspberryPiZeroW => write!(f, "Raspberry Pi Zero W"),
Model::RaspberryPiZero2W => write!(f, "Raspberry Pi Zero 2 W"),
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum GpioInterface {
Bcm,
Rp1,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[non_exhaustive]
pub enum SoC {
Bcm2835,
Bcm2836,
Bcm2837A1,
Bcm2837B0,
Bcm2711,
Bcm2712,
}
impl fmt::Display for SoC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SoC::Bcm2835 => write!(f, "BCM2835"),
SoC::Bcm2836 => write!(f, "BCM2836"),
SoC::Bcm2837A1 => write!(f, "BCM2837A1"),
SoC::Bcm2837B0 => write!(f, "BCM2837B0"),
SoC::Bcm2711 => write!(f, "BCM2711"),
SoC::Bcm2712 => write!(f, "BCM2712"),
}
}
}
fn parse_proc_cpuinfo() -> Result<Model> {
let proc_cpuinfo = BufReader::new(match File::open("/proc/cpuinfo") {
Ok(file) => file,
Err(_) => return Err(Error::UnknownModel),
});
let mut hardware: String = String::new();
let mut revision: String = String::new();
for line in proc_cpuinfo.lines().flatten() {
if let Some(line_value) = line.strip_prefix("Hardware\t: ") {
hardware = String::from(line_value);
} else if let Some(line_value) = line.strip_prefix("Revision\t: ") {
revision = String::from(line_value).to_lowercase();
}
}
match &hardware[..] {
"BCM2708" | "BCM2835" | "BCM2709" | "BCM2836" | "BCM2710" | "BCM2837" | "BCM2837A1"
| "BCM2837B0" | "RP3A0-AU" | "BCM2710A1" | "BCM2711" | "BCM2712" => {}
_ => return Err(Error::UnknownModel),
}
let model = if (revision.len() == 4) || (revision.len() == 8) {
match &revision[revision.len() - 4..] {
"0007" | "0008" | "0009" | "0015" => Model::RaspberryPiA,
"beta" | "0002" | "0003" => Model::RaspberryPiBRev1,
"0004" | "0005" | "0006" | "000d" | "000e" | "000f" => Model::RaspberryPiBRev2,
"0012" => Model::RaspberryPiAPlus,
"0010" | "0013" => Model::RaspberryPiBPlus,
"0011" | "0014" => Model::RaspberryPiComputeModule,
_ => return Err(Error::UnknownModel),
}
} else if revision.len() >= 6 {
let revision_type = match u64::from_str_radix(&revision, 16) {
Ok(revision_type) => (revision_type >> 4) & 0xff,
Err(_) => return Err(Error::UnknownModel),
};
match revision_type {
0x00 => Model::RaspberryPiA,
0x01 => Model::RaspberryPiBRev2,
0x02 => Model::RaspberryPiAPlus,
0x03 => Model::RaspberryPiBPlus,
0x04 => Model::RaspberryPi2B,
0x06 => Model::RaspberryPiComputeModule,
0x08 => Model::RaspberryPi3B,
0x09 => Model::RaspberryPiZero,
0x0a => Model::RaspberryPiComputeModule3,
0x0c => Model::RaspberryPiZeroW,
0x0d => Model::RaspberryPi3BPlus,
0x0e => Model::RaspberryPi3APlus,
0x10 => Model::RaspberryPiComputeModule3Plus,
0x11 => Model::RaspberryPi4B,
0x12 => Model::RaspberryPiZero2W,
0x13 => Model::RaspberryPi400,
0x14 => Model::RaspberryPiComputeModule4,
0x15 => Model::RaspberryPiComputeModule4S,
0x17 => Model::RaspberryPi5,
_ => return Err(Error::UnknownModel),
}
} else {
return Err(Error::UnknownModel);
};
Ok(model)
}
fn parse_base_compatible() -> Result<Model> {
let base_compatible = match fs::read_to_string("/sys/firmware/devicetree/base/compatible") {
Ok(buffer) => buffer,
Err(_) => return Err(Error::UnknownModel),
};
for comp_id in base_compatible.split('\0') {
let model = match comp_id {
"raspberrypi,model-b-i2c0" => Model::RaspberryPiBRev1,
"raspberrypi,model-b" => Model::RaspberryPiBRev1,
"raspberrypi,model-a" => Model::RaspberryPiA,
"raspberrypi,model-b-rev2" => Model::RaspberryPiBRev2,
"raspberrypi,model-a-plus" => Model::RaspberryPiAPlus,
"raspberrypi,model-b-plus" => Model::RaspberryPiBPlus,
"raspberrypi,2-model-b" => Model::RaspberryPi2B,
"raspberrypi,compute-module" => Model::RaspberryPiComputeModule,
"raspberrypi,3-model-b" => Model::RaspberryPi3B,
"raspberrypi,model-zero" => Model::RaspberryPiZero,
"raspberrypi,3-compute-module" => Model::RaspberryPiComputeModule3,
"raspberrypi,3-compute-module-plus" => Model::RaspberryPiComputeModule3Plus,
"raspberrypi,model-zero-w" => Model::RaspberryPiZeroW,
"raspberrypi,model-zero-2" => Model::RaspberryPiZero2W,
"raspberrypi,3-model-b-plus" => Model::RaspberryPi3BPlus,
"raspberrypi,3-model-a-plus" => Model::RaspberryPi3APlus,
"raspberrypi,4-model-b" => Model::RaspberryPi4B,
"raspberrypi,400" => Model::RaspberryPi400,
"raspberrypi,4-compute-module" => Model::RaspberryPiComputeModule4,
"raspberrypi,4-compute-module-s" => Model::RaspberryPiComputeModule4S,
"raspberrypi,5-model-b" => Model::RaspberryPi5,
_ => continue,
};
return Ok(model);
}
Err(Error::UnknownModel)
}
fn parse_base_model() -> Result<Model> {
let mut base_model = match fs::read_to_string("/sys/firmware/devicetree/base/model") {
Ok(mut buffer) => {
if let Some(idx) = buffer.find('\0') {
buffer.truncate(idx);
}
buffer
}
Err(_) => return Err(Error::UnknownModel),
};
match &base_model[..] {
"Raspberry Pi Model B Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
"Raspberry Pi Model B rev2 Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
_ => (),
}
if let Some(idx) = base_model.find(" Rev ") {
base_model.truncate(idx);
}
let model = match &base_model[..] {
"Raspberry Pi Model B (no P5)" => Model::RaspberryPiBRev1,
"Raspberry Pi Model B" => Model::RaspberryPiBRev1,
"Raspberry Pi Model A" => Model::RaspberryPiA,
"Raspberry Pi Model B rev2" => Model::RaspberryPiBRev2,
"Raspberry Pi Model A+" => Model::RaspberryPiAPlus,
"Raspberry Pi Model A Plus" => Model::RaspberryPiAPlus,
"Raspberry Pi Model B+" => Model::RaspberryPiBPlus,
"Raspberry Pi Model B Plus" => Model::RaspberryPiBPlus,
"Raspberry Pi 2 Model B" => Model::RaspberryPi2B,
"Raspberry Pi Compute Module" => Model::RaspberryPiComputeModule,
"Raspberry Pi 3 Model B" => Model::RaspberryPi3B,
"Raspberry Pi Zero" => Model::RaspberryPiZero,
"Raspberry Pi Compute Module 3" => Model::RaspberryPiComputeModule3,
"Raspberry Pi Compute Module 3 Plus" => Model::RaspberryPiComputeModule3Plus,
"Raspberry Pi Zero W" => Model::RaspberryPiZeroW,
"Raspberry Pi Zero 2" => Model::RaspberryPiZero2W,
"Raspberry Pi 3 Model B+" => Model::RaspberryPi3BPlus,
"Raspberry Pi 3 Model B Plus" => Model::RaspberryPi3BPlus,
"Raspberry Pi 3 Model A Plus" => Model::RaspberryPi3APlus,
"Raspberry Pi 4 Model B" => Model::RaspberryPi4B,
"Raspberry Pi 400" => Model::RaspberryPi400,
"Raspberry Pi Compute Module 4" => Model::RaspberryPiComputeModule4,
"Raspberry Pi Compute Module 4S" => Model::RaspberryPiComputeModule4S,
"Raspberry Pi 5 Model B" => Model::RaspberryPi5,
_ => return Err(Error::UnknownModel),
};
Ok(model)
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct DeviceInfo {
model: Model,
soc: SoC,
peripheral_base: u32,
gpio_offset: u32,
gpio_lines: u8,
gpio_interface: GpioInterface,
}
impl DeviceInfo {
pub fn new() -> Result<DeviceInfo> {
let model = parse_proc_cpuinfo()
.or_else(|_| parse_base_compatible().or_else(|_| parse_base_model()))?;
match model {
Model::RaspberryPiA
| Model::RaspberryPiAPlus
| Model::RaspberryPiBRev1
| Model::RaspberryPiBRev2
| Model::RaspberryPiBPlus
| Model::RaspberryPiComputeModule
| Model::RaspberryPiZero
| Model::RaspberryPiZeroW => Ok(DeviceInfo {
model,
soc: SoC::Bcm2835,
peripheral_base: PERIPHERAL_BASE_RPI,
gpio_offset: GPIO_OFFSET,
gpio_lines: GPIO_LINES_BCM283X,
gpio_interface: GpioInterface::Bcm,
}),
Model::RaspberryPi2B => Ok(DeviceInfo {
model,
soc: SoC::Bcm2836,
peripheral_base: PERIPHERAL_BASE_RPI2,
gpio_offset: GPIO_OFFSET,
gpio_lines: GPIO_LINES_BCM283X,
gpio_interface: GpioInterface::Bcm,
}),
Model::RaspberryPi3B | Model::RaspberryPiComputeModule3 | Model::RaspberryPiZero2W => {
Ok(DeviceInfo {
model,
soc: SoC::Bcm2837A1,
peripheral_base: PERIPHERAL_BASE_RPI2,
gpio_offset: GPIO_OFFSET,
gpio_lines: GPIO_LINES_BCM283X,
gpio_interface: GpioInterface::Bcm,
})
}
Model::RaspberryPi3BPlus
| Model::RaspberryPi3APlus
| Model::RaspberryPiComputeModule3Plus => Ok(DeviceInfo {
model,
soc: SoC::Bcm2837B0,
peripheral_base: PERIPHERAL_BASE_RPI2,
gpio_offset: GPIO_OFFSET,
gpio_lines: GPIO_LINES_BCM283X,
gpio_interface: GpioInterface::Bcm,
}),
Model::RaspberryPi4B
| Model::RaspberryPi400
| Model::RaspberryPiComputeModule4
| Model::RaspberryPiComputeModule4S => Ok(DeviceInfo {
model,
soc: SoC::Bcm2711,
peripheral_base: PERIPHERAL_BASE_RPI4,
gpio_offset: GPIO_OFFSET,
gpio_lines: GPIO_LINES_BCM2711,
gpio_interface: GpioInterface::Bcm,
}),
Model::RaspberryPi5 => Ok(DeviceInfo {
model,
soc: SoC::Bcm2712,
peripheral_base: PERIPHERAL_BASE_RP1,
gpio_offset: GPIO_OFFSET_RP1,
gpio_lines: GPIO_LINES_RP1,
gpio_interface: GpioInterface::Rp1,
}),
}
}
pub fn model(&self) -> Model {
self.model
}
pub fn soc(&self) -> SoC {
self.soc
}
pub(crate) fn peripheral_base(&self) -> u32 {
self.peripheral_base
}
pub(crate) fn gpio_offset(&self) -> u32 {
self.gpio_offset
}
pub(crate) fn gpio_lines(&self) -> u8 {
self.gpio_lines
}
pub(crate) fn gpio_interface(&self) -> GpioInterface {
self.gpio_interface
}
}