blob: 30709dcdd1b8937fc7e7fa44e08802123e2597bd [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 std::cell::{Cell, RefCell};
use std::fmt;
use std::io::{Read, Write};
use std::rc::Rc;
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use crate::io::emu::EmuState;
use crate::io::gpio::{self, GpioError, GpioPin, PullMode};
use crate::transport::ti50emulator::Inner;
use crate::transport::ti50emulator::Ti50Emulator;
const GPIO_BUF_SIZE: usize = 16;
/// Drive mode of I/O pins (that is, the subset supported by this emulator).
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PinMode {
Input,
PushPull,
OpenDrain,
}
/// Enumeration representing the GPIO control commands
/// sent through the socket.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
enum GpioStreamCommand {
SetZero = b'0',
SetOne = b'1',
GetState = b'%',
}
impl From<bool> for GpioStreamCommand {
fn from(value: bool) -> GpioStreamCommand {
match value {
false => GpioStreamCommand::SetZero,
true => GpioStreamCommand::SetOne,
}
}
}
/// Enumeration that represents GPIO status updates caused
/// by a response to a `GetState` command.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum GpioStateUpdate {
Value(bool),
PinMode(Option<PinMode>),
PullMode(PullMode),
}
impl TryFrom<u8> for GpioStateUpdate {
type Error = GpioError;
/// A function designed to convert the bytes sent by the GPIO stream to state changes.
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
b'0' => Ok(GpioStateUpdate::Value(false)),
b'1' => Ok(GpioStateUpdate::Value(true)),
b'z' => Ok(GpioStateUpdate::PinMode(None)),
b'i' => Ok(GpioStateUpdate::PinMode(Some(PinMode::Input))),
b'o' => Ok(GpioStateUpdate::PinMode(Some(PinMode::OpenDrain))),
b'-' => Ok(GpioStateUpdate::PullMode(PullMode::None)),
b'u' => Ok(GpioStateUpdate::PullMode(PullMode::PullUp)),
b'd' => Ok(GpioStateUpdate::PullMode(PullMode::PullDown)),
_ => Err(GpioError::Generic(format!(
"Invalid byte value during decoding GPIO stream hex: {:#04x} char: '{}'",
value, value as char
))),
}
}
}
/// Structure representing GPIO pin configuration state.
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct GpioConfiguration {
pin_mode: Option<PinMode>,
pull_mode: PullMode,
value: bool,
}
impl GpioConfiguration {
/// Update current GPIO configurations by applying changes
/// encoded in `buf`.
pub fn update(&mut self, buf: &[u8]) -> Result<()> {
if !buf.is_empty() {
for data in buf.iter() {
match GpioStateUpdate::try_from(*data)? {
GpioStateUpdate::Value(value) => {
self.value = value;
}
GpioStateUpdate::PinMode(mode) => {
self.pin_mode = mode;
}
GpioStateUpdate::PullMode(mode) => {
self.pull_mode = mode;
}
}
}
return Ok(());
}
bail!("GPIO configuration buffer is empty");
}
}
impl fmt::Display for GpioConfiguration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} {} {}", self.pin_mode, self.pull_mode, self.value)
}
}
/// Multi value logic with drive strength representations.
/// (Subset of IEEE 1164).
enum Logic {
StrongZero,
StrongOne,
WeakZero,
WeakOne,
HiImpedance,
}
impl fmt::Display for Logic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Logic::StrongZero => write!(f, "0"),
Logic::StrongOne => write!(f, "1"),
Logic::WeakZero => write!(f, "L"),
Logic::WeakOne => write!(f, "H"),
Logic::HiImpedance => write!(f, "Z"),
}
}
}
impl From<GpioConfiguration> for Logic {
/// Convert the GPIO state stored in `state` to multi-valued logic.
fn from(state: GpioConfiguration) -> Logic {
match (state.pin_mode, state.pull_mode, state.value) {
(Some(PinMode::PushPull), _, false) => Logic::StrongZero,
(Some(PinMode::PushPull), _, true) => Logic::StrongOne,
(Some(PinMode::OpenDrain), _, false) => Logic::StrongZero,
(Some(PinMode::OpenDrain), PullMode::PullUp, true) => Logic::WeakOne,
(Some(PinMode::OpenDrain), PullMode::PullDown, true) => Logic::WeakZero,
(Some(PinMode::OpenDrain), PullMode::None, true) => Logic::HiImpedance,
(Some(PinMode::Input), PullMode::PullUp, _) => Logic::WeakOne,
(Some(PinMode::Input), PullMode::PullDown, _) => Logic::WeakZero,
(Some(PinMode::Input), PullMode::None, _) => Logic::HiImpedance,
(None, _, _) => Logic::HiImpedance,
}
}
}
/// The function calculates the state of the "virtual wire" that connects the "host" and DUT GPIO port.
/// If any inconsistencies are detected between the sides, it returns an error with the problem description.
/// The state on the host side is passed by `host` argument and on DUT side by `dut`.
/// Parameter `name` contain name of GPIO pin.
fn resolve_state(name: &str, host: &Logic, dut: &Logic) -> Result<bool> {
match (host, dut) {
(Logic::StrongOne, Logic::StrongZero) => {
Err(
GpioError::PinValueConflict(name.to_string(), host.to_string(), dut.to_string())
.into(),
)
}
(Logic::StrongOne, _) => Ok(true),
(Logic::StrongZero, Logic::StrongOne) => {
Err(
GpioError::PinValueConflict(name.to_string(), host.to_string(), dut.to_string())
.into(),
)
}
(Logic::StrongZero, _) => Ok(false),
// Note: `dut` weak drive is stronger than `host` weak drive.
// This selection was made for compatibility with the Ti50 chip.
(
Logic::WeakZero | Logic::WeakOne | Logic::HiImpedance,
Logic::StrongOne | Logic::WeakOne,
) => Ok(true),
(
Logic::WeakZero | Logic::WeakOne | Logic::HiImpedance,
Logic::StrongZero | Logic::WeakZero,
) => Ok(false),
(Logic::WeakOne, Logic::HiImpedance) => Ok(true),
(Logic::WeakZero, Logic::HiImpedance) => Ok(false),
(Logic::HiImpedance, Logic::HiImpedance) => {
Err(GpioError::PinValueUndefined(name.to_string()).into())
}
}
}
/// Structure representing an emulated GPIO pin.
pub struct Ti50GpioPin {
/// Handle to Ti50Emulator internal data.
inner: Rc<RefCell<Inner>>,
/// This socket is valid as long as SubProcess is running.
socket: RefCell<Option<UnixStream>>,
/// Full path to socket file.
path: PathBuf,
/// Last SubProcess ID.
last_id: Cell<u64>,
/// Current state of DUT GPIO
dut_state: Cell<GpioConfiguration>,
/// Current state of host GPIO
host_state: Cell<GpioConfiguration>,
}
impl Ti50GpioPin {
pub fn open(ti50: &Ti50Emulator, path: &str, state: &GpioConfiguration) -> Result<Self> {
let soc_path = ti50.inner.borrow().process.get_runtime_dir().join(path);
Ok(Self {
inner: ti50.inner.clone(),
socket: RefCell::new(None),
path: soc_path,
last_id: Cell::new(0),
dut_state: Cell::new(*state),
host_state: Cell::new(GpioConfiguration {
pin_mode: None,
pull_mode: PullMode::None,
value: false,
}),
})
}
/// Function re-connect socket to `SubProcess` when detect
/// that process was restarted.
fn reconnect(&self) -> Result<()> {
let mut socket = self.socket.borrow_mut();
let id = self.inner.borrow_mut().process.get_id();
if self.last_id.get() != id {
*socket = Some(UnixStream::connect(&self.path)?);
self.last_id.set(id);
}
Ok(())
}
/// The function updates the current state of the GPIO configuration
/// by sending a query to the TockOS emulator process.
fn update_dut_state(&self) -> Result<()> {
if let Some(ref mut pin_fd) = *self.socket.borrow_mut() {
let mut buf: [u8; GPIO_BUF_SIZE] = [0; GPIO_BUF_SIZE];
pin_fd.write_all(&[GpioStreamCommand::GetState as u8])?;
let len = pin_fd.read(&mut buf[..])?;
let mut state = self.dut_state.get();
state.update(&buf[0..len])?;
self.dut_state.set(state);
return Ok(());
}
bail!("GPIO update DUT state fail - invalid socket");
}
/// Write the GPIO state in DUT by sending the SetZero/SetOne command to the TockOS emulator process.
fn write_dut_value(&self, value: bool) -> Result<()> {
if let Some(ref mut pin_fd) = *self.socket.borrow_mut() {
pin_fd.write_all(&[GpioStreamCommand::from(value) as u8])?;
return Ok(());
}
bail!("GPIO write DUT state fail - invalid socket");
}
/// Function checks whether the state of the sub-process allows GPIO operations to be performed
fn check_state(&self) -> Result<()> {
let process = &mut self.inner.borrow_mut().process;
process.update_status()?;
match process.get_state() {
EmuState::On => Ok(()),
state => Err(GpioError::Generic(format!(
"Operation not supported in Emulator state: {}",
state
))
.into()),
}
}
/// Function validate GPIO configuration on DUT and Host side and
/// write `value` to Emulator process by a socket.
fn validate_and_write(&self, value: bool) -> Result<()> {
let mut host_state = self.host_state.get();
let mut dut_state = self.dut_state.get();
host_state.value = value;
match (
self.host_state.get().pin_mode,
self.dut_state.get().pin_mode,
) {
(Some(PinMode::PushPull) | Some(PinMode::OpenDrain), None) => {
log::warn!(
"GPIO write to disable pin on target device, states host:{} target:{}",
host_state,
dut_state,
);
}
(Some(PinMode::PushPull) | Some(PinMode::OpenDrain), Some(PinMode::Input)) => {
log::debug!("GPIO write host:{} target:{}", host_state, dut_state);
}
(_, _) => {
bail!(GpioError::PinModeConflict(
self.path.display().to_string(),
host_state.to_string(),
dut_state.to_string()
));
}
}
dut_state.value = resolve_state(
&self.path.to_string_lossy(),
&Logic::from(host_state),
&Logic::from(dut_state),
)?;
self.write_dut_value(dut_state.value)?;
self.dut_state.set(dut_state);
self.host_state.set(host_state);
Ok(())
}
/// Function validate GPIO configuration on DUT and Host side
/// and then returns resolved value of GPIO pin.
fn validate_and_read(&self) -> Result<bool> {
let mut host_state = self.host_state.get();
let dut_state = self.dut_state.get();
match (host_state.pin_mode, dut_state.pin_mode) {
(Some(PinMode::Input), None) => {
log::warn!(
"GPIO read from disable pin on target device, states host:{} target:{}",
host_state,
dut_state,
);
}
(Some(PinMode::Input), Some(PinMode::PushPull) | Some(PinMode::OpenDrain)) => {
log::debug!("GPIO read host:{} target:{}", host_state, dut_state);
}
(_, _) => {
bail!(GpioError::PinModeConflict(
self.path.display().to_string(),
host_state.to_string(),
dut_state.to_string()
));
}
}
host_state.value = resolve_state(
&self.path.to_string_lossy(),
&Logic::from(host_state),
&Logic::from(dut_state),
)?;
self.host_state.set(host_state);
Ok(host_state.value)
}
}
/// A trait which represents a single GPIO pin.
impl GpioPin for Ti50GpioPin {
/// Reads the value of the GPIO pin.
fn read(&self) -> Result<bool> {
self.check_state()?;
self.reconnect()?;
self.update_dut_state()?;
self.validate_and_read()
}
/// Sets the value of the GPIO pin to `value`.
fn write(&self, value: bool) -> Result<()> {
self.check_state()?;
self.reconnect()?;
self.update_dut_state()?;
self.validate_and_write(value)
}
/// Sets the mode of the GPIO pin as input, output, or open drain I/O.
fn set_mode(&self, mode: gpio::PinMode) -> Result<()> {
let mut state = self.host_state.get();
match mode {
gpio::PinMode::Input => state.pin_mode = Some(PinMode::Input),
gpio::PinMode::OpenDrain => state.pin_mode = Some(PinMode::OpenDrain),
gpio::PinMode::PushPull => state.pin_mode = Some(PinMode::PushPull),
_ => return Err(GpioError::UnsupportedPinMode(mode).into()),
}
self.host_state.set(state);
Ok(())
}
/// Sets the pull mode of the GPIO pin.
fn set_pull_mode(&self, mode: PullMode) -> Result<()> {
let mut state = self.host_state.get();
state.pull_mode = mode;
self.host_state.set(state);
Ok(())
}
}