diff --git a/default.nix b/default.nix index a9b7f19..267019e 100644 --- a/default.nix +++ b/default.nix @@ -20,7 +20,7 @@ let name = "firmware"; src = ./src; - cargoSha256 = "0p9d2j7qp00wpxm48phl5rq26simzry6w0m673lyhrlbzqdz4frb"; + cargoSha256 = "sha256-uiwESZNwPdVnDkA1n0v1DQHp3rTazDkgIYscVTpgNq0="; nativeBuildInputs = [ pkgs.gnumake diff --git a/src/libboard_artiq/Cargo.toml b/src/libboard_artiq/Cargo.toml new file mode 100644 index 0000000..8b9f7b8 --- /dev/null +++ b/src/libboard_artiq/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "libboard_artiq" +version = "0.0.0" +authors = ["M-Labs"] +edition = "2018" + +[lib] +name = "libboard_artiq" + +[features] +target_zc706 = [] +target_kasli_soc = [] + +[build-dependencies] +build_zynq = { path = "../libbuild_zynq" } + +[dependencies] +log = "0.4" +log_buffer = { version = "1.2" } +crc = { version = "1.7", default-features = false } +core_io = { version = "0.1", features = ["collections"] } +embedded-hal = "0.2" +nb = "1.0" +void = { version = "1", default-features = false } + +io = { path = "../libio", features = ["byteorder"] } +libboard_zynq = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git"} +libregister = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libconfig = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git"} +libcortex_a9 = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } +libasync = { git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } \ No newline at end of file diff --git a/src/libboard_artiq/build.rs b/src/libboard_artiq/build.rs new file mode 100644 index 0000000..ba3b31b --- /dev/null +++ b/src/libboard_artiq/build.rs @@ -0,0 +1,5 @@ +extern crate build_zynq; + +fn main() { + build_zynq::cfg(); +} diff --git a/src/libboard_artiq/src/drtio_routing.rs b/src/libboard_artiq/src/drtio_routing.rs new file mode 100644 index 0000000..9688ee3 --- /dev/null +++ b/src/libboard_artiq/src/drtio_routing.rs @@ -0,0 +1,109 @@ +use libconfig::Config; +#[cfg(has_drtio_routing)] +use crate::pl::csr; +use core::fmt; + +use log::{warn, info}; + +#[cfg(has_drtio_routing)] +pub const DEST_COUNT: usize = 256; +#[cfg(not(has_drtio_routing))] +pub const DEST_COUNT: usize = 0; +pub const MAX_HOPS: usize = 32; +pub const INVALID_HOP: u8 = 0xff; + +pub struct RoutingTable(pub [[u8; MAX_HOPS]; DEST_COUNT]); + +impl RoutingTable { + // default routing table is for star topology with no repeaters + pub fn default_master(default_n_links: usize) -> RoutingTable { + let mut ret = RoutingTable([[INVALID_HOP; MAX_HOPS]; DEST_COUNT]); + let n_entries = default_n_links + 1; // include local RTIO + for i in 0..n_entries { + ret.0[i][0] = i as u8; + } + for i in 1..n_entries { + ret.0[i][1] = 0x00; + } + ret + } + + // use this by default on satellite, as they receive + // the routing table from the master + pub fn default_empty() -> RoutingTable { + RoutingTable([[INVALID_HOP; MAX_HOPS]; DEST_COUNT]) + } +} + +impl fmt::Display for RoutingTable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RoutingTable {{")?; + for i in 0..DEST_COUNT { + if self.0[i][0] != INVALID_HOP { + write!(f, " {}:", i)?; + for j in 0..MAX_HOPS { + if self.0[i][j] == INVALID_HOP { + break; + } + write!(f, " {}", self.0[i][j])?; + } + write!(f, ";")?; + } + } + write!(f, " }}")?; + Ok(()) + } +} + +pub fn config_routing_table(default_n_links: usize, cfg: &Config) -> RoutingTable { + let mut ret = RoutingTable::default_master(default_n_links); + if let Ok(data) = cfg.read("routing_table") { + if data.len() == DEST_COUNT*MAX_HOPS + { + for i in 0..DEST_COUNT { + for j in 0..MAX_HOPS { + ret.0[i][j] = data[i*MAX_HOPS+j]; + } + } + } + else { + warn!("length of the routing table is incorrect, using default"); + } + } + else { + warn!("could not read routing table from configuration, using default"); + } + info!("routing table: {}", ret); + ret +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_enable(routing_table: &RoutingTable, rank: u8, destination: u8) { + let hop = routing_table.0[destination as usize][rank as usize]; + unsafe { + csr::routing_table::destination_write(destination); + csr::routing_table::hop_write(hop); + } +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_disable(destination: u8) { + unsafe { + csr::routing_table::destination_write(destination); + csr::routing_table::hop_write(INVALID_HOP); + } +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_enable_all(routing_table: &RoutingTable, rank: u8) { + for i in 0..DEST_COUNT { + interconnect_enable(routing_table, rank, i as u8); + } +} + +#[cfg(has_drtio_routing)] +pub fn interconnect_disable_all() { + for i in 0..DEST_COUNT { + interconnect_disable(i as u8); + } +} diff --git a/src/libboard_artiq/src/drtioaux.rs b/src/libboard_artiq/src/drtioaux.rs new file mode 100644 index 0000000..8d3a7a9 --- /dev/null +++ b/src/libboard_artiq/src/drtioaux.rs @@ -0,0 +1,174 @@ +use crc; + +use core_io::{ErrorKind as IoErrorKind, Error as IoError}; + +use io::{proto::ProtoRead, proto::ProtoWrite, Cursor}; +use libboard_zynq::{timer::GlobalTimer, time::Milliseconds}; +use crate::mem::mem::DRTIOAUX_MEM; +use crate::pl::csr::DRTIOAUX; +use crate::drtioaux_proto::Error as ProtocolError; + +pub use crate::drtioaux_proto::Packet; + +#[derive(Debug)] +pub enum Error { + GatewareError, + CorruptedPacket, + + LinkDown, + TimedOut, + UnexpectedReply, + + RoutingError, + + Protocol(ProtocolError) +} + +impl From for Error { + fn from(value: ProtocolError) -> Error { + Error::Protocol(value) + } +} + +impl From for Error { + fn from(value: IoError) -> Error { + Error::Protocol(ProtocolError::Io(value)) + } +} + +pub fn reset(linkno: u8) { + let linkno = linkno as usize; + unsafe { + // clear buffer first to limit race window with buffer overflow + // error. We assume the CPU is fast enough so that no two packets + // will be received between the buffer and the error flag are cleared. + (DRTIOAUX[linkno].aux_rx_present_write)(1); + (DRTIOAUX[linkno].aux_rx_error_write)(1); + } +} + +pub fn has_rx_error(linkno: u8) -> bool { + let linkno = linkno as usize; + unsafe { + let error = (DRTIOAUX[linkno].aux_rx_error_read)() != 0; + if error { + (DRTIOAUX[linkno].aux_rx_error_write)(1) + } + error + } +} + +pub fn copy_with_swap(src: *mut u8, dst: *mut u8, len: isize) { + // for some reason, everything except checksum arrives + // with byte order swapped. and it must be sent as such too. + unsafe { + for i in (0..(len-4)).step_by(4) { + *dst.offset(i) = *src.offset(i+3); + *dst.offset(i+1) = *src.offset(i+2); + *dst.offset(i+2) = *src.offset(i+1); + *dst.offset(i+3) = *src.offset(i); + } + // checksum untouched + // unrolled for performance + *dst.offset(len-4) = *src.offset(len-4); + *dst.offset(len-3) = *src.offset(len-3); + *dst.offset(len-2) = *src.offset(len-2); + *dst.offset(len-1) = *src.offset(len-1); + } +} + +fn receive(linkno: u8, f: F) -> Result, Error> + where F: FnOnce(&[u8]) -> Result +{ + let linkidx = linkno as usize; + unsafe { + if (DRTIOAUX[linkidx].aux_rx_present_read)() == 1 { + let ptr = (DRTIOAUX_MEM[linkidx].base + DRTIOAUX_MEM[linkidx].size / 2) as *mut u8; + let len = (DRTIOAUX[linkidx].aux_rx_length_read)() as usize; + // work buffer, as byte order will need to be swapped, cannot be in place + let mut buf: [u8; 1024] = [0; 1024]; + copy_with_swap(ptr, buf.as_mut_ptr(), len as isize); + let result = f(&buf[0..len]); + (DRTIOAUX[linkidx].aux_rx_present_write)(1); + Ok(Some(result?)) + } else { + Ok(None) + } + } +} + +pub fn recv(linkno: u8) -> Result, Error> { + if has_rx_error(linkno) { + return Err(Error::GatewareError) + } + + receive(linkno, |buffer| { + if buffer.len() < 8 { + return Err(IoError::new(IoErrorKind::UnexpectedEof, "Unexpected end").into()) + } + + let mut reader = Cursor::new(buffer); + + let checksum_at = buffer.len() - 4; + let checksum = crc::crc32::checksum_ieee(&reader.get_ref()[0..checksum_at]); + reader.set_position(checksum_at); + if reader.read_u32()? != checksum { + return Err(Error::CorruptedPacket) + } + reader.set_position(0); + + Ok(Packet::read_from(&mut reader)?) + }) +} + +pub fn recv_timeout(linkno: u8, timeout_ms: Option, + timer: GlobalTimer) -> Result +{ + let timeout_ms = Milliseconds(timeout_ms.unwrap_or(10)); + let limit = timer.get_time() + timeout_ms; + while timer.get_time() < limit { + match recv(linkno)? { + None => (), + Some(packet) => return Ok(packet), + } + } + Err(Error::TimedOut) +} + +fn transmit(linkno: u8, f: F) -> Result<(), Error> + where F: FnOnce(&mut [u8]) -> Result +{ + let linkno = linkno as usize; + unsafe { + while (DRTIOAUX[linkno].aux_tx_read)() != 0 {} + let ptr = DRTIOAUX_MEM[linkno].base as *mut u8; + let len = DRTIOAUX_MEM[linkno].size / 2; + // work buffer, works with unaligned mem access + let mut buf: [u8; 1024] = [0; 1024]; + let len = f(&mut buf[0..len])?; + copy_with_swap(buf.as_mut_ptr(), ptr, len as isize); + (DRTIOAUX[linkno].aux_tx_length_write)(len as u16); + (DRTIOAUX[linkno].aux_tx_write)(1); + Ok(()) + } +} + +pub fn send(linkno: u8, packet: &Packet) -> Result<(), Error> { + transmit(linkno, |buffer| { + let mut writer = Cursor::new(buffer); + + packet.write_to(&mut writer)?; + + let padding = 4 - (writer.position() % 4); + if padding != 4 { + for _ in 0..padding { + writer.write_u8(0)?; + } + } + + let checksum = crc::crc32::checksum_ieee(&writer.get_ref()[0..writer.position()]); + writer.write_u32(checksum)?; + + Ok(writer.position()) + }) +} diff --git a/src/libboard_artiq/src/drtioaux_async.rs b/src/libboard_artiq/src/drtioaux_async.rs new file mode 100644 index 0000000..eb7f219 --- /dev/null +++ b/src/libboard_artiq/src/drtioaux_async.rs @@ -0,0 +1,139 @@ +use crc; + +use core_io::{ErrorKind as IoErrorKind, Error as IoError}; +use void::Void; +use nb; + +use libboard_zynq::{timer::GlobalTimer, time::Milliseconds}; +use libasync::{task, block_async}; + +use io::{proto::ProtoRead, proto::ProtoWrite, Cursor}; +use crate::mem::mem::DRTIOAUX_MEM; +use crate::pl::csr::DRTIOAUX; +use crate::drtioaux::{Error, has_rx_error, copy_with_swap}; + +pub use crate::drtioaux_proto::Packet; + +pub async fn reset(linkno: u8) { + let linkno = linkno as usize; + unsafe { + // clear buffer first to limit race window with buffer overflow + // error. We assume the CPU is fast enough so that no two packets + // will be received between the buffer and the error flag are cleared. + (DRTIOAUX[linkno].aux_rx_present_write)(1); + (DRTIOAUX[linkno].aux_rx_error_write)(1); + } +} + +fn tx_ready(linkno: usize) -> nb::Result<(), Void> { + unsafe { + if (DRTIOAUX[linkno].aux_tx_read)() != 0 { + Err(nb::Error::WouldBlock) + } + else { + Ok(()) + } + } +} + +async fn receive(linkno: u8, f: F) -> Result, Error> + where F: FnOnce(&[u8]) -> Result +{ + let linkidx = linkno as usize; + unsafe { + if (DRTIOAUX[linkidx].aux_rx_present_read)() == 1 { + let ptr = (DRTIOAUX_MEM[linkidx].base + DRTIOAUX_MEM[linkidx].size / 2) as *mut u8; + let len = (DRTIOAUX[linkidx].aux_rx_length_read)() as usize; + // work buffer, as byte order will need to be swapped, cannot be in place + let mut buf: [u8; 1024] = [0; 1024]; + copy_with_swap(ptr, buf.as_mut_ptr(), len as isize); + let result = f(&buf[0..len]); + (DRTIOAUX[linkidx].aux_rx_present_write)(1); + Ok(Some(result?)) + } else { + Ok(None) + } + } +} + +pub async fn recv(linkno: u8) -> Result, Error> { + if has_rx_error(linkno) { + return Err(Error::GatewareError) + } + + receive(linkno, |buffer| { + if buffer.len() < 8 { + return Err(IoError::new(IoErrorKind::UnexpectedEof, "Unexpected end").into()) + } + + let mut reader = Cursor::new(buffer); + + let checksum_at = buffer.len() - 4; + let checksum = crc::crc32::checksum_ieee(&reader.get_ref()[0..checksum_at]); + reader.set_position(checksum_at); + if reader.read_u32()? != checksum { + return Err(Error::CorruptedPacket) + } + reader.set_position(0); + + Ok(Packet::read_from(&mut reader)?) + }).await +} + +pub async fn recv_timeout(linkno: u8, timeout_ms: Option, + timer: GlobalTimer) -> Result +{ + let timeout_ms = Milliseconds(timeout_ms.unwrap_or(10)); + let limit = timer.get_time() + timeout_ms; + let mut would_block = false; + while timer.get_time() < limit { + // to ensure one last time recv would run one last time + // in case async would return after timeout + if would_block { + task::r#yield().await; + } + match recv(linkno).await? { + None => { would_block = true; }, + Some(packet) => return Ok(packet), + } + } + Err(Error::TimedOut) +} + +async fn transmit(linkno: u8, f: F) -> Result<(), Error> + where F: FnOnce(&mut [u8]) -> Result +{ + let linkno = linkno as usize; + unsafe { + let _ = block_async!(tx_ready(linkno)).await; + let ptr = DRTIOAUX_MEM[linkno].base as *mut u8; + let len = DRTIOAUX_MEM[linkno].size / 2; + // work buffer, works with unaligned mem access + let mut buf: [u8; 1024] = [0; 1024]; + let len = f(&mut buf[0..len])?; + copy_with_swap(buf.as_mut_ptr(), ptr, len as isize); + (DRTIOAUX[linkno].aux_tx_length_write)(len as u16); + (DRTIOAUX[linkno].aux_tx_write)(1); + Ok(()) + } +} + +pub async fn send(linkno: u8, packet: &Packet) -> Result<(), Error> { + transmit(linkno, |buffer| { + let mut writer = Cursor::new(buffer); + + packet.write_to(&mut writer)?; + + let padding = 4 - (writer.position() % 4); + if padding != 4 { + for _ in 0..padding { + writer.write_u8(0)?; + } + } + + let checksum = crc::crc32::checksum_ieee(&writer.get_ref()[0..writer.position()]); + writer.write_u32(checksum)?; + + Ok(writer.position()) + }).await +} diff --git a/src/libboard_artiq/src/drtioaux_proto.rs b/src/libboard_artiq/src/drtioaux_proto.rs new file mode 100644 index 0000000..7d13785 --- /dev/null +++ b/src/libboard_artiq/src/drtioaux_proto.rs @@ -0,0 +1,364 @@ +use core_io::{Write, Read, Error as IoError}; + +use io::proto::{ProtoWrite, ProtoRead}; + +#[derive(Debug)] +pub enum Error { + UnknownPacket(u8), + Io(IoError) +} + +impl From for Error { + fn from(value: IoError) -> Error { + Error::Io(value) + } +} + +#[derive(PartialEq, Debug)] +pub enum Packet { + EchoRequest, + EchoReply, + ResetRequest, + ResetAck, + TSCAck, + + DestinationStatusRequest { destination: u8 }, + DestinationDownReply, + DestinationOkReply, + DestinationSequenceErrorReply { channel: u16 }, + DestinationCollisionReply { channel: u16 }, + DestinationBusyReply { channel: u16 }, + + RoutingSetPath { destination: u8, hops: [u8; 32] }, + RoutingSetRank { rank: u8 }, + RoutingAck, + + MonitorRequest { destination: u8, channel: u16, probe: u8 }, + MonitorReply { value: u32 }, + InjectionRequest { destination: u8, channel: u16, overrd: u8, value: u8 }, + InjectionStatusRequest { destination: u8, channel: u16, overrd: u8 }, + InjectionStatusReply { value: u8 }, + + I2cStartRequest { destination: u8, busno: u8 }, + I2cRestartRequest { destination: u8, busno: u8 }, + I2cStopRequest { destination: u8, busno: u8 }, + I2cWriteRequest { destination: u8, busno: u8, data: u8 }, + I2cWriteReply { succeeded: bool, ack: bool }, + I2cReadRequest { destination: u8, busno: u8, ack: bool }, + I2cReadReply { succeeded: bool, data: u8 }, + I2cBasicReply { succeeded: bool }, + + SpiSetConfigRequest { destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8 }, + SpiWriteRequest { destination: u8, busno: u8, data: u32 }, + SpiReadRequest { destination: u8, busno: u8 }, + SpiReadReply { succeeded: bool, data: u32 }, + SpiBasicReply { succeeded: bool }, + + JdacBasicRequest { destination: u8, dacno: u8, reqno: u8, param: u8 }, + JdacBasicReply { succeeded: bool, retval: u8 }, +} + +impl Packet { + pub fn read_from(reader: &mut R) -> Result + where R: Read + ?Sized + { + Ok(match reader.read_u8()? { + 0x00 => Packet::EchoRequest, + 0x01 => Packet::EchoReply, + 0x02 => Packet::ResetRequest, + 0x03 => Packet::ResetAck, + 0x04 => Packet::TSCAck, + + 0x20 => Packet::DestinationStatusRequest { + destination: reader.read_u8()? + }, + 0x21 => Packet::DestinationDownReply, + 0x22 => Packet::DestinationOkReply, + 0x23 => Packet::DestinationSequenceErrorReply { + channel: reader.read_u16()? + }, + 0x24 => Packet::DestinationCollisionReply { + channel: reader.read_u16()? + }, + 0x25 => Packet::DestinationBusyReply { + channel: reader.read_u16()? + }, + + 0x30 => { + let destination = reader.read_u8()?; + let mut hops = [0; 32]; + reader.read_exact(&mut hops)?; + Packet::RoutingSetPath { + destination: destination, + hops: hops + } + }, + 0x31 => Packet::RoutingSetRank { + rank: reader.read_u8()? + }, + 0x32 => Packet::RoutingAck, + + 0x40 => Packet::MonitorRequest { + destination: reader.read_u8()?, + channel: reader.read_u16()?, + probe: reader.read_u8()? + }, + 0x41 => Packet::MonitorReply { + value: reader.read_u32()? + }, + 0x50 => Packet::InjectionRequest { + destination: reader.read_u8()?, + channel: reader.read_u16()?, + overrd: reader.read_u8()?, + value: reader.read_u8()? + }, + 0x51 => Packet::InjectionStatusRequest { + destination: reader.read_u8()?, + channel: reader.read_u16()?, + overrd: reader.read_u8()? + }, + 0x52 => Packet::InjectionStatusReply { + value: reader.read_u8()? + }, + + 0x80 => Packet::I2cStartRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x81 => Packet::I2cRestartRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x82 => Packet::I2cStopRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x83 => Packet::I2cWriteRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + data: reader.read_u8()? + }, + 0x84 => Packet::I2cWriteReply { + succeeded: reader.read_bool()?, + ack: reader.read_bool()? + }, + 0x85 => Packet::I2cReadRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + ack: reader.read_bool()? + }, + 0x86 => Packet::I2cReadReply { + succeeded: reader.read_bool()?, + data: reader.read_u8()? + }, + 0x87 => Packet::I2cBasicReply { + succeeded: reader.read_bool()? + }, + + 0x90 => Packet::SpiSetConfigRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + flags: reader.read_u8()?, + length: reader.read_u8()?, + div: reader.read_u8()?, + cs: reader.read_u8()? + }, + /* 0x91: was Packet::SpiSetXferRequest */ + 0x92 => Packet::SpiWriteRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()?, + data: reader.read_u32()? + }, + 0x93 => Packet::SpiReadRequest { + destination: reader.read_u8()?, + busno: reader.read_u8()? + }, + 0x94 => Packet::SpiReadReply { + succeeded: reader.read_bool()?, + data: reader.read_u32()? + }, + 0x95 => Packet::SpiBasicReply { + succeeded: reader.read_bool()? + }, + + 0xa0 => Packet::JdacBasicRequest { + destination: reader.read_u8()?, + dacno: reader.read_u8()?, + reqno: reader.read_u8()?, + param: reader.read_u8()?, + }, + 0xa1 => Packet::JdacBasicReply { + succeeded: reader.read_bool()?, + retval: reader.read_u8()? + }, + + ty => return Err(Error::UnknownPacket(ty)) + }) + } + + pub fn write_to(&self, writer: &mut W) -> Result<(), IoError> + where W: Write + ?Sized + { + + match *self { + Packet::EchoRequest => + writer.write_u8(0x00)?, + Packet::EchoReply => + writer.write_u8(0x01)?, + Packet::ResetRequest => + writer.write_u8(0x02)?, + Packet::ResetAck => + writer.write_u8(0x03)?, + Packet::TSCAck => + writer.write_u8(0x04)?, + + Packet::DestinationStatusRequest { destination } => { + writer.write_u8(0x20)?; + writer.write_u8(destination)?; + }, + Packet::DestinationDownReply => + writer.write_u8(0x21)?, + Packet::DestinationOkReply => + writer.write_u8(0x22)?, + Packet::DestinationSequenceErrorReply { channel } => { + writer.write_u8(0x23)?; + writer.write_u16(channel)?; + }, + Packet::DestinationCollisionReply { channel } => { + writer.write_u8(0x24)?; + writer.write_u16(channel)?; + }, + Packet::DestinationBusyReply { channel } => { + writer.write_u8(0x25)?; + writer.write_u16(channel)?; + }, + + Packet::RoutingSetPath { destination, hops } => { + writer.write_u8(0x30)?; + writer.write_u8(destination)?; + writer.write_all(&hops)?; + }, + Packet::RoutingSetRank { rank } => { + writer.write_u8(0x31)?; + writer.write_u8(rank)?; + }, + Packet::RoutingAck => + writer.write_u8(0x32)?, + + Packet::MonitorRequest { destination, channel, probe } => { + writer.write_u8(0x40)?; + writer.write_u8(destination)?; + writer.write_u16(channel)?; + writer.write_u8(probe)?; + }, + Packet::MonitorReply { value } => { + writer.write_u8(0x41)?; + writer.write_u32(value)?; + }, + Packet::InjectionRequest { destination, channel, overrd, value } => { + writer.write_u8(0x50)?; + writer.write_u8(destination)?; + writer.write_u16(channel)?; + writer.write_u8(overrd)?; + writer.write_u8(value)?; + }, + Packet::InjectionStatusRequest { destination, channel, overrd } => { + writer.write_u8(0x51)?; + writer.write_u8(destination)?; + writer.write_u16(channel)?; + writer.write_u8(overrd)?; + }, + Packet::InjectionStatusReply { value } => { + writer.write_u8(0x52)?; + writer.write_u8(value)?; + }, + + Packet::I2cStartRequest { destination, busno } => { + writer.write_u8(0x80)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::I2cRestartRequest { destination, busno } => { + writer.write_u8(0x81)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::I2cStopRequest { destination, busno } => { + writer.write_u8(0x82)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::I2cWriteRequest { destination, busno, data } => { + writer.write_u8(0x83)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_u8(data)?; + }, + Packet::I2cWriteReply { succeeded, ack } => { + writer.write_u8(0x84)?; + writer.write_bool(succeeded)?; + writer.write_bool(ack)?; + }, + Packet::I2cReadRequest { destination, busno, ack } => { + writer.write_u8(0x85)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_bool(ack)?; + }, + Packet::I2cReadReply { succeeded, data } => { + writer.write_u8(0x86)?; + writer.write_bool(succeeded)?; + writer.write_u8(data)?; + }, + Packet::I2cBasicReply { succeeded } => { + writer.write_u8(0x87)?; + writer.write_bool(succeeded)?; + }, + + Packet::SpiSetConfigRequest { destination, busno, flags, length, div, cs } => { + writer.write_u8(0x90)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_u8(flags)?; + writer.write_u8(length)?; + writer.write_u8(div)?; + writer.write_u8(cs)?; + }, + Packet::SpiWriteRequest { destination, busno, data } => { + writer.write_u8(0x92)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + writer.write_u32(data)?; + }, + Packet::SpiReadRequest { destination, busno } => { + writer.write_u8(0x93)?; + writer.write_u8(destination)?; + writer.write_u8(busno)?; + }, + Packet::SpiReadReply { succeeded, data } => { + writer.write_u8(0x94)?; + writer.write_bool(succeeded)?; + writer.write_u32(data)?; + }, + Packet::SpiBasicReply { succeeded } => { + writer.write_u8(0x95)?; + writer.write_bool(succeeded)?; + }, + + Packet::JdacBasicRequest { destination, dacno, reqno, param } => { + writer.write_u8(0xa0)?; + writer.write_u8(destination)?; + writer.write_u8(dacno)?; + writer.write_u8(reqno)?; + writer.write_u8(param)?; + } + Packet::JdacBasicReply { succeeded, retval } => { + writer.write_u8(0xa1)?; + writer.write_bool(succeeded)?; + writer.write_u8(retval)?; + }, + } + Ok(()) + } + +} diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs new file mode 100644 index 0000000..b85481c --- /dev/null +++ b/src/libboard_artiq/src/lib.rs @@ -0,0 +1,69 @@ +#![no_std] +#![feature(never_type)] + +extern crate log; +extern crate crc; +extern crate embedded_hal; +extern crate core_io; +extern crate io; +extern crate libboard_zynq; +extern crate libregister; +extern crate libconfig; +extern crate libcortex_a9; +extern crate libasync; +extern crate log_buffer; + +#[path = "../../../build/pl.rs"] +pub mod pl; +pub mod drtioaux_proto; +pub mod drtio_routing; +pub mod logger; +#[cfg(has_si5324)] +pub mod si5324; +#[cfg(has_drtio)] +pub mod drtioaux; +#[cfg(has_drtio)] +pub mod drtioaux_async; +#[cfg(has_drtio)] +#[path = "../../../build/mem.rs"] +pub mod mem; + +use core::{cmp, str}; +use libboard_zynq::slcr; +use libregister::RegisterW; + +pub fn identifier_read(buf: &mut [u8]) -> &str { + unsafe { + pl::csr::identifier::address_write(0); + let len = pl::csr::identifier::data_read(); + let len = cmp::min(len, buf.len() as u8); + for i in 0..len { + pl::csr::identifier::address_write(1 + i); + buf[i as usize] = pl::csr::identifier::data_read(); + } + str::from_utf8_unchecked(&buf[..len as usize]) + } +} + +pub fn init_gateware() { + // Set up PS->PL clocks + slcr::RegisterBlock::unlocked(|slcr| { + // As we are touching the mux, the clock may glitch, so reset the PL. + slcr.fpga_rst_ctrl.write( + slcr::FpgaRstCtrl::zeroed() + .fpga0_out_rst(true) + .fpga1_out_rst(true) + .fpga2_out_rst(true) + .fpga3_out_rst(true) + ); + slcr.fpga0_clk_ctrl.write( + slcr::Fpga0ClkCtrl::zeroed() + .src_sel(slcr::PllSource::IoPll) + .divisor0(8) + .divisor1(1) + ); + slcr.fpga_rst_ctrl.write( + slcr::FpgaRstCtrl::zeroed() + ); + }); +} \ No newline at end of file diff --git a/src/runtime/src/logger.rs b/src/libboard_artiq/src/logger.rs similarity index 100% rename from src/runtime/src/logger.rs rename to src/libboard_artiq/src/logger.rs diff --git a/src/runtime/src/si5324.rs b/src/libboard_artiq/src/si5324.rs similarity index 72% rename from src/runtime/src/si5324.rs rename to src/libboard_artiq/src/si5324.rs index f981c00..c083fae 100644 --- a/src/runtime/src/si5324.rs +++ b/src/libboard_artiq/src/si5324.rs @@ -3,14 +3,14 @@ use log::info; use libboard_zynq::{i2c::I2c, timer::GlobalTimer, time::Milliseconds}; use embedded_hal::blocking::delay::DelayUs; #[cfg(not(si5324_soft_reset))] -use pl::csr; +use crate::pl::csr; type Result = result::Result; const ADDRESS: u8 = 0x68; #[cfg(not(si5324_soft_reset))] -fn hard_reset(timer: GlobalTimer) { +fn hard_reset(timer: &mut GlobalTimer) { unsafe { csr::si5324_rst_n::out_write(0); } timer.delay_us(1_000); unsafe { csr::si5324_rst_n::out_write(1); } @@ -104,6 +104,7 @@ fn write(i2c: &mut I2c, reg: u8, val: u8) -> Result<()> { Ok(()) } +#[allow(dead_code)] fn write_no_ack_value(i2c: &mut I2c, reg: u8, val: u8) -> Result<()> { i2c.start().unwrap(); if !i2c.write(ADDRESS << 1).unwrap() { @@ -146,8 +147,9 @@ fn ident(i2c: &mut I2c) -> Result { } #[cfg(si5324_soft_reset)] -fn soft_reset(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { - write_no_ack_value(i2c, 136, read(i2c, 136)? | 0x80)?; +fn soft_reset(i2c: &mut I2c, timer: &mut GlobalTimer) -> Result<()> { + let val = read(i2c, 136)?; + write_no_ack_value(i2c, 136, val | 0x80)?; timer.delay_us(10_000); Ok(()) } @@ -167,7 +169,7 @@ fn locked(i2c: &mut I2c) -> Result { Ok((read(i2c, 130)? & 0x01) == 0) // LOL_INT=0 } -fn monitor_lock(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { +fn monitor_lock(i2c: &mut I2c, timer: &mut GlobalTimer) -> Result<()> { info!("waiting for Si5324 lock..."); let timeout = timer.get_time() + Milliseconds(20_000); while !locked(i2c)? { @@ -180,7 +182,7 @@ fn monitor_lock(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { Ok(()) } -fn init(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { +fn init(i2c: &mut I2c, timer: &mut GlobalTimer) -> Result<()> { #[cfg(not(si5324_soft_reset))] hard_reset(timer); @@ -189,6 +191,10 @@ fn init(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { i2c.pca9548_select(0x70, 0)?; i2c.pca9548_select(0x71, 1 << 3)?; } + #[cfg(feature = "target_zc706")] + { + i2c.pca9548_select(0x74, 1 << 4)?; + } if ident(i2c)? != 0x0182 { return Err("Si5324 does not have expected product number"); @@ -199,7 +205,7 @@ fn init(i2c: &mut I2c, timer: GlobalTimer) -> Result<()> { Ok(()) } -pub fn bypass(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<()> { +pub fn bypass(i2c: &mut I2c, input: Input, timer: &mut GlobalTimer) -> Result<()> { let cksel_reg = match input { Input::Ckin1 => 0b00, Input::Ckin2 => 0b01, @@ -213,7 +219,7 @@ pub fn bypass(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<()> { Ok(()) } -pub fn setup(i2c: &mut I2c, settings: &FrequencySettings, input: Input, timer: GlobalTimer) -> Result<()> { +pub fn setup(i2c: &mut I2c, settings: &FrequencySettings, input: Input, timer: &mut GlobalTimer) -> Result<()> { let s = map_frequency_settings(settings)?; let cksel_reg = match input { Input::Ckin1 => 0b00, @@ -259,7 +265,7 @@ pub fn setup(i2c: &mut I2c, settings: &FrequencySettings, input: Input, timer: G Ok(()) } -pub fn select_input(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<()> { +pub fn select_input(i2c: &mut I2c, input: Input, timer: &mut GlobalTimer) -> Result<()> { let cksel_reg = match input { Input::Ckin1 => 0b00, Input::Ckin2 => 0b01, @@ -270,4 +276,78 @@ pub fn select_input(i2c: &mut I2c, input: Input, timer: GlobalTimer) -> Result<( } monitor_lock(i2c, timer)?; Ok(()) +} + +#[cfg(has_siphaser)] +pub mod siphaser { + use super::*; + use crate::pl::csr; + + pub fn select_recovered_clock(i2c: &mut I2c, rc: bool, timer: &mut GlobalTimer) -> Result<()> { + let val = read(i2c, 3)?; + write(i2c, 3, (val & 0xdf) | (1 << 5))?; // DHOLD=1 + unsafe { + csr::siphaser::switch_clocks_write(if rc { 1 } else { 0 }); + } + let val = read(i2c, 3)?; + write(i2c, 3, (val & 0xdf) | (0 << 5))?; // DHOLD=0 + monitor_lock(i2c, timer)?; + Ok(()) + } + + fn phase_shift(direction: u8, timer: &mut GlobalTimer) { + unsafe { + csr::siphaser::phase_shift_write(direction); + while csr::siphaser::phase_shift_done_read() == 0 {} + } + // wait for the Si5324 loop to stabilize + timer.delay_us(500); + } + + fn has_error(timer: &mut GlobalTimer) -> bool { + unsafe { + csr::siphaser::error_write(1); + } + timer.delay_us(5_000); + unsafe { + csr::siphaser::error_read() != 0 + } + } + + fn find_edge(target: bool, timer: &mut GlobalTimer) -> Result { + let mut nshifts = 0; + + let mut previous = has_error(timer); + loop { + phase_shift(1, timer); + nshifts += 1; + let current = has_error(timer); + if previous != target && current == target { + return Ok(nshifts); + } + if nshifts > 5000 { + return Err("failed to find timing error edge"); + } + previous = current; + } + } + + pub fn calibrate_skew(timer: &mut GlobalTimer) -> Result<()> { + let jitter_margin = 32; + let lead = find_edge(false, timer)?; + for _ in 0..jitter_margin { + phase_shift(1, timer); + } + let width = find_edge(true, timer)? + jitter_margin; + // width is 360 degrees (one full rotation of the phase between s/h limits) minus jitter + info!("calibration successful, lead: {}, width: {} ({}deg)", lead, width, width*360/(56*8)); + + // Apply reverse phase shift for half the width to get into the + // middle of the working region. + for _ in 0..width/2 { + phase_shift(0, timer); + } + + Ok(()) + } } \ No newline at end of file diff --git a/src/libbuild_zynq/Cargo.toml b/src/libbuild_zynq/Cargo.toml new file mode 100644 index 0000000..b5cc9e6 --- /dev/null +++ b/src/libbuild_zynq/Cargo.toml @@ -0,0 +1,8 @@ +[package] +authors = ["M-Labs"] +name = "build_zynq" +version = "0.0.0" + +[lib] +name = "build_zynq" +path = "lib.rs" diff --git a/src/libbuild_zynq/lib.rs b/src/libbuild_zynq/lib.rs new file mode 100644 index 0000000..6e5c851 --- /dev/null +++ b/src/libbuild_zynq/lib.rs @@ -0,0 +1,13 @@ +use std::fs::File; +use std::io::{BufRead, BufReader}; + +pub fn cfg() { + // Handle rustc-cfg file + let cfg_path = "../../build/rustc-cfg"; + println!("cargo:rerun-if-changed={}", cfg_path); + + let f = BufReader::new(File::open(cfg_path).unwrap()); + for line in f.lines() { + println!("cargo:rustc-cfg={}", line.unwrap()); + } +} diff --git a/src/libio/Cargo.toml b/src/libio/Cargo.toml new file mode 100644 index 0000000..c02f08a --- /dev/null +++ b/src/libio/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["M-Labs"] +name = "io" +version = "0.0.0" + +[lib] +name = "io" +path = "lib.rs" + +[dependencies] +core_io = { version = "0.1", features = ["collections"] } +byteorder = { version = "1.0", default-features = false, optional = true } + +libsupport_zynq = { default-features = false, features = ["alloc_core"], git = "https://git.m-labs.hk/M-Labs/zynq-rs.git" } + +[features] +alloc = [] diff --git a/src/libio/cursor.rs b/src/libio/cursor.rs new file mode 100644 index 0000000..c31c8da --- /dev/null +++ b/src/libio/cursor.rs @@ -0,0 +1,81 @@ +use core_io::{Read, Write, Error as IoError}; + +#[derive(Debug, Clone)] +pub struct Cursor { + inner: T, + pos: usize +} + +impl Cursor { + #[inline] + pub fn new(inner: T) -> Cursor { + Cursor { inner, pos: 0 } + } + + #[inline] + pub fn into_inner(self) -> T { + self.inner + } + + #[inline] + pub fn get_ref(&self) -> &T { + &self.inner + } + + #[inline] + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } + + #[inline] + pub fn position(&self) -> usize { + self.pos + } + + #[inline] + pub fn set_position(&mut self, pos: usize) { + self.pos = pos + } +} + +impl> Read for Cursor { + + fn read(&mut self, buf: &mut [u8]) -> Result { + let data = &self.inner.as_ref()[self.pos..]; + let len = buf.len().min(data.len()); + buf[..len].copy_from_slice(&data[..len]); + self.pos += len; + Ok(len) + } +} + +impl Write for Cursor<&mut [u8]> { + + fn write(&mut self, buf: &[u8]) -> Result { + let data = &mut self.inner[self.pos..]; + let len = buf.len().min(data.len()); + data[..len].copy_from_slice(&buf[..len]); + self.pos += len; + Ok(len) + } + + #[inline] + fn flush(&mut self) -> Result<(), IoError> { + Ok(()) + } +} + +#[cfg(feature = "alloc")] +impl Write for Cursor<::alloc::Vec> { + + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> Result<(), IoError> { + Ok(()) + } +} diff --git a/src/libio/lib.rs b/src/libio/lib.rs new file mode 100644 index 0000000..e754be4 --- /dev/null +++ b/src/libio/lib.rs @@ -0,0 +1,22 @@ +#![no_std] +#![feature(never_type)] +#![cfg_attr(feature = "alloc", feature(alloc))] + +extern crate alloc; +extern crate core_io; + +#[cfg(feature = "alloc")] +#[macro_use] +use alloc; +#[cfg(feature = "byteorder")] +extern crate byteorder; + +pub mod cursor; +#[cfg(feature = "byteorder")] +pub mod proto; + +pub use cursor::Cursor; +#[cfg(feature = "byteorder")] +pub use proto::{ProtoRead, ProtoWrite}; +#[cfg(all(feature = "byteorder", feature = "alloc"))] +pub use proto::ReadStringError; \ No newline at end of file diff --git a/src/runtime/src/proto_core_io.rs b/src/libio/proto.rs similarity index 100% rename from src/runtime/src/proto_core_io.rs rename to src/libio/proto.rs diff --git a/src/runtime/Cargo.toml b/src/runtime/Cargo.toml index c813611..804e8ec 100644 --- a/src/runtime/Cargo.toml +++ b/src/runtime/Cargo.toml @@ -6,10 +6,13 @@ authors = ["M-Labs"] edition = "2018" [features] -target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706"] -target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc"] +target_zc706 = ["libboard_zynq/target_zc706", "libsupport_zynq/target_zc706", "libconfig/target_zc706", "libboard_artiq/target_zc706"] +target_kasli_soc = ["libboard_zynq/target_kasli_soc", "libsupport_zynq/target_kasli_soc", "libconfig/target_kasli_soc", "libboard_artiq/target_kasli_soc"] default = ["target_zc706"] +[build-dependencies] +build_zynq = { path = "../libbuild_zynq" } + [dependencies] num-traits = { version = "0.2", default-features = false } num-derive = "0.3" @@ -37,3 +40,5 @@ dyld = { path = "../libdyld" } dwarf = { path = "../libdwarf" } unwind = { path = "../libunwind" } libc = { path = "../libc" } +io = { path = "../libio" } +libboard_artiq = { path = "../libboard_artiq" } \ No newline at end of file diff --git a/src/runtime/build.rs b/src/runtime/build.rs index e0fdb0c..04ead8d 100644 --- a/src/runtime/build.rs +++ b/src/runtime/build.rs @@ -1,9 +1,10 @@ use std::env; use std::fs::File; use std::io::Write; -use std::io::{BufRead, BufReader}; use std::path::PathBuf; +extern crate build_zynq; + fn main() { // Put the linker script somewhere the linker can find it let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); @@ -16,13 +17,5 @@ fn main() { // Only re-run the build script when link.x is changed, // instead of when any part of the source code changes. println!("cargo:rerun-if-changed=link.x"); - - // Handle rustc-cfg file - let cfg_path = "../../build/rustc-cfg"; - println!("cargo:rerun-if-changed={}", cfg_path); - - let f = BufReader::new(File::open(cfg_path).unwrap()); - for line in f.lines() { - println!("cargo:rustc-cfg={}", line.unwrap()); - } + build_zynq::cfg(); } diff --git a/src/runtime/src/comms.rs b/src/runtime/src/comms.rs index 06ff331..f90b6de 100644 --- a/src/runtime/src/comms.rs +++ b/src/runtime/src/comms.rs @@ -21,6 +21,7 @@ use libcortex_a9::{semaphore::Semaphore, mutex::Mutex, sync_channel::{Sender, Re use futures::{select_biased, future::FutureExt}; use libasync::{smoltcp::{Sockets, TcpStream}, task}; use libconfig::{Config, net_settings}; +use libboard_artiq::drtio_routing; use crate::proto_async::*; use crate::kernel; @@ -28,7 +29,9 @@ use crate::rpc; use crate::moninj; use crate::mgmt; use crate::analyzer; - +use crate::rtio_mgt; +#[cfg(has_drtio)] +use crate::pl; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { @@ -387,8 +390,21 @@ pub fn main(timer: GlobalTimer, cfg: Config) { Sockets::init(32); + // before, mutex was on io, but now that io isn't used...? + let aux_mutex: Rc> = Rc::new(Mutex::new(false)); + #[cfg(has_drtio)] + let drtio_routing_table = Rc::new(RefCell::new( + drtio_routing::config_routing_table(pl::csr::DRTIO.len(), &cfg))); + #[cfg(not(has_drtio))] + let drtio_routing_table = Rc::new(RefCell::new(drtio_routing::RoutingTable::default_empty())); + let up_destinations = Rc::new(RefCell::new([false; drtio_routing::DEST_COUNT])); + #[cfg(has_drtio_routing)] + drtio_routing::interconnect_disable_all(); + + rtio_mgt::startup(&aux_mutex, &drtio_routing_table, &up_destinations, timer); + analyzer::start(); - moninj::start(timer); + moninj::start(timer, aux_mutex, drtio_routing_table); let control: Rc> = Rc::new(RefCell::new(kernel::Control::start())); let idle_kernel = Rc::new(cfg.read("idle").ok()); diff --git a/src/runtime/src/main.rs b/src/runtime/src/main.rs index f59d2b9..973afbe 100644 --- a/src/runtime/src/main.rs +++ b/src/runtime/src/main.rs @@ -11,82 +11,43 @@ extern crate alloc; -use core::{cmp, str}; use log::{info, warn, error}; -use libboard_zynq::{timer::GlobalTimer, mpcore, gic, slcr}; +use libboard_zynq::{timer::GlobalTimer, mpcore, gic}; use libasync::{task, block_async}; use libsupport_zynq::ram; use nb; use void::Void; use embedded_hal::blocking::delay::DelayMs; use libconfig::Config; -use libregister::RegisterW; use libcortex_a9::l2c::enable_l2_cache; +use libboard_artiq::{logger, identifier_read, init_gateware, pl}; +#[cfg(has_si5324)] +use libboard_artiq::si5324; -mod proto_core_io; mod proto_async; mod comms; mod rpc; -#[path = "../../../build/pl.rs"] -mod pl; #[cfg(ki_impl = "csr")] #[path = "rtio_csr.rs"] mod rtio; #[cfg(ki_impl = "acp")] #[path = "rtio_acp.rs"] mod rtio; +mod rtio_mgt; mod kernel; mod moninj; mod eh_artiq; mod panic; -mod logger; mod mgmt; mod analyzer; mod irq; mod i2c; -#[cfg(has_si5324)] -mod si5324; -fn init_gateware() { - // Set up PS->PL clocks - slcr::RegisterBlock::unlocked(|slcr| { - // As we are touching the mux, the clock may glitch, so reset the PL. - slcr.fpga_rst_ctrl.write( - slcr::FpgaRstCtrl::zeroed() - .fpga0_out_rst(true) - .fpga1_out_rst(true) - .fpga2_out_rst(true) - .fpga3_out_rst(true) - ); - slcr.fpga0_clk_ctrl.write( - slcr::Fpga0ClkCtrl::zeroed() - .src_sel(slcr::PllSource::IoPll) - .divisor0(8) - .divisor1(1) - ); - slcr.fpga_rst_ctrl.write( - slcr::FpgaRstCtrl::zeroed() - ); - }); -} - -fn identifier_read(buf: &mut [u8]) -> &str { - unsafe { - pl::csr::identifier::address_write(0); - let len = pl::csr::identifier::data_read(); - let len = cmp::min(len, buf.len() as u8); - for i in 0..len { - pl::csr::identifier::address_write(1 + i); - buf[i as usize] = pl::csr::identifier::data_read(); - } - str::from_utf8_unchecked(&buf[..len as usize]) - } -} - -fn init_rtio(timer: &mut GlobalTimer, cfg: &Config) { +fn init_rtio(timer: &mut GlobalTimer, _cfg: &Config) { + #[cfg(has_rtio_crg_clock_sel)] let clock_sel = - if let Ok(rtioclk) = cfg.read_str("rtioclk") { + if let Ok(rtioclk) = _cfg.read_str("rtioclk") { match rtioclk.as_ref() { "internal" => { info!("using internal RTIO clock"); @@ -130,6 +91,19 @@ fn init_rtio(timer: &mut GlobalTimer, cfg: &Config) { } } +#[cfg(has_drtio)] +fn init_drtio(timer: &mut GlobalTimer) +{ + unsafe { + pl::csr::drtio_transceiver::stable_clkin_write(1); + } + timer.delay_ms(2); // wait for CPLL/QPLL lock + unsafe { + pl::csr::drtio_transceiver::txenable_write(0xffffffffu32 as _); + } +} + + fn wait_for_async_rtio_error() -> nb::Result<(), Void> { unsafe { if pl::csr::rtio_core::async_error_read() != 0 { @@ -201,7 +175,7 @@ pub fn main_core0() { i2c::init(); #[cfg(has_si5324)] si5324::setup(unsafe { (&mut i2c::I2C_BUS).as_mut().unwrap() }, - &SI5324_SETTINGS, si5324::Input::Ckin2, timer).expect("cannot initialize Si5324"); + &SI5324_SETTINGS, si5324::Input::Ckin2, &mut timer).expect("cannot initialize Si5324"); let cfg = match Config::new() { Ok(cfg) => cfg, @@ -211,6 +185,9 @@ pub fn main_core0() { } }; + #[cfg(has_drtio)] + init_drtio(&mut timer); + init_rtio(&mut timer, &cfg); task::spawn(report_async_rtio_errors()); diff --git a/src/runtime/src/mgmt.rs b/src/runtime/src/mgmt.rs index 06ed8d5..51adeeb 100644 --- a/src/runtime/src/mgmt.rs +++ b/src/runtime/src/mgmt.rs @@ -6,7 +6,7 @@ use core::cell::RefCell; use alloc::{rc::Rc, vec::Vec, string::String}; use log::{self, info, debug, warn, error, LevelFilter}; -use crate::logger::{BufferLogger, LogBufferRef}; +use libboard_artiq::logger::{BufferLogger, LogBufferRef}; use crate::proto_async::*; use num_derive::FromPrimitive; use num_traits::FromPrimitive; diff --git a/src/runtime/src/moninj.rs b/src/runtime/src/moninj.rs index d1124b4..77b7942 100644 --- a/src/runtime/src/moninj.rs +++ b/src/runtime/src/moninj.rs @@ -1,17 +1,19 @@ -use core::fmt; -use alloc::collections::BTreeMap; +use core::{fmt, cell::RefCell}; +use alloc::{collections::BTreeMap, rc::Rc}; use log::{debug, info, warn}; use void::Void; +use libboard_artiq::drtio_routing; + use libboard_zynq::{smoltcp, timer::GlobalTimer, time::Milliseconds}; use libasync::{task, smoltcp::TcpStream, block_async, nb}; +use libcortex_a9::mutex::Mutex; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use futures::{pin_mut, select_biased, FutureExt}; use crate::proto_async::*; -use crate::pl::csr; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -19,7 +21,6 @@ pub enum Error { NetworkError(smoltcp::Error), UnexpectedPattern, UnrecognizedPacket, - } pub type Result = core::result::Result; @@ -54,32 +55,109 @@ enum DeviceMessage { InjectionStatus = 1 } -fn read_probe(channel: i32, probe: i8) -> i32 { - unsafe { - csr::rtio_moninj::mon_chan_sel_write(channel as _); - csr::rtio_moninj::mon_probe_sel_write(probe as _); - csr::rtio_moninj::mon_value_update_write(1); - csr::rtio_moninj::mon_value_read() as i32 +#[cfg(has_drtio)] +mod remote_moninj { + use super::*; + use libboard_artiq::drtioaux; + use crate::rtio_mgt::drtio; + use log::error; + + pub fn read_probe(aux_mutex: &Rc>, timer: GlobalTimer, linkno: u8, destination: u8, channel: i32, probe: i8) -> i32 { + let reply = task::block_on(drtio::aux_transact(aux_mutex, linkno, &drtioaux::Packet::MonitorRequest { + destination: destination, + channel: channel as _, + probe: probe as _}, + timer)); + match reply { + Ok(drtioaux::Packet::MonitorReply { value }) => return value as i32, + Ok(packet) => error!("received unexpected aux packet: {:?}", packet), + Err(e) => error!("aux packet error ({})", e) + } + 0 + } + + pub fn inject(aux_mutex: &Rc>, _timer: GlobalTimer, linkno: u8, destination: u8, channel: i32, overrd: i8, value: i8) { + let _lock = aux_mutex.lock(); + drtioaux::send(linkno, &drtioaux::Packet::InjectionRequest { + destination: destination, + channel: channel as _, + overrd: overrd as _, + value: value as _ + }).unwrap(); + } + + pub fn read_injection_status(aux_mutex: &Rc>, timer: GlobalTimer, linkno: u8, destination: u8, channel: i32, overrd: i8) -> i8 { + let reply = task::block_on(drtio::aux_transact(aux_mutex, + linkno, + &drtioaux::Packet::InjectionStatusRequest { + destination: destination, + channel: channel as _, + overrd: overrd as _}, + timer)); + match reply { + Ok(drtioaux::Packet::InjectionStatusReply { value }) => return value as i8, + Ok(packet) => error!("received unexpected aux packet: {:?}", packet), + Err(e) => error!("aux packet error ({})", e) + } + 0 } } -fn inject(channel: i32, overrd: i8, value: i8) { - unsafe { - csr::rtio_moninj::inj_chan_sel_write(channel as _); - csr::rtio_moninj::inj_override_sel_write(overrd as _); - csr::rtio_moninj::inj_value_write(value as _); +mod local_moninj { + use libboard_artiq::pl::csr; + + pub fn read_probe(channel: i32, probe: i8) -> i32 { + unsafe { + csr::rtio_moninj::mon_chan_sel_write(channel as _); + csr::rtio_moninj::mon_probe_sel_write(probe as _); + csr::rtio_moninj::mon_value_update_write(1); + csr::rtio_moninj::mon_value_read() as i32 + } + } + + pub fn inject(channel: i32, overrd: i8, value: i8) { + unsafe { + csr::rtio_moninj::inj_chan_sel_write(channel as _); + csr::rtio_moninj::inj_override_sel_write(overrd as _); + csr::rtio_moninj::inj_value_write(value as _); + } + } + + pub fn read_injection_status(channel: i32, overrd: i8) -> i8 { + unsafe { + csr::rtio_moninj::inj_chan_sel_write(channel as _); + csr::rtio_moninj::inj_override_sel_write(overrd as _); + csr::rtio_moninj::inj_value_read() as i8 + } } } -fn read_injection_status(channel: i32, overrd: i8) -> i8 { - unsafe { - csr::rtio_moninj::inj_chan_sel_write(channel as _); - csr::rtio_moninj::inj_override_sel_write(overrd as _); - csr::rtio_moninj::inj_value_read() as i8 - } +#[cfg(has_drtio)] +macro_rules! dispatch { + ($timer:ident, $aux_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ + let destination = ($channel >> 16) as u8; + let channel = $channel; + let routing_table = $routing_table.borrow_mut(); + let hop = routing_table.0[destination as usize][0]; + if hop == 0 { + local_moninj::$func(channel.into(), $($param, )*) + } else { + let linkno = hop - 1 as u8; + remote_moninj::$func($aux_mutex, $timer, linkno, destination, channel, $($param, )*) + } + }} } -async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> { +#[cfg(not(has_drtio))] +macro_rules! dispatch { + ($timer:ident, $aux_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ + let channel = $channel as u16; + local_moninj::$func(channel.into(), $($param, )*) + }} +} + +async fn handle_connection(stream: &TcpStream, timer: GlobalTimer, + _aux_mutex: &Rc>, _routing_table: &Rc>) -> Result<()> { if !expect(&stream, b"ARTIQ moninj\n").await? { return Err(Error::UnexpectedPattern); } @@ -135,13 +213,13 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> let channel = read_i32(&stream).await?; let overrd = read_i8(&stream).await?; let value = read_i8(&stream).await?; - inject(channel, overrd, value); + dispatch!(timer, _aux_mutex, _routing_table, channel, inject, overrd, value); debug!("INJECT channel {}, overrd {}, value {}", channel, overrd, value); }, HostMessage::GetInjectionStatus => { let channel = read_i32(&stream).await?; let overrd = read_i8(&stream).await?; - let value = read_injection_status(channel, overrd); + let value = dispatch!(timer, _aux_mutex, _routing_table, channel, read_injection_status, overrd); write_i8(&stream, DeviceMessage::InjectionStatus.to_i8().unwrap()).await?; write_i32(&stream, channel).await?; write_i8(&stream, overrd).await?; @@ -151,7 +229,7 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> }, _ = timeout_f => { for (&(channel, probe), previous) in probe_watch_list.iter_mut() { - let current = read_probe(channel, probe); + let current = dispatch!(timer, _aux_mutex, _routing_table, channel, read_probe, probe); if previous.is_none() || previous.unwrap() != current { write_i8(&stream, DeviceMessage::MonitorStatus.to_i8().unwrap()).await?; write_i32(&stream, channel).await?; @@ -161,7 +239,7 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> } } for (&(channel, overrd), previous) in inject_watch_list.iter_mut() { - let current = read_injection_status(channel, overrd); + let current = dispatch!(timer, _aux_mutex, _routing_table, channel, read_injection_status, overrd); if previous.is_none() || previous.unwrap() != current { write_i8(&stream, DeviceMessage::InjectionStatus.to_i8().unwrap()).await?; write_i32(&stream, channel).await?; @@ -176,13 +254,15 @@ async fn handle_connection(stream: &TcpStream, timer: GlobalTimer) -> Result<()> } } -pub fn start(timer: GlobalTimer) { +pub fn start(timer: GlobalTimer, aux_mutex: Rc>, routing_table: Rc>) { task::spawn(async move { loop { + let aux_mutex = aux_mutex.clone(); + let routing_table = routing_table.clone(); let stream = TcpStream::accept(1383, 2048, 2048).await.unwrap(); task::spawn(async move { info!("received connection"); - let result = handle_connection(&stream, timer).await; + let result = handle_connection(&stream, timer, &aux_mutex, &routing_table).await; match result { Err(Error::NetworkError(smoltcp::Error::Finished)) => info!("peer closed connection"), Err(error) => warn!("connection terminated: {}", error), diff --git a/src/runtime/src/rpc.rs b/src/runtime/src/rpc.rs index bb17f50..ef4e659 100644 --- a/src/runtime/src/rpc.rs +++ b/src/runtime/src/rpc.rs @@ -10,7 +10,7 @@ use libasync::smoltcp::TcpStream; use alloc::boxed::Box; use async_recursion::async_recursion; -use crate::proto_core_io::ProtoWrite; +use io::proto::ProtoWrite; use crate::proto_async; use self::tag::{Tag, TagIterator, split_tag}; diff --git a/src/runtime/src/rtio_mgt.rs b/src/runtime/src/rtio_mgt.rs new file mode 100644 index 0000000..d72a902 --- /dev/null +++ b/src/runtime/src/rtio_mgt.rs @@ -0,0 +1,352 @@ +use core::cell::RefCell; +use alloc::rc::Rc; +use libboard_zynq::timer::GlobalTimer; +use libboard_artiq::{pl::csr, drtio_routing}; +use libcortex_a9::mutex::Mutex; + + +#[cfg(has_drtio)] +pub mod drtio { + use super::*; + use libboard_artiq::drtioaux_async; + use libboard_artiq::drtioaux_async::Packet; + use libboard_artiq::drtioaux::Error; + use log::{warn, error, info}; + use embedded_hal::blocking::delay::DelayMs; + use libasync::{task, delay}; + use libboard_zynq::time::Milliseconds; + + pub fn startup(aux_mutex: &Rc>, + routing_table: &Rc>, + up_destinations: &Rc>, + timer: GlobalTimer) { + let aux_mutex = aux_mutex.clone(); + let routing_table = routing_table.clone(); + let up_destinations = up_destinations.clone(); + task::spawn(async move { + let routing_table = routing_table.borrow(); + link_task(&aux_mutex, &routing_table, &up_destinations, timer).await; + }); + } + + async fn link_rx_up(linkno: u8) -> bool { + let linkno = linkno as usize; + unsafe { + (csr::DRTIO[linkno].rx_up_read)() == 1 + } + } + + async fn recv_aux_timeout(linkno: u8, timeout: u64, timer: GlobalTimer) -> Result { + if !link_rx_up(linkno).await { + return Err("link went down"); + } + match drtioaux_async::recv_timeout(linkno, Some(timeout), timer).await { + Ok(packet) => return Ok(packet), + Err(Error::TimedOut) => return Err("timed out"), + Err(_) => return Err("aux packet error"), + } + } + + pub async fn aux_transact(aux_mutex: &Mutex, linkno: u8, request: &Packet, + timer: GlobalTimer) -> Result { + let _lock = aux_mutex.lock(); + drtioaux_async::send(linkno, request).await.unwrap(); + recv_aux_timeout(linkno, 200, timer).await + } + + async fn drain_buffer(linkno: u8, draining_time: Milliseconds, timer: GlobalTimer) { + let max_time = timer.get_time() + draining_time; + loop { + if timer.get_time() > max_time { + return; + } //could this be cut short? + let _ = drtioaux_async::recv(linkno).await; + } + } + + async fn ping_remote(aux_mutex: &Rc>, linkno: u8, timer: GlobalTimer) -> u32 { + let mut count = 0; + loop { + if !link_rx_up(linkno).await { + return 0 + } + count += 1; + if count > 100 { + return 0; + } + let reply = aux_transact(aux_mutex, linkno, &Packet::EchoRequest, timer).await; + match reply { + Ok(Packet::EchoReply) => { + // make sure receive buffer is drained + let draining_time = Milliseconds(200); + drain_buffer(linkno, draining_time, timer).await; + return count; + } + _ => {} + } + } + } + + async fn sync_tsc(aux_mutex: &Rc>, linkno: u8, timer: GlobalTimer) -> Result<(), &'static str> { + let _lock = aux_mutex.lock(); + + unsafe { + (csr::DRTIO[linkno as usize].set_time_write)(1); + while (csr::DRTIO[linkno as usize].set_time_read)() == 1 {} + } + // TSCAck is the only aux packet that is sent spontaneously + // by the satellite, in response to a TSC set on the RT link. + let reply = recv_aux_timeout(linkno, 10000, timer).await?; + if reply == Packet::TSCAck { + return Ok(()); + } else { + return Err("unexpected reply"); + } + } + + async fn load_routing_table(aux_mutex: &Rc>, linkno: u8, routing_table: &drtio_routing::RoutingTable, + timer: GlobalTimer) -> Result<(), &'static str> { + for i in 0..drtio_routing::DEST_COUNT { + let reply = aux_transact(aux_mutex, linkno, &Packet::RoutingSetPath { + destination: i as u8, + hops: routing_table.0[i] + }, timer).await?; + if reply != Packet::RoutingAck { + return Err("unexpected reply"); + } + } + Ok(()) + } + + async fn set_rank(aux_mutex: &Rc>, linkno: u8, rank: u8, timer: GlobalTimer) -> Result<(), &'static str> { + let reply = aux_transact(aux_mutex, linkno, &Packet::RoutingSetRank { + rank: rank + }, timer).await?; + if reply != Packet::RoutingAck { + return Err("unexpected reply"); + } + Ok(()) + } + + async fn init_buffer_space(destination: u8, linkno: u8) { + let linkno = linkno as usize; + unsafe { + (csr::DRTIO[linkno].destination_write)(destination); + (csr::DRTIO[linkno].force_destination_write)(1); + (csr::DRTIO[linkno].o_get_buffer_space_write)(1); + while (csr::DRTIO[linkno].o_wait_read)() == 1 {} + info!("[DEST#{}] buffer space is {}", + destination, (csr::DRTIO[linkno].o_dbg_buffer_space_read)()); + (csr::DRTIO[linkno].force_destination_write)(0); + } + } + + async fn process_unsolicited_aux(aux_mutex: &Rc>, linkno: u8) { + let _lock = aux_mutex.lock(); + match drtioaux_async::recv(linkno).await { + Ok(Some(packet)) => warn!("[LINK#{}] unsolicited aux packet: {:?}", linkno, packet), + Ok(None) => (), + Err(_) => warn!("[LINK#{}] aux packet error", linkno) + } + } + + async fn process_local_errors(linkno: u8) { + let errors; + let linkidx = linkno as usize; + unsafe { + errors = (csr::DRTIO[linkidx].protocol_error_read)(); + (csr::DRTIO[linkidx].protocol_error_write)(errors); + } + if errors != 0 { + error!("[LINK#{}] error(s) found (0x{:02x}):", linkno, errors); + if errors & 1 != 0 { + error!("[LINK#{}] received packet of an unknown type", linkno); + } + if errors & 2 != 0 { + error!("[LINK#{}] received truncated packet", linkno); + } + if errors & 4 != 0 { + error!("[LINK#{}] timeout attempting to get remote buffer space", linkno); + } + } + } + + async fn destination_set_up(routing_table: &drtio_routing::RoutingTable, + up_destinations: &Rc>, + destination: u8, up: bool) { + let mut up_destinations = up_destinations.borrow_mut(); + up_destinations[destination as usize] = up; + if up { + drtio_routing::interconnect_enable(routing_table, 0, destination); + info!("[DEST#{}] destination is up", destination); + } else { + drtio_routing::interconnect_disable(destination); + info!("[DEST#{}] destination is down", destination); + } + } + + async fn destination_up(up_destinations: &Rc>, destination: u8) -> bool { + let up_destinations = up_destinations.borrow(); + up_destinations[destination as usize] + } + + async fn destination_survey(aux_mutex: &Rc>, routing_table: &drtio_routing::RoutingTable, + up_links: &[bool], + up_destinations: &Rc>, + timer: GlobalTimer) { + for destination in 0..drtio_routing::DEST_COUNT { + let hop = routing_table.0[destination][0]; + let destination = destination as u8; + + if hop == 0 { + /* local RTIO */ + if !destination_up(up_destinations, destination).await { + destination_set_up(routing_table, up_destinations, destination, true).await; + } + } else if hop as usize <= csr::DRTIO.len() { + let linkno = hop - 1; + if destination_up(up_destinations, destination).await { + if up_links[linkno as usize] { + let reply = aux_transact(aux_mutex, linkno, &Packet::DestinationStatusRequest { + destination: destination + }, timer).await; + match reply { + Ok(Packet::DestinationDownReply) => + destination_set_up(routing_table, up_destinations, destination, false).await, + Ok(Packet::DestinationOkReply) => (), + Ok(Packet::DestinationSequenceErrorReply { channel }) => + error!("[DEST#{}] RTIO sequence error involving channel 0x{:04x}", destination, channel), + Ok(Packet::DestinationCollisionReply { channel }) => + error!("[DEST#{}] RTIO collision involving channel 0x{:04x}", destination, channel), + Ok(Packet::DestinationBusyReply { channel }) => + error!("[DEST#{}] RTIO busy error involving channel 0x{:04x}", destination, channel), + Ok(packet) => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet), + Err(e) => error!("[DEST#{}] communication failed ({})", destination, e) + } + } else { + destination_set_up(routing_table, up_destinations, destination, false).await; + } + } else { + if up_links[linkno as usize] { + let reply = aux_transact(aux_mutex, linkno, &Packet::DestinationStatusRequest { + destination: destination + }, timer).await; + match reply { + Ok(Packet::DestinationDownReply) => (), + Ok(Packet::DestinationOkReply) => { + destination_set_up(routing_table, up_destinations, destination, true).await; + init_buffer_space(destination as u8, linkno).await; + }, + Ok(packet) => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet), + Err(e) => error!("[DEST#{}] communication failed ({})", destination, e) + } + } + } + } + } + } + + pub async fn link_task(aux_mutex: &Rc>, + routing_table: &drtio_routing::RoutingTable, + up_destinations: &Rc>, + timer: GlobalTimer) { + let mut up_links = [false; csr::DRTIO.len()]; + loop { + for linkno in 0..csr::DRTIO.len() { + let linkno = linkno as u8; + if up_links[linkno as usize] { + /* link was previously up */ + if link_rx_up(linkno).await { + process_unsolicited_aux(aux_mutex, linkno).await; + process_local_errors(linkno).await; + } else { + info!("[LINK#{}] link is down", linkno); + up_links[linkno as usize] = false; + } + } else { + /* link was previously down */ + if link_rx_up(linkno).await { + info!("[LINK#{}] link RX became up, pinging", linkno); + let ping_count = ping_remote(aux_mutex, linkno, timer).await; + if ping_count > 0 { + info!("[LINK#{}] remote replied after {} packets", linkno, ping_count); + up_links[linkno as usize] = true; + if let Err(e) = sync_tsc(aux_mutex, linkno, timer).await { + error!("[LINK#{}] failed to sync TSC ({})", linkno, e); + } + if let Err(e) = load_routing_table(aux_mutex, linkno, routing_table, timer).await { + error!("[LINK#{}] failed to load routing table ({})", linkno, e); + } + if let Err(e) = set_rank(aux_mutex, linkno, 1 as u8, timer).await { + error!("[LINK#{}] failed to set rank ({})", linkno, e); + } + info!("[LINK#{}] link initialization completed", linkno); + } else { + error!("[LINK#{}] ping failed", linkno); + } + } + } + } + destination_survey(aux_mutex, routing_table, &up_links, up_destinations, timer).await; + let mut countdown = timer.countdown(); + delay(&mut countdown, Milliseconds(200)).await; + } + } + + #[allow(dead_code)] + pub fn reset(aux_mutex: Rc>, mut timer: GlobalTimer) { + for linkno in 0..csr::DRTIO.len() { + unsafe { + (csr::DRTIO[linkno].reset_write)(1); + } + } + timer.delay_ms(1); + for linkno in 0..csr::DRTIO.len() { + unsafe { + (csr::DRTIO[linkno].reset_write)(0); + } + } + + for linkno in 0..csr::DRTIO.len() { + let linkno = linkno as u8; + if task::block_on(link_rx_up(linkno)) { + let reply = task::block_on(aux_transact(&aux_mutex, linkno, + &Packet::ResetRequest, timer)); + match reply { + Ok(Packet::ResetAck) => (), + Ok(_) => error!("[LINK#{}] reset failed, received unexpected aux packet", linkno), + Err(e) => error!("[LINK#{}] reset failed, aux packet error ({})", linkno, e) + } + } + } + } +} + +#[cfg(not(has_drtio))] +pub mod drtio { + use super::*; + + pub fn startup(_aux_mutex: &Rc>, _routing_table: &Rc>, + _up_destinations: &Rc>, _timer: GlobalTimer) {} + + #[allow(dead_code)] + pub fn reset(_aux_mutex: Rc>, mut _timer: GlobalTimer) {} +} + +pub fn startup(aux_mutex: &Rc>, + routing_table: &Rc>, + up_destinations: &Rc>, + timer: GlobalTimer) { + drtio::startup(aux_mutex, routing_table, up_destinations, timer); + unsafe { + csr::rtio_core::reset_phy_write(1); + } +} + +#[allow(dead_code)] +pub fn reset(aux_mutex: Rc>, timer: GlobalTimer) { + unsafe { + csr::rtio_core::reset_write(1); + } + drtio::reset(aux_mutex, timer) +}