| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| use anyhow::{ensure, Context, Result}; |
| use std::cell::{Cell, RefCell}; |
| use std::collections::HashMap; |
| use std::fs::{File, OpenOptions}; |
| use std::io::{self, ErrorKind, Read, Write}; |
| use std::rc::Rc; |
| use std::time::Duration; |
| |
| use crate::io::gpio::{GpioError, GpioPin, PinMode, PullMode}; |
| use crate::transport::verilator::transport::Inner; |
| use crate::transport::TransportError; |
| use crate::util::file; |
| |
| pub struct VerilatorGpioPin { |
| inner: Rc<RefCell<Inner>>, |
| pinname: u8, |
| out_value: Cell<bool>, |
| pinmode: Cell<PinMode>, |
| pullmode: Cell<PullMode>, |
| } |
| |
| impl VerilatorGpioPin { |
| pub(crate) fn new(inner: Rc<RefCell<Inner>>, pinname: u8) -> Rc<Self> { |
| Rc::new(VerilatorGpioPin { |
| inner, |
| pinname, |
| out_value: Cell::new(false), |
| pinmode: Cell::new(PinMode::PushPull), |
| pullmode: Cell::new(PullMode::None), |
| }) |
| } |
| |
| fn set(&self) -> Result<()> { |
| let mut inner = self.inner.borrow_mut(); |
| inner.gpio.set( |
| self.pinname, |
| self.out_value.get(), |
| self.pinmode.get(), |
| self.pullmode.get(), |
| ) |
| } |
| } |
| |
| impl GpioPin for VerilatorGpioPin { |
| fn read(&self) -> Result<bool> { |
| let mut inner = self.inner.borrow_mut(); |
| inner.gpio.get(self.pinname) |
| } |
| |
| fn write(&self, value: bool) -> Result<()> { |
| self.out_value.set(value); |
| self.set() |
| } |
| |
| fn set_mode(&self, mode: PinMode) -> Result<()> { |
| self.pinmode.set(mode); |
| self.set() |
| } |
| |
| fn set_pull_mode(&self, mode: PullMode) -> Result<()> { |
| self.pullmode.set(mode); |
| self.set() |
| } |
| |
| /// Atomically sets mode, value, and weak pull. |
| fn set( |
| &self, |
| mode: Option<PinMode>, |
| value: Option<bool>, |
| pull: Option<PullMode>, |
| analog_value: Option<f32>, |
| ) -> Result<()> { |
| if analog_value.is_some() { |
| return Err(TransportError::UnsupportedOperation.into()); |
| } |
| if let Some(mode) = mode { |
| self.pinmode.set(mode); |
| } |
| if let Some(pull) = pull { |
| self.pullmode.set(pull); |
| } |
| if let Some(value) = value { |
| self.out_value.set(value); |
| } |
| self.set() |
| } |
| } |
| |
| pub(crate) struct GpioInner { |
| read: File, |
| write: File, |
| rval: u32, |
| pub pins: HashMap<u8, Rc<dyn GpioPin>>, |
| } |
| |
| impl GpioInner { |
| pub fn new(read: &str, write: &str) -> Result<Self> { |
| let read = OpenOptions::new().read(true).open(read)?; |
| let write = OpenOptions::new().write(true).open(write)?; |
| Ok(GpioInner { |
| read, |
| write, |
| rval: 0, |
| pins: HashMap::default(), |
| }) |
| } |
| |
| fn read_pipe(&mut self) -> Result<()> { |
| loop { |
| match file::wait_read_timeout(&self.read, Duration::ZERO) { |
| Ok(()) => { |
| let mut buf = [0u8; 33]; |
| let n = self.read.read(&mut buf).context("GPIO read error")?; |
| ensure!( |
| n == buf.len(), |
| GpioError::Generic(format!( |
| "Invalid gpio read length from simulator: {}", |
| n |
| )) |
| ); |
| for (i, val) in buf.iter().enumerate() { |
| self.rval = match val { |
| b'0' => self.rval & !(1 << (31 - i)), |
| b'1' => self.rval | 1 << (31 - i), |
| b'\n' | b'X' => self.rval, |
| _ => { |
| return Err(GpioError::Generic(format!( |
| "Invalid gpio status from simulator: {:?}", |
| std::str::from_utf8(&buf) |
| )) |
| .into()); |
| } |
| }; |
| } |
| } |
| Err(e) => { |
| if let Some(ioerr) = e.downcast_ref::<io::Error>() { |
| if ioerr.kind() == ErrorKind::TimedOut { |
| break; |
| } |
| } |
| return Err(e).context("GPIO read error"); |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| fn get(&mut self, index: u8) -> Result<bool> { |
| self.read_pipe()?; |
| Ok(self.rval & (1 << index) != 0) |
| } |
| |
| fn set(&mut self, index: u8, val: bool, mode: PinMode, weak_pull: PullMode) -> Result<()> { |
| let code = match (mode, val, weak_pull) { |
| (PinMode::PushPull, true, _) => "h", |
| (PinMode::PushPull, false, _) => "l", |
| (PinMode::OpenDrain, false, _) => "l", |
| // If none of the above match, then there is no strong drive, inspect weak pull mode. |
| (_, _, PullMode::PullUp) => "wh", |
| (_, _, PullMode::PullDown) => "wl", |
| (_, _, PullMode::None) => "wl", // High-Z not implemented in gpiodpi |
| }; |
| let command = format!("{}{}\n", code, index); |
| self.write |
| .write(command.as_bytes()) |
| .context("GPIO write error")?; |
| Ok(()) |
| } |
| } |