Skip to main content

vlfd_rs/
session.rs

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
347/// A rolling VeriComm pipeline that keeps up to `capacity` transfers in flight.
348///
349/// All transfers in one window have the same word length chosen up front.
350/// Submit frames with [`Self::submit`] and retire them in order with
351/// [`Self::receive_into`]. Dropping the window cancels any remaining transfers
352/// and recycles their buffers back into the parent [`IoSession`].
353pub 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    /// Opens a fixed-size rolling transfer window that can keep `capacity`
482    /// VeriComm transfers of `words` words outstanding at once.
483    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    /// Queues one transfer into the rolling window.
878    pub fn submit(&mut self, tx: &[u16]) -> Result<()> {
879        self.submit_with_profile(tx, None)
880    }
881
882    /// Queues one transfer and returns a stage profile for that submission.
883    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    /// Retires the oldest in-flight transfer into `output`.
890    pub fn receive_into(&mut self, output: &mut [u16]) -> Result<()> {
891        self.receive_into_with_profile(output, None)
892    }
893
894    /// Retires the oldest in-flight transfer and returns its stage profile.
895    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}