blob: 614350f522b77e5dc8a5462764937dc0bb39cf38 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use anyhow::Result;
use lazy_static::lazy_static;
use regex::Regex;
use std::rc::Rc;
use crate::io::gpio::{
ClockNature, Edge, GpioError, GpioMonitoring, GpioPin, MonitoringEvent, MonitoringReadResponse,
MonitoringStartResponse, PinMode, PullMode,
};
use crate::transport::hyperdebug::Inner;
use crate::transport::TransportError;
pub struct HyperdebugGpioPin {
inner: Rc<Inner>,
pinname: String,
}
impl HyperdebugGpioPin {
pub fn open(inner: &Rc<Inner>, pinname: &str) -> Result<Self> {
let result = Self {
inner: Rc::clone(inner),
pinname: pinname.to_string(),
};
Ok(result)
}
}
impl GpioPin for HyperdebugGpioPin {
/// Reads the value of the the GPIO pin `id`.
fn read(&self) -> Result<bool> {
let line = self
.inner
.cmd_one_line_output(&format!("gpioget {}", &self.pinname))
.map_err(|_| {
TransportError::CommunicationError("No output from gpioget".to_string())
})?;
Ok(line.trim_start().starts_with('1'))
}
/// Sets the value of the GPIO pin `id` to `value`.
fn write(&self, value: bool) -> Result<()> {
self.inner
.cmd_no_output(&format!("gpioset {} {}", &self.pinname, u32::from(value)))
}
fn set_mode(&self, mode: PinMode) -> Result<()> {
self.inner.execute_command(
&format!(
"gpiomode {} {}",
&self.pinname,
match mode {
PinMode::Input => "input",
PinMode::OpenDrain => "opendrain",
PinMode::PushPull => "pushpull",
PinMode::AnalogInput => "adc",
PinMode::AnalogOutput => "dac",
PinMode::Alternate => "alternate",
}
),
|_| {},
)
}
fn set_pull_mode(&self, mode: PullMode) -> Result<()> {
self.inner.cmd_no_output(&format!(
"gpiopullmode {} {}",
&self.pinname,
match mode {
PullMode::None => "none",
PullMode::PullUp => "up",
PullMode::PullDown => "down",
}
))
}
fn analog_read(&self) -> Result<f32> {
let line = self
.inner
.cmd_one_line_output(&format!("adc {}", &self.pinname))
.map_err(|_| TransportError::CommunicationError("No output from adc".to_string()))?;
lazy_static! {
pub static ref ADC_REGEX: Regex = Regex::new("^ +([^ ])+ = ([0-9]+) mV").unwrap();
}
if let Some(captures) = ADC_REGEX.captures(&line) {
let milli_volts: u32 = captures.get(2).unwrap().as_str().parse()?;
Ok(milli_volts as f32 / 1000.0)
} else {
Err(TransportError::CommunicationError("Unrecognized adc output".to_string()).into())
}
}
fn analog_write(&self, volts: f32) -> Result<()> {
if volts > 3.3 || volts < 0.0 {
return Err(GpioError::UnsupportedPinVoltage(volts).into());
}
let milli_volts = (volts * 1000.0) as u32;
self.inner.cmd_no_output(&format!(
"gpio analog-set {} {}",
&self.pinname, milli_volts,
))
}
fn set(
&self,
mode: Option<PinMode>,
value: Option<bool>,
pull: Option<PullMode>,
volts: Option<f32>,
) -> Result<()> {
if let Some(v) = volts {
if v > 3.3 || v < 0.0 {
return Err(GpioError::UnsupportedPinVoltage(v).into());
}
}
self.inner
.cmd_no_output(&format!(
"gpio multiset {} {} {} {} {}",
&self.pinname,
match value {
Some(false) => "0",
Some(true) => "1",
None => "-",
},
match mode {
Some(PinMode::Input) => "input",
Some(PinMode::OpenDrain) => "opendrain",
Some(PinMode::PushPull) => "pushpull",
Some(PinMode::AnalogInput) => "adc",
Some(PinMode::AnalogOutput) => "dac",
Some(PinMode::Alternate) => "alternate",
None => "-",
},
match pull {
Some(PullMode::None) => "none",
Some(PullMode::PullUp) => "up",
Some(PullMode::PullDown) => "down",
None => "-",
},
if let Some(v) = volts {
format!("{}", (v * 1000.0) as u32)
} else {
"-".to_string()
},
))
.or_else(|_| {
// HyperDebug firmware does not support atomically setting all three, fall back to
// separate commands.
if let Some(mode) = mode {
self.set_mode(mode)?;
}
if let Some(pull) = pull {
self.set_pull_mode(pull)?;
}
if let Some(value) = value {
self.write(value)?;
}
if let Some(volts) = volts {
self.analog_write(volts)?;
}
Ok(())
})
}
fn get_internal_pin_name(&self) -> Option<&str> {
Some(&self.pinname)
}
}
pub struct HyperdebugGpioMonitoring {
inner: Rc<Inner>,
}
impl HyperdebugGpioMonitoring {
pub fn open(inner: &Rc<Inner>) -> Result<Self> {
Ok(Self {
inner: Rc::clone(inner),
})
}
}
impl GpioMonitoring for HyperdebugGpioMonitoring {
fn get_clock_nature(&self) -> Result<ClockNature> {
Ok(ClockNature::Wallclock {
resolution: 1_000_000,
offset: None,
})
}
/// Set up edge trigger detection on the given set of pins, transport will buffer the list
/// internally.
fn monitoring_start(&self, pins: &[&dyn GpioPin]) -> Result<MonitoringStartResponse> {
let mut pin_names = Vec::new();
for pin in pins {
pin_names.push(
pin.get_internal_pin_name()
.ok_or(TransportError::InvalidOperation)?,
);
}
lazy_static! {
pub static ref START_TIME_REGEX: Regex = Regex::new("^ +@([0-9]+)").unwrap();
pub static ref SIGNAL_REGEX: Regex = Regex::new("^ +([0-9]+) ([^ ])+ ([01])").unwrap();
}
let mut start_time: u64 = 0;
let mut signals = Vec::new();
self.inner.execute_command(
&format!("gpio monitoring start {}", pin_names.join(" ")),
|line| {
if let Some(captures) = START_TIME_REGEX.captures(line) {
start_time = captures.get(1).unwrap().as_str().parse().unwrap();
} else if let Some(captures) = SIGNAL_REGEX.captures(line) {
signals.push(captures.get(3).unwrap().as_str() != "0");
} else {
log::error!("Unexpected HyperDebug output: {}\n", line);
};
},
)?;
Ok(MonitoringStartResponse {
timestamp: start_time,
initial_levels: signals,
})
}
/// Retrieve list of events detected thus far, optionally stopping the possibly expensive edge
/// detection. Buffer overrun will be reported as an `Err`, and result in the stopping of the
/// edge detection irrespective of the parameter value.
fn monitoring_read(
&self,
pins: &[&dyn GpioPin],
continue_monitoring: bool,
) -> Result<MonitoringReadResponse> {
let mut pin_names = Vec::new();
for pin in pins {
pin_names.push(
pin.get_internal_pin_name()
.ok_or(TransportError::InvalidOperation)?,
);
}
lazy_static! {
pub static ref START_TIME_REGEX: Regex = Regex::new("^ +@([0-9]+)").unwrap();
pub static ref EDGE_REGEX: Regex = Regex::new("^ +([0-9]+) (-?[0-9]+) ([RF])").unwrap();
}
let mut reference_time: u64 = 0;
let mut events = Vec::new();
loop {
let mut more_data = false;
self.inner.execute_command(
&format!("gpio monitoring read {}", pin_names.join(" ")),
|line| {
if let Some(captures) = START_TIME_REGEX.captures(line) {
reference_time = captures.get(1).unwrap().as_str().parse().unwrap();
} else if let Some(captures) = EDGE_REGEX.captures(line) {
events.push(MonitoringEvent {
signal_index: captures.get(1).unwrap().as_str().parse().unwrap(),
edge: if captures.get(3).unwrap().as_str() == "R" {
Edge::Rising
} else {
Edge::Falling
},
timestamp: (reference_time as i64
+ captures.get(2).unwrap().as_str().parse::<i64>().unwrap())
as u64,
});
} else if line == "Warning: more data" {
more_data = true;
} else {
log::error!("Unexpected HyperDebug output: {}\n", line);
}
},
)?;
if !more_data {
break;
}
}
if !continue_monitoring {
self.inner
.cmd_no_output(&format!("gpio monitoring stop {}", pin_names.join(" ")))?;
}
Ok(MonitoringReadResponse {
events,
timestamp: reference_time, // TODO: adjust in case of event later than this timestamp
})
}
}