1use regex::Regex;
8use std::error;
9use std::fmt;
10use std::fs;
11use std::fs::File;
12use std::io::{BufRead, BufReader};
13use std::result;
14const PERIPHERAL_BASE_RPI: u32 = 0x2000_0000;
16const PERIPHERAL_BASE_RPI2: u32 = 0x3f00_0000;
17const PERIPHERAL_BASE_RPI4: u32 = 0xfe00_0000;
18const PERIPHERAL_BASE_RP1: u32 = 0x4000_0000;
19
20const GPIO_OFFSET: u32 = 0x20_0000;
22const GPIO_OFFSET_RP1: u32 = 0x0d_0000;
23
24const GPIO_LINES_BCM283X: u8 = 54;
26const GPIO_LINES_BCM2711: u8 = 58;
27const GPIO_LINES_RP1: u8 = 28;
30
31#[derive(Debug)]
33pub enum Error {
34 UnknownModel,
48 UnknownKernel,
54}
55
56impl fmt::Display for Error {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match *self {
59 Error::UnknownModel => write!(f, "Unknown Raspberry Pi model"),
60 Error::UnknownKernel => write!(f, "Unknown Raspberry Pi kernel"),
61 }
62 }
63}
64
65impl error::Error for Error {}
66
67pub type Result<T> = result::Result<T, Error>;
69
70#[derive(Debug, PartialEq, Eq, Copy, Clone)]
76#[non_exhaustive]
77pub enum Model {
78 RaspberryPiA,
79 RaspberryPiAPlus,
80 RaspberryPiBRev1,
81 RaspberryPiBRev2,
82 RaspberryPiBPlus,
83 RaspberryPi2B,
84 RaspberryPi3APlus,
85 RaspberryPi3B,
86 RaspberryPi3BPlus,
87 RaspberryPi4B,
88 RaspberryPi400,
89 RaspberryPi5,
90 RaspberryPi500,
91 RaspberryPiComputeModule,
92 RaspberryPiComputeModule3,
93 RaspberryPiComputeModule3Plus,
94 RaspberryPiComputeModule4,
95 RaspberryPiComputeModule4S,
96 RaspberryPiComputeModule5,
97 RaspberryPiComputeModule5Lite,
98 RaspberryPiZero,
99 RaspberryPiZeroW,
100 RaspberryPiZero2W,
101}
102
103impl fmt::Display for Model {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 match *self {
106 Model::RaspberryPiA => write!(f, "Raspberry Pi A"),
107 Model::RaspberryPiAPlus => write!(f, "Raspberry Pi A+"),
108 Model::RaspberryPiBRev1 => write!(f, "Raspberry Pi B Rev 1"),
109 Model::RaspberryPiBRev2 => write!(f, "Raspberry Pi B Rev 2"),
110 Model::RaspberryPiBPlus => write!(f, "Raspberry Pi B+"),
111 Model::RaspberryPi2B => write!(f, "Raspberry Pi 2 B"),
112 Model::RaspberryPi3B => write!(f, "Raspberry Pi 3 B"),
113 Model::RaspberryPi3BPlus => write!(f, "Raspberry Pi 3 B+"),
114 Model::RaspberryPi3APlus => write!(f, "Raspberry Pi 3 A+"),
115 Model::RaspberryPi4B => write!(f, "Raspberry Pi 4 B"),
116 Model::RaspberryPi400 => write!(f, "Raspberry Pi 400"),
117 Model::RaspberryPi5 => write!(f, "Raspberry Pi 5"),
118 Model::RaspberryPi500 => write!(f, "Raspberry Pi 500"),
119 Model::RaspberryPiComputeModule => write!(f, "Raspberry Pi Compute Module"),
120 Model::RaspberryPiComputeModule3 => write!(f, "Raspberry Pi Compute Module 3"),
121 Model::RaspberryPiComputeModule3Plus => write!(f, "Raspberry Pi Compute Module 3+"),
122 Model::RaspberryPiComputeModule4 => write!(f, "Raspberry Pi Compute Module 4"),
123 Model::RaspberryPiComputeModule4S => write!(f, "Raspberry Pi Compute Module 4S"),
124 Model::RaspberryPiComputeModule5 => write!(f, "Raspberry Pi Compute Module 5"),
125 Model::RaspberryPiComputeModule5Lite => write!(f, "Raspberry Pi Compute Module 5 Lite"),
126 Model::RaspberryPiZero => write!(f, "Raspberry Pi Zero"),
127 Model::RaspberryPiZeroW => write!(f, "Raspberry Pi Zero W"),
128 Model::RaspberryPiZero2W => write!(f, "Raspberry Pi Zero 2 W"),
129 }
130 }
131}
132
133#[derive(Debug, PartialEq, Eq, Copy, Clone)]
136pub(crate) enum GpioInterface {
137 Bcm,
138 Rp1,
139}
140
141#[derive(Debug, PartialEq, Eq, Copy, Clone)]
147#[non_exhaustive]
148pub enum SoC {
149 Bcm2835,
150 Bcm2836,
151 Bcm2837A1,
152 Bcm2837B0,
153 Bcm2711,
154 Bcm2712,
155}
156
157impl fmt::Display for SoC {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match *self {
160 SoC::Bcm2835 => write!(f, "BCM2835"),
161 SoC::Bcm2836 => write!(f, "BCM2836"),
162 SoC::Bcm2837A1 => write!(f, "BCM2837A1"),
163 SoC::Bcm2837B0 => write!(f, "BCM2837B0"),
164 SoC::Bcm2711 => write!(f, "BCM2711"),
165 SoC::Bcm2712 => write!(f, "BCM2712"),
166 }
167 }
168}
169
170fn parse_proc_cpuinfo() -> Result<Model> {
172 let proc_cpuinfo = BufReader::new(match File::open("/proc/cpuinfo") {
173 Ok(file) => file,
174 Err(_) => return Err(Error::UnknownModel),
175 });
176
177 let mut revision: String = String::new();
178 for line in proc_cpuinfo.lines().map_while(result::Result::ok) {
179 if let Some(line_value) = line.strip_prefix("Revision\t: ") {
180 revision = String::from(line_value).to_lowercase();
181 }
182 }
183
184 let model = if (revision.len() == 4) || (revision.len() == 8) {
185 match &revision[revision.len() - 4..] {
187 "0007" | "0008" | "0009" | "0015" => Model::RaspberryPiA,
188 "beta" | "0002" | "0003" => Model::RaspberryPiBRev1,
189 "0004" | "0005" | "0006" | "000d" | "000e" | "000f" => Model::RaspberryPiBRev2,
190 "0012" => Model::RaspberryPiAPlus,
191 "0010" | "0013" => Model::RaspberryPiBPlus,
192 "0011" | "0014" => Model::RaspberryPiComputeModule,
193 _ => return Err(Error::UnknownModel),
194 }
195 } else if revision.len() >= 6 {
196 let revision_type = match u64::from_str_radix(&revision, 16) {
200 Ok(revision_type) => (revision_type >> 4) & 0xff,
201 Err(_) => return Err(Error::UnknownModel),
202 };
203
204 match revision_type {
205 0x00 => Model::RaspberryPiA,
206 0x01 => Model::RaspberryPiBRev2,
207 0x02 => Model::RaspberryPiAPlus,
208 0x03 => Model::RaspberryPiBPlus,
209 0x04 => Model::RaspberryPi2B,
210 0x06 => Model::RaspberryPiComputeModule,
211 0x08 => Model::RaspberryPi3B,
212 0x09 => Model::RaspberryPiZero,
213 0x0a => Model::RaspberryPiComputeModule3,
214 0x0c => Model::RaspberryPiZeroW,
215 0x0d => Model::RaspberryPi3BPlus,
216 0x0e => Model::RaspberryPi3APlus,
217 0x10 => Model::RaspberryPiComputeModule3Plus,
218 0x11 => Model::RaspberryPi4B,
219 0x12 => Model::RaspberryPiZero2W,
220 0x13 => Model::RaspberryPi400,
221 0x14 => Model::RaspberryPiComputeModule4,
222 0x15 => Model::RaspberryPiComputeModule4S,
223 0x17 => Model::RaspberryPi5,
224 0x18 => Model::RaspberryPiComputeModule5,
225 0x19 => Model::RaspberryPi500,
226 0x1a => Model::RaspberryPiComputeModule5Lite,
227 _ => return Err(Error::UnknownModel),
228 }
229 } else {
230 return Err(Error::UnknownModel);
231 };
232
233 Ok(model)
234}
235
236fn parse_base_compatible() -> Result<Model> {
238 let base_compatible = match fs::read_to_string("/sys/firmware/devicetree/base/compatible") {
239 Ok(buffer) => buffer,
240 Err(_) => return Err(Error::UnknownModel),
241 };
242
243 for comp_id in base_compatible.split('\0') {
245 let model = match comp_id {
246 "raspberrypi,model-b-i2c0" => Model::RaspberryPiBRev1,
247 "raspberrypi,model-b" => Model::RaspberryPiBRev1,
248 "raspberrypi,model-a" => Model::RaspberryPiA,
249 "raspberrypi,model-b-rev2" => Model::RaspberryPiBRev2,
250 "raspberrypi,model-a-plus" => Model::RaspberryPiAPlus,
251 "raspberrypi,model-b-plus" => Model::RaspberryPiBPlus,
252 "raspberrypi,2-model-b" => Model::RaspberryPi2B,
253 "raspberrypi,compute-module" => Model::RaspberryPiComputeModule,
254 "raspberrypi,3-model-b" => Model::RaspberryPi3B,
255 "raspberrypi,model-zero" => Model::RaspberryPiZero,
256 "raspberrypi,3-compute-module" => Model::RaspberryPiComputeModule3,
257 "raspberrypi,3-compute-module-plus" => Model::RaspberryPiComputeModule3Plus,
258 "raspberrypi,model-zero-w" => Model::RaspberryPiZeroW,
259 "raspberrypi,model-zero-2" => Model::RaspberryPiZero2W,
260 "raspberrypi,model-zero-2-w" => Model::RaspberryPiZero2W,
261 "raspberrypi,3-model-b-plus" => Model::RaspberryPi3BPlus,
262 "raspberrypi,3-model-a-plus" => Model::RaspberryPi3APlus,
263 "raspberrypi,4-model-b" => Model::RaspberryPi4B,
264 "raspberrypi,400" => Model::RaspberryPi400,
265 "raspberrypi,4-compute-module" => Model::RaspberryPiComputeModule4,
266 "raspberrypi,4-compute-module-s" => Model::RaspberryPiComputeModule4S,
267 "raspberrypi,5-model-b" => Model::RaspberryPi5,
268 "raspberrypi,5-compute-module" => Model::RaspberryPiComputeModule5,
269 "raspberrypi,500" => Model::RaspberryPi500,
270 _ => continue,
271 };
272
273 return Ok(model);
274 }
275
276 Err(Error::UnknownModel)
277}
278
279fn parse_base_model() -> Result<Model> {
281 let mut base_model = match fs::read_to_string("/sys/firmware/devicetree/base/model") {
282 Ok(mut buffer) => {
283 if let Some(idx) = buffer.find('\0') {
284 buffer.truncate(idx);
285 }
286
287 buffer
288 }
289 Err(_) => return Err(Error::UnknownModel),
290 };
291
292 match &base_model[..] {
296 "Raspberry Pi Model B Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
297 "Raspberry Pi Model B rev2 Rev 2.0" => return Ok(Model::RaspberryPiBRev2),
298 "Raspberry Pi Zero 2 W Rev 1.0" => return Ok(Model::RaspberryPiZero2W),
299 _ => (),
300 }
301
302 if let Some(idx) = base_model.find(" Rev ") {
303 base_model.truncate(idx);
304 }
305
306 let model = match &base_model[..] {
308 "Raspberry Pi Model B (no P5)" => Model::RaspberryPiBRev1,
309 "Raspberry Pi Model B" => Model::RaspberryPiBRev1,
310 "Raspberry Pi Model A" => Model::RaspberryPiA,
311 "Raspberry Pi Model B rev2" => Model::RaspberryPiBRev2,
312 "Raspberry Pi Model A+" => Model::RaspberryPiAPlus,
313 "Raspberry Pi Model A Plus" => Model::RaspberryPiAPlus,
314 "Raspberry Pi Model B+" => Model::RaspberryPiBPlus,
315 "Raspberry Pi Model B Plus" => Model::RaspberryPiBPlus,
316 "Raspberry Pi 2 Model B" => Model::RaspberryPi2B,
317 "Raspberry Pi Compute Module" => Model::RaspberryPiComputeModule,
318 "Raspberry Pi 3 Model B" => Model::RaspberryPi3B,
319 "Raspberry Pi Zero" => Model::RaspberryPiZero,
320 "Raspberry Pi Compute Module 3" => Model::RaspberryPiComputeModule3,
321 "Raspberry Pi Compute Module 3 Plus" => Model::RaspberryPiComputeModule3Plus,
322 "Raspberry Pi Zero W" => Model::RaspberryPiZeroW,
323 "Raspberry Pi Zero 2" => Model::RaspberryPiZero2W,
324 "Raspberry Pi Zero 2 W" => Model::RaspberryPiZero2W,
325 "Raspberry Pi 3 Model B+" => Model::RaspberryPi3BPlus,
326 "Raspberry Pi 3 Model B Plus" => Model::RaspberryPi3BPlus,
327 "Raspberry Pi 3 Model A Plus" => Model::RaspberryPi3APlus,
328 "Raspberry Pi 4 Model B" => Model::RaspberryPi4B,
329 "Raspberry Pi 400" => Model::RaspberryPi400,
330 "Raspberry Pi Compute Module 4" => Model::RaspberryPiComputeModule4,
331 "Raspberry Pi Compute Module 4S" => Model::RaspberryPiComputeModule4S,
332 "Raspberry Pi 5 Model B" => Model::RaspberryPi5,
333 "Raspberry Pi Compute Module 5" => Model::RaspberryPiComputeModule5,
334 "Raspberry Pi Compute Module 5 Lite" => Model::RaspberryPiComputeModule5Lite,
335 "Raspberry Pi 500" => Model::RaspberryPi500,
336 _ => return Err(Error::UnknownModel),
337 };
338
339 Ok(model)
340}
341
342#[derive(Debug, PartialEq, Eq, PartialOrd)]
344struct KernelVersion {
345 major: u32,
346 minor: u32,
347 patch: u32,
348}
349
350impl KernelVersion {
351 pub fn new() -> Result<KernelVersion> {
356 let contents = fs::read_to_string("/proc/version").map_err(|_| Error::UnknownKernel)?;
357 let re = Regex::new(r"Linux version (?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)")
359 .or(Err(Error::UnknownKernel))?;
360 let captures = re
361 .captures(&contents)
362 .map_or_else(|| Err(Error::UnknownKernel), Ok)?;
363 let major = captures
364 .name("major")
365 .map_or_else(|| Err(Error::UnknownKernel), |major| Ok(major.as_str()))?
366 .parse::<u32>()
367 .or(Err(Error::UnknownKernel))?;
368 let minor = captures
369 .name("minor")
370 .map_or_else(|| Err(Error::UnknownKernel), |minor| Ok(minor.as_str()))?
371 .parse::<u32>()
372 .or(Err(Error::UnknownKernel))?;
373 let patch = captures
374 .name("patch")
375 .map_or_else(|| Err(Error::UnknownKernel), |patch| Ok(patch.as_str()))?
376 .parse::<u32>()
377 .or(Err(Error::UnknownKernel))?;
378
379 Ok(KernelVersion {
380 major,
381 minor,
382 patch,
383 })
384 }
385}
386
387#[derive(Debug, PartialEq, Eq, Copy, Clone)]
389pub struct DeviceInfo {
390 model: Model,
391 soc: SoC,
392 peripheral_base: u32,
394 gpio_offset: u32,
396 gpio_lines: u8,
398 gpio_interface: GpioInterface,
400 pwm_chip: u8,
402 pwm_channels: u8,
404}
405
406impl DeviceInfo {
407 pub fn new() -> Result<DeviceInfo> {
413 let model = parse_proc_cpuinfo()
415 .or_else(|_| parse_base_compatible().or_else(|_| parse_base_model()))?;
416
417 match model {
419 Model::RaspberryPiA
420 | Model::RaspberryPiAPlus
421 | Model::RaspberryPiBRev1
422 | Model::RaspberryPiBRev2
423 | Model::RaspberryPiBPlus
424 | Model::RaspberryPiComputeModule
425 | Model::RaspberryPiZero
426 | Model::RaspberryPiZeroW => Ok(DeviceInfo {
427 model,
428 soc: SoC::Bcm2835,
429 peripheral_base: PERIPHERAL_BASE_RPI,
430 gpio_offset: GPIO_OFFSET,
431 gpio_lines: GPIO_LINES_BCM283X,
432 gpio_interface: GpioInterface::Bcm,
433 pwm_chip: 0,
434 pwm_channels: 2,
435 }),
436 Model::RaspberryPi2B => Ok(DeviceInfo {
437 model,
438 soc: SoC::Bcm2836,
439 peripheral_base: PERIPHERAL_BASE_RPI2,
440 gpio_offset: GPIO_OFFSET,
441 gpio_lines: GPIO_LINES_BCM283X,
442 gpio_interface: GpioInterface::Bcm,
443 pwm_chip: 0,
444 pwm_channels: 2,
445 }),
446 Model::RaspberryPi3B | Model::RaspberryPiComputeModule3 | Model::RaspberryPiZero2W => {
447 Ok(DeviceInfo {
448 model,
449 soc: SoC::Bcm2837A1,
450 peripheral_base: PERIPHERAL_BASE_RPI2,
451 gpio_offset: GPIO_OFFSET,
452 gpio_lines: GPIO_LINES_BCM283X,
453 gpio_interface: GpioInterface::Bcm,
454 pwm_chip: 0,
455 pwm_channels: 2,
456 })
457 }
458 Model::RaspberryPi3BPlus
459 | Model::RaspberryPi3APlus
460 | Model::RaspberryPiComputeModule3Plus => Ok(DeviceInfo {
461 model,
462 soc: SoC::Bcm2837B0,
463 peripheral_base: PERIPHERAL_BASE_RPI2,
464 gpio_offset: GPIO_OFFSET,
465 gpio_lines: GPIO_LINES_BCM283X,
466 gpio_interface: GpioInterface::Bcm,
467 pwm_chip: 0,
468 pwm_channels: 2,
469 }),
470 Model::RaspberryPi4B
471 | Model::RaspberryPi400
472 | Model::RaspberryPiComputeModule4
473 | Model::RaspberryPiComputeModule4S => Ok(DeviceInfo {
474 model,
475 soc: SoC::Bcm2711,
476 peripheral_base: PERIPHERAL_BASE_RPI4,
477 gpio_offset: GPIO_OFFSET,
478 gpio_lines: GPIO_LINES_BCM2711,
479 gpio_interface: GpioInterface::Bcm,
480 pwm_chip: 0,
481 pwm_channels: 2,
482 }),
483 Model::RaspberryPi5
484 | Model::RaspberryPi500
485 | Model::RaspberryPiComputeModule5
486 | Model::RaspberryPiComputeModule5Lite => {
487 let kernel = KernelVersion::new()?;
490 let chip0_kernel_version = KernelVersion {
491 major: 6,
492 minor: 12,
493 patch: 0,
494 };
495 Ok(DeviceInfo {
496 model,
497 soc: SoC::Bcm2712,
498 peripheral_base: PERIPHERAL_BASE_RP1,
499 gpio_offset: GPIO_OFFSET_RP1,
500 gpio_lines: GPIO_LINES_RP1,
501 gpio_interface: GpioInterface::Rp1,
502 pwm_chip: if kernel < chip0_kernel_version { 2 } else { 0 },
503 pwm_channels: 4,
504 })
505 }
506 }
507 }
508
509 pub fn model(&self) -> Model {
511 self.model
512 }
513
514 pub fn soc(&self) -> SoC {
516 self.soc
517 }
518
519 pub fn pwm_channels(&self) -> u8 {
521 self.pwm_channels
522 }
523
524 pub(crate) fn peripheral_base(&self) -> u32 {
526 self.peripheral_base
527 }
528
529 pub(crate) fn gpio_offset(&self) -> u32 {
531 self.gpio_offset
532 }
533
534 pub(crate) fn gpio_lines(&self) -> u8 {
536 self.gpio_lines
537 }
538
539 pub(crate) fn gpio_interface(&self) -> GpioInterface {
541 self.gpio_interface
542 }
543
544 pub(crate) fn pwm_chip(&self) -> u8 {
546 self.pwm_chip
547 }
548}