Compare commits

...

10 Commits

Author SHA1 Message Date
4c9c9dfd6c kasli-soc: support shuttler as a peripheral of kasli-soc satellite 2025-03-05 11:59:54 +08:00
e07dad71d5 libksupport: add cxp syscall support
cxp: add read/write 32 bit value
cxp: add xml file download
lib: gate cxp import
api: add cxp syscalls
2025-03-04 14:46:39 +08:00
d0c34671d7 artiq error: add cxp error for syscall 2025-03-04 14:46:39 +08:00
82a1b38a19 runtime main: add cxp grabber support
main: init cxp phys
main: start cxp grabber task
2025-03-04 14:46:39 +08:00
db76dfc209 cxp_grabber fw: add cxp grabber handler
cxp_grabber: add cxp grabber tick task
cxp_grabber: add camera_connected and with_tag helper fns for syscall
libboard_artiq: add cxp_grabber.rs
2025-03-04 14:46:39 +08:00
b0ceac0f3a cxp_camera_setup fw: initalize camera
camera_setup: add setup error and error message
camera_setup: add camera discovery
camera_setup: add camera setup sequence
camera_setup: add placeholder HOST_CONNECTION_ID
libboard_artiq: add cxp_camera_setup.rs
2025-03-04 14:46:39 +08:00
a0673f13a1 cxp_packet fw: add cxp packet handler
packet: add receiving/sending control packet (w/ or w/o tags) handling
packet: add sending test packet
packet: add read/write register interface
packet: add read bytes for xml file download
libboard_artiq: add cxp_packet.rs
2025-03-04 14:46:39 +08:00
6086b867c8 cxp_ctrl fw: add cxp control packet parser
ctrl: add control packet error and error message
ctrl: add CXPCRC32 calculation
ctrl: use byteoder crate to handle endianness
ctrl: add control packer reader and writer
ctrl: add error correction for reading 4x char
libboard_artiq: add cxp_ctrl.rs
2025-03-04 14:46:39 +08:00
17f59e2353 cxp_phys fw: add CXP TRX phys support
phys: add tx & rx setup
tx: add csr to change linerate between 20.83/41.6Mpbs
rx: add GTX and QPLL DRP to change linerate 1.25-12.5Gbps
libboard artiq: add cxp_phys.rs
2025-03-04 14:46:39 +08:00
f080bee029 libboard_artiq cargo: add byteorder 2025-03-04 14:46:39 +08:00
15 changed files with 1400 additions and 13 deletions

3
src/Cargo.lock generated
View File

@ -273,6 +273,7 @@ name = "libboard_artiq"
version = "0.0.0"
dependencies = [
"build_zynq",
"byteorder",
"core_io",
"crc",
"embedded-hal",
@ -546,9 +547,7 @@ name = "satman"
version = "0.0.0"
dependencies = [
"build_zynq",
"byteorder",
"core_io",
"crc",
"cslice",
"embedded-hal",
"io",

View File

@ -432,6 +432,7 @@ class GenericSatellite(SoCCore):
clk_freq = description["rtio_frequency"]
with_wrpll = description["enable_wrpll"]
has_drtio_over_eem = any(peripheral["type"] == "shuttler" for peripheral in description["peripherals"])
self.acpki = acpki
platform = kasli_soc.Platform()
@ -480,6 +481,8 @@ class GenericSatellite(SoCCore):
self.rtio_channels = []
has_grabber = any(peripheral["type"] == "grabber" for peripheral in description["peripherals"])
if has_drtio_over_eem:
self.eem_drtio_channels = []
if has_grabber:
self.grabber_csr_group = []
eem_7series.add_peripherals(self, description["peripherals"], iostandard=eem_iostandard)
@ -494,15 +497,15 @@ class GenericSatellite(SoCCore):
self.submodules.rtio_tsc = rtio.TSC(glbl_fine_ts_width=3)
drtioaux_csr_group = []
drtioaux_memory_group = []
drtiorep_csr_group = []
self.drtioaux_csr_group = []
self.drtioaux_memory_group = []
self.drtiorep_csr_group = []
self.drtio_cri = []
for i in range(len(self.gt_drtio.channels)):
coreaux_name = "drtioaux" + str(i)
memory_name = "drtioaux" + str(i) + "_mem"
drtioaux_csr_group.append(coreaux_name)
drtioaux_memory_group.append(memory_name)
self.drtioaux_csr_group.append(coreaux_name)
self.drtioaux_memory_group.append(memory_name)
cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)})
@ -515,7 +518,7 @@ class GenericSatellite(SoCCore):
self.csr_devices.append("drtiosat")
else:
corerep_name = "drtiorep" + str(i-1)
drtiorep_csr_group.append(corerep_name)
self.drtiorep_csr_group.append(corerep_name)
core = cdr(DRTIORepeater(
self.rtio_tsc, self.gt_drtio.channels[i]))
@ -538,9 +541,9 @@ class GenericSatellite(SoCCore):
self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, mem_size * 2)
self.config["HAS_DRTIO"] = None
self.config["HAS_DRTIO_ROUTING"] = None
self.add_csr_group("drtioaux", drtioaux_csr_group)
self.add_memory_group("drtioaux_mem", drtioaux_memory_group)
self.add_csr_group("drtiorep", drtiorep_csr_group)
if has_drtio_over_eem:
self.add_eem_drtio(self.eem_drtio_channels)
self.add_drtio_cpuif_groups()
if self.acpki:
self.config["KI_IMPL"] = "acp"
@ -625,6 +628,50 @@ class GenericSatellite(SoCCore):
self.comb += [self.virtual_leds.get(i).eq(channel.rx_ready)
for i, channel in enumerate(self.gt_drtio.channels)]
def add_eem_drtio(self, eem_drtio_channels):
# Must be called before constructing CRIInterconnectShared
self.submodules.eem_transceiver = eem_serdes.EEMSerdes(self.platform, eem_drtio_channels)
self.csr_devices.append("eem_transceiver")
self.config["HAS_DRTIO_EEM"] = None
self.config["EEM_DRTIO_COUNT"] = len(eem_drtio_channels)
for i in range(len(self.eem_transceiver.channels)):
channel = i + len(self.gt_drtio.channels)
coreaux_name = "drtioaux" + str(channel)
memory_name = "drtioaux" + str(channel) + "_mem"
self.drtioaux_csr_group.append(coreaux_name)
self.drtioaux_memory_group.append(memory_name)
cdr = ClockDomainsRenamer({"rtio_rx": "sys"})
corerep_name = "drtiorep" + str(channel-1)
self.drtiorep_csr_group.append(corerep_name)
core = cdr(DRTIORepeater(
self.rtio_tsc, self.eem_transceiver.channels[i]))
setattr(self.submodules, corerep_name, core)
self.drtio_cri.append(core.cri)
self.csr_devices.append(corerep_name)
coreaux = cdr(drtio_aux_controller.DRTIOAuxControllerBare(core.link_layer))
setattr(self.submodules, coreaux_name, coreaux)
self.csr_devices.append(coreaux_name)
mem_size = coreaux.get_mem_size()
tx_port = coreaux.get_tx_port()
rx_port = coreaux.get_rx_port()
memory_address = self.axi2csr.register_port(tx_port, mem_size)
# rcv in upper half of the memory, thus added second
self.axi2csr.register_port(rx_port, mem_size)
# and registered in PS interface
# manually, because software refers to rx/tx by halves of entire memory block, not names
self.add_memory_region(memory_name, self.mem_map["csr"] + memory_address, mem_size * 2)
def add_drtio_cpuif_groups(self):
self.add_csr_group("drtiorep", self.drtiorep_csr_group)
self.add_csr_group("drtioaux", self.drtioaux_csr_group)
self.add_memory_group("drtioaux_mem", self.drtioaux_memory_group)
def main():
parser = argparse.ArgumentParser(
description="ARTIQ device binary builder for generic Kasli-SoC systems")

