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}