Files
Deshab-OS/CODE/UTSM/vmm/virtio_net.c
T
2026-07-28 21:11:10 +08:00

219 lines
7.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 发包)。
* 每帧前置 virtio_net_hdr(10/12 字节,无 GSO 时全零)。
*
* 最小可用实现:
* - 无 GSO/TSO/checksum offload(feature 不声明,guest 走软件路径)
* - RX 轮询:guest RX 队列提交空 buffer,queue_notify 时尝试 rx_poll
* - TX:queue_notify 时取帧直接 net->tx 发出
*/
#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
/* feature bits(我们都不声明,保持最简) */
#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;
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;
}
/* 从描述符链拼接一个连续帧(virtio-net 帧通常 2 个描述符:hdr + data)。
* 返回帧长度,数据拷入 out_buf(out_cap 容量)。 */
static u32 net_gather_frame(struct virtq_desc *desc, u16 head,
u8 *out_buf, u32 out_cap, int skip_hdr) {
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;
/* 跳过第一个描述符中的 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;
}
}
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];
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);
}
used->ring[used_idx % qnum].id = head;
used->ring[used_idx % qnum].len = 0;
used_idx++;
cur++;
}
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 重投)。 */
static u16 g_rx_last_avail;
static void 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;
const dkm_net_api *net = net_get_api();
if (!net || g_net_bound < 0) return;
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;
}
if (!hdrp || !datap || datalen == 0) { cur++; continue; }
u32 got = 0;
if (net->rx_poll((u32)g_net_bound, datap, datalen, &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++;
} else {
/* 无包:不消耗该 buffer,留待下次(但为避免死循环,跳过) */
}
cur++;
}
virtio_queue_bump_used(used, used_idx);
g_rx_last_avail = cur;
}
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);
}