View File

@ -24,6 +24,7 @@ core_io = { git = "https://git.m-labs.hk/M-Labs/rs-core_io.git", rev = "e9d3edf0
embedded-hal = "0.2"
nb = "1.0"
void = { version = "1", default-features = false }
byteorder = { version = "1.3", default-features = false }
io = { path = "../libio", features = ["byteorder"] }
libboard_zynq = { path = "@@ZYNQ_RS@@/libboard_zynq" }

View File

@ -0,0 +1,305 @@
use core::fmt;
use embedded_hal::blocking::delay::DelayMs;
use libboard_zynq::{time::Milliseconds, timer::GlobalTimer};
use log::debug;
use crate::{cxp_ctrl::Error as CtrlErr,
cxp_packet::{read_u32, read_u64, reset_tag, send_test_packet, write_bytes_no_ack, write_u32, write_u64},
cxp_phys::{rx, tx, CXPSpeed},
pl::csr};
// Bootstrap registers address
const REVISION: u32 = 0x0004;
const CONNECTION_RESET: u32 = 0x4000;
const DEVICE_CONNECTION_ID: u32 = 0x4004;
const MASTER_HOST_CONNECTION_ID: u32 = 0x4008;
const STREAM_PACKET_SIZE_MAX: u32 = 0x4010;
const CONNECTION_CFG: u32 = 0x4014;
const CONNECTION_CFG_DEFAULT: u32 = 0x4018;
const TESTMODE: u32 = 0x401C;
const TEST_ERROR_COUNT_SELECTOR: u32 = 0x4020;
const TEST_ERROR_COUNT: u32 = 0x4024;
const TEST_PACKET_COUNT_TX: u32 = 0x4028;
const TEST_PACKET_COUNT_RX: u32 = 0x4030;
const VERSION_SUPPORTED: u32 = 0x4044;
const VERSION_USED: u32 = 0x4048;
// Setup const
const CHANNEL_LEN: u8 = 1;
const HOST_CONNECTION_ID: u32 = 0x00006303; // TODO: rename to CXP grabber sinara number when it comes out
// The MAX_STREAM_PAK_SIZE should be set as large as possible - Section 9.5.2 (CXP-001-2021)
// Since the ROI pipeline just consume all pixel data without buffering, any big number will do.
const MAX_STREAM_PAK_SIZE: u32 = 16384; // 16 KiB
const TX_TEST_CNT: u8 = 10;
// From DS191 (v1.18.1), max CDR time lock is 37*10^6 UI,
// 37*10^6 UI at lowest CXP linerate of 1.25Gbps = 29.6 ms, double it to account for overhead
const MONITOR_TIMEOUT_MS: u64 = 60;
pub enum Error {
CameraNotDetected,
ConnectionLost,
UnstableRX,
UnstableTX,
UnsupportedSpeed(u32),
UnsupportedTopology,
UnsupportedVersion,
CtrlPacketError(CtrlErr),
}
impl From<CtrlErr> for Error {
fn from(value: CtrlErr) -> Error {
Error::CtrlPacketError(value)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::CameraNotDetected => write!(f, "CameraNotDetected"),
&Error::ConnectionLost => write!(f, "ConnectionLost - Channel #0 cannot be detected"),
&Error::UnstableRX => write!(f, "UnstableRX - RX connection test failed"),
&Error::UnstableTX => write!(f, "UnstableTX - TX connection test failed"),
&Error::UnsupportedSpeed(linerate_code) => write!(
f,
"UnsupportedSpeed - {:#X} linerate code is not supported",
linerate_code
),
&Error::UnsupportedTopology => {
write!(
f,
"UnsupportedTopology - Channel #0 should be connected to the master channel"
)
}
&Error::UnsupportedVersion => write!(
f,
"UnsupportedVersion - Cannot find a compatible protocol version between the cxp grabber & camera"
),
&Error::CtrlPacketError(ref err) => write!(f, "{}", err),
}
}
}
pub fn master_channel_ready() -> bool {
unsafe { csr::cxp_grabber::core_rx_ready_read() == 1 }
}
fn monitor_channel_status_timeout(timer: GlobalTimer) -> Result<(), Error> {
let limit = timer.get_time() + Milliseconds(MONITOR_TIMEOUT_MS);
while timer.get_time() < limit {
if master_channel_ready() {
return Ok(());
}
}
Err(Error::ConnectionLost)
}
pub fn discover_camera(mut timer: GlobalTimer) -> Result<(), Error> {
// Section 7.6 (CXP-001-2021)
// 1.25Gbps (CXP_1) and 3.125Gbps (CXP_3) are the discovery rate
// both linerate need to be checked as camera only support ONE of discovery rates
for speed in [CXPSpeed::CXP1, CXPSpeed::CXP3].iter() {
// Section 12.1.2 (CXP-001-2021)
// set tx linerate -> send ConnectionReset -> wait 200ms -> set rx linerate -> monitor connection status with a timeout
tx::change_linerate(*speed);
write_bytes_no_ack(CONNECTION_RESET, &1_u32.to_be_bytes(), false)?;
timer.delay_ms(200);
rx::change_linerate(*speed);
if monitor_channel_status_timeout(timer).is_ok() {
debug!("camera detected at linerate {:}", speed);
return Ok(());
}
}
Err(Error::CameraNotDetected)
}
fn check_master_channel() -> Result<(), Error> {
if read_u32(DEVICE_CONNECTION_ID, false)? == 0 {
Ok(())
} else {
Err(Error::UnsupportedTopology)
}
}
fn disable_excess_channels(timer: GlobalTimer) -> Result<(), Error> {
let current_cfg = read_u32(CONNECTION_CFG, false)?;
let active_camera_chs = current_cfg >> 16;
// After camera receive ConnectionReset, only the master connection should be active while
// the extension connections shall not be active - Section 12.3.33 (CXP-001-2021)
// In case some camera didn't follow the spec properly (e.g. Basler boA2448-250cm),
// the grabber need to manually disable the excess channels
if active_camera_chs > CHANNEL_LEN as u32 {
debug!(
"only {} channel(s) is available on cxp grabber, disabling excess channels on camera",
CHANNEL_LEN
);
// disable excess channels and preserve the discovery linerate
write_u32(CONNECTION_CFG, current_cfg & 0xFFFF | (CHANNEL_LEN as u32) << 16, false)?;
// check if the master channel is down after the cfg change
monitor_channel_status_timeout(timer)
} else {
Ok(())
}
}
fn set_host_connection_id() -> Result<(), Error> {
debug!("set host connection id to = {:#X}", HOST_CONNECTION_ID);
write_u32(MASTER_HOST_CONNECTION_ID, HOST_CONNECTION_ID, false)?;
Ok(())
}
fn negotiate_cxp_version() -> Result<bool, Error> {
let rev = read_u32(REVISION, false)?;
let mut major_rev: u32 = rev >> 16;
let mut minor_rev: u32 = rev & 0xFF;
debug!("camera's CoaXPress revision is {}.{}", major_rev, minor_rev);
// Section 12.1.4 (CXP-001-2021)
// For CXP 2.0 and onward, Host need to check the VersionSupported register to determine
// the highest common version that supported by both device & host
if major_rev >= 2 {
let reg = read_u32(VERSION_SUPPORTED, false)?;
// grabber support CXP 2.1, 2.0 and 1.1 only
if ((reg >> 3) & 1) == 1 {
major_rev = 2;
minor_rev = 1;
} else if ((reg >> 2) & 1) == 1 {
major_rev = 2;
minor_rev = 0;
} else if ((reg >> 1) & 1) == 1 {
major_rev = 1;
minor_rev = 1;
} else {
return Err(Error::UnsupportedVersion);
}
write_u32(VERSION_USED, major_rev << 16 | minor_rev, false)?;
}
debug!(
"both camera and cxp grabber support CoaXPress {}.{}, switch to CoaXPress {}.{} protocol now",
major_rev, minor_rev, major_rev, minor_rev
);
Ok(major_rev >= 2)
}
fn negotiate_pak_max_size(with_tag: bool) -> Result<(), Error> {
write_u32(STREAM_PACKET_SIZE_MAX, MAX_STREAM_PAK_SIZE, with_tag)?;
Ok(())
}
fn decode_cxp_speed(linerate_code: u32) -> Option<CXPSpeed> {
match linerate_code {
0x28 => Some(CXPSpeed::CXP1),
0x30 => Some(CXPSpeed::CXP2),
0x38 => Some(CXPSpeed::CXP3),
0x40 => Some(CXPSpeed::CXP5),
0x48 => Some(CXPSpeed::CXP6),
0x50 => Some(CXPSpeed::CXP10),
0x58 => Some(CXPSpeed::CXP12),
_ => None,
}
}
fn set_operation_linerate(with_tag: bool, timer: GlobalTimer) -> Result<(), Error> {
let recommended_linerate_code = read_u32(CONNECTION_CFG_DEFAULT, with_tag)? & 0xFFFF;
if let Some(speed) = decode_cxp_speed(recommended_linerate_code) {
debug!("changing linerate to {}", speed);
// preserve the number of active channels
let current_cfg = read_u32(CONNECTION_CFG, with_tag)?;
write_u32(
CONNECTION_CFG,
current_cfg & 0xFFFF0000 | recommended_linerate_code,
with_tag,
)?;
tx::change_linerate(speed);
rx::change_linerate(speed);
monitor_channel_status_timeout(timer)
} else {
Err(Error::UnsupportedSpeed(recommended_linerate_code))
}
}
fn test_counter_reset(with_tag: bool) -> Result<(), Error> {
unsafe { csr::cxp_grabber::core_rx_test_counts_reset_write(1) };
write_u32(TEST_ERROR_COUNT_SELECTOR, 0, with_tag)?;
write_u32(TEST_ERROR_COUNT, 0, with_tag)?;
write_u64(TEST_PACKET_COUNT_TX, 0, with_tag)?;
write_u64(TEST_PACKET_COUNT_RX, 0, with_tag)?;
Ok(())
}
fn verify_test_result(with_tag: bool) -> Result<(), Error> {
write_u32(TEST_ERROR_COUNT_SELECTOR, 0, with_tag)?;
// Section 9.9.3 (CXP-001-2021)
// verify grabber -> camera connection test result
if read_u64(TEST_PACKET_COUNT_RX, with_tag)? != TX_TEST_CNT as u64 {
return Err(Error::UnstableTX);
};
if read_u32(TEST_ERROR_COUNT, with_tag)? > 0 {
return Err(Error::UnstableTX);
};
// Section 9.9.4 (CXP-001-2021)
// verify camera -> grabber connection test result
let camera_test_pak_cnt = read_u64(TEST_PACKET_COUNT_TX, true)?;
unsafe {
if csr::cxp_grabber::core_rx_test_packet_counter_read() != camera_test_pak_cnt as u16 {
return Err(Error::UnstableRX);
};
if csr::cxp_grabber::core_rx_test_error_counter_read() > 0 {
return Err(Error::UnstableRX);
};
};
debug!("channel #0 passed connection test");
Ok(())
}
fn test_channel_stability(with_tag: bool, mut timer: GlobalTimer) -> Result<(), Error> {
test_counter_reset(with_tag)?;
// cxp grabber -> camera connection test
for _ in 0..TX_TEST_CNT {
send_test_packet()?;
// sending the whole test sequence @ 20.833Mbps will take a minimum of 1.972ms
// and leave some room to send IDLE word
timer.delay_ms(2);
}
// camera -> grabber connection test
// enabling the TESTMODE on master channel will send test packets on all channels
// and ctrl packet write overhead is used as a delay
write_u32(TESTMODE, 1, with_tag)?;
write_u32(TESTMODE, 0, with_tag)?;
verify_test_result(with_tag)?;
Ok(())
}
pub fn camera_setup(timer: GlobalTimer) -> Result<bool, Error> {
reset_tag();
check_master_channel()?;
disable_excess_channels(timer)?;
set_host_connection_id()?;
let with_tag = negotiate_cxp_version()?;
negotiate_pak_max_size(with_tag)?;
set_operation_linerate(with_tag, timer)?;
test_channel_stability(with_tag, timer)?;
Ok(with_tag)
}

