FUCKYOU
This commit is contained in:
+152
-45
@@ -5,12 +5,12 @@
|
||||
*
|
||||
* Virtio Spec 1.1 §5.1 (Network Device)。
|
||||
* 队列:0=RX(device→driver 收包),1=TX(driver→device 发包)。
|
||||
* 每帧前置 virtio_net_hdr(10/12 字节,无 GSO 时全零)。
|
||||
* 每帧前置 virtio_net_hdr(10 字节,无 GSO/MRG_RXBUF 协商时全零)。
|
||||
*
|
||||
* 最小可用实现:
|
||||
* - 无 GSO/TSO/checksum offload(feature 不声明,guest 走软件路径)
|
||||
* - RX 轮询:guest RX 队列提交空 buffer,queue_notify 时尝试 rx_poll
|
||||
* - TX:queue_notify 时取帧直接 net->tx 发出
|
||||
* - RX:guest 提交空 buffer,queue_notify / 周期 poll 时调 host rx_poll 填充
|
||||
* - TX:queue_notify 时拼接描述符链成帧,直接 net->tx 发出
|
||||
*/
|
||||
|
||||
#include <utsm/virtio_mmio.h>
|
||||
@@ -32,7 +32,7 @@ struct virtio_net_hdr {
|
||||
|
||||
#define VIRTIO_NET_HDR_LEN 10
|
||||
|
||||
/* feature bits(我们都不声明,保持最简) */
|
||||
/* feature bits(除 MAC/STATUS 外都不声明,保持最简) */
|
||||
#define VIRTIO_NET_F_CSUM 0
|
||||
#define VIRTIO_NET_F_GUEST_CSUM 1
|
||||
#define VIRTIO_NET_F_MAC 5
|
||||
@@ -48,6 +48,25 @@ struct virtio_net_config {
|
||||
static u8 g_net_mac[6] = { 0x52, 0x54, 0x00, 0xDE, 0x5A, 0x01 };
|
||||
static int g_net_bound = -1;
|
||||
|
||||
/* ===== 数据路径统计(低频串口摘要,便于现场诊断 guest 网络问题) ===== */
|
||||
static u64 g_stat_rx_filled; /* 已填充给 guest 的 RX 帧数 */
|
||||
static u64 g_stat_rx_drop_nobuf; /* host 有包但 guest 无可用 RX buffer → 主动丢弃 */
|
||||
static u64 g_stat_rx_badbuf; /* guest RX 描述符异常(未映射/hdr 太小/无 data 段) */
|
||||
static u64 g_stat_tx_sent; /* 成功送交 host 网卡的 TX 帧数 */
|
||||
static u64 g_stat_tx_drop; /* TX 丢弃帧数(链异常/超 MTU/host 发送失败) */
|
||||
|
||||
/* 每 512 次数据路径事件打印一行摘要(避免刷串口)。 */
|
||||
static void net_stat_tick(void) {
|
||||
u64 sum = g_stat_rx_filled + g_stat_rx_drop_nobuf + g_stat_rx_badbuf +
|
||||
g_stat_tx_sent + g_stat_tx_drop;
|
||||
if ((sum & 0x1FF) != 0) return;
|
||||
log_hex64("[VNET] stat rx_filled=", g_stat_rx_filled);
|
||||
log_hex64("[VNET] stat rx_drop_nobuf=", g_stat_rx_drop_nobuf);
|
||||
log_hex64("[VNET] stat rx_badbuf=", g_stat_rx_badbuf);
|
||||
log_hex64("[VNET] stat tx_sent=", g_stat_tx_sent);
|
||||
log_hex64("[VNET] stat tx_drop=", g_stat_tx_drop);
|
||||
}
|
||||
|
||||
static u32 net_read_config(u32 offset, int width) {
|
||||
(void)width;
|
||||
struct virtio_net_config cfg;
|
||||
@@ -64,31 +83,31 @@ static u32 net_read_config(u32 offset, int width) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 从描述符链拼接一个连续帧(virtio-net 帧通常 2 个描述符:hdr + data)。
|
||||
* 返回帧长度,数据拷入 out_buf(out_cap 容量)。 */
|
||||
/* 从 TX 描述符链拼接一个连续帧到 out_buf(out_cap 容量),返回帧长度。
|
||||
* 第一个描述符前置 virtio_net_hdr:Linux 驱动把 hdr 作为【独立的 10 字节
|
||||
* 描述符】(len 恰好等于 VIRTIO_NET_HDR_LEN),必须无条件跳过首描述符的
|
||||
* 前 min(len,10) 字节,否则 hdr 会被拼进帧首导致 host 发出坏包。
|
||||
* 任一段放不下 out_cap(超 MTU 截断)或描述符 GPA 未映射时返回 0(整帧丢弃)。 */
|
||||
static u32 net_gather_frame(struct virtq_desc *desc, u16 head,
|
||||
u8 *out_buf, u32 out_cap, int skip_hdr) {
|
||||
u8 *out_buf, u32 out_cap) {
|
||||
u32 total = 0;
|
||||
u16 cur = head;
|
||||
int hops = 0;
|
||||
while (hops < 32) {
|
||||
u8 *src = (u8 *)virtio_gpa_to_host(desc[cur].addr);
|
||||
u32 len = desc[cur].len;
|
||||
if (!src) break;
|
||||
if (!src) return 0;
|
||||
|
||||
/* 跳过第一个描述符中的 virtio_net_hdr */
|
||||
if (hops == 0 && skip_hdr && len > VIRTIO_NET_HDR_LEN) {
|
||||
u32 dl = len - VIRTIO_NET_HDR_LEN;
|
||||
if (total + dl <= out_cap) {
|
||||
u8 *d = src + VIRTIO_NET_HDR_LEN;
|
||||
for (u32 i = 0; i < dl; i++) out_buf[total + i] = d[i];
|
||||
total += dl;
|
||||
}
|
||||
} else {
|
||||
if (total + len <= out_cap) {
|
||||
for (u32 i = 0; i < len; i++) out_buf[total + i] = src[i];
|
||||
total += len;
|
||||
}
|
||||
/* 跳过首描述符中的 virtio_net_hdr(hdr 与数据同段时也正确) */
|
||||
if (hops == 0) {
|
||||
u32 skip = len < VIRTIO_NET_HDR_LEN ? len : VIRTIO_NET_HDR_LEN;
|
||||
src += skip;
|
||||
len -= skip;
|
||||
}
|
||||
if (len > 0) {
|
||||
if (total + len > out_cap) return 0;
|
||||
for (u32 i = 0; i < len; i++) out_buf[total + i] = src[i];
|
||||
total += len;
|
||||
}
|
||||
|
||||
if (!(desc[cur].flags & VIRTQ_DESC_F_NEXT)) break;
|
||||
@@ -115,65 +134,143 @@ static void net_handle_tx(void) {
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u32 flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch), 1);
|
||||
if (flen > 0 && net && g_net_bound >= 0) {
|
||||
net->tx((u32)g_net_bound, g_tx_scratch, flen);
|
||||
u32 flen = 0;
|
||||
if (head < qnum) {
|
||||
flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch));
|
||||
}
|
||||
if (flen > 0 && net && g_net_bound >= 0) {
|
||||
if (net->tx((u32)g_net_bound, g_tx_scratch, flen) == 0) {
|
||||
g_stat_tx_sent++;
|
||||
} else {
|
||||
/* host ring 满/发送失败:丢帧(guest 不感知 TX 成败) */
|
||||
g_stat_tx_drop++;
|
||||
}
|
||||
} else {
|
||||
/* 链异常/超 MTU 截断保护/未绑定网卡:丢帧 */
|
||||
g_stat_tx_drop++;
|
||||
}
|
||||
/* TX buffer 必须回执(guest 回收 skb),len 字段对 TX 无意义 */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
cur++;
|
||||
net_stat_tick();
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_tx_last_avail = cur;
|
||||
}
|
||||
|
||||
/* RX queue(idx=0):guest 提交空 buffer,我们尝试 rx_poll 填充。
|
||||
* guest 每提交一批 buffer 就 notify 一次;有包则填充 + used,无包则
|
||||
* 保留 buffer 等下次 notify(简化:直接返回 0 长度 used,让 guest 重投)。 */
|
||||
/* RX queue(idx=0):guest 提交空 buffer,我们调 host rx_poll 填充。
|
||||
*
|
||||
* buffer 所有权规则(关键,避免泄漏):
|
||||
* - guest 一次性投满 RX 队列后只 notify 一次,之后等 used 事件。
|
||||
* - 有包:填充 hdr+data → used ring 回执 → guest 收包后重投 buffer。
|
||||
* - 无包:【必须停止且不消耗该 buffer】(g_rx_last_avail 保持指向它),
|
||||
* 由 virtio_net_poll() 在 preemption timer 路径周期性重试;
|
||||
* 否则 buffer 被跳过且永远不进 used ring → guest RX buffer 耗尽饿死。
|
||||
* - guest 无可用 buffer 时:主动把 host 收到的包收出来丢弃并统计,
|
||||
* 避免 e1000 8 深 RX ring 堵死后网卡级无差别丢包(virtio 语义上
|
||||
* device 在 RX 无 buffer 时丢包是标准行为)。
|
||||
* 填充 ≥1 个 buffer 后由调用方负责注入 guest IRQ。 */
|
||||
static u16 g_rx_last_avail;
|
||||
static u8 g_rx_drop_scratch[2048]; /* 无 guest buffer 时的丢弃暂存 */
|
||||
|
||||
static void net_handle_rx(void) {
|
||||
/* 解析 RX 描述符链的 data 接收区(跳过首描述符的 hdr 区)。
|
||||
* 支持两种布局:
|
||||
* A. hdr(10B, W) + data(N, W) 两个描述符(Linux add_recvbuf_small 布局)
|
||||
* B. hdr+data 同在一个描述符(len > 10,data 从偏移 10 起)
|
||||
* 成功返回 0 并填充 datap/datacap/hdrp 输出参数;失败返回 -1(链异常)。 */
|
||||
static int net_rx_data_region(struct virtq_desc *desc, u16 head,
|
||||
u8 **datap, u32 *datacap, u8 **hdrp) {
|
||||
struct virtq_desc *d0 = &desc[head];
|
||||
u8 *hdr = (u8 *)virtio_gpa_to_host(d0->addr);
|
||||
if (!hdr || d0->len < VIRTIO_NET_HDR_LEN) return -1;
|
||||
*hdrp = hdr;
|
||||
|
||||
if (d0->len > VIRTIO_NET_HDR_LEN) {
|
||||
/* 布局 B:hdr 与 data 同段 */
|
||||
if (!(d0->flags & VIRTQ_DESC_F_WRITE)) return -1;
|
||||
*datap = hdr + VIRTIO_NET_HDR_LEN;
|
||||
*datacap = d0->len - VIRTIO_NET_HDR_LEN;
|
||||
return 0;
|
||||
}
|
||||
/* 布局 A:hdr 独立描述符,data 在 NEXT 段 */
|
||||
if (!(d0->flags & VIRTQ_DESC_F_NEXT)) return -1;
|
||||
struct virtq_desc *d1 = &desc[d0->next];
|
||||
if (!(d1->flags & VIRTQ_DESC_F_WRITE)) return -1;
|
||||
u8 *data = (u8 *)virtio_gpa_to_host(d1->addr);
|
||||
if (!data || d1->len == 0) return -1;
|
||||
*datap = data;
|
||||
*datacap = d1->len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 处理 RX 队列。返回本次填充的 buffer 数(0 = 无包/无可填)。 */
|
||||
static int net_handle_rx(void) {
|
||||
struct virtq_desc *desc;
|
||||
struct virtq_avail *avail;
|
||||
struct virtq_used *used;
|
||||
u32 qnum;
|
||||
if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 0, &desc, &avail, &used, &qnum) != 0)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
const dkm_net_api *net = net_get_api();
|
||||
if (!net || g_net_bound < 0) return;
|
||||
if (!net || g_net_bound < 0) return 0;
|
||||
|
||||
int filled = 0;
|
||||
u16 cur = g_rx_last_avail;
|
||||
u16 used_idx = used->idx;
|
||||
while (cur != avail->idx) {
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
/* RX 描述符链:hdr(W) + data(W)。尝试收一包。 */
|
||||
u16 curd = head;
|
||||
u8 *hdrp = (u8 *)virtio_gpa_to_host(desc[curd].addr);
|
||||
u8 *datap = (u8 *)0;
|
||||
u32 datalen = 0;
|
||||
if (desc[curd].flags & VIRTQ_DESC_F_NEXT) {
|
||||
u16 di = desc[curd].next;
|
||||
datap = (u8 *)virtio_gpa_to_host(desc[di].addr);
|
||||
datalen = desc[di].len;
|
||||
int budget = 32; /* 单次调用处理上限,避免 vmexit/poll 路径停留过久 */
|
||||
|
||||
while (budget-- > 0) {
|
||||
if (cur == avail->idx) {
|
||||
/* guest 无可用 RX buffer:主动收包丢弃,防 host RX ring 堵死 */
|
||||
u32 got = 0;
|
||||
if (net->rx_poll((u32)g_net_bound, g_rx_drop_scratch,
|
||||
sizeof(g_rx_drop_scratch), &got) == 0 && got > 0) {
|
||||
g_stat_rx_drop_nobuf++;
|
||||
net_stat_tick();
|
||||
continue;
|
||||
}
|
||||
break; /* host 也无包 */
|
||||
}
|
||||
|
||||
u16 head = avail->ring[cur % qnum];
|
||||
u8 *hdrp = (u8 *)0;
|
||||
u8 *datap = (u8 *)0;
|
||||
u32 datacap = 0;
|
||||
if (head >= qnum ||
|
||||
net_rx_data_region(desc, head, &datap, &datacap, &hdrp) != 0) {
|
||||
/* 异常 buffer:len=0 回执让 guest 回收重投,避免卡死 */
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = 0;
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
g_stat_rx_badbuf++;
|
||||
net_stat_tick();
|
||||
continue;
|
||||
}
|
||||
if (!hdrp || !datap || datalen == 0) { cur++; continue; }
|
||||
|
||||
u32 got = 0;
|
||||
if (net->rx_poll((u32)g_net_bound, datap, datalen, &got) == 0 && got > 0) {
|
||||
if (net->rx_poll((u32)g_net_bound, datap, datacap, &got) == 0 && got > 0) {
|
||||
/* 填 virtio_net_hdr 全零(无 GSO) */
|
||||
for (int i = 0; i < VIRTIO_NET_HDR_LEN; i++) hdrp[i] = 0;
|
||||
used->ring[used_idx % qnum].id = head;
|
||||
used->ring[used_idx % qnum].len = VIRTIO_NET_HDR_LEN + got;
|
||||
used_idx++;
|
||||
cur++;
|
||||
filled++;
|
||||
g_stat_rx_filled++;
|
||||
net_stat_tick();
|
||||
} else {
|
||||
/* 无包:不消耗该 buffer,留待下次(但为避免死循环,跳过) */
|
||||
/* 无包:停止,不消耗 cur 指向的 buffer,下次 poll 重试 */
|
||||
break;
|
||||
}
|
||||
cur++;
|
||||
}
|
||||
virtio_queue_bump_used(used, used_idx);
|
||||
g_rx_last_avail = cur;
|
||||
return filled;
|
||||
}
|
||||
|
||||
static void net_queue_notify(u32 queue_idx) {
|
||||
@@ -216,3 +313,13 @@ void virtio_net_backend_init(void) {
|
||||
}
|
||||
virtio_mmio_register(&g_net_backend);
|
||||
}
|
||||
|
||||
/* 周期轮询入口(vmexit preemption timer 路径调用,~1kHz)。
|
||||
* guest 投满 RX buffer 后不再 notify,host 侧包到达时靠此轮询填充
|
||||
* 并注入 IRQ,guest 才能及时收包(pacman/ping 等依赖)。 */
|
||||
void virtio_net_poll(void) {
|
||||
if (g_net_bound < 0) return;
|
||||
if (net_handle_rx() > 0) {
|
||||
virtio_mmio_raise_irq(VIRTIO_ID_NET);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user