From e6863263b45e6fec4c84f00c288afd7b4e5f53ab Mon Sep 17 00:00:00 2001 From: mwojcik Date: Wed, 6 Oct 2021 13:02:28 +0800 Subject: [PATCH] add libboard_artiq (to be shared between runtime and satman) Reviewed-on: https://git.m-labs.hk/M-Labs/artiq-zynq/pulls/139 Co-authored-by: mwojcik Co-committed-by: mwojcik --- src/libboard_artiq/Cargo.toml | 31 ++ src/libboard_artiq/build.rs | 5 + src/libboard_artiq/src/drtio_routing.rs | 109 +++++++ src/libboard_artiq/src/drtioaux.rs | 174 +++++++++++ src/libboard_artiq/src/drtioaux_async.rs | 139 +++++++++ src/libboard_artiq/src/drtioaux_proto.rs | 364 +++++++++++++++++++++++ src/libboard_artiq/src/lib.rs | 68 +++++ src/libboard_artiq/src/logger.rs | 123 ++++++++ src/libboard_artiq/src/si5324.rs | 353 ++++++++++++++++++++++ 9 files changed, 1366 insertions(+) create mode 100644 src/libboard_artiq/Cargo.toml create mode 100644 src/libboard_artiq/build.rs create mode 100644 src/libboard_artiq/src/drtio_routing.rs create mode 100644 src/libboard_artiq/src/drtioaux.rs create mode 100644 src/libboard_artiq/src/drtioaux_async.rs create mode 100644 src/libboard_artiq/src/drtioaux_proto.rs create mode 100644 src/libboard_artiq/src/lib.rs create mode 100644 src/libboard_artiq/src/logger.rs create mode 100644 src/libboard_artiq/src/si5324.rs 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..c1b1a51 --- /dev/null +++ b/src/libboard_artiq/src/lib.rs @@ -0,0 +1,68 @@ +#![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; +#[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/libboard_artiq/src/logger.rs b/src/libboard_artiq/src/logger.rs new file mode 100644 index 0000000..3ae9643 --- /dev/null +++ b/src/libboard_artiq/src/logger.rs @@ -0,0 +1,123 @@ +use core::cell::Cell; +use core::fmt::Write; +use log::{Log, LevelFilter}; +use log_buffer::LogBuffer; +use libcortex_a9::mutex::{Mutex, MutexGuard}; +use libboard_zynq::{println, timer::GlobalTimer}; + +pub struct LogBufferRef<'a> { + buffer: MutexGuard<'a, LogBuffer<&'static mut [u8]>>, + old_log_level: LevelFilter +} + +impl<'a> LogBufferRef<'a> { + fn new(buffer: MutexGuard<'a, LogBuffer<&'static mut [u8]>>) -> LogBufferRef<'a> { + let old_log_level = log::max_level(); + log::set_max_level(LevelFilter::Off); + LogBufferRef { buffer, old_log_level } + } + + pub fn is_empty(&self) -> bool { + self.buffer.is_empty() + } + + pub fn clear(&mut self) { + self.buffer.clear() + } + + pub fn extract(&mut self) -> &str { + self.buffer.extract() + } +} + +impl<'a> Drop for LogBufferRef<'a> { + fn drop(&mut self) { + log::set_max_level(self.old_log_level) + } +} + +pub struct BufferLogger { + buffer: Mutex>, + uart_filter: Cell, + buffer_filter: Cell, +} + +static mut LOGGER: Option = None; + +impl BufferLogger { + pub fn new(buffer: &'static mut [u8]) -> BufferLogger { + BufferLogger { + buffer: Mutex::new(LogBuffer::new(buffer)), + uart_filter: Cell::new(LevelFilter::Info), + buffer_filter: Cell::new(LevelFilter::Trace), + } + } + + pub fn register(self) { + unsafe { + LOGGER = Some(self); + log::set_logger(LOGGER.as_ref().unwrap()) + .expect("global logger can only be initialized once"); + } + } + + pub unsafe fn get_logger() -> &'static mut Option { + &mut LOGGER + } + + pub fn buffer<'a>(&'a self) -> Option> { + self.buffer + .try_lock() + .map(LogBufferRef::new) + } + + pub fn uart_log_level(&self) -> LevelFilter { + self.uart_filter.get() + } + + pub fn set_uart_log_level(&self, max_level: LevelFilter) { + self.uart_filter.set(max_level) + } + + pub fn buffer_log_level(&self) -> LevelFilter { + self.buffer_filter.get() + } + + /// this should be reserved for mgmt module + pub fn set_buffer_log_level(&self, max_level: LevelFilter) { + self.buffer_filter.set(max_level) + } +} + +// required for impl Log +unsafe impl Sync for BufferLogger {} + +impl Log for BufferLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + let timestamp = unsafe { + GlobalTimer::get() + }.get_us().0; + let seconds = timestamp / 1_000_000; + let micros = timestamp % 1_000_000; + + if record.level() <= self.buffer_log_level() { + let mut buffer = self.buffer.lock(); + writeln!(buffer, "[{:6}.{:06}s] {:>5}({}): {}", seconds, micros, + record.level(), record.target(), record.args()).unwrap(); + } + + if record.level() <= self.uart_log_level() { + println!("[{:6}.{:06}s] {:>5}({}): {}", seconds, micros, + record.level(), record.target(), record.args()); + } + } + } + + fn flush(&self) { + } +} diff --git a/src/libboard_artiq/src/si5324.rs b/src/libboard_artiq/src/si5324.rs new file mode 100644 index 0000000..c083fae --- /dev/null +++ b/src/libboard_artiq/src/si5324.rs @@ -0,0 +1,353 @@ +use core::result; +use log::info; +use libboard_zynq::{i2c::I2c, timer::GlobalTimer, time::Milliseconds}; +use embedded_hal::blocking::delay::DelayUs; +#[cfg(not(si5324_soft_reset))] +use crate::pl::csr; + +type Result = result::Result; + +const ADDRESS: u8 = 0x68; + +#[cfg(not(si5324_soft_reset))] +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); } + timer.delay_us(10_000); +} + +// NOTE: the logical parameters DO NOT MAP to physical values written +// into registers. They have to be mapped; see the datasheet. +// DSPLLsim reports the logical parameters in the design summary, not +// the physical register values. +pub struct FrequencySettings { + pub n1_hs: u8, + pub nc1_ls: u32, + pub n2_hs: u8, + pub n2_ls: u32, + pub n31: u32, + pub n32: u32, + pub bwsel: u8, + pub crystal_ref: bool +} + +pub enum Input { + Ckin1, + Ckin2, +} + +fn map_frequency_settings(settings: &FrequencySettings) -> Result { + if settings.nc1_ls != 0 && (settings.nc1_ls % 2) == 1 { + return Err("NC1_LS must be 0 or even") + } + if settings.nc1_ls > (1 << 20) { + return Err("NC1_LS is too high") + } + if (settings.n2_ls % 2) == 1 { + return Err("N2_LS must be even") + } + if settings.n2_ls > (1 << 20) { + return Err("N2_LS is too high") + } + if settings.n31 > (1 << 19) { + return Err("N31 is too high") + } + if settings.n32 > (1 << 19) { + return Err("N32 is too high") + } + let r = FrequencySettings { + n1_hs: match settings.n1_hs { + 4 => 0b000, + 5 => 0b001, + 6 => 0b010, + 7 => 0b011, + 8 => 0b100, + 9 => 0b101, + 10 => 0b110, + 11 => 0b111, + _ => return Err("N1_HS has an invalid value") + }, + nc1_ls: settings.nc1_ls - 1, + n2_hs: match settings.n2_hs { + 4 => 0b000, + 5 => 0b001, + 6 => 0b010, + 7 => 0b011, + 8 => 0b100, + 9 => 0b101, + 10 => 0b110, + 11 => 0b111, + _ => return Err("N2_HS has an invalid value") + }, + n2_ls: settings.n2_ls - 1, + n31: settings.n31 - 1, + n32: settings.n32 - 1, + bwsel: settings.bwsel, + crystal_ref: settings.crystal_ref + }; + Ok(r) +} + +fn write(i2c: &mut I2c, reg: u8, val: u8) -> Result<()> { + i2c.start().unwrap(); + if !i2c.write(ADDRESS << 1).unwrap() { + return Err("Si5324 failed to ack write address") + } + if !i2c.write(reg).unwrap() { + return Err("Si5324 failed to ack register") + } + if !i2c.write(val).unwrap() { + return Err("Si5324 failed to ack value") + } + i2c.stop().unwrap(); + 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() { + return Err("Si5324 failed to ack write address") + } + if !i2c.write(reg).unwrap() { + return Err("Si5324 failed to ack register") + } + i2c.write(val).unwrap(); + i2c.stop().unwrap(); + Ok(()) +} + +fn read(i2c: &mut I2c, reg: u8) -> Result { + i2c.start().unwrap(); + if !i2c.write(ADDRESS << 1).unwrap() { + return Err("Si5324 failed to ack write address") + } + if !i2c.write(reg).unwrap() { + return Err("Si5324 failed to ack register") + } + i2c.restart().unwrap(); + if !i2c.write((ADDRESS << 1) | 1).unwrap() { + return Err("Si5324 failed to ack read address") + } + let val = i2c.read(false).unwrap(); + i2c.stop().unwrap(); + Ok(val) +} + +fn rmw(i2c: &mut I2c, reg: u8, f: F) -> Result<()> where + F: Fn(u8) -> u8 { + let value = read(i2c, reg)?; + write(i2c, reg, f(value))?; + Ok(()) +} + +fn ident(i2c: &mut I2c) -> Result { + Ok(((read(i2c, 134)? as u16) << 8) | (read(i2c, 135)? as u16)) +} + +#[cfg(si5324_soft_reset)] +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(()) +} + +fn has_xtal(i2c: &mut I2c) -> Result { + Ok((read(i2c, 129)? & 0x01) == 0) // LOSX_INT=0 +} + +fn has_ckin(i2c: &mut I2c, input: Input) -> Result { + match input { + Input::Ckin1 => Ok((read(i2c, 129)? & 0x02) == 0), // LOS1_INT=0 + Input::Ckin2 => Ok((read(i2c, 129)? & 0x04) == 0), // LOS2_INT=0 + } +} + +fn locked(i2c: &mut I2c) -> Result { + Ok((read(i2c, 130)? & 0x01) == 0) // LOL_INT=0 +} + +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)? { + // Yes, lock can be really slow. + if timer.get_time() > timeout { + return Err("Si5324 lock timeout"); + } + } + info!(" ...locked"); + Ok(()) +} + +fn init(i2c: &mut I2c, timer: &mut GlobalTimer) -> Result<()> { + #[cfg(not(si5324_soft_reset))] + hard_reset(timer); + + #[cfg(feature = "target_kasli_soc")] + { + 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"); + } + + #[cfg(si5324_soft_reset)] + soft_reset(i2c, timer)?; + Ok(()) +} + +pub fn bypass(i2c: &mut I2c, input: Input, timer: &mut GlobalTimer) -> Result<()> { + let cksel_reg = match input { + Input::Ckin1 => 0b00, + Input::Ckin2 => 0b01, + }; + init(i2c, timer)?; + rmw(i2c, 21, |v| v & 0xfe)?; // CKSEL_PIN=0 + rmw(i2c, 3, |v| (v & 0x3f) | (cksel_reg << 6))?; // CKSEL_REG + rmw(i2c, 4, |v| (v & 0x3f) | (0b00 << 6))?; // AUTOSEL_REG=b00 + rmw(i2c, 6, |v| (v & 0xc0) | 0b111111)?; // SFOUT2_REG=b111 SFOUT1_REG=b111 + rmw(i2c, 0, |v| (v & 0xfd) | 0x02)?; // BYPASS_REG=1 + Ok(()) +} + +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, + Input::Ckin2 => 0b01, + }; + + init(i2c, timer)?; + if settings.crystal_ref { + rmw(i2c, 0, |v| v | 0x40)?; // FREE_RUN=1 + } + rmw(i2c, 2, |v| (v & 0x0f) | (s.bwsel << 4))?; + rmw(i2c, 21, |v| v & 0xfe)?; // CKSEL_PIN=0 + rmw(i2c, 3, |v| (v & 0x2f) | (cksel_reg << 6) | 0x10)?; // CKSEL_REG, SQ_ICAL=1 + rmw(i2c, 4, |v| (v & 0x3f) | (0b00 << 6))?; // AUTOSEL_REG=b00 + rmw(i2c, 6, |v| (v & 0xc0) | 0b111111)?; // SFOUT2_REG=b111 SFOUT1_REG=b111 + write(i2c, 25, (s.n1_hs << 5 ) as u8)?; + write(i2c, 31, (s.nc1_ls >> 16) as u8)?; + write(i2c, 32, (s.nc1_ls >> 8 ) as u8)?; + write(i2c, 33, (s.nc1_ls) as u8)?; + write(i2c, 34, (s.nc1_ls >> 16) as u8)?; // write to NC2_LS as well + write(i2c, 35, (s.nc1_ls >> 8 ) as u8)?; + write(i2c, 36, (s.nc1_ls) as u8)?; + write(i2c, 40, (s.n2_hs << 5 ) as u8 | (s.n2_ls >> 16) as u8)?; + write(i2c, 41, (s.n2_ls >> 8 ) as u8)?; + write(i2c, 42, (s.n2_ls) as u8)?; + write(i2c, 43, (s.n31 >> 16) as u8)?; + write(i2c, 44, (s.n31 >> 8) as u8)?; + write(i2c, 45, (s.n31) as u8)?; + write(i2c, 46, (s.n32 >> 16) as u8)?; + write(i2c, 47, (s.n32 >> 8) as u8)?; + write(i2c, 48, (s.n32) as u8)?; + rmw(i2c, 137, |v| v | 0x01)?; // FASTLOCK=1 + rmw(i2c, 136, |v| v | 0x40)?; // ICAL=1 + + if !has_xtal(i2c)? { + return Err("Si5324 misses XA/XB signal"); + } + if !has_ckin(i2c, input)? { + return Err("Si5324 misses clock input signal"); + } + + monitor_lock(i2c, timer)?; + Ok(()) +} + +pub fn select_input(i2c: &mut I2c, input: Input, timer: &mut GlobalTimer) -> Result<()> { + let cksel_reg = match input { + Input::Ckin1 => 0b00, + Input::Ckin2 => 0b01, + }; + rmw(i2c, 3, |v| (v & 0x3f) | (cksel_reg << 6))?; + if !has_ckin(i2c, input)? { + return Err("Si5324 misses clock input signal"); + } + 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