View File

@ -0,0 +1,338 @@
use core::fmt;
use byteorder::{ByteOrder, NetworkEndian};
use core_io::{Error as IoError, Read, Write};
use crc::crc32::checksum_ieee;
use io::Cursor;
pub const CTRL_PACKET_MAXSIZE: usize = 128; // for compatibility with version1.x compliant Devices - Section 12.1.6 (CXP-001-2021)
pub const DATA_MAXSIZE: usize =
CTRL_PACKET_MAXSIZE - /*packet start KCodes, data packet types, CMD, Tag, Addr, CRC, packet end KCode*/4*7;
pub enum Error {
CorruptedPacket,
CtrlAckError(u8),
Io(IoError),
LengthOutOfRange(u32),
TagMismatch,
TimedOut,
UnexpectedReply,
UnknownPacket(u8),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::CorruptedPacket => write!(f, "CorruptedPacket - Received packet fail CRC test"),
&Error::CtrlAckError(ref ack_code) => match ack_code {
0x40 => write!(f, "CtrlAckError - Invalid Address"),
0x41 => write!(f, "CtrlAckError - Invalid data for the address"),
0x42 => write!(f, "CtrlAckError - Invalid operation code"),
0x43 => write!(f, "CtrlAckError - Write attempted to a read-only address"),
0x44 => write!(f, "CtrlAckError - Read attempted from a write-only address"),
0x45 => write!(f, "CtrlAckError - Size field too large, exceed packet size limit"),
0x46 => write!(f, "CtrlAckError - Message size is inconsistent with size field"),
0x47 => write!(f, "CtrlAckError - Malformed packet"),
0x80 => write!(f, "CtrlAckError - Failed CRC test in last received command"),
_ => write!(f, "CtrlAckError - Unknown ack code {:#X}", ack_code),
},
&Error::Io(ref err) => write!(f, "IoError - {:?}", err),
&Error::LengthOutOfRange(length) => write!(
f,
"LengthOutOfRange - Message length {} is not between 1 and {}",
length, DATA_MAXSIZE
),
&Error::TagMismatch => write!(f, "TagMismatch - Received tag is different from the transmitted tag"),
&Error::TimedOut => write!(f, "MessageTimedOut"),
&Error::UnexpectedReply => write!(f, "UnexpectedReply"),
&Error::UnknownPacket(packet_type) => {
write!(f, "UnknownPacket - Unknown packet type id {:#X} ", packet_type)
}
}
}
}
impl From<IoError> for Error {
fn from(value: IoError) -> Error {
Error::Io(value)
}
}
fn get_cxp_crc(bytes: &[u8]) -> u32 {
// Section 9.2.2.2 (CXP-001-2021)
// Only Control packet need CRC32 appended in the end of the packet
// CoaXpress use the polynomial of IEEE-802.3 (Ethernet) CRC but the checksum calculation is different
(!checksum_ieee(bytes)).swap_bytes()
}
trait CxpRead {
fn read_u8(&mut self) -> Result<u8, Error>;
fn read_u16(&mut self) -> Result<u16, Error>;
fn read_u32(&mut self) -> Result<u32, Error>;
fn read_u64(&mut self) -> Result<u64, Error>;
fn read_exact_4x(&mut self, buf: &mut [u8]) -> Result<(), Error>;
fn read_4x_u8(&mut self) -> Result<u8, Error>;
fn read_4x_u16(&mut self) -> Result<u16, Error>;
fn read_4x_u32(&mut self) -> Result<u32, Error>;
}
impl<Cursor: Read> CxpRead for Cursor {
fn read_u8(&mut self) -> Result<u8, Error> {
let mut bytes = [0; 1];
self.read_exact(&mut bytes)?;
Ok(bytes[0])
}
fn read_u16(&mut self) -> Result<u16, Error> {
let mut bytes = [0; 2];
self.read_exact(&mut bytes)?;
Ok(NetworkEndian::read_u16(&bytes))
}
fn read_u32(&mut self) -> Result<u32, Error> {
let mut bytes = [0; 4];
self.read_exact(&mut bytes)?;
Ok(NetworkEndian::read_u32(&bytes))
}
fn read_u64(&mut self) -> Result<u64, Error> {
let mut bytes = [0; 8];
self.read_exact(&mut bytes)?;
Ok(NetworkEndian::read_u64(&bytes))
}
fn read_exact_4x(&mut self, buf: &mut [u8]) -> Result<(), Error> {
for byte in buf {
// Section 9.2.2.1 (CXP-001-2021)
// decoder should immune to single bit errors when handling 4x duplicated characters
let a = self.read_u8()?;
let b = self.read_u8()?;
let c = self.read_u8()?;
let d = self.read_u8()?;
// vote and return majority
*byte = a & b & c | a & b & d | a & c & d | b & c & d;
}
Ok(())
}
fn read_4x_u8(&mut self) -> Result<u8, Error> {
let mut bytes = [0; 1];
self.read_exact_4x(&mut bytes)?;
Ok(bytes[0])
}
fn read_4x_u16(&mut self) -> Result<u16, Error> {
let mut bytes = [0; 2];
self.read_exact_4x(&mut bytes)?;
Ok(NetworkEndian::read_u16(&bytes))
}
fn read_4x_u32(&mut self) -> Result<u32, Error> {
let mut bytes = [0; 4];
self.read_exact_4x(&mut bytes)?;
Ok(NetworkEndian::read_u32(&bytes))
}
}
#[derive(Debug)]
pub enum RXCTRLPacket {
CtrlReply {
tag: Option<u8>,
length: u32,
data: [u8; DATA_MAXSIZE],
},
CtrlDelay {
tag: Option<u8>,
time: u32,
},
CtrlAck {
tag: Option<u8>,
},
}
impl RXCTRLPacket {
pub fn read_from(reader: &mut Cursor<&mut [u8]>) -> Result<Self, Error> {
match reader.read_4x_u8()? {
0x03 => RXCTRLPacket::get_ctrl_packet(reader, false),
0x06 => RXCTRLPacket::get_ctrl_packet(reader, true),
ty => Err(Error::UnknownPacket(ty)),
}
}
fn get_ctrl_packet(reader: &mut Cursor<&mut [u8]>, with_tag: bool) -> Result<Self, Error> {
let mut tag: Option<u8> = None;
if with_tag {
tag = Some(reader.read_4x_u8()?);
}
let ackcode = reader.read_4x_u8()?;
match ackcode {
0x00 | 0x04 => {
let length = reader.read_u32()?;
let mut data: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
reader.read(&mut data[0..length as usize])?;
// Section 9.6.3 (CXP-001-2021)
// when length is not multiple of 4, dummy bits are padded to align to the word boundary
// set position to next word boundary for CRC calculation
let padding = (4 - (reader.position() % 4)) % 4;
reader.set_position(reader.position() + padding);
// Section 9.6.3 (CXP-001-2021)
// only bytes after the first 4 are used in calculating the checksum
let checksum = get_cxp_crc(&reader.get_ref()[4..reader.position()]);
if reader.read_u32()? != checksum {
return Err(Error::CorruptedPacket);
}
if ackcode == 0x00 {
return Ok(RXCTRLPacket::CtrlReply { tag, length, data });
} else {
return Ok(RXCTRLPacket::CtrlDelay {
tag,
time: NetworkEndian::read_u32(&data[..4]),
});
}
}
0x01 => return Ok(RXCTRLPacket::CtrlAck { tag }),
_ => return Err(Error::CtrlAckError(ackcode)),
}
}
}
trait CxpWrite {
fn write_u8(&mut self, value: u8) -> Result<(), Error>;
fn write_u32(&mut self, value: u32) -> Result<(), Error>;
fn write_all_4x(&mut self, buf: &[u8]) -> Result<(), Error>;
fn write_4x_u8(&mut self, value: u8) -> Result<(), Error>;
fn write_4x_u16(&mut self, value: u16) -> Result<(), Error>;
fn write_4x_u32(&mut self, value: u32) -> Result<(), Error>;
}
impl<Cursor: Write> CxpWrite for Cursor {
fn write_u8(&mut self, value: u8) -> Result<(), Error> {
self.write_all(&[value])?;
Ok(())
}
fn write_u32(&mut self, value: u32) -> Result<(), Error> {
let mut bytes = [0; 4];
NetworkEndian::write_u32(&mut bytes, value);
self.write_all(&bytes)?;
Ok(())
}
fn write_all_4x(&mut self, buf: &[u8]) -> Result<(), Error> {
for byte in buf {
self.write_all(&[*byte; 4])?;
}
Ok(())
}
fn write_4x_u8(&mut self, value: u8) -> Result<(), Error> {
self.write_all_4x(&[value])
}
fn write_4x_u16(&mut self, value: u16) -> Result<(), Error> {
let mut bytes = [0; 2];
NetworkEndian::write_u16(&mut bytes, value);
self.write_all_4x(&bytes)
}
fn write_4x_u32(&mut self, value: u32) -> Result<(), Error> {
let mut bytes = [0; 4];
NetworkEndian::write_u32(&mut bytes, value);
self.write_all_4x(&bytes)
}
}
#[derive(Debug)]
pub enum TXCTRLPacket {
CtrlRead {
tag: Option<u8>,
addr: u32,
length: u32,
},
CtrlWrite {
tag: Option<u8>,
addr: u32,
length: u32,
data: [u8; DATA_MAXSIZE],
},
}
impl TXCTRLPacket {
pub fn write_to(&self, writer: &mut Cursor<&mut [u8]>) -> Result<(), Error> {
match *self {
TXCTRLPacket::CtrlRead { tag, addr, length } => {
match tag {
Some(t) => {
writer.write_4x_u8(0x05)?;
writer.write_4x_u8(t)?;
}
None => {
writer.write_4x_u8(0x02)?;
}
}
let mut bytes = [0; 3];
NetworkEndian::write_u24(&mut bytes, length);
writer.write_all(&[0x00, bytes[0], bytes[1], bytes[2]])?;
writer.write_u32(addr)?;
// Section 9.6.2 (CXP-001-2021)
// only bytes after the first 4 are used in calculating the checksum
let checksum = get_cxp_crc(&writer.get_ref()[4..writer.position()]);
writer.write_u32(checksum)?;
}
TXCTRLPacket::CtrlWrite {
tag,
addr,
length,
data,
} => {
match tag {
Some(t) => {
writer.write_4x_u8(0x05)?;
writer.write_4x_u8(t)?;
}
None => {
writer.write_4x_u8(0x02)?;
}
}
let mut bytes = [0; 3];
NetworkEndian::write_u24(&mut bytes, length);
writer.write_all(&[0x01, bytes[0], bytes[1], bytes[2]])?;
writer.write_u32(addr)?;
writer.write_all(&data[0..length as usize])?;
// Section 9.6.2 (CXP-001-2021)
// when length is not multiple of 4, dummy bites are padded to align to the word boundary
let padding = (4 - (writer.position() % 4)) % 4;
for _ in 0..padding {
writer.write_u8(0)?;
}
// Section 9.6.2 (CXP-001-2021)
// only bytes after the first 4 are used in calculating the checksum
let checksum = get_cxp_crc(&writer.get_ref()[4..writer.position()]);
writer.write_u32(checksum)?;
}
}
Ok(())
}
}

