use nix::{ioctl_read_bad, ioctl_readwrite_bad};
use std::{
ffi::{CStr, c_char, c_uchar, c_uint, c_ulong},
fs::{File, OpenOptions},
io,
mem::MaybeUninit,
os::fd::AsRawFd,
path::Path,
};
use crate::XvcServer;
#[repr(C)]
#[derive(Clone, Debug)]
pub struct XvcProperties {
debug_bridge_base_addr: c_ulong,
debug_bridge_size: c_ulong,
debug_bridge_compat_string: [c_char; 64],
}
impl XvcProperties {
pub fn debug_bridge_base_address(&self) -> u64 {
self.debug_bridge_base_addr
}
pub fn debug_bridge_size(&self) -> u64 {
self.debug_bridge_size
}
pub fn debug_bridge_compat_string(&self) -> String {
let slice = unsafe { CStr::from_ptr(self.debug_bridge_compat_string.as_ptr()) };
slice
.to_str()
.expect("Compatibility string should only be ASCII")
.to_owned()
}
}
#[repr(C)]
#[derive(Clone, Debug)]
struct XvcIoc {
opcode: c_uint,
length: c_uint,
tms_buf: *const c_uchar,
tdi_buf: *const c_uchar,
tdo_buf: *mut c_uchar,
}
const XDMA_RDXVC_PROPS_NR: u32 = 0xD6534402;
const XDMA_IOCXVC_NR: u32 = 0xD6634401;
ioctl_read_bad!(xvc_read_properties, XDMA_RDXVC_PROPS_NR, XvcProperties);
ioctl_readwrite_bad!(xvc_do_ioc, XDMA_IOCXVC_NR, XvcIoc);
pub struct KernelDriverBackend {
file: File,
}
impl KernelDriverBackend {
pub fn new(device_path: impl AsRef<Path>) -> io::Result<KernelDriverBackend> {
let path = device_path.as_ref();
log::debug!("Opening kernel driver device: {}", path.display());
let file = OpenOptions::new()
.read(true)
.write(true)
.open(device_path)?;
log::debug!("Device file opened successfully");
let properties = unsafe {
let mut props = MaybeUninit::zeroed();
xvc_read_properties(file.as_raw_fd(), props.as_mut_ptr())?;
props.assume_init()
};
log::info!(
"Debug bridge properties: base_addr=0x{:x}, size=0x{:x}, compat_string={}",
properties.debug_bridge_base_address(),
properties.debug_bridge_size(),
properties.debug_bridge_compat_string()
);
Ok(KernelDriverBackend { file })
}
pub fn shift_data(
&self,
num_bits: u32,
tms: &[u8],
tdi: &[u8],
tdo: &mut [u8],
) -> io::Result<()> {
let num_bytes = num_bits.div_ceil(8) as usize;
if tms.len() != num_bytes {
log::error!(
"TMS buffer size mismatch: expected {}, got {}",
num_bytes,
tms.len()
);
return Err(io::Error::other("TMS has incorrect size"));
}
if tdi.len() != num_bytes {
log::error!(
"TDI buffer size mismatch: expected {}, got {}",
num_bytes,
tdi.len()
);
return Err(io::Error::other("TDI has incorrect size"));
}
if tdo.len() != num_bytes {
log::error!(
"TDO buffer size mismatch: expected {}, got {}",
num_bytes,
tdo.len()
);
return Err(io::Error::other("TDO has incorrect size"));
}
log::debug!(
"Kernel driver shift: num_bits={}, num_bytes={}",
num_bits,
num_bytes
);
log::trace!("Kernel driver shift TMS: {:02x?}", tms);
log::trace!("Kernel driver shift TDI: {:02x?}", tdi);
let mut xvc_ioc = XvcIoc {
opcode: 1,
length: num_bits as c_uint,
tms_buf: tms.as_ptr(),
tdi_buf: tdi.as_ptr(),
tdo_buf: tdo.as_mut_ptr(),
};
unsafe {
xvc_do_ioc(self.file.as_raw_fd(), &mut xvc_ioc)?;
}
Ok(())
}
}
impl XvcServer for KernelDriverBackend {
type Err = io::Error;
fn set_tck(&self, period_ns: u32) -> Result<u32, Self::Err> {
Ok(period_ns)
}
fn shift(
&self,
num_bits: u32,
tms: &[u8],
tdi: &[u8],
tdo: &mut [u8],
) -> Result<(), Self::Err> {
self.shift_data(num_bits, tms, tdi, tdo)
}
}