use crate::channel::{CBM_CHANNEL_CTRL, CBM_CHANNEL_LOAD};
use crate::disk::BYTES_PER_BLOCK;
use crate::string::{AsciiString, PetsciiString};
use crate::validate::{validate_device, DeviceValidation};
use crate::{
BusGuardMut, BusGuardRef, CbmDeviceInfo, CbmDirListing, CbmErrorNumberOk, CbmStatus, CbmString,
DeviceError, Error,
};
use crate::{DEVICE_MAX_NUM, DEVICE_MIN_NUM};
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use parking_lot::Mutex;
use xum1541::Error as Xum1541Error;
use xum1541::{Bus, BusBuilder, CommunicationError, DeviceChannel};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::ops::RangeInclusive;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Cbm {
config: CbmConfig,
handle: Arc<Mutex<Option<Bus>>>,
}
#[derive(Debug, Clone)]
struct CbmConfig {
serial: Option<u8>,
remote: Option<SocketAddr>,
}
impl Cbm {
pub fn new(serial: Option<u8>, remote: Option<SocketAddr>) -> Result<Self, Error> {
trace!("Cbm::new");
let config = CbmConfig { serial, remote };
let mut bus = Self::new_bus(&config)?;
bus.initialize()?;
Ok(Self {
config,
handle: Arc::new(Mutex::new(Some(bus))),
})
}
fn new_bus(config: &CbmConfig) -> Result<Bus, Error> {
let mut builder = BusBuilder::new();
if let Some(serial) = config.serial {
builder.serial(serial);
}
if let Some(remote) = config.remote {
builder.remote(remote)?;
}
builder.build().map_err(|e| e.into())
}
pub fn usb_device_reset(&mut self) -> Result<(), Error> {
let mut handle = self.handle.lock();
let old_bus = handle.take();
drop(old_bus);
let mut new_bus = Self::new_bus(&self.config)?;
new_bus.initialize()?;
*handle = Some(new_bus);
Ok(())
}
pub fn reset_bus(&self) -> Result<(), Error> {
self.handle.lock().bus_mut_or_err()?.reset()?;
Ok(())
}
}
impl Cbm {
pub fn identify(&self, device: u8) -> Result<CbmDeviceInfo, Error> {
let mut buf = [0u8; 2];
self.read_drive_memory(device, 0xff40, &mut buf)?;
let magic: u16 = ((buf[1] as u16) << 8) | (buf[0] as u16);
let magic2 = match magic {
0xaaaa => {
let mut buf = [0u8; 2];
self.read_drive_memory(device, 0xfffe, &mut buf)?;
if buf[0] != 0x67 || buf[1] != 0xFE {
Some(((buf[1] as u16) << 8) | (buf[0] as u16))
} else {
let mut buf = [0u8; 2];
self.read_drive_memory(device, 0xe5c4, &mut buf)?;
Some(((buf[1] as u16) << 8) | (buf[0] as u16))
}
}
0x01ba => {
let mut buf = [0u8; 2];
self.read_drive_memory(device, 0xfffe, &mut buf)?;
let magic2: u16 = ((buf[1] as u16) << 8) | (buf[0] as u16);
Some(magic2)
}
_ => None,
};
let device_info = CbmDeviceInfo::from_magic(magic, magic2);
Ok(device_info)
}
pub fn get_status(&self, device: u8) -> Result<CbmStatus, Error> {
let mut guard = self.handle.lock();
let mut bus = (&mut guard).bus_mut_or_err()?;
Self::get_status_locked(&mut bus, device)
}
pub fn scan_bus(&self) -> Result<HashMap<u8, CbmDeviceInfo>, Error> {
self.scan_bus_range(DEVICE_MIN_NUM..=DEVICE_MAX_NUM)
}
pub fn scan_bus_range(
&self,
range: RangeInclusive<u8>,
) -> Result<HashMap<u8, CbmDeviceInfo>, Error> {
let mut devices = HashMap::new();
for device in range {
if self.drive_exists(device)? {
debug!("Device {device} exists - identify it");
let info = match self.identify(device) {
Err(Error::Device { .. }) => {
warn!("Hit error identifying device {device} status");
continue;
}
Err(e) => return Err(e),
Ok(info) => info,
};
devices.insert(device, info);
} else {
trace!("Device {device} doesn't exist");
}
}
Ok(devices)
}
pub fn drive_exists(&self, device: u8) -> Result<bool, Error> {
let dc = DeviceChannel::new(device, CBM_CHANNEL_CTRL)?;
match self.bus_listen(dc) {
Err(Error::Xum1541(Xum1541Error::Communication {
kind: CommunicationError::StatusValue { .. },
})) => {
debug!("Device {device} doesn't exist");
Ok(false)
}
Err(e) => Err(e.into()),
Ok(_) => {
self.bus_unlisten()?;
Ok(true)
}
}
}
pub fn dir(&self, device: u8, drive_num: Option<u8>) -> Result<CbmDirListing, Error> {
if let Some(drive_num) = drive_num {
if drive_num > 1 {
return Err(DeviceError::invalid_drive_num(device, drive_num));
}
}
let filename = match drive_num {
Some(num) => PetsciiString::from_petscii_bytes(&[b'$', num]),
None => PetsciiString::from_petscii_bytes(&[b'$']),
};
let dir_data = self.load_file_petscii(device, &filename)?;
let mut cursor = 0;
cursor += 2;
let mut output = String::new();
if dir_data.len() >= 2 {
while cursor + 4 <= dir_data.len() {
cursor += 2;
let size = (dir_data[cursor] as u16) | ((dir_data[cursor + 1] as u16) << 8);
cursor += 2;
output.push_str(&format!("{:4} ", size));
let mut filename = Vec::new();
while cursor < dir_data.len() && dir_data[cursor] != 0 {
filename.push(dir_data[cursor]);
cursor += 1;
}
cursor += 1;
let petscii_filename = PetsciiString::from_petscii_bytes(&filename);
let ascii_filename: AsciiString = petscii_filename.into();
let str_filename = ascii_filename.to_string();
output.push_str(&str_filename);
output.push('\n');
if cursor >= dir_data.len() {
break;
}
}
}
CbmDirListing::parse(&output)
}
pub fn validate_disk(&self, device: u8) -> Result<(), Error> {
self.send_command_petscii(device, &PetsciiString::from_ascii_str("v"))?;
self.get_status(device)?.into()
}
pub fn delete_file(&self, device: u8, filename: &AsciiString) -> Result<(), Error> {
let cmd = format!("s0:{}", filename);
self.send_string_command_ascii(device, &cmd)?;
self.get_status(device)?.into()
}
pub fn format_disk(
&self,
device: u8,
name: &AsciiString,
id: &AsciiString,
) -> Result<(), Error> {
let id_len = id.as_bytes().len();
if id_len != 2 {
return Err(Error::Validation {
message: format!(
"Device {device} format error: Disk ID must be 2 characters, not {id_len}"
),
});
}
let cmd = format!("n0:{},{}", name, id);
trace!("Send format command in ascii {}", cmd);
self.send_string_command_ascii(device, &cmd)?;
self.get_status(device)?.into()
}
}
impl Cbm {
pub fn read_drive_memory(&self, device: u8, addr: u16, buf: &mut [u8]) -> Result<(), Error> {
let size = buf.len();
trace!("Cbm::read_drive_memory: device {device} addr 0x{addr:04x} size {size}");
Self::validate_read_args(
size,
format!("Asked to read 0 bytes from device {device} memory address 0x{addr:04x}"),
)?;
let mut addr_low = (addr & 0xFF) as u8;
let mut addr_high = ((addr >> 8) & 0xFF) as u8;
{
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
let result = (|| {
let mut cmd = [b'M', b'-', b'R', addr_low, addr_high];
let dc = DeviceChannel::new(device, CBM_CHANNEL_CTRL)?;
for ii in 0..size {
debug!("Read from memory address 0x{addr_high:02x}{addr_low:02x}");
Self::send_command_petscii_locked(
bus,
dc,
&PetsciiString::from_petscii_bytes(&cmd),
)?;
Self::read_from_drive_locked(bus, dc, &mut buf[ii..ii + 1], true)?;
debug!("Read data: 0x{:02x}", buf[ii]);
if ii < size - 1 {
addr_low = addr_low.wrapping_add(1);
if addr_low == 0 {
addr_high = addr_high.wrapping_add(1);
}
cmd[3] = addr_low;
cmd[4] = addr_high;
}
}
Ok(())
})();
trace!("Read status in order to clear effects of M-R command");
match Self::get_status_locked(bus, device) {
Ok(status) => debug!("Unexpectedly got status OK after M-R command {status} "),
Err(Error::Parse { message }) => {
trace!("Got expectedly bad status when reading status after M-R: {message}")
}
Err(e) => {
let default_error = DeviceError::get_status_failure(
device,
format!("Failed to get status after identify: {e}"),
);
return Err(match e {
Error::Device { device, error } => match error {
DeviceError::NoDevice => DeviceError::no_device(device),
_ => default_error,
},
_ => default_error,
});
}
}
result
}
}
pub fn write_drive_memory(&self, device: u8, addr: u16, data: &[u8]) -> Result<(), Error> {
let addr_low = (addr & 0xFF) as u8;
let addr_high = ((addr >> 8) & 0xFF) as u8;
for (i, &byte) in data.iter().enumerate() {
let cmd = vec![
b'M',
b'-',
b'W',
addr_low.wrapping_add(i as u8),
addr_high,
byte,
];
self.send_command_petscii(device, &PetsciiString::from_petscii_bytes(&cmd))?;
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
let dc = DeviceChannel::new(device, CBM_CHANNEL_CTRL)?;
bus.talk(dc)?;
bus.untalk()?;
}
Ok(())
}
pub fn send_command(&self, device: u8, cmd: &CbmString) -> Result<(), Error> {
self.send_command_petscii(device, &cmd.to_petscii())
}
pub fn send_command_petscii(&self, device: u8, cmd: &PetsciiString) -> Result<(), Error> {
trace!("Cbm::send_command_petscii device {device} cmd {cmd}");
let dc = DeviceChannel::new(device, CBM_CHANNEL_CTRL)?;
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
Self::send_command_petscii_locked(bus, dc, cmd)
}
pub fn send_command_ascii(&self, device: u8, command: &AsciiString) -> Result<(), Error> {
let petscii: PetsciiString = command.into();
trace!("Send string command in petscii {}", petscii);
self.send_command_petscii(device, &petscii)
}
pub fn send_string_command_ascii(&self, device: u8, command: &str) -> Result<(), Error> {
let ascii = AsciiString::try_from(command).map_err(|e| Error::Validation {
message: format!("Unable to parse requested command as ASCII {command}: {e}"),
})?;
trace!("Send string command in ascii {}", ascii);
self.send_command_ascii(device, &ascii)
}
pub fn send_string_command_petscii(&self, device: u8, command: &str) -> Result<(), Error> {
self.send_command_petscii(
device,
&PetsciiString::from_petscii_bytes(command.as_bytes()),
)
}
fn validate_read_args(size: usize, message: String) -> Result<(), Error> {
if size == 0 {
warn!("Asked to read {size} bytes: {message}");
Err(Error::Validation { message })
} else {
Ok(())
}
}
pub fn read_from_drive(
&self,
dc: DeviceChannel,
buf: &mut [u8],
read_all: bool,
) -> Result<usize, Error> {
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
Self::read_from_drive_locked(bus, dc, buf, read_all)
}
pub fn read_file(&self, device: u8, filename: &AsciiString) -> Result<Vec<u8>, Error> {
let dc = {
let _bus = self.handle.lock().bus_ref_or_err()?;
DeviceChannel::new(device, 2)?
};
self.send_command_ascii(device, filename)?;
let status = self.get_status(device)?;
if status.is_ok() != CbmErrorNumberOk::Ok {
return Err(status.into());
}
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
bus.talk(dc).map_err(|e| Error::File {
device,
message: format!("Talk failed: {}", e),
})?;
let mut data = Vec::new();
loop {
let buf = &mut [0u8; BYTES_PER_BLOCK];
let count = Self::bus_read_locked(bus, dc, buf).map_err(|e| Error::File {
device,
message: format!("Read failed: {}", e),
})?;
data.extend_from_slice(&buf[..count as usize]);
if count < BYTES_PER_BLOCK {
debug!("Finished reading file");
break;
}
}
bus.untalk().map_err(|e| Error::File {
device,
message: format!("Untalk failed: {}", e),
})?;
Ok(data)
}
pub fn write_file(&self, device: u8, filename: &AsciiString, data: &[u8]) -> Result<(), Error> {
let dc = {
let _bus = self.handle.lock().bus_ref_or_err()?;
DeviceChannel::new(device, 2)?
};
self.send_string_command_ascii(device, &format!("@:{}", filename))?;
let status = self.get_status(device)?;
if status.is_ok() != CbmErrorNumberOk::Ok {
return Err(status.into());
}
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
bus.listen(dc).map_err(|e| Error::File {
device,
message: format!("Listen failed: {}", e),
})?;
for chunk in data.chunks(BYTES_PER_BLOCK) {
let result = bus.write(chunk).map_err(|e| Error::File {
device,
message: format!("Write failed: {}", e),
})?;
if result != chunk.len() {
return Err(Error::File {
device,
message: "Failed to write complete chunk".into(),
});
}
}
bus.unlisten().map_err(|e| Error::File {
device,
message: format!("Unlisten failed: {}", e),
})?;
Ok(())
}
pub fn open_file(&self, dc: DeviceChannel, filename: &AsciiString) -> Result<(), Error> {
let petscii_name: PetsciiString = filename.into();
{
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
Self::open_file_petscii_locked(bus, dc, &petscii_name)
}
}
pub fn close_file(&self, dc: DeviceChannel) -> Result<(), Error> {
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
Self::close_file_locked(bus, dc)
}
pub fn load_file_petscii(
&self,
device: u8,
filename: &PetsciiString,
) -> Result<Vec<u8>, Error> {
validate_device(Some(device), DeviceValidation::Required)?;
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
Self::load_file_petscii_locked(bus, device, &filename)
}
pub fn load_file_ascii(&self, device: u8, filename: &AsciiString) -> Result<Vec<u8>, Error> {
trace!("Cbm::load_file device: {device} filename: {filename}");
let filename: PetsciiString = filename.into();
self.load_file_petscii(device, &filename)
}
fn load_file_petscii_locked(
bus: &mut Bus,
device: u8,
filename: &PetsciiString,
) -> Result<Vec<u8>, Error> {
debug!("Load file device: {device} filename: {filename}");
let dc = DeviceChannel::new(device, CBM_CHANNEL_LOAD)?;
Self::open_file_petscii_locked(bus, dc, filename)?;
bus.talk(dc).inspect_err(|_| {
let _ = Self::close_file_locked(bus, dc);
})?;
let mut buffer = Vec::new();
let mut read_buf = [0u8; BYTES_PER_BLOCK];
let read_result = loop {
match Self::bus_read_locked(bus, dc, &mut read_buf) {
Ok(bytes_read) if bytes_read == 0 => break Ok(buffer),
Ok(bytes_read) => buffer.extend_from_slice(&read_buf[..bytes_read]),
Err(e) => {
let _ = bus.untalk();
let _ = Self::close_file_locked(bus, dc);
break Err(e);
}
}
}?;
bus.untalk().inspect_err(|_| {
let _ = Self::close_file_locked(bus, dc);
})?;
Self::close_file_locked(bus, dc)?;
Ok(read_result)
}
}
impl Cbm {
fn bus_listen(&self, dc: DeviceChannel) -> Result<(), Error> {
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
bus.listen(dc).map_err(|e| e.into())
}
#[allow(dead_code)]
fn bus_unlisten(&self) -> Result<(), Error> {
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
bus.unlisten().map_err(|e| e.into())
}
#[allow(dead_code)]
fn bus_talk(&self, dc: DeviceChannel) -> Result<(), Error> {
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
bus.talk(dc).map_err(|e| e.into())
}
#[allow(dead_code)]
fn bus_untalk(&self) -> Result<(), Error> {
let mut guard = self.handle.lock();
let bus = (&mut guard).bus_mut_or_err()?;
bus.untalk().map_err(|e| e.into())
}
fn handle_read_result(
result: Result<usize, Xum1541Error>,
bus: &Bus,
dc: DeviceChannel,
) -> Result<usize, Error> {
match result {
Ok(0) => {
if let Some(talking_dc) = bus.is_talking() {
if talking_dc.channel() == CBM_CHANNEL_CTRL {
debug!("Bus is in Talking mode, device=15, and got a 0 byte read response");
Err(DeviceError::no_device(dc.device()))
} else {
Ok(0)
}
} else {
Ok(0)
}
}
Ok(n) => Ok(n),
Err(e) => Err(e.into()),
}
}
fn bus_read_locked(bus: &mut Bus, dc: DeviceChannel, buf: &mut [u8]) -> Result<usize, Error> {
Self::handle_read_result(bus.read(buf), bus, dc)
}
fn bus_read_until_locked(
bus: &mut Bus,
dc: DeviceChannel,
buf: &mut Vec<u8>,
pattern: &[u8],
) -> Result<usize, Error> {
Self::handle_read_result(bus.read_until(buf, pattern), bus, dc)
}
#[allow(dead_code)]
fn bus_read_until_any_locked(
bus: &mut Bus,
dc: DeviceChannel,
buf: &mut Vec<u8>,
pattern: &[u8],
) -> Result<usize, Error> {
Self::handle_read_result(bus.read_until_any(buf, pattern), bus, dc)
}
fn check_for_status_ok(bus: &mut Bus, device: u8, accept_73: bool) -> Result<(), Error> {
Self::get_status_locked(bus, device)
.map_err(|e| {
let default_error =
DeviceError::get_status_failure(device, format!("Failed to get status: {e}"));
match e {
Error::Device { device, error } => match error {
DeviceError::NoDevice => DeviceError::no_device(device),
_ => default_error,
},
_ => default_error,
}
})
.and_then(|status| {
trace!("Status value {}", status);
if accept_73 {
status.into_73_ok()
} else {
status.into()
}
})
}
fn send_command_petscii_locked(
bus: &mut Bus,
dc: DeviceChannel,
cmd: &PetsciiString,
) -> Result<(), Error> {
bus.listen(dc)?;
bus.write(cmd.as_bytes()).inspect_err(|_| {
let _ = bus.unlisten();
})?;
bus.unlisten().map_err(|e| e.into())
}
fn get_status_locked(bus: &mut Bus, device: u8) -> Result<CbmStatus, Error> {
trace!("Cbm::get_status_locked device: {device}");
let dc = DeviceChannel::new(device, CBM_CHANNEL_CTRL)?;
bus.talk(dc)?;
let mut buf = vec![0u8; 64];
let pattern = vec![b'\r'];
let bytes_read =
Self::bus_read_until_locked(bus, dc, &mut buf, &pattern).inspect_err(|e| {
debug!("Hit error while in read_until() loop: {}", e);
let _ = bus.untalk();
})?;
trace!("Read {} bytes of status", bytes_read);
bus.untalk()?;
let status_str = String::from_utf8_lossy(&buf[..bytes_read]).to_string();
CbmStatus::new(&status_str, device)
}
fn read_from_drive_locked(
bus: &mut Bus,
dc: DeviceChannel,
buf: &mut [u8],
read_all: bool,
) -> Result<usize, Error> {
let size = buf.len();
trace!("Cbm::read_from_drive_locked {dc} buf.len(): {size} read_all: {read_all}");
Self::validate_read_args(size, format!("Asked to read {size} bytes from {dc}"))?;
let mut read_total = 0;
{
bus.talk(dc)?;
loop {
let read_len =
Self::bus_read_locked(bus, dc, &mut buf[read_total..]).inspect_err(|_| {
let _ = bus.untalk();
})?;
if read_len == 0 {
break;
} else {
read_total += read_len;
if read_len != size {
continue;
} else {
break;
}
}
}
bus.untalk()?;
}
if read_total != size && read_all {
debug!("Failed to read {size} bytes from {dc}, read {read_total} bytes");
Err(DeviceError::read_error(
dc,
format!("Failed to read {size} bytes, read {read_total}"),
))
} else {
trace!("Successfully read {size} bytes from {dc}");
Ok(read_total)
}
}
fn open_file_petscii_locked(
bus: &mut Bus,
dc: DeviceChannel,
filename: &PetsciiString,
) -> Result<(), Error> {
bus.open(dc)?;
bus.write(filename.as_bytes()).inspect_err(|_| {
let _ = bus.unlisten();
let _ = bus.close(dc);
})?;
bus.unlisten().inspect_err(|_| {
let _ = bus.close(dc);
})?;
Self::check_for_status_ok(bus, dc.device(), false).inspect_err(|_| {
let _ = bus.close(dc);
})
}
fn close_file_locked(bus: &mut Bus, dc: DeviceChannel) -> Result<(), Error> {
bus.close(dc).map_err(|e| e.into())
}
}