View File

@ -0,0 +1,90 @@
use libboard_zynq::timer::GlobalTimer;
use libcortex_a9::mutex::Mutex;
use log::{error, info};
use crate::{cxp_camera_setup::{camera_setup, discover_camera, master_channel_ready},
pl::csr};
#[derive(Clone, Copy, Debug, PartialEq)]
enum State {
Connected,
Detected,
Disconnected,
}
// Mutex as they are needed by core1 cxp api calls
static mut STATE: Mutex<State> = Mutex::new(State::Disconnected);
static mut WITH_TAG: Mutex<bool> = Mutex::new(false);
pub fn camera_connected() -> bool {
unsafe { *STATE.lock() == State::Connected }
}
pub fn with_tag() -> bool {
unsafe { *WITH_TAG.lock() }
}
pub fn tick(timer: GlobalTimer) {
let mut state_guard = unsafe { STATE.lock() };
let mut with_tag_guard = unsafe { WITH_TAG.lock() };
*state_guard = match *state_guard {
State::Disconnected => match discover_camera(timer) {
Ok(_) => {
info!("camera detected, setting up camera...");
State::Detected
}
Err(_) => State::Disconnected,
},
State::Detected => match camera_setup(timer) {
Ok(with_tag) => {
info!("camera setup complete");
*with_tag_guard = with_tag;
State::Connected
}
Err(e) => {
error!("camera setup failure: {}", e);
*with_tag_guard = false;
State::Disconnected
}
},
State::Connected => {
if master_channel_ready() {
unsafe {
if csr::cxp_grabber::stream_decoder_crc_error_read() == 1 {
error!("frame packet has CRC error");
csr::cxp_grabber::stream_decoder_crc_error_write(1);
};
if csr::cxp_grabber::stream_decoder_stream_type_error_read() == 1 {
error!("Non CoaXPress stream type detected, the CXP grabber doesn't support GenDC stream type");
csr::cxp_grabber::stream_decoder_stream_type_error_write(1);
};
if csr::cxp_grabber::core_rx_trigger_ack_read() == 1 {
info!("received CXP linktrigger ack");
csr::cxp_grabber::core_rx_trigger_ack_write(1);
};
if csr::cxp_grabber::stream_decoder_new_frame_read() == 1 {
let width = csr::cxp_grabber::stream_decoder_x_size_read();
let height = csr::cxp_grabber::stream_decoder_y_size_read();
match csr::cxp_grabber::stream_decoder_pixel_format_code_read() {
0x0101 => info!("received frame: {}x{} with MONO8 format", width, height),
0x0102 => info!("received frame: {}x{} with MONO10 format", width, height),
0x0103 => info!("received frame: {}x{} with MONO12 format", width, height),
0x0104 => info!("received frame: {}x{} with MONO14 format", width, height),
0x0105 => info!("received frame: {}x{} with MONO16 format", width, height),
_ => info!("received frame: {}x{} with Unsupported pixel format", width, height),
};
csr::cxp_grabber::stream_decoder_new_frame_write(1);
};
}
State::Connected
} else {
*with_tag_guard = false;
info!("camera disconnected");
State::Disconnected
}
}
};
}

