1use crate::config::Config;
2use crate::constants;
3use crate::error::{Error, Result};
4use crate::usb::{Endpoint, TransportConfig, UsbDevice};
5use nusb::{
6 Endpoint as UsbEndpoint,
7 transfer::{Buffer, Bulk, Completion, EndpointDirection, In, Out},
8};
9use std::collections::VecDeque;
10use std::thread;
11use std::time::{Duration, Instant};
12
13const CONTROL_COMMAND_PREFIX: u8 = 0x01;
14const VERICOMM_TRANSFER_PACKET_BYTES: usize = 8;
15const MAX_PIPELINE_DEPTH: usize = 512;
16
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct TransferStageProfile {
19 pub calls: u64,
20 pub transfers: u64,
21 pub validation: Duration,
22 pub setup: Duration,
23 pub submit: Duration,
24 pub wait_write: Duration,
25 pub wait_read: Duration,
26 pub decode_copy: Duration,
27}
28
29impl TransferStageProfile {
30 pub fn merge(&mut self, other: &Self) {
31 self.calls = self.calls.saturating_add(other.calls);
32 self.transfers = self.transfers.saturating_add(other.transfers);
33 self.validation = self.validation.saturating_add(other.validation);
34 self.setup = self.setup.saturating_add(other.setup);
35 self.submit = self.submit.saturating_add(other.submit);
36 self.wait_write = self.wait_write.saturating_add(other.wait_write);
37 self.wait_read = self.wait_read.saturating_add(other.wait_read);
38 self.decode_copy = self.decode_copy.saturating_add(other.decode_copy);
39 }
40
41 pub fn total_duration(&self) -> Duration {
42 self.validation
43 .saturating_add(self.setup)
44 .saturating_add(self.submit)
45 .saturating_add(self.wait_write)
46 .saturating_add(self.wait_read)
47 .saturating_add(self.decode_copy)
48 }
49}
50
51#[derive(Debug, Clone, Copy)]
52enum TransferProfileStage {
53 Validation,
54 Setup,
55 Submit,
56 WaitWrite,
57 WaitRead,
58 DecodeCopy,
59}
60
61struct TransferProfiler<'a> {
62 profile: Option<&'a mut TransferStageProfile>,
63}
64
65impl<'a> TransferProfiler<'a> {
66 fn new(profile: Option<&'a mut TransferStageProfile>, transfers: usize) -> Self {
67 let mut profiler = Self { profile };
68 if let Some(profile) = profiler.profile.as_deref_mut() {
69 profile.calls = profile.calls.saturating_add(1);
70 profile.transfers = profile.transfers.saturating_add(transfers as u64);
71 }
72 profiler
73 }
74
75 fn borrow(profile: Option<&'a mut TransferStageProfile>) -> Self {
76 Self { profile }
77 }
78
79 fn add(&mut self, stage: TransferProfileStage, elapsed: Duration) {
80 let Some(profile) = self.profile.as_deref_mut() else {
81 return;
82 };
83
84 match stage {
85 TransferProfileStage::Validation => {
86 profile.validation = profile.validation.saturating_add(elapsed);
87 }
88 TransferProfileStage::Setup => {
89 profile.setup = profile.setup.saturating_add(elapsed);
90 }
91 TransferProfileStage::Submit => {
92 profile.submit = profile.submit.saturating_add(elapsed);
93 }
94 TransferProfileStage::WaitWrite => {
95 profile.wait_write = profile.wait_write.saturating_add(elapsed);
96 }
97 TransferProfileStage::WaitRead => {
98 profile.wait_read = profile.wait_read.saturating_add(elapsed);
99 }
100 TransferProfileStage::DecodeCopy => {
101 profile.decode_copy = profile.decode_copy.saturating_add(elapsed);
102 }
103 }
104 }
105}
106
107pub struct Board {
108 usb: UsbDevice,
109 config: Config,
110 crypto: CryptoState,
111 initialized: bool,
112 mode: BoardMode,
113}
114
115impl Board {
116 pub fn open() -> Result<Self> {
117 Self::open_with_transport(TransportConfig::default())
118 }
119
120 pub fn open_with_transport(transport: TransportConfig) -> Result<Self> {
121 let mut usb = UsbDevice::with_transport_config(transport)?;
122 usb.open(constants::DW_VID, constants::DW_PID)?;
123
124 let mut board = Self {
125 usb,
126 config: Config::new(),
127 crypto: CryptoState::default(),
128 initialized: false,
129 mode: BoardMode::Unknown,
130 };
131 board.initialize()?;
132 Ok(board)
133 }
134
135 pub fn transport(&self) -> &TransportConfig {
136 self.usb.transport_config()
137 }
138
139 pub fn config(&self) -> &Config {
140 &self.config
141 }
142
143 pub fn mode(&self) -> BoardMode {
144 self.mode
145 }
146
147 pub fn is_initialized(&self) -> bool {
148 self.initialized
149 }
150
151 pub fn initialize(&mut self) -> Result<()> {
152 match self.initialize_once() {
153 Ok(()) => Ok(()),
154 Err(err) if should_retry_initialize(&err) => {
155 self.try_recover_control_plane()?;
156 self.initialize_once()
157 }
158 Err(err) => Err(err),
159 }
160 }
161
162 fn initialize_once(&mut self) -> Result<()> {
163 self.read_encrypt_table()?;
164 self.crypto.decode_table();
165 self.refresh_config()?;
166 Ok(())
167 }
168
169 pub fn refresh_config(&mut self) -> Result<&Config> {
170 self.sync_delay()?;
171 self.usb
172 .write_bytes(Endpoint::Command, &[CONTROL_COMMAND_PREFIX, 0x01])?;
173
174 let mut words = [0u16; Config::WORD_COUNT];
175 self.usb.read_words(Endpoint::FifoRead, &mut words)?;
176 self.activate_control()?;
177 self.crypto.decrypt_words(&mut words);
178 self.config = Config::from_words(words);
179 self.initialized = true;
180 self.mode = BoardMode::Control;
181 Ok(&self.config)
182 }
183
184 pub fn write_config(&mut self) -> Result<()> {
185 self.sync_delay()?;
186 let mut words = *self.config.words();
187 self.crypto.encrypt_words(&mut words);
188 self.usb
189 .write_bytes(Endpoint::Command, &[CONTROL_COMMAND_PREFIX, 0x11])?;
190 self.usb.write_words(Endpoint::FifoWrite, &words)?;
191 self.activate_control()?;
192 self.initialized = true;
193 self.mode = BoardMode::Control;
194 Ok(())
195 }
196
197 pub fn configure_io(&mut self, settings: &IoConfig) -> Result<IoSession<'_>> {
198 self.ensure_ready()?;
199
200 let actual_version = self.config.smims_version_raw();
201 if actual_version < constants::SMIMS_VERSION {
202 return Err(Error::VersionMismatch {
203 expected: constants::SMIMS_VERSION,
204 actual: actual_version,
205 });
206 }
207 if !self.config.is_programmed() {
208 return Err(Error::NotProgrammed);
209 }
210 if !self.config.vericomm_ability() {
211 return Err(Error::FeatureUnavailable("vericomm"));
212 }
213
214 if let Some(licence_key) = settings.licence_key {
215 self.config.set_licence_key(licence_key);
216 }
217 self.config
218 .set_vericomm_clock_high_delay(settings.clock_high_delay);
219 self.config
220 .set_vericomm_clock_low_delay(settings.clock_low_delay);
221 self.config.set_vericomm_isv(settings.vericomm_isv);
222 self.config
223 .set_vericomm_clock_check_enabled(settings.clock_check_enabled);
224 self.config.set_mode_selector(settings.mode_selector);
225 self.write_config()?;
226 self.activate_mode(BoardMode::VeriComm)?;
227
228 Ok(IoSession {
229 board: self,
230 pipeline_write: None,
231 pipeline_read: None,
232 single_tx_buffer: None,
233 single_rx_buffer: None,
234 tx_pool: Vec::new(),
235 rx_pool: Vec::new(),
236 finished: false,
237 })
238 }
239
240 pub fn programmer(&mut self) -> Result<ProgramSession<'_>> {
241 self.ensure_ready()?;
242 self.activate_mode(BoardMode::FpgaProgrammer)?;
243 Ok(ProgramSession { board: self })
244 }
245
246 pub fn close(mut self) -> Result<()> {
247 self.usb.close()
248 }
249
250 pub(crate) fn encrypt_words(&mut self, words: &mut [u16]) {
251 self.crypto.encrypt_words(words);
252 }
253
254 pub(crate) fn fifo_write_words(&self, words: &[u16]) -> Result<()> {
255 self.usb.write_words(Endpoint::FifoWrite, words)
256 }
257
258 pub(crate) fn command_active(&mut self) -> Result<()> {
259 self.activate_control()
260 }
261
262 pub(crate) fn activate_control(&mut self) -> Result<()> {
263 self.sync_delay()?;
264 self.usb
265 .write_bytes(Endpoint::Command, &[CONTROL_COMMAND_PREFIX, 0x00])?;
266 self.mode = BoardMode::Control;
267 Ok(())
268 }
269
270 fn engine_reset(&mut self) -> Result<()> {
271 self.usb.write_bytes(Endpoint::Command, &[0x02])?;
272 self.mode = BoardMode::Unknown;
273 Ok(())
274 }
275
276 fn try_recover_control_plane(&mut self) -> Result<()> {
277 self.usb.clear_halt_all()?;
278 self.engine_reset()?;
279 thread::sleep(Duration::from_millis(2));
280 Ok(())
281 }
282
283 fn ensure_ready(&mut self) -> Result<()> {
284 if !self.initialized {
285 self.initialize()?;
286 }
287 Ok(())
288 }
289
290 fn ensure_mode(&self, expected: BoardMode) -> Result<()> {
291 if self.mode != expected {
292 return Err(Error::InvalidMode {
293 expected: expected.as_str(),
294 actual: self.mode.as_str(),
295 });
296 }
297 Ok(())
298 }
299
300 fn activate_mode(&mut self, mode: BoardMode) -> Result<()> {
301 let Some(command) = mode.command_byte() else {
302 return Err(Error::UnexpectedResponse("unsupported mode command"));
303 };
304 self.sync_delay()?;
305 self.usb
306 .write_bytes(Endpoint::Command, &[CONTROL_COMMAND_PREFIX, command])?;
307 self.mode = mode;
308 Ok(())
309 }
310
311 fn read_encrypt_table(&mut self) -> Result<()> {
312 self.sync_delay()?;
313 self.usb
314 .write_bytes(Endpoint::Command, &[CONTROL_COMMAND_PREFIX, 0x0f])?;
315 self.usb
316 .read_words(Endpoint::FifoRead, self.crypto.table_mut())
317 }
318
319 fn sync_delay(&self) -> Result<()> {
320 let start = Instant::now();
321 let sync_timeout = self.transport().sync_timeout;
322 let mut buffer = [0u8; 1];
323
324 while start.elapsed() <= sync_timeout {
325 self.usb.write_bytes(Endpoint::Command, &buffer)?;
326 self.usb.read_bytes(Endpoint::Sync, &mut buffer)?;
327 if buffer[0] != 0 {
328 return Ok(());
329 }
330 }
331
332 Err(Error::Timeout("sync_delay"))
333 }
334}
335
336pub struct IoSession<'a> {
337 board: &'a mut Board,
338 pipeline_write: Option<UsbEndpoint<Bulk, Out>>,
339 pipeline_read: Option<UsbEndpoint<Bulk, In>>,
340 single_tx_buffer: Option<Buffer>,
341 single_rx_buffer: Option<Buffer>,
342 tx_pool: Vec<Buffer>,
343 rx_pool: Vec<Buffer>,
344 finished: bool,
345}
346
347pub struct IoTransferWindow<'session, 'board> {
354 io: &'session mut IoSession<'board>,
355 frame_words: usize,
356 frame_bytes: usize,
357 read_request_bytes: usize,
358 capacity: usize,
359 pending_reads: VecDeque<PendingWindowRead>,
360 pending_writes: usize,
361}
362
363struct PendingWindowRead {
364 buffer_id: usize,
365 completion: Option<Completion>,
366}
367
368impl PendingWindowRead {
369 fn new(buffer_id: usize) -> Self {
370 Self {
371 buffer_id,
372 completion: None,
373 }
374 }
375
376 fn is_waiting(&self) -> bool {
377 self.completion.is_none()
378 }
379
380 fn complete(&mut self, completion: Completion) -> Result<()> {
381 if self.completion.is_some() {
382 return Err(Error::UnexpectedResponse(
383 "pipeline read completion matched an already completed transfer",
384 ));
385 }
386 self.completion = Some(completion);
387 Ok(())
388 }
389
390 fn into_completion(mut self) -> Option<Completion> {
391 self.completion.take()
392 }
393}
394
395impl<'a> IoSession<'a> {
396 fn cleanup(&mut self) -> Result<()> {
397 if let Some(pipeline_write) = self.pipeline_write.as_mut() {
398 pipeline_write.cancel_all();
399 }
400 if let Some(pipeline_read) = self.pipeline_read.as_mut() {
401 pipeline_read.cancel_all();
402 }
403 self.pipeline_write = None;
404 self.pipeline_read = None;
405 self.single_tx_buffer = None;
406 self.single_rx_buffer = None;
407 self.tx_pool.clear();
408 self.rx_pool.clear();
409 self.board.try_recover_control_plane()?;
410 self.board.activate_control()
411 }
412
413 fn ensure_pipeline_endpoints(&mut self) -> Result<()> {
414 if self.pipeline_write.is_none() {
415 self.pipeline_write = Some(self.board.usb.open_out_endpoint(Endpoint::FifoWrite)?);
416 }
417 if self.pipeline_read.is_none() {
418 self.pipeline_read = Some(self.board.usb.open_in_endpoint(Endpoint::FifoRead)?);
419 }
420 Ok(())
421 }
422
423 fn take_single_tx_buffer(&mut self, tx_bytes: usize) -> Buffer {
424 if let Some(mut buffer) = self.single_tx_buffer.take() {
425 if buffer.capacity() >= tx_bytes.max(1) {
426 buffer.clear();
427 return buffer;
428 }
429 }
430
431 self.pipeline_write
432 .as_mut()
433 .expect("pipeline write endpoint should be initialized")
434 .allocate(tx_bytes.max(1))
435 }
436
437 fn take_single_rx_buffer(&mut self, request_bytes: usize) -> Buffer {
438 if let Some(mut buffer) = self.single_rx_buffer.take() {
439 if buffer.capacity() >= request_bytes.max(1) {
440 buffer.clear();
441 buffer.set_requested_len(request_bytes.max(1));
442 return buffer;
443 }
444 }
445
446 let mut buffer = self
447 .pipeline_read
448 .as_mut()
449 .expect("pipeline read endpoint should be initialized")
450 .allocate(request_bytes.max(1));
451 buffer.set_requested_len(request_bytes.max(1));
452 buffer
453 }
454
455 fn prepare_pools(&mut self, pipeline_depth: usize, tx_bytes: usize, rx_bytes: usize) {
456 let tx_bytes = tx_bytes.max(1);
457 let rx_bytes = rx_bytes.max(1);
458
459 let pipeline_write = self
460 .pipeline_write
461 .as_mut()
462 .expect("pipeline write endpoint should be initialized");
463 let pipeline_read = self
464 .pipeline_read
465 .as_mut()
466 .expect("pipeline read endpoint should be initialized");
467
468 discard_undersized_buffers(&mut self.tx_pool, tx_bytes);
469 discard_undersized_buffers(&mut self.rx_pool, rx_bytes);
470
471 while self.tx_pool.len() < pipeline_depth {
472 self.tx_pool.push(pipeline_write.allocate(tx_bytes));
473 }
474 while self.rx_pool.len() < pipeline_depth {
475 let mut buffer = pipeline_read.allocate(rx_bytes);
476 buffer.set_requested_len(rx_bytes);
477 self.rx_pool.push(buffer);
478 }
479 }
480
481 pub fn transfer_window(
484 &mut self,
485 words: usize,
486 capacity: usize,
487 ) -> Result<IoTransferWindow<'_, 'a>> {
488 if capacity == 0 {
489 return Err(Error::InvalidBufferLength {
490 context: "vericomm transfer window",
491 expected: 1,
492 actual: 0,
493 });
494 }
495
496 validate_transfer_buffers(
497 words,
498 words,
499 usize::from(self.board.config.fifo_size_words()),
500 )?;
501 self.board.ensure_mode(BoardMode::VeriComm)?;
502 self.ensure_pipeline_endpoints()?;
503
504 let frame_bytes = words * std::mem::size_of::<u16>();
505 let read_request_bytes = request_bytes_for_words(
506 self.pipeline_read
507 .as_ref()
508 .expect("pipeline read endpoint should be initialized")
509 .max_packet_size(),
510 words,
511 );
512 let capacity = capacity.min(MAX_PIPELINE_DEPTH);
513 self.prepare_pools(capacity, frame_bytes, read_request_bytes);
514
515 Ok(IoTransferWindow {
516 io: self,
517 frame_words: words,
518 frame_bytes,
519 read_request_bytes,
520 capacity,
521 pending_reads: VecDeque::with_capacity(capacity),
522 pending_writes: 0,
523 })
524 }
525
526 fn submit_window_transfer(&mut self, tx: &[u16], read_request_bytes: usize) -> usize {
527 let tx_buffer = self.tx_pool.pop().expect("tx pool should be primed");
528 let rx_buffer = self.rx_pool.pop().expect("rx pool should be primed");
529 let rx_buffer_id = buffer_identity(&rx_buffer);
530 submit_pipeline_read(
531 self.pipeline_read
532 .as_mut()
533 .expect("pipeline read endpoint should be initialized"),
534 rx_buffer,
535 read_request_bytes,
536 );
537 submit_pipeline_write(
538 &mut self.board.crypto,
539 self.pipeline_write
540 .as_mut()
541 .expect("pipeline write endpoint should be initialized"),
542 tx,
543 tx_buffer,
544 );
545 rx_buffer_id
546 }
547
548 fn discard_window_pending_transfers(&mut self, pending_writes: usize, pending_reads: usize) {
549 const DRAIN_TIMEOUT: Duration = Duration::from_millis(10);
550
551 if let Some(endpoint) = self.pipeline_write.as_mut() {
552 endpoint.cancel_all();
553 for _ in 0..pending_writes {
554 let Some(completion) = endpoint.wait_next_complete(DRAIN_TIMEOUT) else {
555 break;
556 };
557 self.tx_pool.push(completion.buffer);
558 }
559 }
560
561 if let Some(endpoint) = self.pipeline_read.as_mut() {
562 endpoint.cancel_all();
563 for _ in 0..pending_reads {
564 let Some(completion) = endpoint.wait_next_complete(DRAIN_TIMEOUT) else {
565 break;
566 };
567 self.rx_pool.push(completion.buffer);
568 }
569 }
570 }
571 fn transfer_with_profile(
572 &mut self,
573 tx: &[u16],
574 rx: &mut [u16],
575 profile: Option<&mut TransferStageProfile>,
576 ) -> Result<()> {
577 let mut profiler = TransferProfiler::new(profile, 1);
578
579 let stage_started = Instant::now();
580 validate_transfer_buffers(
581 tx.len(),
582 rx.len(),
583 usize::from(self.board.config.fifo_size_words()),
584 )?;
585 self.board.ensure_mode(BoardMode::VeriComm)?;
586 profiler.add(TransferProfileStage::Validation, stage_started.elapsed());
587
588 let stage_started = Instant::now();
589 self.ensure_pipeline_endpoints()?;
590
591 let tx_byte_len = std::mem::size_of_val(tx);
592 let request_bytes = aligned_request_len(
593 self.pipeline_read
594 .as_ref()
595 .expect("pipeline read endpoint should be initialized")
596 .max_packet_size(),
597 tx_byte_len,
598 );
599 profiler.add(TransferProfileStage::Setup, stage_started.elapsed());
600
601 let stage_started = Instant::now();
602 let rx_buffer = self.take_single_rx_buffer(request_bytes);
603 submit_pipeline_read(
604 self.pipeline_read
605 .as_mut()
606 .expect("pipeline read endpoint should be initialized"),
607 rx_buffer,
608 request_bytes,
609 );
610
611 let mut tx_buffer = self.take_single_tx_buffer(tx_byte_len);
612 let tx_bytes = tx_buffer.extend_fill(tx_byte_len, 0);
613 words_to_bytes(tx, tx_bytes);
614 self.board
615 .crypto
616 .encrypt_words(bytes_as_words_mut(tx_bytes));
617 self.pipeline_write
618 .as_mut()
619 .expect("pipeline write endpoint should be initialized")
620 .submit(tx_buffer);
621 profiler.add(TransferProfileStage::Submit, stage_started.elapsed());
622
623 let timeout = self.board.transport().usb_timeout;
624 let stage_started = Instant::now();
625 let tx_completion = match self
626 .pipeline_write
627 .as_mut()
628 .expect("pipeline write endpoint should be initialized")
629 .wait_next_complete(timeout)
630 {
631 Some(completion) => completion,
632 None => {
633 let tx_cancelled = cancel_pending_transfer(
634 self.pipeline_write
635 .as_mut()
636 .expect("pipeline write endpoint should be initialized"),
637 );
638 self.single_tx_buffer = Some(tx_cancelled.buffer);
639 let rx_cancelled = cancel_pending_transfer(
640 self.pipeline_read
641 .as_mut()
642 .expect("pipeline read endpoint should be initialized"),
643 );
644 self.single_rx_buffer = Some(rx_cancelled.buffer);
645 return Err(Error::Timeout("nusb_bulk_write"));
646 }
647 };
648 let tx_status = tx_completion.status;
649 let tx_buffer = tx_completion.buffer;
650 self.single_tx_buffer = Some(tx_buffer);
651 tx_status.map_err(|err| transfer_error(err, "nusb_bulk_write"))?;
652 profiler.add(TransferProfileStage::WaitWrite, stage_started.elapsed());
653
654 let stage_started = Instant::now();
655 let rx_completion = match self
656 .pipeline_read
657 .as_mut()
658 .expect("pipeline read endpoint should be initialized")
659 .wait_next_complete(timeout)
660 {
661 Some(completion) => completion,
662 None => {
663 let rx_cancelled = cancel_pending_transfer(
664 self.pipeline_read
665 .as_mut()
666 .expect("pipeline read endpoint should be initialized"),
667 );
668 self.single_rx_buffer = Some(rx_cancelled.buffer);
669 return Err(Error::Timeout("nusb_bulk_read"));
670 }
671 };
672 let actual_len = rx_completion.actual_len;
673 let rx_status = rx_completion.status;
674 let mut rx_buffer = rx_completion.buffer;
675 rx_status.map_err(|err| transfer_error(err, "nusb_bulk_read"))?;
676 profiler.add(TransferProfileStage::WaitRead, stage_started.elapsed());
677
678 let stage_started = Instant::now();
679 if actual_len < tx_byte_len {
680 self.single_rx_buffer = Some(rx_buffer);
681 return Err(Error::UnexpectedResponse(
682 "blocking read returned short payload",
683 ));
684 }
685 self.board
686 .crypto
687 .decrypt_words(bytes_as_words_mut(&mut rx_buffer[..tx_byte_len]));
688 rx.copy_from_slice(bytes_as_words(&rx_buffer[..tx_byte_len]));
689 self.single_rx_buffer = Some(rx_buffer);
690 profiler.add(TransferProfileStage::DecodeCopy, stage_started.elapsed());
691 Ok(())
692 }
693
694 pub fn transfer(&mut self, tx: &[u16], rx: &mut [u16]) -> Result<()> {
695 self.transfer_with_profile(tx, rx, None)
696 }
697
698 pub fn transfer_profiled_into(
699 &mut self,
700 tx: &[u16],
701 rx: &mut [u16],
702 ) -> Result<TransferStageProfile> {
703 let mut profile = TransferStageProfile::default();
704 self.transfer_with_profile(tx, rx, Some(&mut profile))?;
705 Ok(profile)
706 }
707
708 pub fn transfer_into(&mut self, tx: &[u16], rx: &mut [u16]) -> Result<()> {
709 self.transfer(tx, rx)
710 }
711
712 pub fn finish(mut self) -> Result<()> {
713 let result = self.cleanup();
714 self.finished = true;
715 result
716 }
717}
718
719impl Drop for IoSession<'_> {
720 fn drop(&mut self) {
721 if !self.finished {
722 let _ = self.cleanup();
723 }
724 }
725}
726
727impl<'session, 'board> IoTransferWindow<'session, 'board> {
728 fn submit_with_profile(
729 &mut self,
730 tx: &[u16],
731 profile: Option<&mut TransferStageProfile>,
732 ) -> Result<()> {
733 if self.is_full() {
734 return Err(Error::PipelineFull {
735 capacity: self.capacity,
736 });
737 }
738
739 let mut profiler = TransferProfiler::new(profile, 1);
740
741 let stage_started = Instant::now();
742 validate_window_frame_words(
743 self.frame_words,
744 tx.len(),
745 "vericomm transfer window submit",
746 )?;
747 profiler.add(TransferProfileStage::Validation, stage_started.elapsed());
748
749 let stage_started = Instant::now();
750 let buffer_id = self.io.submit_window_transfer(tx, self.read_request_bytes);
751 self.pending_reads
752 .push_back(PendingWindowRead::new(buffer_id));
753 self.pending_writes += 1;
754 profiler.add(TransferProfileStage::Submit, stage_started.elapsed());
755 Ok(())
756 }
757
758 fn receive_into_with_profile(
759 &mut self,
760 output: &mut [u16],
761 profile: Option<&mut TransferStageProfile>,
762 ) -> Result<()> {
763 if self.pending_reads.is_empty() {
764 return Err(Error::PipelineEmpty);
765 }
766 validate_window_frame_words(
767 self.frame_words,
768 output.len(),
769 "vericomm transfer window receive",
770 )?;
771
772 let mut profiler = TransferProfiler::borrow(profile);
773
774 let stage_started = Instant::now();
775 self.reclaim_write_buffer()?;
776 profiler.add(TransferProfileStage::WaitWrite, stage_started.elapsed());
777
778 let stage_started = Instant::now();
779 let Completion {
780 buffer: read_buffer,
781 actual_len,
782 status,
783 } = self.collect_oldest_read_completion()?;
784 if let Err(err) = status {
785 self.io.rx_pool.push(read_buffer);
786 return Err(transfer_error(err, "pipeline_read"));
787 }
788 profiler.add(TransferProfileStage::WaitRead, stage_started.elapsed());
789
790 let stage_started = Instant::now();
791 if actual_len < self.frame_bytes {
792 self.io.rx_pool.push(read_buffer);
793 return Err(Error::UnexpectedResponse(
794 "pipeline read returned short payload",
795 ));
796 }
797 bytes_into_words(&read_buffer[..self.frame_bytes], output);
798 self.io.board.crypto.decrypt_words(output);
799 self.io.rx_pool.push(read_buffer);
800 profiler.add(TransferProfileStage::DecodeCopy, stage_started.elapsed());
801 Ok(())
802 }
803
804 fn reclaim_write_buffer(&mut self) -> Result<()> {
805 let Completion { buffer, status, .. } = self
806 .io
807 .pipeline_write
808 .as_mut()
809 .expect("pipeline write endpoint should be initialized")
810 .wait_next_complete(self.io.board.transport().usb_timeout)
811 .ok_or(Error::Timeout("pipeline_write"))?;
812 self.pending_writes = self.pending_writes.saturating_sub(1);
813 self.io.tx_pool.push(buffer);
814 status.map_err(|err| transfer_error(err, "pipeline_write"))
815 }
816
817 fn collect_oldest_read_completion(&mut self) -> Result<Completion> {
818 while self
819 .pending_reads
820 .front()
821 .is_some_and(PendingWindowRead::is_waiting)
822 {
823 let completion = self
824 .io
825 .pipeline_read
826 .as_mut()
827 .expect("pipeline read endpoint should be initialized")
828 .wait_next_complete(self.io.board.transport().usb_timeout)
829 .ok_or(Error::Timeout("pipeline_read"))?;
830 store_window_read_completion(&mut self.pending_reads, completion)?;
831 }
832 Ok(self
833 .pending_reads
834 .pop_front()
835 .and_then(PendingWindowRead::into_completion)
836 .expect("front transfer should have a completed read"))
837 }
838
839 fn recycle_completed_read_buffers(&mut self) -> usize {
840 let mut pending_read_completions = 0usize;
841
842 while let Some(pending) = self.pending_reads.pop_front() {
843 if let Some(completion) = pending.into_completion() {
844 self.io.rx_pool.push(completion.buffer);
845 } else {
846 pending_read_completions += 1;
847 }
848 }
849
850 pending_read_completions
851 }
852
853 pub fn words(&self) -> usize {
854 self.frame_words
855 }
856
857 pub fn capacity(&self) -> usize {
858 self.capacity
859 }
860
861 pub fn pending(&self) -> usize {
862 self.pending_reads.len()
863 }
864
865 pub fn available(&self) -> usize {
866 self.capacity.saturating_sub(self.pending())
867 }
868
869 pub fn is_empty(&self) -> bool {
870 self.pending_reads.is_empty()
871 }
872
873 pub fn is_full(&self) -> bool {
874 self.pending() >= self.capacity
875 }
876
877 pub fn submit(&mut self, tx: &[u16]) -> Result<()> {
879 self.submit_with_profile(tx, None)
880 }
881
882 pub fn submit_profiled(&mut self, tx: &[u16]) -> Result<TransferStageProfile> {
884 let mut profile = TransferStageProfile::default();
885 self.submit_with_profile(tx, Some(&mut profile))?;
886 Ok(profile)
887 }
888
889 pub fn receive_into(&mut self, output: &mut [u16]) -> Result<()> {
891 self.receive_into_with_profile(output, None)
892 }
893
894 pub fn receive_into_profiled(&mut self, output: &mut [u16]) -> Result<TransferStageProfile> {
896 let mut profile = TransferStageProfile::default();
897 self.receive_into_with_profile(output, Some(&mut profile))?;
898 Ok(profile)
899 }
900}
901
902impl Drop for IoTransferWindow<'_, '_> {
903 fn drop(&mut self) {
904 if !self.pending_reads.is_empty() {
905 let pending_read_completions = self.recycle_completed_read_buffers();
906 self.io
907 .discard_window_pending_transfers(self.pending_writes, pending_read_completions);
908 self.pending_writes = 0;
909 }
910 }
911}
912
913pub struct ProgramSession<'a> {
914 board: &'a mut Board,
915}
916
917impl ProgramSession<'_> {
918 pub fn write_bitstream_words(&mut self, words: &[u16]) -> Result<()> {
919 let chunk_len = bitstream_chunk_words(self.board.config())?;
920 let mut encrypted = words.to_vec();
921 self.board.encrypt_words(&mut encrypted);
922 for chunk in encrypted.chunks(chunk_len) {
923 self.board.fifo_write_words(chunk)?;
924 }
925 Ok(())
926 }
927
928 pub fn finish(self) -> Result<()> {
929 self.board.command_active()?;
930 self.board.refresh_config()?;
931 if !self.board.config().is_programmed() {
932 return Err(Error::NotProgrammed);
933 }
934 Ok(())
935 }
936}
937
938#[derive(Debug, Clone, Copy, PartialEq, Eq)]
939pub enum BoardMode {
940 Closed,
941 Unknown,
942 Control,
943 VeriComm,
944 FpgaProgrammer,
945 VeriInstrument,
946 VeriLink,
947 VeriSoc,
948 VeriCommPro,
949 VeriSdk,
950 FlashRead,
951 FlashWrite,
952}
953
954impl BoardMode {
955 pub fn as_str(self) -> &'static str {
956 match self {
957 Self::Closed => "closed",
958 Self::Unknown => "unknown",
959 Self::Control => "control",
960 Self::VeriComm => "vericomm",
961 Self::FpgaProgrammer => "fpga_programmer",
962 Self::VeriInstrument => "veri_instrument",
963 Self::VeriLink => "veri_link",
964 Self::VeriSoc => "veri_soc",
965 Self::VeriCommPro => "vericomm_pro",
966 Self::VeriSdk => "veri_sdk",
967 Self::FlashRead => "flash_read",
968 Self::FlashWrite => "flash_write",
969 }
970 }
971
972 fn command_byte(self) -> Option<u8> {
973 Some(match self {
974 Self::Control => 0x00,
975 Self::FpgaProgrammer => 0x02,
976 Self::VeriComm => 0x03,
977 Self::VeriSdk => 0x04,
978 Self::FlashRead => 0x05,
979 Self::VeriInstrument => 0x08,
980 Self::VeriLink => 0x09,
981 Self::VeriSoc => 0x0a,
982 Self::VeriCommPro => 0x0b,
983 Self::FlashWrite => 0x15,
984 Self::Closed | Self::Unknown => return None,
985 })
986 }
987}
988
989#[derive(Debug, Clone)]
990pub struct IoConfig {
991 pub clock_high_delay: u16,
992 pub clock_low_delay: u16,
993 pub vericomm_isv: u8,
994 pub clock_check_enabled: bool,
995 pub mode_selector: u8,
996 pub licence_key: Option<u16>,
997}
998
999impl Default for IoConfig {
1000 fn default() -> Self {
1001 Self {
1002 clock_high_delay: 11,
1003 clock_low_delay: 11,
1004 vericomm_isv: 0,
1005 clock_check_enabled: false,
1006 mode_selector: 0,
1007 licence_key: Some(0xff40),
1008 }
1009 }
1010}
1011
1012#[derive(Debug, Clone, Default)]
1013struct CryptoState {
1014 table: [u16; 32],
1015 encode_index: usize,
1016 decode_index: usize,
1017}
1018
1019impl CryptoState {
1020 fn table_mut(&mut self) -> &mut [u16; 32] {
1021 &mut self.table
1022 }
1023
1024 fn decode_table(&mut self) {
1025 self.table[0] = !self.table[0];
1026 for idx in 1..self.table.len() {
1027 let prev = self.table[idx - 1];
1028 self.table[idx] ^= prev;
1029 }
1030 self.reset_indices();
1031 }
1032
1033 fn encrypt_words(&mut self, buffer: &mut [u16]) {
1034 let key = &self.table[0..16];
1035 let mut index = self.encode_index;
1036 for word in buffer.iter_mut() {
1037 *word ^= key[index];
1038 index = (index + 1) & 0x0f;
1039 }
1040 self.encode_index = index;
1041 }
1042
1043 fn decrypt_words(&mut self, buffer: &mut [u16]) {
1044 let key = &self.table[16..32];
1045 let mut index = self.decode_index;
1046 for word in buffer.iter_mut() {
1047 *word ^= key[index];
1048 index = (index + 1) & 0x0f;
1049 }
1050 self.decode_index = index;
1051 }
1052
1053 fn reset_indices(&mut self) {
1054 self.encode_index = 0;
1055 self.decode_index = 0;
1056 }
1057}
1058
1059fn validate_transfer_buffers(
1060 write_words: usize,
1061 read_words: usize,
1062 fifo_capacity_words: usize,
1063) -> Result<()> {
1064 if write_words != read_words {
1065 return Err(Error::InvalidBufferLength {
1066 context: "vericomm transfer",
1067 expected: write_words,
1068 actual: read_words,
1069 });
1070 }
1071
1072 if write_words > fifo_capacity_words {
1073 return Err(Error::BufferTooLarge {
1074 context: "vericomm transfer",
1075 max_words: fifo_capacity_words,
1076 actual_words: write_words,
1077 });
1078 }
1079
1080 let write_bytes = write_words * std::mem::size_of::<u16>();
1081 if write_bytes % VERICOMM_TRANSFER_PACKET_BYTES != 0 {
1082 return Err(Error::InvalidBufferLength {
1083 context: "vericomm transfer packet alignment",
1084 expected: write_words
1085 .next_multiple_of(VERICOMM_TRANSFER_PACKET_BYTES / std::mem::size_of::<u16>()),
1086 actual: write_words,
1087 });
1088 }
1089
1090 Ok(())
1091}
1092
1093fn validate_window_frame_words(
1094 expected_words: usize,
1095 actual_words: usize,
1096 context: &'static str,
1097) -> Result<()> {
1098 if expected_words != actual_words {
1099 return Err(Error::InvalidBufferLength {
1100 context,
1101 expected: expected_words,
1102 actual: actual_words,
1103 });
1104 }
1105
1106 Ok(())
1107}
1108
1109pub(crate) fn bitstream_chunk_words(config: &Config) -> Result<usize> {
1110 let fifo_words = usize::from(config.fifo_size_words());
1111 if fifo_words == 0 {
1112 return Err(Error::UnexpectedResponse(
1113 "device reported zero-length programming FIFO",
1114 ));
1115 }
1116 Ok(fifo_words)
1117}
1118
1119fn aligned_request_len(max_packet_size: usize, payload_bytes: usize) -> usize {
1120 let payload_bytes = payload_bytes
1121 .next_multiple_of(VERICOMM_TRANSFER_PACKET_BYTES)
1122 .max(max_packet_size.max(1));
1123 let rem = payload_bytes % max_packet_size.max(1);
1124 if rem == 0 {
1125 payload_bytes
1126 } else {
1127 payload_bytes + (max_packet_size - rem)
1128 }
1129}
1130
1131fn request_bytes_for_words(max_packet_size: usize, word_len: usize) -> usize {
1132 aligned_request_len(max_packet_size, word_len * std::mem::size_of::<u16>())
1133}
1134
1135fn discard_undersized_buffers(pool: &mut Vec<Buffer>, min_capacity: usize) {
1136 pool.retain(|buffer| buffer.capacity() >= min_capacity);
1137}
1138
1139fn buffer_identity(buffer: &Buffer) -> usize {
1140 buffer.as_ptr() as usize
1141}
1142
1143fn store_window_read_completion(
1144 pending_reads: &mut VecDeque<PendingWindowRead>,
1145 completion: Completion,
1146) -> Result<()> {
1147 let buffer_id = buffer_identity(&completion.buffer);
1148 let Some(pending) = pending_reads
1149 .iter_mut()
1150 .find(|pending| pending.buffer_id == buffer_id)
1151 else {
1152 return Err(Error::UnexpectedResponse(
1153 "pipeline read completion did not match a pending transfer",
1154 ));
1155 };
1156
1157 pending.complete(completion)
1158}
1159
1160fn submit_pipeline_write(
1161 crypto: &mut CryptoState,
1162 endpoint: &mut UsbEndpoint<Bulk, Out>,
1163 tx: &[u16],
1164 mut buffer: Buffer,
1165) {
1166 buffer.clear();
1167 let byte_len = std::mem::size_of_val(tx);
1168 buffer.extend_fill(byte_len, 0);
1169 words_to_bytes(tx, &mut buffer[..byte_len]);
1170 crypto.encrypt_words(bytes_as_words_mut(&mut buffer[..byte_len]));
1171 endpoint.submit(buffer);
1172}
1173
1174fn submit_pipeline_read(
1175 endpoint: &mut UsbEndpoint<Bulk, In>,
1176 mut buffer: Buffer,
1177 request_bytes: usize,
1178) {
1179 buffer.clear();
1180 buffer.set_requested_len(request_bytes);
1181 endpoint.submit(buffer);
1182}
1183
1184fn bytes_into_words(bytes: &[u8], out: &mut [u16]) {
1185 for (index, chunk) in bytes.chunks_exact(2).take(out.len()).enumerate() {
1186 out[index] = u16::from_le_bytes([chunk[0], chunk[1]]);
1187 }
1188}
1189
1190fn bytes_as_words(bytes: &[u8]) -> &[u16] {
1191 unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const u16, bytes.len() / 2) }
1192}
1193
1194fn transfer_error(err: nusb::transfer::TransferError, context: &'static str) -> Error {
1195 Error::Usb {
1196 source: Box::new(std::io::Error::other(format!("{context}: {err}"))),
1197 context,
1198 }
1199}
1200
1201fn words_to_bytes(words: &[u16], out: &mut [u8]) {
1202 for (index, word) in words.iter().copied().enumerate() {
1203 let [lo, hi] = word.to_le_bytes();
1204 out[index * 2] = lo;
1205 out[index * 2 + 1] = hi;
1206 }
1207}
1208
1209fn bytes_as_words_mut(bytes: &mut [u8]) -> &mut [u16] {
1210 unsafe { std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut u16, bytes.len() / 2) }
1211}
1212
1213fn cancel_pending_transfer<Dir>(endpoint: &mut UsbEndpoint<Bulk, Dir>) -> Completion
1214where
1215 Dir: EndpointDirection,
1216{
1217 endpoint.cancel_all();
1218 loop {
1219 if let Some(completion) = endpoint.wait_next_complete(Duration::from_secs(1)) {
1220 return completion;
1221 }
1222 }
1223}
1224
1225fn should_retry_initialize(err: &Error) -> bool {
1226 matches!(err, Error::Timeout(_) | Error::Usb { .. })
1227}
1228
1229#[cfg(test)]
1230mod tests {
1231 use nusb::transfer::Buffer;
1232
1233 #[test]
1234 fn words_to_bytes_roundtrip() {
1235 let words = [0x1234u16, 0xabcd];
1236 let mut bytes = [0u8; 4];
1237 super::words_to_bytes(&words, &mut bytes);
1238 assert_eq!(bytes, [0x34, 0x12, 0xcd, 0xab]);
1239 }
1240
1241 #[test]
1242 fn aligned_request_len_rounds_up_to_packet_boundary() {
1243 assert_eq!(super::aligned_request_len(512, 513), 1024);
1244 assert_eq!(super::aligned_request_len(512, 512), 512);
1245 }
1246
1247 #[test]
1248 fn request_bytes_for_words_keep_frame_request_size() {
1249 assert_eq!(super::request_bytes_for_words(512, 256), 512);
1250 assert_eq!(super::request_bytes_for_words(512, 512), 1024);
1251 }
1252
1253 #[test]
1254 fn discard_undersized_buffers_drops_stale_pool_entries() {
1255 let mut pool = vec![
1256 Buffer::from(vec![0u8; 512]),
1257 Buffer::from(vec![0u8; 1024]),
1258 Buffer::from(vec![0u8; 256]),
1259 ];
1260
1261 super::discard_undersized_buffers(&mut pool, 600);
1262
1263 let capacities = pool.iter().map(Buffer::capacity).collect::<Vec<_>>();
1264 assert_eq!(capacities, vec![1024]);
1265 }
1266
1267 #[test]
1268 fn fixed_window_length_error_shape_is_stable() {
1269 let err = Error::InvalidBufferLength {
1270 context: "vericomm transfer window submit",
1271 expected: 1,
1272 actual: 0,
1273 };
1274 assert_eq!(
1275 err.to_string(),
1276 "invalid buffer length for `vericomm transfer window submit` (expected 1, got 0)"
1277 );
1278 }
1279
1280 #[test]
1281 fn io_session_struct_caches_endpoint_adapters() {
1282 let type_name = std::any::type_name::<super::IoSession<'_>>();
1283 assert!(type_name.contains("IoSession"));
1284 }
1285
1286 #[test]
1287 fn io_transfer_window_type_is_stable() {
1288 let type_name = std::any::type_name::<super::IoTransferWindow<'_, '_>>();
1289 assert!(type_name.contains("IoTransferWindow"));
1290 }
1291
1292 #[test]
1293 fn pipeline_error_shapes_are_stable() {
1294 assert_eq!(
1295 Error::PipelineEmpty.to_string(),
1296 "transfer pipeline has no pending transfers"
1297 );
1298 assert_eq!(
1299 Error::PipelineFull { capacity: 4 }.to_string(),
1300 "transfer pipeline is full (capacity 4 outstanding transfers)"
1301 );
1302 }
1303
1304 #[test]
1305 fn transfer_window_rejects_wrong_frame_length() {
1306 let err = super::validate_window_frame_words(256, 128, "vericomm transfer window submit")
1307 .unwrap_err();
1308 assert_eq!(
1309 err.to_string(),
1310 "invalid buffer length for `vericomm transfer window submit` (expected 256, got 128)"
1311 );
1312 }
1313
1314 #[test]
1315 fn transfer_window_matches_reads_by_buffer_identity() {
1316 let mut pending = VecDeque::new();
1317 let rx_a = Buffer::from(vec![0u8; 512]);
1318 let rx_b = Buffer::from(vec![0u8; 512]);
1319 let id_a = super::buffer_identity(&rx_a);
1320 let id_b = super::buffer_identity(&rx_b);
1321 pending.push_back(super::PendingWindowRead::new(id_a));
1322 pending.push_back(super::PendingWindowRead::new(id_b));
1323
1324 super::store_window_read_completion(
1325 &mut pending,
1326 nusb::transfer::Completion {
1327 buffer: rx_b,
1328 actual_len: 512,
1329 status: Ok(()),
1330 },
1331 )
1332 .expect("read completion should match the second transfer");
1333
1334 assert!(pending[0].completion.is_none());
1335 assert_eq!(
1336 super::buffer_identity(&pending[1].completion.as_ref().unwrap().buffer),
1337 id_b
1338 );
1339 }
1340
1341 #[test]
1342 fn initialize_retry_only_triggers_for_transport_failures() {
1343 assert!(super::should_retry_initialize(&Error::Timeout(
1344 "sync_delay"
1345 )));
1346 assert!(super::should_retry_initialize(&Error::Usb {
1347 source: Box::new(std::io::Error::other("boom")),
1348 context: "nusb_bulk_read",
1349 }));
1350 assert!(!super::should_retry_initialize(&Error::NotProgrammed));
1351 assert!(!super::should_retry_initialize(&Error::VersionMismatch {
1352 expected: 0x0220,
1353 actual: 0x0000,
1354 }));
1355 }
1356
1357 use super::{Board, BoardMode, CryptoState, IoConfig, validate_transfer_buffers};
1358 use crate::error::Error;
1359 use crate::usb::TransportConfig;
1360 use std::collections::VecDeque;
1361 use std::time::Duration;
1362
1363 #[test]
1364 fn board_accepts_custom_transport_config() {
1365 let transport = TransportConfig {
1366 usb_timeout: Duration::from_millis(250),
1367 sync_timeout: Duration::from_millis(750),
1368 reset_on_open: true,
1369 clear_halt_on_open: false,
1370 };
1371 let board = Board::open_with_transport(transport);
1372 assert!(
1373 board.is_err()
1374 || board
1375 .as_ref()
1376 .map(|b| b.transport() == &transport)
1377 .unwrap_or(false)
1378 );
1379 }
1380
1381 #[test]
1382 fn encrypted_transfer_buffer_is_copied_before_mutation() {
1383 let mut crypto = CryptoState::default();
1384 crypto.table[0] = 0x00ff;
1385 let input = [0x1234u16, 0xabcd];
1386 let mut encrypted = input;
1387 crypto.encrypt_words(&mut encrypted);
1388
1389 assert_eq!(input, [0x1234, 0xabcd]);
1390 assert_eq!(encrypted, [0x12cb, 0xabcd]);
1391 }
1392
1393 #[test]
1394 fn vericomm_transfer_requires_matching_buffer_lengths() {
1395 let err = validate_transfer_buffers(4, 3, 16).expect_err("validation should fail");
1396 match err {
1397 Error::InvalidBufferLength {
1398 context,
1399 expected,
1400 actual,
1401 } => {
1402 assert_eq!(context, "vericomm transfer");
1403 assert_eq!(expected, 4);
1404 assert_eq!(actual, 3);
1405 }
1406 other => panic!("unexpected error: {other}"),
1407 }
1408 }
1409
1410 #[test]
1411 fn vericomm_transfer_rejects_oversize_payloads() {
1412 let err = validate_transfer_buffers(17, 17, 16).expect_err("validation should fail");
1413 match err {
1414 Error::BufferTooLarge {
1415 context,
1416 max_words,
1417 actual_words,
1418 } => {
1419 assert_eq!(context, "vericomm transfer");
1420 assert_eq!(max_words, 16);
1421 assert_eq!(actual_words, 17);
1422 }
1423 other => panic!("unexpected error: {other}"),
1424 }
1425 }
1426
1427 #[test]
1428 fn io_config_defaults_match_previous_tuning() {
1429 let cfg = IoConfig::default();
1430 assert_eq!(cfg.clock_high_delay, 11);
1431 assert_eq!(cfg.clock_low_delay, 11);
1432 assert_eq!(cfg.licence_key, Some(0xff40));
1433 }
1434
1435 #[test]
1436 fn board_mode_labels_are_stable() {
1437 assert_eq!(BoardMode::Control.as_str(), "control");
1438 assert_eq!(BoardMode::VeriComm.as_str(), "vericomm");
1439 }
1440}