Skip to content

Commit b91c7ad

Browse files
committed
workflow: moved command engine structure in a separate file
1 parent b058b02 commit b91c7ad

3 files changed

Lines changed: 144 additions & 84 deletions

File tree

chips/x86_q35/src/cmd_fifo.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Licensed under the Apache License, Version 2.0 or the MIT License.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// Copyright Tock Contributors 2025.
4+
5+
//! Minimal, non-alloc ring FIFO for small fixed-size queues.
6+
7+
// Why `FifoItem::EMPTY` and const init?
8+
// We need to create the FIFO in static memory (no heap). That means the backing
9+
// array `[T; N]` must be initialized in a `const` context. I believe we can't directly
10+
// call `T::default()` in `const fn`, so `[T::default(); N]` won’t work.
11+
// By requiring `T: FifoItem` with a `const EMPTY`, we can do `[T::EMPTY; N]`
12+
// and safely build the FIFO at compile time
13+
14+
pub(crate) trait FifoItem: Copy {
15+
/// Const zero/empty value used to initialize the backing array.
16+
const EMPTY: Self;
17+
}
18+
19+
pub(crate) struct Fifo<T: FifoItem, const N: usize> {
20+
buf: [T; N],
21+
head: usize,
22+
tail: usize,
23+
len: usize,
24+
}
25+
26+
impl<T: FifoItem, const N: usize> Fifo<T, N> {
27+
pub(crate) const fn new() -> Self {
28+
Self {
29+
buf: [T::EMPTY; N],
30+
head: 0,
31+
tail: 0,
32+
len: 0,
33+
}
34+
}
35+
36+
#[inline]
37+
pub(crate) fn is_empty(&self) -> bool {
38+
self.len == 0
39+
}
40+
#[inline]
41+
pub(crate) fn is_full(&self) -> bool {
42+
self.len == N
43+
}
44+
#[inline]
45+
pub(crate) fn push(&mut self, item: T) -> Result<(), ()> {
46+
if self.is_full() {
47+
return Err(());
48+
}
49+
self.buf[self.head] = item;
50+
self.head = (self.head + 1) % N;
51+
self.len += 1;
52+
Ok(())
53+
}
54+
55+
#[inline]
56+
pub(crate) fn peek(&self) -> Option<&T> {
57+
if self.is_empty() {
58+
None
59+
} else {
60+
Some(&self.buf[self.tail])
61+
}
62+
}
63+
64+
#[inline]
65+
pub(crate) fn peek_mut(&mut self) -> Option<&mut T> {
66+
if self.is_empty() {
67+
None
68+
} else {
69+
Some(&mut self.buf[self.tail])
70+
}
71+
}
72+
73+
#[inline]
74+
pub(crate) fn pop(&mut self) -> Option<T> {
75+
if self.is_empty() {
76+
return None;
77+
}
78+
let item = self.buf[self.tail];
79+
self.tail = (self.tail + 1) % N;
80+
self.len -= 1;
81+
Some(item)
82+
}
83+
}

chips/x86_q35/src/keyboard.rs

Lines changed: 60 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//! - OSDev: PS/2 Keyboard — https://wiki.osdev.org/PS/2_Keyboard
1414
//! - OSDev: Keyboard / Scan Code Set 2 — https://wiki.osdev.org/Keyboard#Scan_Code_Set_2
1515
16+
use crate::cmd_fifo::Fifo as CmdFifo;
1617
use crate::ps2::Ps2Client;
1718
use crate::ps2::Ps2Controller;
1819
use core::cell::{Cell, RefCell};
@@ -86,6 +87,28 @@ impl CmdEntry {
8687
fn is_done(&self) -> bool {
8788
(self.idx as usize) >= (self.len as usize)
8889
}
90+
91+
// helper to avoid duplicating the “turn a byte slice into a queued command” logic
92+
fn try_from_bytes(seq: &[u8]) -> Option<Self> {
93+
if seq.is_empty() || seq.len() > CMD_MAX_LEN {
94+
return None;
95+
}
96+
let mut e = CmdEntry::empty();
97+
e.bytes[..seq.len()].copy_from_slice(seq);
98+
e.len = seq.len() as u8;
99+
Some(e)
100+
}
101+
}
102+
103+
// this is needed so CmdEntry expects a FifoItem, and not a const default
104+
impl crate::cmd_fifo::FifoItem for CmdEntry {
105+
const EMPTY: Self = Self::empty();
106+
}
107+
108+
impl Default for CmdEntry {
109+
fn default() -> Self {
110+
CmdEntry::empty()
111+
}
89112
}
90113