View File

@ -0,0 +1,192 @@
use core::slice;
use byteorder::{ByteOrder, NetworkEndian};
use io::Cursor;
use libboard_zynq::{time::Milliseconds, timer::GlobalTimer};
use crate::{cxp_ctrl::{Error, RXCTRLPacket, TXCTRLPacket, CTRL_PACKET_MAXSIZE, DATA_MAXSIZE},
mem::mem,
pl::csr};
const TRANSMISSION_TIMEOUT: u64 = 200;
// Section 9.6.1.2 (CXP-001-2021)
// CTRL packet need to be tagged for CXP 2.0 or greater
static mut TAG: u8 = 0;
pub fn reset_tag() {
unsafe { TAG = 0 };
}
fn increment_tag() {
unsafe { TAG = TAG.wrapping_add(1) };
}
fn check_tag(tag: Option<u8>) -> Result<(), Error> {
unsafe {
if tag.is_some() && tag != Some(TAG) {
Err(Error::TagMismatch)
} else {
Ok(())
}
}
}
fn receive_ctrl_packet() -> Result<Option<RXCTRLPacket>, Error> {
if unsafe { csr::cxp_grabber::core_rx_pending_packet_read() == 1 } {
unsafe {
let read_buffer_ptr = csr::cxp_grabber::core_rx_read_ptr_read() as usize;
let ptr = (mem::CXP_MEM_BASE + mem::CXP_MEM_SIZE / 2 + read_buffer_ptr * CTRL_PACKET_MAXSIZE) as *mut u32;
let mut reader = Cursor::new(slice::from_raw_parts_mut(ptr as *mut u8, CTRL_PACKET_MAXSIZE));
let packet = RXCTRLPacket::read_from(&mut reader);
csr::cxp_grabber::core_rx_pending_packet_write(1);
Ok(Some(packet?))
}
} else {
Ok(None)
}
}
fn receive_ctrl_packet_timeout(timeout_ms: u64) -> Result<RXCTRLPacket, Error> {
// assume timer was initialized successfully
let timer = unsafe { GlobalTimer::get() };
let limit = timer.get_time() + Milliseconds(timeout_ms);
while timer.get_time() < limit {
match receive_ctrl_packet()? {
None => (),
Some(packet) => return Ok(packet),
}
}
Err(Error::TimedOut)
}
fn send_ctrl_packet(packet: &TXCTRLPacket) -> Result<(), Error> {
unsafe {
while csr::cxp_grabber::core_tx_writer_busy_read() == 1 {}
let ptr = mem::CXP_MEM_BASE as *mut u32;
let mut writer = Cursor::new(slice::from_raw_parts_mut(ptr as *mut u8, CTRL_PACKET_MAXSIZE));
packet.write_to(&mut writer)?;
csr::cxp_grabber::core_tx_writer_word_len_write((writer.position() / 4) as u8);
csr::cxp_grabber::core_tx_writer_stb_write(1);
}
Ok(())
}
pub fn send_test_packet() -> Result<(), Error> {
unsafe {
while csr::cxp_grabber::core_tx_writer_busy_read() == 1 {}
csr::cxp_grabber::core_tx_writer_stb_testseq_write(1);
}
Ok(())
}
fn get_ctrl_ack(timeout: u64) -> Result<(), Error> {
match receive_ctrl_packet_timeout(timeout) {
Ok(RXCTRLPacket::CtrlAck { tag }) => {
check_tag(tag)?;
Ok(())
}
Ok(RXCTRLPacket::CtrlDelay { tag, time }) => {
check_tag(tag)?;
get_ctrl_ack(time as u64)
}
Ok(_) => Err(Error::UnexpectedReply),
Err(e) => Err(e),
}
}
fn get_ctrl_reply(timeout: u64, expected_length: u32) -> Result<[u8; DATA_MAXSIZE], Error> {
match receive_ctrl_packet_timeout(timeout) {
Ok(RXCTRLPacket::CtrlReply { tag, length, data }) => {
check_tag(tag)?;
if length != expected_length {
return Err(Error::UnexpectedReply);
};
Ok(data)
}
Ok(RXCTRLPacket::CtrlDelay { tag, time }) => {
check_tag(tag)?;
get_ctrl_reply(time as u64, expected_length)
}
Ok(_) => Err(Error::UnexpectedReply),
Err(e) => Err(e),
}
}
fn check_length(length: u32) -> Result<(), Error> {
if length > DATA_MAXSIZE as u32 || length == 0 {
Err(Error::LengthOutOfRange(length))
} else {
Ok(())
}
}
pub fn write_bytes_no_ack(addr: u32, val: &[u8], with_tag: bool) -> Result<(), Error> {
let length = val.len() as u32;
check_length(length)?;
let mut data: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
data[..length as usize].clone_from_slice(val);
let tag: Option<u8> = if with_tag { Some(unsafe { TAG }) } else { None };
send_ctrl_packet(&TXCTRLPacket::CtrlWrite {
tag,
addr,
length,
data,
})
}
pub fn write_bytes(addr: u32, val: &[u8], with_tag: bool) -> Result<(), Error> {
write_bytes_no_ack(addr, val, with_tag)?;
get_ctrl_ack(TRANSMISSION_TIMEOUT)?;
if with_tag {
increment_tag();
};
Ok(())
}
pub fn write_u32(addr: u32, val: u32, with_tag: bool) -> Result<(), Error> {
write_bytes(addr, &val.to_be_bytes(), with_tag)
}
pub fn write_u64(addr: u32, val: u64, with_tag: bool) -> Result<(), Error> {
write_bytes(addr, &val.to_be_bytes(), with_tag)
}
pub fn read_bytes(addr: u32, bytes: &mut [u8], with_tag: bool) -> Result<(), Error> {
let length = bytes.len() as u32;
check_length(length)?;
let tag: Option<u8> = if with_tag { Some(unsafe { TAG }) } else { None };
send_ctrl_packet(&TXCTRLPacket::CtrlRead { tag, addr, length })?;
let data = get_ctrl_reply(TRANSMISSION_TIMEOUT, length)?;
bytes.clone_from_slice(&data[..length as usize]);
if with_tag {
increment_tag();
};
Ok(())
}
pub fn read_u32(addr: u32, with_tag: bool) -> Result<u32, Error> {
let mut bytes: [u8; 4] = [0; 4];
read_bytes(addr, &mut bytes, with_tag)?;
let val = NetworkEndian::read_u32(&bytes);
Ok(val)
}
pub fn read_u64(addr: u32, with_tag: bool) -> Result<u64, Error> {
let mut bytes: [u8; 8] = [0; 8];
read_bytes(addr, &mut bytes, with_tag)?;
let val = NetworkEndian::read_u64(&bytes);
Ok(val)
}

