Skip to main content

atmos/kernel/
usb_toggle.rs

1//! L1: USB バルク転送のデータトグル(DATA0/DATA1)管理。
2//!
3//! ## なぜ純粋モジュールにするか
4//! データトグルがずれると、デバイスは受け取ったパケットを「重複」とみなして
5//! 黙って捨てる。**エラーも出ないまま通信が止まる**ため、
6//! 今回の「NAK でチャネルが張り付く」欠陥と同じく発見が難しい。
7//! レジスタから独立した純粋ロジックにして単体試験で固定する。
8//!
9//! ## USB 2.0 の規則(§8.6 Data Toggle Synchronization)
10//! - バルク転送は DATA0/DATA1 を**トランザクションごとに交互**に使う
11//! - **ACK されたときだけ**トグルを進める
12//! - **NAK ではトグルを進めない**(デバイスは受け取っていない)
13//! - **STALL 後はエンドポイントを CLEAR_FEATURE でリセットし、DATA0 に戻す**
14//! - 送信側の複数パケット転送では、パケット数の偶奇でトグルが決まる
15
16/// パケット識別子(DWC2 の HCTSIZ PID フィールド表現)。
17pub const PID_DATA0: u32 = 0;
18pub const PID_DATA2: u32 = 1;
19pub const PID_DATA1: u32 = 2;
20pub const PID_SETUP_MDATA: u32 = 3;
21
22/// トランザクションの結果。
23#[derive(Debug, PartialEq, Eq, Clone, Copy)]
24pub enum TxnResult {
25    /// デバイスが受理した。トグルを進める。
26    Acked,
27    /// デバイスが一時的に受け取れなかった。**トグルは進めない**。
28    Nak,
29    /// エンドポイント停止。**DATA0 へリセット**する。
30    Stall,
31    /// 転送エラー。デバイスが受理したか不明なので**進めない**
32    /// (進めると以後すべてのパケットが捨てられる)。
33    Error,
34}
35
36/// トグル値(0 = DATA0, 1 = DATA1)。
37pub type Toggle = u32;
38
39/// 1 トランザクションの結果からトグルを更新する。
40pub fn advance(current: Toggle, result: TxnResult) -> Toggle {
41    match result {
42        TxnResult::Acked => (current + 1) % 2,
43        TxnResult::Nak | TxnResult::Error => current % 2,
44        TxnResult::Stall => 0,
45    }
46}
47
48/// 複数パケットをまとめて送った場合のトグル更新。
49///
50/// `packet_count` はそのトランスファに含まれるパケット数。
51/// **転送が成功したときだけ呼ぶこと。** 失敗時に進めるとずれる。
52pub fn advance_by_packets(current: Toggle, packet_count: u32, result: TxnResult) -> Toggle {
53    match result {
54        TxnResult::Acked => (current + packet_count) % 2,
55        TxnResult::Nak | TxnResult::Error => current % 2,
56        TxnResult::Stall => 0,
57    }
58}
59
60/// 転送長と最大パケットサイズからパケット数を求める。
61///
62/// 長さ 0 でも 1 パケット(ゼロ長パケット)を送るので 1 を返す。
63/// `mps` が 0 は不正なので、黙って 0 除算せずエラーログを出して `None` を返す。
64pub fn packet_count(total_len: usize, mps: u32) -> Option<u32> {
65    if mps == 0 {
66        crate::error!("[USB][TOGGLE] 最大パケットサイズが 0 です");
67        return None;
68    }
69    if total_len == 0 {
70        return Some(1);
71    }
72    Some((total_len as u32).div_ceil(mps))
73}
74
75/// DWC2 の HCTSIZ PID フィールドからトグル値を復元する。
76///
77/// `DATA1` は 2、それ以外(DATA0/DATA2/MDATA)は 0 として扱う。
78pub fn toggle_from_pid(pid: u32) -> Toggle {
79    if pid == PID_DATA1 {
80        1
81    } else {
82        0
83    }
84}
85
86/// トグル値を HCTSIZ に書く PID 値へ変換する。
87pub fn pid_from_toggle(toggle: Toggle) -> u32 {
88    if toggle % 2 == 0 {
89        PID_DATA0
90    } else {
91        PID_DATA1
92    }
93}