Skip to main content

xvc_client/
lib.rs

1//! # XVC Client
2//!
3//! A Rust client library for connecting to Xilinx Virtual Cable (XVC) servers
4//! and performing remote JTAG operations on FPGA devices.
5//!
6//! ## Overview
7//!
8//! This crate provides a high-level async client interface to XVC servers, allowing
9//! applications to interact with FPGA debug interfaces over network connections.
10//! It handles protocol communication, message serialization, and provides convenient
11//! methods for JTAG operations.
12//!
13//! ## Protocol Support
14//!
15//! This implementation supports the XVC 1.0 protocol with the following operations:
16//!
17//! - **GetInfo**: Query server capabilities (version, max vector size)
18//! - **SetTck**: Configure the JTAG Test Clock (TCK) period
19//! - **Shift**: Perform JTAG vector shifting (TMS/TDI/TDO)
20//!
21//! For detailed protocol information, see the [`xvc_protocol`](https://docs.rs/xvc-protocol/) crate.
22//!
23//! ## Basic Usage
24//!
25//! ### Connecting to a Server
26//!
27//! ```ignore
28//! use xvc_client::XvcClient;
29//!
30//! let mut client = XvcClient::connect("127.0.0.1:2542").await?;
31//!
32//! // Query server capabilities
33//! let info = client.get_info().await?;
34//! println!("Server version: {}", info.version());
35//! println!("Max vector size: {} bytes", info.max_vector_len());
36//! ```
37//!
38//! ### Setting Clock Frequency
39//!
40//! ```ignore
41//! // Set TCK period to 10 nanoseconds
42//! let actual_period = client.set_tck(10).await?;
43//! println!("Set TCK to {} ns", actual_period);
44//! ```
45//!
46//! ### Performing JTAG Shifts
47//!
48//! ```ignore
49//! // Perform an 8-bit JTAG shift
50//! let num_bits = 8;
51//! let tms = [0x00u8];
52//! let tdi = [0xA5u8];
53//!
54//! let tdo = client.shift(num_bits, &tms, &tdi).await?;
55//! println!("TDO data: {:?}", tdo);
56//! ```
57//!
58//! ## Related Crates
59//!
60//! - [`xvc_server`](https://docs.rs/xvc-server/) - Server implementation
61//! - [`xvc_protocol`](https://docs.rs/xvc-protocol/) - Protocol encoding/decoding
62//! - [`xvc_server_linux`](https://docs.rs/xvc-server-debugbridge/) - Linux server drivers
63use std::io;
64
65use bytes::BytesMut;
66use tokio::{
67    io::{AsyncReadExt, AsyncWriteExt},
68    net::{TcpStream, ToSocketAddrs},
69};
70use tokio_util::codec::Decoder;
71
72use xvc_protocol::{
73    BorrowedMessage, Message, XvcInfo, error::ReadError, tokio_codec::XvcInfoDecoder,
74};
75
76/// XVC client for remote JTAG operations.
77///
78/// Connects to an XVC server and provides async methods for JTAG operations.
79/// All methods share a single persistent TCP connection.
80pub struct XvcClient {
81    tcp: TcpStream,
82}
83
84impl XvcClient {
85    /// Connect to an XVC server at `addr`.
86    pub async fn connect(addr: impl ToSocketAddrs) -> io::Result<XvcClient> {
87        Ok(XvcClient {
88            tcp: TcpStream::connect(addr).await?,
89        })
90    }
91
92    /// Query server capabilities and version information.
93    pub async fn get_info(&mut self) -> Result<XvcInfo, ReadError> {
94        self.write_message(Message::GetInfo).await?;
95
96        let mut buf = BytesMut::new();
97        loop {
98            match XvcInfoDecoder.decode(&mut buf)? {
99                Some(info) => return Ok(info),
100                None => {
101                    if self.tcp.read_buf(&mut buf).await? == 0 {
102                        return Err(io::Error::new(
103                            io::ErrorKind::UnexpectedEof,
104                            "connection closed while reading server info",
105                        )
106                        .into());
107                    }
108                }
109            }
110        }
111    }
112
113    /// Set the JTAG Test Clock (TCK) period.
114    ///
115    /// Returns the actual period set by the server, which may differ from the
116    /// requested value if the hardware has limited frequency resolution.
117    pub async fn set_tck(&mut self, period_ns: u32) -> Result<u32, ReadError> {
118        self.write_message(Message::SetTck { period_ns }).await?;
119        let mut buf = [0u8; 4];
120        self.tcp.read_exact(&mut buf).await?;
121        Ok(u32::from_le_bytes(buf))
122    }
123
124    /// Perform a JTAG shift operation.
125    ///
126    /// # Arguments
127    ///
128    /// * `num_bits` - Number of bits to shift
129    /// * `tms` - Test Mode Select vector (length must be ⌈num_bits / 8⌉)
130    /// * `tdi` - Test Data In vector (length must be ⌈num_bits / 8⌉)
131    ///
132    /// # Returns
133    ///
134    /// Test Data Out vector from the JTAG chain of the same length as `tms` and `tdi`.
135    pub async fn shift(
136        &mut self,
137        num_bits: u32,
138        tms: &[u8],
139        tdi: &[u8],
140    ) -> Result<Box<[u8]>, ReadError> {
141        self.write_message(BorrowedMessage::Shift { num_bits, tms, tdi })
142            .await?;
143        let num_bytes = num_bits.div_ceil(8) as usize;
144        let mut buf = vec![0u8; num_bytes];
145        self.tcp.read_exact(&mut buf).await?;
146        Ok(buf.into_boxed_slice())
147    }
148
149    async fn write_message(&mut self, msg: BorrowedMessage<'_>) -> Result<(), ReadError> {
150        let mut buf = Vec::new();
151        msg.write_to(&mut buf)?;
152        self.tcp.write_all(&buf).await?;
153        Ok(())
154    }
155}