View File

@ -0,0 +1,193 @@
use core::fmt;
use crate::pl::csr;
#[derive(Clone, Copy)]
pub enum CXPSpeed {
CXP1,
CXP2,
CXP3,
CXP5,
CXP6,
CXP10,
CXP12,
}
impl fmt::Display for CXPSpeed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&CXPSpeed::CXP1 => write!(f, "1.25 Gbps"),
&CXPSpeed::CXP2 => write!(f, "2.5 Gbps"),
&CXPSpeed::CXP3 => write!(f, "3.125 Gbps"),
&CXPSpeed::CXP5 => write!(f, "5 Gbps"),
&CXPSpeed::CXP6 => write!(f, "6.25 Gbps"),
&CXPSpeed::CXP10 => write!(f, "10 Gbps"),
&CXPSpeed::CXP12 => write!(f, "12.5 Gbps"),
}
}
}
pub fn setup() {
let init_speed = CXPSpeed::CXP1;
tx::setup();
tx::change_linerate(init_speed);
rx::setup();
rx::change_linerate(init_speed);
}
pub mod tx {
use super::*;
pub fn setup() {
unsafe {
csr::cxp_grabber::phy_tx_enable_write(1);
}
}
pub fn change_linerate(speed: CXPSpeed) {
unsafe {
match speed {
CXPSpeed::CXP1 | CXPSpeed::CXP2 | CXPSpeed::CXP3 | CXPSpeed::CXP5 | CXPSpeed::CXP6 => {
csr::cxp_grabber::phy_tx_bitrate2x_enable_write(0);
}
CXPSpeed::CXP10 | CXPSpeed::CXP12 => {
csr::cxp_grabber::phy_tx_bitrate2x_enable_write(1);
}
};
csr::cxp_grabber::phy_tx_clk_reset_write(1);
}
}
}
pub mod rx {
use super::*;
pub fn setup() {
unsafe {
csr::cxp_grabber::phy_rx_gtx_refclk_stable_write(1);
}
}
pub fn change_linerate(speed: CXPSpeed) {
change_qpll_fb_divider(speed);
change_gtx_divider(speed);
change_cdr_cfg(speed);
unsafe {
csr::cxp_grabber::phy_rx_qpll_reset_write(1);
while csr::cxp_grabber::phy_rx_qpll_locked_read() != 1 {}
// Changing RXOUT_DIV via DRP requires a manual reset
// https://adaptivesupport.amd.com/s/question/0D52E00006hplwnSAA/re-gtx-line-rate-change
csr::cxp_grabber::phy_rx_gtx_restart_write(1);
}
}
fn change_qpll_fb_divider(speed: CXPSpeed) {
let qpll_div_reg = match speed {
CXPSpeed::CXP1 | CXPSpeed::CXP2 | CXPSpeed::CXP5 | CXPSpeed::CXP10 => 0x0120, // FB_Divider = 80, QPLL VCO @ 10GHz
CXPSpeed::CXP3 | CXPSpeed::CXP6 | CXPSpeed::CXP12 => 0x0170, // FB_Divider = 100, QPLL VCO @ 12.5GHz
};
qpll_write(0x36, qpll_div_reg);
}
fn change_gtx_divider(speed: CXPSpeed) {
let div_reg = match speed {
CXPSpeed::CXP1 => 0x33, // RXOUT_DIV = 8
CXPSpeed::CXP2 | CXPSpeed::CXP3 => 0x22, // RXOUT_DIV = 4
CXPSpeed::CXP5 | CXPSpeed::CXP6 => 0x11, // RXOUT_DIV = 2
CXPSpeed::CXP10 | CXPSpeed::CXP12 => 0x00, // RXOUT_DIV = 1
};
gtx_write(0x88, div_reg);
}
fn change_cdr_cfg(speed: CXPSpeed) {
struct CdrConfig {
pub cfg_reg0: u16, // addr = 0xA8
pub cfg_reg1: u16, // addr = 0xA9
pub cfg_reg2: u16, // addr = 0xAA
pub cfg_reg3: u16, // addr = 0xAB
pub cfg_reg4: u16, // addr = 0xAC
}
let cdr_cfg = match speed {
// when RXOUT_DIV = 8
CXPSpeed::CXP1 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1008,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV = 4
CXPSpeed::CXP2 | CXPSpeed::CXP5 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1010,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV= 2
CXPSpeed::CXP3 | CXPSpeed::CXP6 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1020,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV= 1
CXPSpeed::CXP10 | CXPSpeed::CXP12 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1040,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x000B,
},
};
gtx_write(0x0A8, cdr_cfg.cfg_reg0);
gtx_write(0x0A9, cdr_cfg.cfg_reg1);
gtx_write(0x0AA, cdr_cfg.cfg_reg2);
gtx_write(0x0AB, cdr_cfg.cfg_reg3);
gtx_write(0x0AC, cdr_cfg.cfg_reg4);
}
#[allow(dead_code)]
fn gtx_read(address: u16) -> u16 {
unsafe {
csr::cxp_grabber::phy_rx_gtx_daddr_write(address);
csr::cxp_grabber::phy_rx_gtx_dread_write(1);
while csr::cxp_grabber::phy_rx_gtx_dready_read() != 1 {}
csr::cxp_grabber::phy_rx_gtx_dout_read()
}
}
fn gtx_write(address: u16, value: u16) {
unsafe {
csr::cxp_grabber::phy_rx_gtx_daddr_write(address);
csr::cxp_grabber::phy_rx_gtx_din_write(value);
csr::cxp_grabber::phy_rx_gtx_din_stb_write(1);
while csr::cxp_grabber::phy_rx_gtx_dready_read() != 1 {}
}
}
#[allow(dead_code)]
fn qpll_read(address: u8) -> u16 {
unsafe {
csr::cxp_grabber::phy_rx_qpll_daddr_write(address);
csr::cxp_grabber::phy_rx_qpll_dread_write(1);
while csr::cxp_grabber::phy_rx_qpll_dready_read() != 1 {}
csr::cxp_grabber::phy_rx_qpll_dout_read()
}
}
fn qpll_write(address: u8, value: u16) {
unsafe {
csr::cxp_grabber::phy_rx_qpll_daddr_write(address);
csr::cxp_grabber::phy_rx_qpll_din_write(value);
csr::cxp_grabber::phy_rx_qpll_din_stb_write(1);
while csr::cxp_grabber::phy_rx_qpll_dready_read() != 1 {}
}
}
}

