Files
Deshab-OS/CODE/UTSM/vmm/virtio_net.c
T

326 lines
12 KiB
C
Raw Normal View History

2026-07-28 21:11:10 +08:00
/* virtio_net.c — UTSM virtio-net 设备模拟后端。
*
* 为 Linux guest 提供 virtio-net 网卡。guest 的收发包请求经 virtqueue
* 传入,本后端桥接到 UTSM 真实网卡(net API,e1000/virtio_net DKM 驱动)。
*
* Virtio Spec 1.1 §5.1 (Network Device)。
* 队列:0=RX(device→driver 收包),1=TX(driver→device 发包)。
2026-07-31 13:53:49 +08:00
* 每帧前置 virtio_net_hdr(10 字节,无 GSO/MRG_RXBUF 协商时全零)。
2026-07-28 21:11:10 +08:00
*
* 最小可用实现:
* - 无 GSO/TSO/checksum offload(feature 不声明,guest 走软件路径)
2026-07-31 13:53:49 +08:00
* - RX:guest 提交空 buffer,queue_notify / 周期 poll 时调 host rx_poll 填充
* - TX:queue_notify 时拼接描述符链成帧,直接 net->tx 发出
2026-07-28 21:11:10 +08:00
*/
#include <utsm/virtio_mmio.h>
#include <utsm/linux_loader.h>
#include <utsm/net.h>
#include <utsm/log.h>
#include <utsm/types.h>
/* virtio_net_hdr(VIRTIO_NET_F_CSUM/GSO 未协商时为 10 字节 legacy) */
struct virtio_net_hdr {
u8 flags;
u8 gso_type;
u16 hdr_len;
u16 gso_size;
u16 csum_start;
u16 csum_offset;
/* u16 num_buffers; // 仅 VIRTIO_NET_F_MRG_RXBUF 协商后存在 */
} __attribute__((packed));
#define VIRTIO_NET_HDR_LEN 10
2026-07-31 13:53:49 +08:00
/* feature bits(除 MAC/STATUS 外都不声明,保持最简) */
2026-07-28 21:11:10 +08:00
#define VIRTIO_NET_F_CSUM 0
#define VIRTIO_NET_F_GUEST_CSUM 1
#define VIRTIO_NET_F_MAC 5
#define VIRTIO_NET_F_STATUS 16
/* device config(VIRTIO_MMIO_CONFIG 起) */
struct virtio_net_config {
u8 mac[6];
u16 status; /* bit0 = link up */
u16 max_virtqueue_pairs;
} __attribute__((packed));
static u8 g_net_mac[6] = { 0x52, 0x54, 0x00, 0xDE, 0x5A, 0x01 };
static int g_net_bound = -1;
2026-07-31 13:53:49 +08:00
/* ===== 数据路径统计(低频串口摘要,便于现场诊断 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);
}
2026-07-28 21:11:10 +08:00
static u32 net_read_config(u32 offset, int width) {
(void)width;
struct virtio_net_config cfg;
u8 *c = (u8 *)&cfg;
for (u32 i = 0; i < sizeof(cfg); i++) c[i] = 0;
for (int i = 0; i < 6; i++) cfg.mac[i] = g_net_mac[i];
cfg.status = 1; /* link up */
cfg.max_virtqueue_pairs = 1;
if (offset + 4 <= sizeof(cfg)) {
u8 *src = (u8 *)&cfg + offset;
return (u32)src[0] | ((u32)src[1] << 8) | ((u32)src[2] << 16) | ((u32)src[3] << 24);
}
return 0;
}
2026-07-31 13:53:49 +08:00
/* 从 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(整帧丢弃)。 */
2026-07-28 21:11:10 +08:00
static u32 net_gather_frame(struct virtq_desc *desc, u16 head,
2026-07-31 13:53:49 +08:00
u8 *out_buf, u32 out_cap) {
2026-07-28 21:11:10 +08:00
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;
2026-07-31 13:53:49 +08:00
if (!src) return 0;
/* 跳过首描述符中的 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;
2026-07-28 21:11:10 +08:00
}
if (!(desc[cur].flags & VIRTQ_DESC_F_NEXT)) break;
cur = desc[cur].next;
hops++;
}
return total;
}
/* TX queue(idx=1):guest 发包。 */
static u16 g_tx_last_avail;
static u8 g_tx_scratch[2048];
static void net_handle_tx(void) {
struct virtq_desc *desc;
struct virtq_avail *avail;
struct virtq_used *used;
u32 qnum;
if (virtio_queue_get_ptrs(VIRTIO_ID_NET, 1, &desc, &avail, &used, &qnum) != 0)
return;
const dkm_net_api *net = net_get_api();
u16 cur = g_tx_last_avail;
u16 used_idx = used->idx;
while (cur != avail->idx) {
u16 head = avail->ring[cur % qnum];
2026-07-31 13:53:49 +08:00
u32 flen = 0;
if (head < qnum) {
flen = net_gather_frame(desc, head, g_tx_scratch, sizeof(g_tx_scratch));
}
2026-07-28 21:11:10 +08:00
if (flen > 0 && net && g_net_bound >= 0) {
2026-07-31 13:53:49 +08:00
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++;
2026-07-28 21:11:10 +08:00
}
2026-07-31 13:53:49 +08:00
/* TX buffer 必须回执(guest 回收 skb),len 字段对 TX 无意义 */
2026-07-28 21:11:10 +08:00
used->ring[used_idx % qnum].id = head;
used->ring[used_idx % qnum].len = 0;
used_idx++;
cur++;
2026-07-31 13:53:49 +08:00
net_stat_tick();
2026-07-28 21:11:10 +08:00
}
virtio_queue_bump_used(used, used_idx);
g_tx_last_avail = cur;
}
2026-07-31 13:53:49 +08:00
/* 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。 */
2026-07-28 21:11:10 +08:00
static u16 g_rx_last_avail;
2026-07-31 13:53:49 +08:00
static u8 g_rx_drop_scratch[2048]; /* 无 guest buffer 时的丢弃暂存 */
2026-07-28 21:11:10 +08:00
2026-07-31 13:53:49 +08:00
/* 解析 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) {
2026-07-28 21:11:10 +08:00
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)
2026-07-31 13:53:49 +08:00
return 0;
2026-07-28 21:11:10 +08:00
const dkm_net_api *net = net_get_api();
2026-07-31 13:53:49 +08:00
if (!net || g_net_bound < 0) return 0;
2026-07-28 21:11:10 +08:00
2026-07-31 13:53:49 +08:00
int filled = 0;
2026-07-28 21:11:10 +08:00
u16 cur = g_rx_last_avail;
u16 used_idx = used->idx;
2026-07-31 13:53:49 +08:00
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 也无包 */
}
2026-07-28 21:11:10 +08:00
u16 head = avail->ring[cur % qnum];
2026-07-31 13:53:49 +08:00
u8 *hdrp = (u8 *)0;
2026-07-28 21:11:10 +08:00
u8 *datap = (u8 *)0;
2026-07-31 13:53:49 +08:00
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;
2026-07-28 21:11:10 +08:00
}
u32 got = 0;
2026-07-31 13:53:49 +08:00
if (net->rx_poll((u32)g_net_bound, datap, datacap, &got) == 0 && got > 0) {
2026-07-28 21:11:10 +08:00
/* 填 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++;
2026-07-31 13:53:49 +08:00
cur++;
filled++;
g_stat_rx_filled++;
net_stat_tick();
2026-07-28 21:11:10 +08:00
} else {
2026-07-31 13:53:49 +08:00
/* 无包:停止,不消耗 cur 指向的 buffer,下次 poll 重试 */
break;
2026-07-28 21:11:10 +08:00
}
}
virtio_queue_bump_used(used, used_idx);
g_rx_last_avail = cur;
2026-07-31 13:53:49 +08:00
return filled;
2026-07-28 21:11:10 +08:00
}
static void net_queue_notify(u32 queue_idx) {
if (queue_idx == 1) net_handle_tx();
else if (queue_idx == 0) net_handle_rx();
}
static void net_reset(void) {
g_tx_last_avail = 0;
g_rx_last_avail = 0;
}
static struct virtio_backend g_net_backend = {
.device_id = VIRTIO_ID_NET,
.gpa_base = VIRTIO_MMIO_NET_GPA,
.irq = 6, /* guest ISA IRQ6(cmdline :6,vector 0x36) */
.num_queues = 2,
.queue_size = 128,
.device_features = (1ULL << VIRTIO_F_VERSION_1) |
(1ULL << VIRTIO_NET_F_MAC) | (1ULL << VIRTIO_NET_F_STATUS),
.config_len = sizeof(struct virtio_net_config),
.read_config = net_read_config,
.queue_notify = net_queue_notify,
.reset = net_reset,
};
void virtio_net_backend_init(void) {
/* 绑定 UTSM 第 0 个网卡(e1000 或 virtio_net DKM 驱动) */
const dkm_net_api *net = net_get_api();
if (net && net->device_count() > 0) {
g_net_bound = 0;
dkm_net_device_info info;
if (net->device_info(0, &info) == 0) {
for (int i = 0; i < 6; i++) g_net_mac[i] = info.mac[i];
}
log_info("[VNET] bound to UTSM net device 0");
} else {
g_net_bound = -1;
log_warn("[VNET] no UTSM net device, net backend degraded");
}
virtio_mmio_register(&g_net_backend);
}
2026-07-31 13:53:49 +08:00
/* 周期轮询入口(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);
}
}