atmos/kernel/net/tx_queue.rs
1//! L2: NIC 送信キューの容量判定(純粋ロジック)。
2//!
3//! ## なぜ必要か
4//! 【2026-07-30 実測】背景写真(3.7MB)の取得中に
5//! ```text
6//! [NET][DIAG] nic tx queue FULL (size=8) dropped_total=2192
7//! ```
8//! と、**送信パケットを 2192 個も捨てていた**。
9//! 送信キューは 8 エントリしかなく、USB の送信が追いつかないと溢れる。
10//!
11//! 捨てられた送信は上位から見ると「送ったのに応答が来ない」に見え、
12//! DNS タイムアウト・TCP 接続タイムアウト・取得の停止として現れる。
13//! **上層では原因が分からない典型**なので、容量判定を純粋関数にして
14//! 単体試験で固定する。
15//!
16//! グローバル状態にもハードウェアにも依存しない。
17
18/// 投入結果。
19#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20pub enum EnqueueOutcome {
21 /// 空きスロットへ格納した。
22 Stored { slot: usize },
23 /// 満杯。**捨てた**(上位は再送で回復する必要がある)。
24 Dropped,
25}
26
27/// 空きスロットを探す。`is_free(i)` が真のスロットのうち最小の添字を返す。
28///
29/// 満杯なら `Dropped`。**満杯を黙って上書きしない**
30/// (送信待ちのパケットを壊すと、相手から見て途中が欠けたストリームになる)。
31pub fn find_slot(len: usize, is_free: impl Fn(usize) -> bool) -> EnqueueOutcome {
32 for i in 0..len {
33 if is_free(i) {
34 return EnqueueOutcome::Stored { slot: i };
35 }
36 }
37 EnqueueOutcome::Dropped
38}
39
40/// キューの使用率(0〜100%)。監視・診断用。
41///
42/// `len` が 0 は不正(ゼロ除算)なので、黙って戻らず
43/// エラーログを出して `None` を返す。
44pub fn utilization_percent(used: usize, len: usize) -> Option<u32> {
45 if len == 0 {
46 crate::error!("[NET][TXQ] キュー長が 0 です");
47 return None;
48 }
49 if used > len {
50 crate::error!("[NET][TXQ] 使用数がキュー長を超えています used={} len={}", used, len);
51 return None;
52 }
53 Some(((used * 100) / len) as u32)
54}
55
56/// この使用率なら送信元へ背圧をかけるべきか。
57///
58/// 満杯になってから捨てるのではなく、**手前で送信を絞る**方が
59/// パケットロスによる再送より安上がり。
60pub const BACKPRESSURE_THRESHOLD_PERCENT: u32 = 75;
61
62pub fn should_apply_backpressure(used: usize, len: usize) -> bool {
63 match utilization_percent(used, len) {
64 Some(p) => p >= BACKPRESSURE_THRESHOLD_PERCENT,
65 // 判定不能なら安全側=背圧をかけない(送信を止めて通信不能にしない)。
66 None => false,
67 }
68}