View File

@ -24,7 +24,7 @@ pub mod fiq;
#[cfg(feature = "target_kasli_soc")]
pub mod io_expander;
pub mod logger;
#[cfg(has_drtio)]
#[cfg(any(has_drtio, has_cxp_grabber))]
#[rustfmt::skip]
#[path = "../../../build/mem.rs"]
pub mod mem;
@ -41,6 +41,17 @@ pub mod si5324;
pub mod si549;
use core::{cmp, str};
#[cfg(has_cxp_grabber)]
pub mod cxp_camera_setup;
#[cfg(has_cxp_grabber)]
pub mod cxp_ctrl;
#[cfg(has_cxp_grabber)]
pub mod cxp_grabber;
#[cfg(has_cxp_grabber)]
pub mod cxp_packet;
#[cfg(has_cxp_grabber)]
pub mod cxp_phys;
pub fn identifier_read(buf: &mut [u8]) -> &str {
unsafe {
pl::csr::identifier::address_write(0);

151
src/libksupport/src/cxp.rs Normal file
View File

@ -0,0 +1,151 @@
use alloc::{string::{String, ToString},
vec::Vec};
use core::fmt;
use byteorder::{ByteOrder, NetworkEndian};
use cslice::CMutSlice;
use libboard_artiq::{cxp_ctrl::{Error as CtrlErr, DATA_MAXSIZE},
cxp_grabber::{camera_connected, with_tag},
cxp_packet::{read_bytes, read_u32, write_u32}};
use log::info;
use crate::artiq_raise;
enum Error {
BufferSizeTooSmall(usize, usize),
InvalidLocalUrl(String),
CtrlPacketError(CtrlErr),
}
impl From<CtrlErr> for Error {
fn from(value: CtrlErr) -> Error {
Error::CtrlPacketError(value)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::BufferSizeTooSmall(required_size, buffer_size) => {
write!(
f,
"BufferSizeTooSmall - The required size is {} bytes but the buffer size is {} bytes",
required_size, buffer_size
)
}
&Error::InvalidLocalUrl(ref s) => {
write!(f, "InvalidLocalUrl - Cannot download xml file locally from {}", s)
}
&Error::CtrlPacketError(ref err) => write!(f, "{}", err),
}
}
}
fn read_xml_url(with_tag: bool) -> Result<String, Error> {
let mut addr = read_u32(0x0018, with_tag)?;
let mut buffer = Vec::new();
// Strings stored in the bootstrap and manufacturer-specific registers space shall be NULL-terminated, encoded ASCII - Section 12.3.1 (CXP-001-2021)
// String length is not known during runtime, grabber must read 4 bytes at a time until NULL-terminated
loop {
let mut bytes: [u8; 4] = [0; 4];
read_bytes(addr, &mut bytes, with_tag)?;
addr += 4;
for b in bytes {
if b == 0 {
// UTF-8 is compatible with ASCII encoding
// use U+FFFD REPLACEMENT_CHARACTER to represent decoding error
return Ok(String::from_utf8_lossy(&buffer).to_string());
} else {
buffer.push(b);
}
}
}
}
fn read_xml_location(with_tag: bool) -> Result<(String, u32, u32), Error> {
let url = read_xml_url(with_tag)?;
// url example - Section 13.2.3 (CXP-001-2021)
// Available on camera - "Local:MyFilename.zip;B8000;33A?SchemaVersion=1.0.0"
// => ZIP file starting at address 0xB8000 in the Device with a length of 0x33A bytes
//
// Available online - "Web:http://www.example.com/xml/MyFilename.xml"
// => xml is available at http://www.example.com/xml/MyFilename.xml
let mut splitter = url.split(|c| c == ':' || c == ';' || c == '?');
let scheme = splitter.next().unwrap();
if scheme.eq_ignore_ascii_case("local") {
if let (Some(file_name), Some(addr_str), Some(size_str)) = (splitter.next(), splitter.next(), splitter.next()) {
let addr = u32::from_str_radix(addr_str, 16).map_err(|_| Error::InvalidLocalUrl(url.to_string()))?;
let size = u32::from_str_radix(size_str, 16).map_err(|_| Error::InvalidLocalUrl(url.to_string()))?;
return Ok((file_name.to_string(), addr, size));
}
}
Err(Error::InvalidLocalUrl(url.to_string()))
}
fn read_xml_file(buffer: &mut [i32], with_tag: bool) -> Result<u32, Error> {
let (file_name, base_addr, size) = read_xml_location(with_tag)?;
if buffer.len() * 4 < size as usize {
return Err(Error::BufferSizeTooSmall(size as usize, buffer.len() * 4));
};
info!("downloading xml file {} with {} bytes...", file_name, size);
let mut v: Vec<u8> = Vec::new();
let mut addr = base_addr;
let mut bytesleft = size;
let mut bytes: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE];
while bytesleft > 0 {
let read_len = DATA_MAXSIZE.min(bytesleft as usize);
read_bytes(addr, &mut bytes[..read_len], with_tag)?;
v.extend(&bytes[..read_len]);
addr += read_len as u32;
bytesleft -= read_len as u32;
}
info!("download successful");
// pad to 32 bit boundary
let padding = (4 - (size % 4)) % 4;
for _ in 0..padding {
v.push(0);
}
NetworkEndian::read_i32_into(&v, &mut buffer[..((size + padding) / 4) as usize]);
Ok((size + padding) / 4)
}
pub extern "C" fn download_xml_file(buffer: &mut CMutSlice<i32>) -> i32 {
if camera_connected() {
match read_xml_file(buffer.as_mut_slice(), with_tag()) {
Ok(size_read) => size_read as i32,
Err(e) => artiq_raise!("CXPError", format!("{}", e)),
}
} else {
artiq_raise!("CXPError", "Camera is not connected");
}
}
pub extern "C" fn read32(addr: i32) -> i32 {
if camera_connected() {
match read_u32(addr as u32, with_tag()) {
Ok(result) => result as i32,
Err(e) => artiq_raise!("CXPError", format!("{}", e)),
}
} else {
artiq_raise!("CXPError", "Camera is not connected");
}
}
pub extern "C" fn write32(addr: i32, val: i32) {
if camera_connected() {
match write_u32(addr as u32, val as u32, with_tag()) {
Ok(_) => {}
Err(e) => artiq_raise!("CXPError", format!("{}", e)),
}
} else {
artiq_raise!("CXPError", "Camera is not connected");
}
}

