opentitanlib/transport/verilator/
gpio.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Context, Result, ensure};
6use std::cell::{Cell, RefCell};
7use std::collections::HashMap;
8use std::fs::{File, OpenOptions};
9use std::io::{self, ErrorKind, Read, Write};
10use std::rc::Rc;
11use std::time::Duration;
12
13use crate::io::gpio::{GpioError, GpioPin, PinMode, PullMode};
14use crate::transport::TransportError;
15use crate::transport::verilator::transport::Inner;
16use crate::util::file;
17
18pub struct VerilatorGpioPin {
19    inner: Rc<RefCell<Inner>>,
20    pinname: u8,
21    out_value: Cell<bool>,
22    pinmode: Cell<PinMode>,
23    pullmode: Cell<PullMode>,
24}
25
26impl VerilatorGpioPin {
27    pub(crate) fn new(inner: Rc<RefCell<Inner>>, pinname: u8) -> Rc<Self> {
28        Rc::new(VerilatorGpioPin {
29            inner,
30            pinname,
31            out_value: Cell::new(false),
32            pinmode: Cell::new(PinMode::PushPull),
33            pullmode: Cell::new(PullMode::None),
34        })
35    }
36
37    fn set(&self) -> Result<()> {
38        let mut inner = self.inner.borrow_mut();
39        inner.gpio.set(
40            self.pinname,
41            self.out_value.get(),
42            self.pinmode.get(),
43            self.pullmode.get(),
44        )
45    }
46}
47
48impl GpioPin for VerilatorGpioPin {
49    fn read(&self) -> Result<bool> {
50        let mut inner = self.inner.borrow_mut();
51        inner.gpio.get(self.pinname)
52    }
53
54    fn write(&self, value: bool) -> Result<()> {
55        self.out_value.set(value);
56        self.set()
57    }
58
59    fn set_mode(&self, mode: PinMode) -> Result<()> {
60        self.pinmode.set(mode);
61        self.set()
62    }
63
64    fn set_pull_mode(&self, mode: PullMode) -> Result<()> {
65        self.pullmode.set(mode);
66        self.set()
67    }
68
69    /// Atomically sets mode, value, and weak pull.
70    fn set(
71        &self,
72        mode: Option<PinMode>,
73        value: Option<bool>,
74        pull: Option<PullMode>,
75        analog_value: Option<f32>,
76    ) -> Result<()> {
77        if analog_value.is_some() {
78            return Err(TransportError::UnsupportedOperation.into());
79        }
80        if let Some(mode) = mode {
81            self.pinmode.set(mode);
82        }
83        if let Some(pull) = pull {
84            self.pullmode.set(pull);
85        }
86        if let Some(value) = value {
87            self.out_value.set(value);
88        }
89        self.set()
90    }
91}
92
93pub(crate) struct GpioInner {
94    read: File,
95    write: File,
96    rval: u32,
97    pub pins: HashMap<u8, Rc<dyn GpioPin>>,
98}
99
100impl GpioInner {
101    pub fn new(read: &str, write: &str) -> Result<Self> {
102        let read = OpenOptions::new().read(true).open(read)?;
103        let write = OpenOptions::new().write(true).open(write)?;
104        Ok(GpioInner {
105            read,
106            write,
107            rval: 0,
108            pins: HashMap::default(),
109        })
110    }
111
112    fn read_pipe(&mut self) -> Result<()> {
113        loop {
114            match file::wait_read_timeout(&self.read, Duration::ZERO) {
115                Ok(()) => {
116                    let mut buf = [0u8; 33];
117                    let n = self.read.read(&mut buf).context("GPIO read error")?;
118                    ensure!(
119                        n == buf.len(),
120                        GpioError::Generic(format!(
121                            "Invalid gpio read length from simulator: {}",
122                            n
123                        ))
124                    );
125                    for (i, val) in buf.iter().enumerate() {
126                        self.rval = match val {
127                            b'0' => self.rval & !(1 << (31 - i)),
128                            b'1' => self.rval | 1 << (31 - i),
129                            b'\n' | b'X' => self.rval,
130                            _ => {
131                                return Err(GpioError::Generic(format!(
132                                    "Invalid gpio status from simulator: {:?}",
133                                    std::str::from_utf8(&buf)
134                                ))
135                                .into());
136                            }
137                        };
138                    }
139                }
140                Err(e) => {
141                    if let Some(ioerr) = e.downcast_ref::<io::Error>()
142                        && ioerr.kind() == ErrorKind::TimedOut
143                    {
144                        break;
145                    }
146                    return Err(e).context("GPIO read error");
147                }
148            }
149        }
150        Ok(())
151    }
152
153    fn get(&mut self, index: u8) -> Result<bool> {
154        self.read_pipe()?;
155        Ok(self.rval & (1 << index) != 0)
156    }
157
158    fn set(&mut self, index: u8, val: bool, mode: PinMode, weak_pull: PullMode) -> Result<()> {
159        let code = match (mode, val, weak_pull) {
160            (PinMode::PushPull, true, _) => "h",
161            (PinMode::PushPull, false, _) => "l",
162            (PinMode::OpenDrain, false, _) => "l",
163            // If none of the above match, then there is no strong drive, inspect weak pull mode.
164            (_, _, PullMode::PullUp) => "wh",
165            (_, _, PullMode::PullDown) => "wl",
166            (_, _, PullMode::None) => "wl", // High-Z not implemented in gpiodpi
167        };
168        let command = format!("{}{}\n", code, index);
169        self.write
170            .write(command.as_bytes())
171            .context("GPIO write error")?;
172        Ok(())
173    }
174}