use crate::constants::{CTRL_RESET, PROTO_CBM};
use crate::device::SwDebugInfo;
#[allow(unused_imports)]
use crate::DeviceChannel;
use crate::{CommunicationError, DeviceAccessError, DeviceConfig, Error};
use crate::{DeviceInfo, DeviceType, RemoteUsbDeviceConfig, UsbDeviceConfig};
use crate::{Ioctl, RemoteUsbInfo, UsbInfo};
use cmd::{BusCommand, BusMode};
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use std::time::Duration;
pub mod builder;
pub mod cmd;
pub use builder::BusBuilder;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
#[derive(Debug, Clone, PartialEq, Default)]
pub enum BusRecoveryType {
#[default]
Off,
All,
Serial,
}
#[derive(Debug)]
pub struct Bus {
device: DeviceType,
_timeout: Duration,
mode: BusMode,
serial: Option<String>,
auto_recover: BusRecoveryType,
}
impl Bus {
#[must_use]
pub fn new(device: DeviceType, timeout: Duration) -> Self {
trace!("Bus::new");
Bus {
device,
_timeout: timeout,
mode: BusMode::default(),
serial: None,
auto_recover: BusRecoveryType::default(),
}
}
pub fn set_recovery_type(&mut self, recovery_type: BusRecoveryType) {
self.auto_recover = recovery_type;
}
pub fn initialize(&mut self) -> Result<(), Error> {
self.initialize_retry(true)
}
pub fn reset(&mut self) -> Result<(), Error> {
trace!("Entered Bus::reset");
self.mode = BusMode::Idle;
self.with_retry(|device| device.write_control(CTRL_RESET, 0, &[]))
}
pub fn talk(&mut self, dc: DeviceChannel) -> Result<(), Error> {
self.execute_command(&BusCommand::Talk(dc))
}
pub fn listen(&mut self, dc: DeviceChannel) -> Result<(), Error> {
self.execute_command(&BusCommand::Listen(dc))
}
pub fn listen_no_channel(&mut self, device: u8) -> Result<(), Error> {
self.execute_command(&BusCommand::ListenNoChannel(device))
}
pub fn untalk(&mut self) -> Result<(), Error> {
self.execute_command(&BusCommand::Untalk)
}
pub fn unlisten(&mut self) -> Result<(), Error> {
self.execute_command(&BusCommand::Unlisten)
}
pub fn open(&mut self, dc: DeviceChannel) -> Result<(), Error> {
self.execute_command(&BusCommand::Open(dc))
}
pub fn close(&mut self, dc: DeviceChannel) -> Result<(), Error> {
self.execute_command(&BusCommand::Close(dc))
}
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
trace!("Entered Bus::read buf.len(): {}", buf.len());
Self::validate_read_params(buf, None, false)?;
if let BusMode::Listening(_) = self.mode {
warn!("Instructed to write data when Bus in mode {}", self.mode);
}
self.with_retry(|device| device.read_data(PROTO_CBM, buf))
}
pub fn read_until(&mut self, buf: &mut [u8], pattern: &[u8]) -> Result<usize, Error> {
let size = buf.len();
trace!(
"Bus::read_until buf.len() {size} pattern.len() {}",
pattern.len()
);
Self::validate_read_params(buf, Some(pattern), true)?;
let mut total_read = 0;
let pattern_len = pattern.len();
while total_read < size {
match self.read_one_byte()? {
None => break, Some(byte) => {
buf[total_read] = byte;
total_read += 1;
if total_read >= pattern_len
&& buf[total_read - pattern_len..total_read] == pattern[..]
{
trace!("Found pattern in read data");
break;
}
}
}
}
Ok(total_read)
}
pub fn read_until_any(&mut self, buf: &mut [u8], pattern: &[u8]) -> Result<usize, Error> {
let size = buf.len();
trace!(
"Bus::read_until_any buf.len() {size} pattern.len() {}",
pattern.len()
);
Self::validate_read_params(buf, Some(pattern), false)?;
let mut total_read = 0;
while total_read < size {
match self.read_one_byte()? {
None => break, Some(byte) => {
buf[total_read] = byte;
total_read += 1;
if pattern.contains(&byte) {
break;
}
}
}
}
Ok(total_read)
}
pub fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
trace!("Entered Bus::write buf.len(): {}", buf.len());
let size = buf.len();
if size == 0 {
warn!("Attempt to write 0 bytes");
return Err(Error::Args {
message: "Attempt to write 0 bytes".to_string(),
});
}
if let BusMode::Talking(_) = self.mode {
warn!("Instructed to write data when Bus in mode {}", self.mode);
}
self.with_retry(|device| device.write_data(PROTO_CBM, buf))
}
pub fn ioctl(
&mut self,
ioctl: Ioctl,
address: u8,
secondary_address: u8,
) -> Result<Option<u16>, Error> {
self.with_retry(|device| device.ioctl(ioctl, address, secondary_address))
}
pub fn get_eoi(&mut self) -> Result<u16, Error> {
trace!("Bus::get_eoi");
self.device
.ioctl(Ioctl::GetEoi, 0, 0)?
.ok_or(Error::Communication {
kind: CommunicationError::IoctlFailed,
})
}
pub fn clear_eoi(&mut self) -> Result<u16, Error> {
trace!("Bus::clear_eoi");
self.device
.ioctl(Ioctl::ClearEoi, 0, 0)?
.ok_or(Error::Communication {
kind: CommunicationError::IoctlFailed,
})
}
pub fn wait_for_status(&mut self) -> Result<u16, Error> {
self.with_retry(super::device::DeviceType::wait_for_status)
}
pub fn device_info(&mut self) -> Option<DeviceInfo> {
self.device.info() }
pub fn usb_device_info(&mut self) -> Option<UsbInfo> {
self.device.usb_info() }
pub fn remote_usb_device_info(&mut self) -> Option<RemoteUsbInfo> {
self.device.remote_usb_info() }
pub fn device_sw_debug_info(&mut self) -> SwDebugInfo {
self.device.sw_debug_info() }
#[must_use]
pub fn is_listening(&self) -> Option<&DeviceChannel> {
match &self.mode {
BusMode::Listening(dev) => Some(dev),
_ => None,
}
}
#[must_use]
pub fn is_talking(&self) -> Option<&DeviceChannel> {
match &self.mode {
BusMode::Talking(dev) => Some(dev),
_ => None,
}
}
}
impl Bus {
fn read_one_byte(&mut self) -> Result<Option<u8>, Error> {
trace!("Bus::read_one_byte");
let mut temp = vec![0u8; 1];
let result = self.with_retry(|device| device.read_data(PROTO_CBM, &mut temp));
match result {
Ok(0) => Ok(None), Ok(1) => {
trace!("Got a byte 0x{:02x}", temp[0]);
Ok(Some(temp[0]))
}
Ok(_) => unreachable!(), Err(e) => Err(e),
}
}
fn validate_read_params(
buf: &[u8],
pattern: Option<&[u8]>,
check_pattern_size: bool,
) -> Result<(), Error> {
let size = buf.len();
let pattern_len = pattern.map(<[u8]>::len);
trace!("Bus::validate_read_params buf.len(): {size} pattern_len: {pattern_len:?} check_pattern_size: {check_pattern_size}");
if size == 0 {
let message = "Attempt to read 0 bytes".to_string();
warn!("{message}");
return Err(Error::Args { message });
}
if let Some(pattern_len) = pattern_len {
if pattern_len == 0 {
let message = "Attempt to read match against empty pattern".to_string();
warn!("{message}");
return Err(Error::Args { message });
}
if check_pattern_size && pattern_len > size {
let message = format!("Attempt to read match against pattern of larger {pattern_len} than requested read size {size}");
warn!("{message}");
return Err(Error::Args { message });
}
}
Ok(())
}
fn execute_command(&mut self, command: &BusCommand) -> Result<(), Error> {
trace!(
"Entered Bus::execute_command command {command} bus in mode {}",
self.mode
);
match command {
BusCommand::Open(dc) => {
if self.mode != BusMode::Idle {
warn!("Open called when the bus was in state {}", self.mode);
}
self.mode = BusMode::Listening(*dc);
}
BusCommand::Close(_) => {
if self.mode != BusMode::Idle {
warn!("Close called when the bus was in state {}", self.mode);
}
}
BusCommand::Listen(dc) => {
if self.mode != BusMode::Idle {
warn!("Listen called when bus was in state {}", self.mode);
}
self.mode = BusMode::Listening(*dc);
}
BusCommand::ListenNoChannel(dc) => {
if self.mode != BusMode::Idle {
warn!("ListenNoChannel called when bus was in state {}", self.mode);
}
self.mode = BusMode::Listening(DeviceChannel::new(*dc, 0).unwrap());
}
BusCommand::Unlisten => {
if let BusMode::Listening(_) = self.mode {
} else {
warn!("Unisten called when bus was in state {}", self.mode);
}
self.mode = BusMode::Idle;
}
BusCommand::Talk(dc) => {
if self.mode != BusMode::Idle {
warn!("Talk called when bus was in state {}", self.mode);
}
self.mode = BusMode::Talking(*dc);
}
BusCommand::Untalk => {
if let BusMode::Talking(_) = self.mode {
} else {
warn!("Untalk called when bus was in state {}", self.mode);
}
self.mode = BusMode::Idle;
}
}
debug!("Bus:execute_command {command}");
let result = self.with_retry(|device| {
device
.write_data(command.protocol(), &command.command_bytes())
.map(|_| ())
});
if let Err(Error::Communication {
kind: CommunicationError::StatusValue { value: _ },
}) = result
{
match command {
BusCommand::Talk(_) | BusCommand::Listen(_) | BusCommand::Open(_) => {
debug!("Bus command {command} failed, setting bus back to Idle");
self.mode = BusMode::Idle;
}
_ => debug!(
"Bus command {command} failed, leaving bus in state {}",
self.mode
),
}
}
result
}
fn with_retry<T>(
&mut self,
mut f: impl FnMut(&mut DeviceType) -> Result<T, Error>,
) -> Result<T, Error> {
match f(&mut self.device) {
Ok(result) => Ok(result),
Err(e) => match e.clone() {
Error::DeviceAccess { kind } => match kind {
DeviceAccessError::NoDevice
| DeviceAccessError::Permission
| DeviceAccessError::NetworkConnection { .. } => {
self.attempt_device_recovery(e.clone())?;
info!("Device successfully recovered after error {}", e);
f(&mut self.device)
}
_ => {
warn!("Fatal device error - not retrying due to error type ({e})");
Err(e)
}
},
e => {
warn!("Fatal device error - not retrying due to error type ({e})");
Err(e)
}
},
}
}
fn initialize_retry(&mut self, retry: bool) -> Result<(), Error> {
trace!("Bus::initialize");
if retry {
self.with_retry(super::device::DeviceType::init)?;
} else {
self.device.init()?;
}
let info = self.device.info(); if let Some(info) = info {
self.serial = info.serial_number;
}
Ok(())
}
fn attempt_device_creation(&self, serial_num: Option<u8>) -> Result<DeviceType, Error> {
match &self.device {
DeviceType::Usb(_) => DeviceType::new(DeviceConfig::Usb(UsbDeviceConfig {
context: None,
serial_num,
})),
DeviceType::RemoteUsb(device) => {
let remote_addr = device.get_remote_addr();
DeviceType::new(DeviceConfig::RemoteUsb(RemoteUsbDeviceConfig {
serial_num,
remote_addr,
}))
}
}
}
fn attempt_device_recovery(&mut self, error: Error) -> Result<(), Error> {
if self.auto_recover == BusRecoveryType::Off {
debug!("Didn't try to auto-recover Device, as recovery disabled");
return Err(error);
}
let serial = match &self.serial {
None => {
if self.auto_recover == BusRecoveryType::All {
None
} else {
warn!("Auto-recovery of device requested, but we didn't have the device's serial bumber");
return Err(error);
}
}
Some(serial_string) => match serial_string.parse::<u8>() {
Ok(num) => Some(num),
Err(e) => {
warn!("Auto-recovery of device requested, but couldn't convert device's serial: {serial_string} to u8: {e}");
return Err(error);
}
},
};
let was_initialized = self.device.initialized();
let result = self.attempt_device_creation(serial);
let result = match result {
Ok(device) => Ok(device),
Err(Error::DeviceAccess {
kind: DeviceAccessError::SerialMismatch { .. },
}) if serial.is_some() && self.auto_recover == BusRecoveryType::All => {
self.attempt_device_creation(None)
}
Err(e) => Err(e),
};
self.device = match result {
Ok(device) => device,
Err(e) => {
warn!("Device auto-recovery failed: {e}");
return Err(error);
}
};
if was_initialized {
match self.initialize_retry(false) {
Ok(()) => Ok(()),
Err(e) => {
warn!("Device auto-recovery initialization failed: {e}");
Err(error)
}
}
} else {
Ok(())
}
}
}