View File

@ -482,7 +482,7 @@ extern "C" fn stop_fn(
}
// Must be kept in sync with preallocate_runtime_exception_names() in `artiq.compiler.embedding`
static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
static EXCEPTION_ID_LOOKUP: [(&str, u32); 23] = [
("RTIOUnderflow", 0),
("RTIOOverflow", 1),
("RTIODestinationUnreachable", 2),
@ -505,6 +505,7 @@ static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
("ZeroDivisionError", 19),
("LinAlgError", 20),
("UnwrapNoneError", 21),
("CXPError", 22),
];
pub fn get_exception_id(name: &str) -> u32 {

View File

@ -11,6 +11,8 @@ use super::{cache,
core1::rtio_get_destination_status,
dma, linalg,
rpc::{rpc_recv, rpc_send, rpc_send_async}};
#[cfg(has_cxp_grabber)]
use crate::cxp;
use crate::{eh_artiq, i2c, rtio};
extern "C" {
@ -126,6 +128,14 @@ pub fn resolve(required: &[u8]) -> Option<u32> {
#[cfg(has_drtio)]
api!(subkernel_await_message = subkernel::await_message),
// cxp grabber
#[cfg(has_cxp_grabber)]
api!(cxp_download_xml_file = cxp::download_xml_file),
#[cfg(has_cxp_grabber)]
api!(cxp_read32 = cxp::read32),
#[cfg(has_cxp_grabber)]
api!(cxp_write32 = cxp::write32),
// Double-precision floating-point arithmetic helper functions
// RTABI chapter 4.1.2, Table 2
api!(__aeabi_dadd),

View File

@ -34,6 +34,8 @@ pub mod rtio;
#[path = "../../../build/pl.rs"]
pub mod pl;
#[cfg(has_cxp_grabber)]
pub mod cxp;
#[derive(Debug, Clone)]
pub struct RPCException {

View File

@ -14,6 +14,8 @@ use core::cell::RefCell;
use ksupport;
use libasync::task;
#[cfg(has_cxp_grabber)]
use libboard_artiq::cxp_phys;
#[cfg(has_drtio_eem)]
use libboard_artiq::drtio_eem;
#[cfg(feature = "target_kasli_soc")]
@ -79,6 +81,23 @@ mod grabber {
}
}
#[cfg(has_cxp_grabber)]
mod cxp {
use libasync::delay;
use libboard_artiq::cxp_grabber;
use libboard_zynq::time::Milliseconds;
use crate::GlobalTimer;
pub async fn grabber_thread(timer: GlobalTimer) {
let mut countdown = timer.countdown();
loop {
cxp_grabber::tick(timer);
delay(&mut countdown, Milliseconds(200)).await;
}
}
}
static mut LOG_BUFFER: [u8; 1 << 17] = [0; 1 << 17];
#[no_mangle]
@ -151,5 +170,11 @@ pub fn main_core0() {
task::spawn(ksupport::report_async_rtio_errors());
#[cfg(has_cxp_grabber)]
{
cxp_phys::setup();
task::spawn(cxp::grabber_thread(timer));
}
comms::main(timer, cfg);
}

View File

@ -27,6 +27,8 @@ extern crate alloc;
use analyzer::Analyzer;
use dma::Manager as DmaManager;
use embedded_hal::blocking::delay::DelayUs;
#[cfg(has_drtio_eem)]
use libboard_artiq::drtio_eem;
#[cfg(has_grabber)]
use libboard_artiq::grabber;
#[cfg(feature = "target_kasli_soc")]
@ -1336,6 +1338,12 @@ fn process_aux_packet(
unsafe {
csr::gt_drtio::txenable_write(0);
}
#[cfg(has_drtio_eem)]
unsafe {
csr::eem_transceiver::txenable_write(0);
}
core_manager.write_image();
info!("reboot imminent");
slcr::reboot();
@ -1569,6 +1577,12 @@ pub extern "C" fn main_core0() -> i32 {
unsafe {
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
}
#[cfg(has_drtio_eem)]
unsafe {
csr::eem_transceiver::txenable_write(0xffffffffu32 as _);
}
#[cfg(has_si549)]
si549::helper_setup(&mut timer, &SI549_SETTINGS).expect("cannot initialize helper Si549");
@ -1594,6 +1608,14 @@ pub extern "C" fn main_core0() -> i32 {
toggle_sed_spread(0);
}
#[cfg(has_drtio_eem)]
{
drtio_eem::init(&mut timer, &cfg);
unsafe {
csr::eem_transceiver::rx_ready_write(1)
}
}
#[cfg(has_drtio_routing)]
let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()];
#[cfg(not(has_drtio_routing))]