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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|