91114
/// We capture bytes and will later add:
@@ -108,11 +131,8 @@ pub struct Keyboard<'a> {
108131
shift_r: Cell<bool>,
109132
caps: Cell<bool>,
110133

111-
// here comes the engine
112-
cmd_q: RefCell<[CmdEntry; CMDQ_LEN]>,
113-
q_head: Cell<usize>, // write cursor
114-
q_tail: Cell<usize>, // read cursor
115-
q_count: Cell<usize>, // number of entries enqueued
134+
// command engine queue (ring FIFO)
135+
cmd_q: RefCell<CmdFifo<CmdEntry, CMDQ_LEN>>,
116136

117137
in_flight: Cell<bool>, // waiting for ACK/RESEND to the last sent byte
118138
retries_left: Cell<u8>, // remaining entries for the current byte
@@ -138,10 +158,7 @@ impl<'a> Keyboard<'a> {
138158
shift_r: Cell::new(false),
139159
caps: Cell::new(false),
140160

141-
cmd_q: RefCell::new([CmdEntry::empty(); CMDQ_LEN]),
142-
q_head: Cell::new(0),
143-
q_tail: Cell::new(0),
144-
q_count: Cell::new(0),
161+
cmd_q: RefCell::new(CmdFifo::new()),
145162

146163
in_flight: Cell::new(false),
147164
retries_left: Cell::new(0),
@@ -285,122 +302,81 @@ impl<'a> Keyboard<'a> {
285302
/// No completion callback; failures are tracked internally (telemetry counters).
286303
287304
pub fn enqueue_command(&self, seq: &[u8]) -> bool {
288-
if seq.is_empty() || seq.len() > CMD_MAX_LEN || self.q_count.get() >= CMDQ_LEN {
305+
let Some(entry) = CmdEntry::try_from_bytes(seq) else {
306+
return false;
307+
};
308+
let mut q = self.cmd_q.borrow_mut();
309+
if q.is_full() {
289310
return false;
290311
}
291-
292-
// Copy into the queue at head
293-
let head = self.q_head.get();
294-
{
295-
let mut q = self.cmd_q.borrow_mut();
296-
if let Some(e) = q.get_mut(head) {
297-
e.bytes = [0; CMD_MAX_LEN];
298-
e.bytes[..seq.len()].copy_from_slice(seq);
299-
e.len = seq.len() as u8;
300-
e.idx = 0;
301-
} else {
302-
debug_assert!(false, "cmd_q head OOB: head={}", head);
303-
return false;
304-
}
305-
}
306-
self.q_head.set((head + 1) % CMDQ_LEN);
307-
self.q_count.set(self.q_count.get() + 1);
312+
let _ = q.push(entry);
313+
drop(q);
308314
self.drive_tx();
309-
310315
true
311316
}
312317

313318
/// Try to transmit the next byte of the current command
314319
fn drive_tx(&self) {
315-
if self.in_flight.get() || self.q_count.get() == 0 {
320+
if self.in_flight.get() {
316321
return;
317322
}
318323

319-
let tail = self.q_tail.get();
320-
// Peek current entry at tail and the next byte to send
321-
let (byte_opt, done);
322-
{
324+
// Peek current command
325+
let (byte_opt, done_opt) = {
323326
let q = self.cmd_q.borrow();
324-
let e = match q.get(tail) {
325-
Some(e) => e,
326-
None => {
327-
debug_assert!(false, "cmd_q tail OOB: tail={}", tail);
328-
return;
329-
}
330-
};
331-
if e.is_done() {
332-
byte_opt = None;
333-
done = true;
334-
} else {
335-
byte_opt = Some(e.bytes[e.idx as usize]);
336-
done = false;
327+
match q.peek() {
328+
None => (None, None), // empty
329+
Some(e) if e.is_done() => (None, Some(true)),
330+
Some(e) => (Some(e.bytes[e.idx as usize]), Some(false)),
337331
}
338-
}
332+
};
339333

340-
if done {
341-
// This shouldn't persist-pop and try again
342-
self.pop_cmd();
343-
self.drive_tx();
344-
return;
334+
match done_opt {
335+
None => return, // queue empty
336+
Some(true) => {
337+
// finished entry => pop and try next
338+
self.cmd_q.borrow_mut().pop();
339+
self.drive_tx();
340+
return;
341+
}
342+
Some(false) => {}
345343
}
346344

347345
if let Some(b) = byte_opt {
348-
// Attempt to send. If the controller times out, do not mark inflight,
349-
// so a later call may retry. We also don't advance idx here, only on ACK
350346
match self.ps2.send_port1(b) {
351347
Ok(()) => {
352348
self.in_flight.set(true);
353349
if self.retries_left.get() == 0 {
354-
// first attempt for this byte
355350
self.retries_left.set(MAX_RETRIES);
356351
}
357352
self.cmd_sent_bytes
358353
.set(self.cmd_sent_bytes.get().wrapping_add(1));
359354
}
360-
361355
Err(_e) => {
362-
// Controller busy/timeout; count and let a later tick retry
363356
self.cmd_send_errors
364357
.set(self.cmd_send_errors.get().wrapping_add(1));
365358
}
366359
}
367360
}
368361
}
369362

370-
fn pop_cmd(&self) {
371-
if self.q_count.get() == 0 {
372-
return;
373-
}
374-
self.q_tail.set((self.q_tail.get() + 1) % CMDQ_LEN);
375-
self.q_count.set(self.q_count.get() - 1);
376-
}
377-
378363
fn advance_idx_after_ack(&self) {
379-
let tail = self.q_tail.get();
380-
// Increment idx of the current entry; if complete, pop
381364
let mut finished = false;
382365
{
383366
let mut q = self.cmd_q.borrow_mut();
384-
let e = match q.get_mut(tail) {
385-
Some(e) => e,
386-
None => {
387-
debug_assert!(false, "cmd_q tail OOB on ACK: tail={}", tail);
388-
return;
367+
if let Some(e) = q.peek_mut() {
368+
if !e.is_done() {
369+
e.idx = e.idx.saturating_add(1);
370+
}
371+
if e.is_done() {
372+
finished = true;
389373
}
390-
};
391-
392-
if !e.is_done() {
393-
e.idx = e.idx.saturating_add(1);
394374
}
395-
if e.is_done() {
396-
finished = true;
375+
if finished {
376+
q.pop();
397377
}
398378
}
399-
if finished {
400-
self.pop_cmd();
401-
}
402-
403-
// New byte will get a fresh budget retry on first send
379+
// New byte will get a fresh retry budget on first send
404380
self.retries_left.set(0);
405381
}
406382

@@ -492,7 +468,7 @@ impl Ps2Client for Keyboard<'_> {
492468
// Give up on this command
493469
self.cmd_drops.set(self.cmd_drops.get().wrapping_add(1));
494470
self.in_flight.set(false);
495-
self.pop_cmd();
471+
self.cmd_q.borrow_mut().pop();
496472
self.retries_left.set(0); // clear for the next new byte
497473
self.drive_tx();
498474
}

chips/x86_q35/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod pit;
2626
pub mod ps2;
2727
pub mod serial;
2828

29+
mod cmd_fifo;
2930
pub mod keyboard;
3031
pub mod vga;
3132
pub mod vga_uart_driver;

0 commit comments

Comments
 (0)