qemu virtio net lwip

整個改動在這個commit 實現 https://github.com/x213212/mini-riscv-os/commit/64d82fd26d69ecdd1985264ac2261d8a8919f46a 研究一下怎麼在 qemu 裡面新增一張 virtio 網卡並註冊到 lwip,使用一些常見的協議 image 這邊要讓 qemu 網卡收到封包,要透過橋接的方式送qemu虛擬網卡封包

code
# 建立 bridge
sudo ip addr add 192.168.100.1/24 dev br0
sudo ip link set br0 up
sudo ip link add name br0 type bridge

# 把實體網卡加入橋接(先下線)
sudo ip link set ens33 down
sudo ip link set ens33 master br0
sudo ip link set ens33 up

# 建立 tap 裝置(若沒有)
sudo ip tuntap add dev tap0 mode tap user $(whoami)

# 把 tap0 加入橋接並開啟
sudo ip link set tap0 master br0
sudo ip link set tap0 up

# 開啟橋接介面
sudo ip link set br0 up

檢查狀態

code
ip link show br0
ip link show ens33
ip link show tap0

反正到時候看到staus 裡面都要up就對了。 注意tap0是在你qemu run 起來 tap 再透過ip link show tap0 查看status才看的到 status up

code
4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 4e:5d:19:e5:6c:cd brd ff:ff:ff:ff:ff:ff
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:d4:9c:38 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
code
x213212@x213212-VMware-Virtual-Platform:~/qemu2/qemu/build$ ip link show tap0 
3: tap0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast master br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether e6:69:14:e9:1e:b3 brd ff:ff:ff:ff:ff:ff
x213212@x213212-VMware-Virtual-Platform:~/qemu2/qemu/build$ ip link show tap0 
3: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br0 state UP mode DEFAULT group default qlen 1000
    link/ether e6:69:14:e9:1e:b3 brd ff:ff:ff:ff:ff:ff

qemu 網卡設定,這邊為了掩飾方便,我找了之前研究的 git repo https://github.com/x213212/mini-riscv-os/tree/master/08-BlockDeviceDriver 這邊makefile要做幾種新增

code
CC = /home/x213212/riscv/bin/riscv32-unknown-elf-gcc
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32imac_zicsr -mabi=ilp32  -DLWIP_NO_CTYPE_H -g -Wall -w -I./ -I./lwip/src/include -I./lwip/src/core -I./lwip/src/netif  
GDB = /home/x213212/riscv/bin/riscv64-unknown-elf-gdb

現在要引入lwip這樣就可以在 bare metal 使用簡單的tcp.udp 協議 這邊還要額外下載lwip https://github.com/lwip-tcpip/lwip 放到你的根目錄 add .c

code
OBJ = \
start.s \
sys.s \
lib.c \
timer.c \
task.c \
os.c \
user.c \
trap.c \
lock.c \
plic.c \
virtio.c \
string.c \
lwip/src/core/def.c\
lwip/src/core/init.c\
lwip/src/core/mem.c\
lwip/src/core/memp.c\
lwip/src/core/netif.c\
lwip/src/core/pbuf.c\
lwip/src/core/raw.c\
lwip/src/core/sys.c\
lwip/src/core/tcp.c\
lwip/src/core/tcp_in.c\
lwip/src/core/tcp_out.c\
lwip/src/core/udp.c\
lwip/src/core/ip.c\
lwip/src/netif/ethernet.c\
lwip/src/netif/lowpan6.c\
lwip/src/core/timeouts.c\
lwip/src/core/inet_chksum.c\
lwip/src/core/ipv4/ip4.c\
lwip/src/core/ipv4/acd.c\
lwip/src/core/ipv4/autoip.c\
lwip/src/core/ipv4/igmp.c\
lwip/src/core/ipv4/ip4_addr.c\
lwip/src/core/ipv4/icmp.c\
lwip/src/core/ipv4/dhcp.c\
lwip/src/core/ipv4/etharp.c\
lwip/src/core/ipv4/ip4_frag.c\
lwip/src/core/sys.c

接下就要新增大概三個檔案 lwipopts.h 這個放在當前目錄

code
#define SYS_LIGHTWEIGHT_PROT 0
#define NO_SYS 1                   // 不使用RTOS,裸機模式
#define LWIP_ARP 1                // 啟用ARP
#define LWIP_ICMP 1                // 啟用ICMP
#define LWIP_TCP 1                // 啟用TCP
#define LWIP_UDP 1                 // 啟用UDP
#define LWIP_DHCP 1                // DHCP可選
#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define MEM_SIZE 16000             // lwIP核心記憶體大小
#define MEMP_NUM_PBUF 16
#define MEMP_NUM_TCP_PCB 5
#define MEMP_NUM_TCP_PBUF 16
#define PBUF_POOL_SIZE 16
#define TCP_MSS 1460
#define TCP_WND (4 * TCP_MSS)
#define LWIP_DEBUG 0
#define LWIP_PLATFORM_DIAG(x) do { } while(0)
#define LWIP_DEBUG 0
#define LWIP_NO_STDOUT 1
#define LWIP_STATS 0
// #define LWIP_SOCKET     1
// #define LWIP_IPV4       1

你還要再 /home/x213212/mini-riscv-os/08-BlockDeviceDriver/lwip/src/include/lwip/arch 裡面新增兩個檔案 cc.h

code
#ifndef LWIP_ARCH_CC_H
#define LWIP_ARCH_CC_H

#include <stdint.h>
#include "riscv.h"
// Fixed-width integer types
typedef uint8_t  u8_t;
typedef int8_t   s8_t;
typedef uint16_t u16_t;
typedef int16_t  s16_t;
typedef uint32_t u32_t;
typedef int32_t  s32_t;
#define MSTATUS_MIE (1 << 3)

// Type used for interrupt protection
typedef unsigned int sys_prot_t;

// Implement interrupt protection using your own enable/disable interrupt functions
static inline sys_prot_t sys_arch_protect(void) {
    // Disable machine mode interrupts (e.g., clear MIE)
    unsigned int mstatus = r_mstatus();
    w_mstatus(mstatus & ~MSTATUS_MIE);
    return mstatus; // Return old status for restoration
}

static inline void sys_arch_unprotect(sys_prot_t state) {
    // Restore previous interrupt status
    w_mstatus(state);
}

// Define empty macros for lwIP protection functions (customize for finer granularity if needed)
#define SYS_ARCH_DECL_PROTECT(level)  sys_prot_t level
#define SYS_ARCH_PROTECT(level)       (level = sys_arch_protect())
#define SYS_ARCH_UNPROTECT(level)     sys_arch_unprotect(level)

// Byte order
#define LWIP_PLATFORM_BYTESWAP 0

#endif /* LWIP_ARCH_CC_H */

sys_arch.h

code
#ifndef LWIP_HDR_ARCH_SYS_ARCH_H
#define LWIP_HDR_ARCH_SYS_ARCH_H

/* For NO_SYS=1, define empty types and macros for compatibility with lwIP. */

typedef uint32_t sys_prot_t;

#define SYS_MBOX_NULL  NULL   // Mailbox null placeholder
#define SYS_SEM_NULL   NULL   // Semaphore null placeholder

typedef int sys_sem_t;        // Semaphore type (dummy for NO_SYS=1)
typedef int sys_mbox_t;       // Mailbox type (dummy for NO_SYS=1)
typedef int sys_thread_t;     // Thread type (dummy for NO_SYS=1)

/* No actual interrupt protection in NO_SYS=1, just stubs */
static inline sys_prot_t sys_arch_protect(void) { return 0; }
static inline void sys_arch_unprotect(sys_prot_t p) { (void)p; }

#endif /* LWIP_HDR_ARCH_SYS_ARCH_H */

然後qemu這邊可以配置你的網卡,這邊本來順便初始化鍵盤與滑鼠驅動但是可能我run在windows 虛擬機裡面,收不到滑鼠與鍵盤的irq訊號,這部份過幾天再研究。

code
QEMU = /home/x213212/qemu2/qemu/build/qemu-system-riscv32  \
   -smp 1 -machine virt -bios none \
  -drive if=none,format=raw,file=hdd.dsk,id=x0 \
  -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 \
  -device virtio-net-device,netdev=net0,mac=52:54:00:12:34:56 \
  -netdev tap,id=net0,ifname=tap0,script=no,downscript=no \
  -d guest_errors -icount shift=0,align=off,sleep=off \
  -device virtio-keyboard-device \
  -device virtio-mouse-device \
  -monitor telnet:localhost:4321,server,nowait

這邊要注意我在編譯過程中有遇到某些c code 是有使用ctype 類型的function -DLWIP_NO_CTYPE_H 所以我在編譯有額外再加入這個 /home/x213212//mini-riscv-os/08-BlockDeviceDriver/lwip/src/include/lwip/arch.h

code
#ifndef LWIP_NO_CTYPE_H
#define LWIP_NO_CTYPE_H 0
#endif

#if LWIP_NO_CTYPE_H
#define lwip_in_range(c, lo, up)  ((u8_t)(c) >= (lo) && (u8_t)(c) <= (up))
#define lwip_isdigit(c)           lwip_in_range((c), '0', '9')
#define lwip_isxdigit(c)          (lwip_isdigit(c) || lwip_in_range((c), 'a', 'f') || lwip_in_range((c), 'A', 'F'))
#define lwip_islower(c)           lwip_in_range((c), 'a', 'z')
#define lwip_isspace(c)           ((c) == ' ' || (c) == '\f' || (c) == '\n' || (c) == '\r' || (c) == '\t' || (c) == '\v')
#define lwip_isupper(c)           lwip_in_range((c), 'A', 'Z')
#define lwip_tolower(c)           (lwip_isupper(c) ? (c) - 'A' + 'a' : c)
#define lwip_toupper(c)           (lwip_islower(c) ? (c) - 'a' + 'A' : c)
#else
#include <ctype.h>
#define lwip_isdigit(c)           isdigit((unsigned char)(c))
#define lwip_isxdigit(c)          isxdigit((unsigned char)(c))
#define lwip_islower(c)           islower((unsigned char)(c))
#define lwip_isspace(c)           isspace((unsigned char)(c))
#define lwip_isupper(c)           isupper((unsigned char)(c))
#define lwip_tolower(c)           tolower((unsigned char)(c))
#define lwip_toupper(c)           toupper((unsigned char)(c))
#endif

然後在 lib.c新增幾個glibc 簡單的 function 實作

code
// abort implementation: infinite loop or system reset trigger
void abort(void)
{
    while (1)
    {
        // You can place watchdog reset or blink LED code here
    }
}
int fflush(void* stream) {
    // No flush needed on baremetal, just return success
    (void)stream;  // Avoid unused parameter warning
    return 0;
}
int atoi(const char *s) {
    int n = 0;
    while (*s >= '0' && *s <= '9') {
        n = n * 10 + (*s - '0');
        s++;
    }
    return n;
}

int memcmp(const void *s1, const void *s2, size_t n) {
    const unsigned char *p1 = s1, *p2 = s2;
    for (size_t i = 0; i < n; i++) {
        if (p1[i] != p2[i])
            return (int)(p1[i] - p2[i]);
    }
    return 0;
}

// Minimal bare-metal strstr, not NULL-safe
char* strstr(const char* haystack, const char* needle)
{
    if (!*needle) return (char*)haystack;
    for (; *haystack; haystack++) {
        const char *h = haystack, *n = needle;
        while (*h && *n && *h == *n) { h++; n++; }
        if (!*n) return (char*)haystack;
    }
    return 0;
}
void panic(char *s)
{
    lib_puts(s);
    for (;;)
    {
    }
}

// strlen implementation
size_t strlen(const char *s)
{
    size_t len = 0;
    while (s[len] != '\0')
        len++;
    return len;
}

// strncmp implementation
int strncmp(const char *s1, const char *s2, size_t n)
{
    for (size_t i = 0; i < n; i++)
    {
        if (s1[i] != s2[i])
            return (unsigned char)s1[i] - (unsigned char)s2[i];
        if (s1[i] == '\0')
            return 0;
    }
    return 0;
}

注意在bare metal 還要加入一些64bit的 soft function 實作,這邊靠chatgpt幫忙gen

code
// 64-bit unsigned modulo, return remainder
uint64_t __umoddi3(uint64_t n, uint64_t d) {
    uint64_t r = 0;
    for (int i = 63; i >= 0; i--) {
        r = (r << 1) | ((n >> i) & 1);
        if (r >= d) {
            r -= d;
        }
    }
    return r;
}

// 64-bit unsigned division software implementation for __udivdi3
uint64_t __udivdi3(uint64_t numerator, uint64_t denominator) {
    uint64_t quotient = 0, remainder = 0;
    int i;

    if (denominator == 0) {
        // Handle divide by zero if needed
        return 0xFFFFFFFFFFFFFFFFULL;
    }

    for (i = 63; i >= 0; i--) {
        remainder = (remainder << 1) | ((numerator >> i) & 1);
        if (remainder >= denominator) {
            remainder -= denominator;
            quotient |= (1ULL << i);
        }
    }
    return quotient;
}

// Signed division
int64_t __divdi3(int64_t n, int64_t d) {
    int sign = 1;
    if (n < 0) { n = -n; sign = -sign; }
    if (d < 0) { d = -d; sign = -sign; }
    uint64_t q = __udivdi3((uint64_t)n, (uint64_t)d);
    return sign * (int64_t)q;
}

// Signed modulo
int64_t __moddi3(int64_t n, int64_t d) {
    int sign = 1;
    if (n < 0) { n = -n; sign = -sign; }
    if (d < 0) { d = -d; }
    uint64_t r = __umoddi3((uint64_t)n, (uint64_t)d);
    return sign * (int64_t)r;
}

這邊初始化9個irq,可能實際有8個這邊不確定。 plic.c

code
// plic.c
#include "os.h"
#include <stdint.h>

// ---------- PLIC definitions ----------
#define PLIC_BASE             0x0c000000UL
#define PLIC_PRIORITY(id)     (PLIC_BASE + ((id) * 4))
#define PLIC_PENDING(id)      (PLIC_BASE + 0x1000 + (((id) / 32) * 4))
#define PLIC_MENABLE(hart)    (PLIC_BASE + 0x2000 + (hart) * 0x80)
#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart) * 0x1000)
#define PLIC_MCLAIM(hart)     (PLIC_BASE + 0x200004 + (hart) * 0x1000)
#define PLIC_MCOMPLETE(hart)  PLIC_MCLAIM(hart) // Write back to the same register to complete

// helpers
static inline void write32(uintptr_t addr, uint32_t val) {
    *(volatile uint32_t *)addr = val;
    asm volatile("" ::: "memory");
}
static inline uint32_t read32(uintptr_t addr) {
    return *(volatile uint32_t *)addr;
}

// ---------- mscratch setup (xv6-style) ----------
#define MAX_HARTS 4
#define SCRATCH_PER_HART 256  // 256 bytes per hart, enough for reg_save/reg_load

__attribute__((aligned(16))) static uint8_t scratch_space[MAX_HARTS * SCRATCH_PER_HART];

void setup_mscratch_for_hart(void) {
    int hart = r_mhartid();
    uintptr_t base = (uintptr_t)(scratch_space + hart * SCRATCH_PER_HART);
    // optional: zero it out to avoid stale data
    for (int i = 0; i < SCRATCH_PER_HART; i += 8) {
        *(uint64_t *)(base + i) = 0;
    }
    w_mscratch(base);
}

// PLIC priority limits: read from your platform (hardcoded here based on qemu virt)
#define PLIC_MAX_PRIORITY 7

static void plic_enable_irq(int hart, uint32_t irq, uint32_t priority) {
    if (irq == 0) return;

    // clamp priority
    if (priority == 0) priority = 1;
    if (priority > PLIC_MAX_PRIORITY) priority = PLIC_MAX_PRIORITY;
    write32(PLIC_PRIORITY(irq), priority);

    // enable in the appropriate word
    uintptr_t enable_word = PLIC_MENABLE(hart) + (irq / 32) * 4;
    uint32_t v = read32(enable_word);
    v |= (1u << (irq % 32));
    write32(enable_word, v);
}

void plic_init(void)
{
    int hart = r_mhartid();

    // Make sure mscratch is set if your trap vector uses it
    // setup_mscratch_for_hart();

    // Set threshold = 0 to accept priority >=1
    write32(PLIC_MTHRESHOLD(hart), 0);

    // Enable relevant sources with priorities (example list)
    struct { uint32_t irq; uint32_t prio; } sources[] = {
        { UART0_IRQ, 1 },
        { VIRTIO_IRQ, 2 },
        { VIRTIO_IRQ2, 3 },
        { VIRTIO_IRQ3, 4 },
        { VIRTIO_IRQ4, 5 },
        { VIRTIO_IRQ5, 7 },
        { VIRTIO_IRQ6, 7 },
        { VIRTIO_IRQ7, 7 },
        { VIRTIO_IRQ8, 7 },
        { VIRTIO_IRQ9, 7 },
    };
    int n = sizeof(sources) / sizeof(sources[0]);
    for (int i = 0; i < n; i++) {
        plic_enable_irq(hart, sources[i].irq, sources[i].prio);
    }

    // Enable external interrupts in mie & global
    w_mie(r_mie() | MIE_MEIE);
    w_mstatus(r_mstatus() | MSTATUS_MIE);
}

int plic_claim()
{
    int hart = r_mhartid();
    return (int)read32(PLIC_MCLAIM(hart));
}

void plic_complete(int irq)
{
    if (irq == 0) return;
    int hart = r_mhartid();
    write32(PLIC_MCOMPLETE(hart), (uint32_t)irq);
}

riscv.h

code
#ifndef __RISCV_H__
#define __RISCV_H__

#include <stdint.h>

#ifdef __riscv_xlen64
  typedef uint64_t reg_t;
#else
  typedef uint32_t reg_t;
#endif
#define PGSIZE 4096 // bytes per page

// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/
#define UART 0x10000000L
#define UART_THR (volatile uint8_t *)(UART + 0x00) // THR: transmitter holding register
#define UART_RHR (volatile uint8_t *)(UART + 0x00) // RHR: receive holding register
#define UART_DLL (volatile uint8_t *)(UART + 0x00) // LSB of divisor latch (write mode)
#define UART_DLM (volatile uint8_t *)(UART + 0x01) // MSB of divisor latch (write mode)
#define UART_IER (volatile uint8_t *)(UART + 0x01) // Interrupt Enable Register
#define UART_LCR (volatile uint8_t *)(UART + 0x03) // Line Control Register
#define UART_LSR (volatile uint8_t *)(UART + 0x05) // LSR: line status register
#define UART_LSR_EMPTY_MASK 0x40                   // LSR Bit 6: transmitter empty; both THR and LSR are empty

#define UART_REGR(reg) (*(reg))
#define UART_REGW(reg, v) ((*reg) = (v))

// ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h
// enum {
//     UART0_IRQ = 10,
//     RTC_IRQ = 11,
//     VIRTIO_IRQ = 1, /* 1 to 8 */
//     VIRTIO_COUNT = 8,
//     PCIE_IRQ = 0x20, /* 32 to 35 */
//     VIRTIO_NDEV = 0x35 /* Arbitrary maximum number of interrupts */
// };
#define UART0_IRQ 10
#define VIRTIO_IRQ 1
#define VIRTIO_IRQ2 2
#define VIRTIO_IRQ3 3
#define VIRTIO_IRQ4 4
#define VIRTIO_IRQ5 5
#define VIRTIO_IRQ6 6
#define VIRTIO_IRQ7 7
#define VIRTIO_IRQ8 8
#define VIRTIO_IRQ9 9

// Saved registers for kernel context switches
struct context
{
  reg_t ra;
  reg_t sp;

  // callee-saved
  reg_t s0;
  reg_t s1;
  reg_t s2;
  reg_t s3;
  reg_t s4;
  reg_t s5;
  reg_t s6;
  reg_t s7;
  reg_t s8;
  reg_t s9;
  reg_t s10;
  reg_t s11;
};

// ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h
//
// Local interrupt controller, contains the timer
// ================== Timer Interrupt ====================

#define NCPU 8 // maximum number of CPUs
#define CLINT 0x2000000
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4 * (hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot

static inline reg_t r_tp()
{
  reg_t x;
  asm volatile("mv %0, tp"
               : "=r"(x));
  return x;
}

// Return which hart (core) this is

static inline reg_t r_mhartid()
{
  reg_t x;
  asm volatile("csrr %0, mhartid"
               : "=r"(x));
  return x;
}

// Machine Status Register, mstatus
#define MSTATUS_MPP_MASK (3L << 11) // previous mode
#define MSTATUS_MPP_M (3L << 11)
#define MSTATUS_MPP_S (1L << 11)
#define MSTATUS_MPP_U (0L << 11)
#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable

static inline reg_t r_mstatus()
{
  reg_t x;
  asm volatile("csrr %0, mstatus"
               : "=r"(x));
  return x;
}

static inline void w_mstatus(reg_t x)
{
  asm volatile("csrw mstatus, %0"
               :
               : "r"(x));
}

// Machine exception program counter, holds the
// instruction address to return to from exception
static inline void w_mepc(reg_t x)
{
  asm volatile("csrw mepc, %0"
               :
               : "r"(x));
}

static inline reg_t r_mepc()
{
  reg_t x;
  asm volatile("csrr %0, mepc"
               : "=r"(x));
  return x;
}

// Machine Scratch register, for early trap handler
static inline void w_mscratch(reg_t x)
{
  asm volatile("csrw mscratch, %0"
               :
               : "r"(x));
}

// Machine-mode interrupt vector
static inline void w_mtvec(reg_t x)
{
  asm volatile("csrw mtvec, %0"
               :
               : "r"(x));
}

// Machine-mode Interrupt Enable
#define MIE_MEIE (1L << 11) // external
#define MIE_MTIE (1L << 7)  // timer
#define MIE_MSIE (1L << 3)  // software

static inline reg_t r_mie()
{
  reg_t x;
  asm volatile("csrr %0, mie"
               : "=r"(x));
  return x;
}

static inline void w_mie(reg_t x)
{
  asm volatile("csrw mie, %0"
               :
               : "r"(x));
}

#endif

timer.c

code
#ifndef __RISCV_H__
#define __RISCV_H__

#include <stdint.h>

// Register width type (64-bit or 32-bit depending on architecture)
#ifdef __riscv_xlen64
  typedef uint64_t reg_t;
#else
  typedef uint32_t reg_t;
#endif

#define PGSIZE 4096 // bytes per page

// UART base address and register definitions (see: https://www.activexperts.com/serial-port-component/tutorials/uart/)
#define UART 0x10000000L
#define UART_THR (volatile uint8_t *)(UART + 0x00) // THR: transmitter holding register
#define UART_RHR (volatile uint8_t *)(UART + 0x00) // RHR: receiver holding register
#define UART_DLL (volatile uint8_t *)(UART + 0x00) // LSB of divisor latch (write mode)
#define UART_DLM (volatile uint8_t *)(UART + 0x01) // MSB of divisor latch (write mode)
#define UART_IER (volatile uint8_t *)(UART + 0x01) // Interrupt Enable Register
#define UART_LCR (volatile uint8_t *)(UART + 0x03) // Line Control Register
#define UART_LSR (volatile uint8_t *)(UART + 0x05) // LSR: line status register
#define UART_LSR_EMPTY_MASK 0x40                   // LSR Bit 6: transmitter empty; both THR and LSR are empty

#define UART_REGR(reg) (*(reg))
#define UART_REGW(reg, v) ((*reg) = (v))

// IRQ definitions (see: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h)
// UART0 IRQ = 10, VirtIO IRQs = 1-9
#define UART0_IRQ 10
#define VIRTIO_IRQ 1
#define VIRTIO_IRQ2 2
#define VIRTIO_IRQ3 3
#define VIRTIO_IRQ4 4
#define VIRTIO_IRQ5 5
#define VIRTIO_IRQ6 6
#define VIRTIO_IRQ7 7
#define VIRTIO_IRQ8 8
#define VIRTIO_IRQ9 9

// Saved registers for kernel context switches
struct context
{
  reg_t ra;   // Return address
  reg_t sp;   // Stack pointer

  // Callee-saved registers
  reg_t s0;
  reg_t s1;
  reg_t s2;
  reg_t s3;
  reg_t s4;
  reg_t s5;
  reg_t s6;
  reg_t s7;
  reg_t s8;
  reg_t s9;
  reg_t s10;
  reg_t s11;
};

// Local interrupt controller, contains the timer (see: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h)
// ================== Timer Interrupt ====================

#define NCPU 8 // Maximum number of CPUs
#define CLINT 0x2000000
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4 * (hartid)) // Timer compare register per hart
#define CLINT_MTIME (CLINT + 0xBFF8) // Cycles since boot

// Read thread pointer register
static inline reg_t r_tp()
{
  reg_t x;
  asm volatile("mv %0, tp"
               : "=r"(x));
  return x;
}

// Read hardware thread (hart) ID
static inline reg_t r_mhartid()
{
  reg_t x;
  asm volatile("csrr %0, mhartid"
               : "=r"(x));
  return x;
}

// Machine Status Register, mstatus
#define MSTATUS_MPP_MASK (3L << 11) // Previous mode mask
#define MSTATUS_MPP_M (3L << 11)    // Machine mode
#define MSTATUS_MPP_S (1L << 11)    // Supervisor mode
#define MSTATUS_MPP_U (0L << 11)    // User mode
#define MSTATUS_MIE (1L << 3)       // Machine-mode interrupt enable

// Read mstatus CSR
static inline reg_t r_mstatus()
{
  reg_t x;
  asm volatile("csrr %0, mstatus"
               : "=r"(x));
  return x;
}

// Write mstatus CSR
static inline void w_mstatus(reg_t x)
{
  asm volatile("csrw mstatus, %0"
               :
               : "r"(x));
}

// Machine exception program counter (holds the instruction address to return to from exception)
static inline void w_mepc(reg_t x)
{
  asm volatile("csrw mepc, %0"
               :
               : "r"(x));
}

// Read mepc CSR
static inline reg_t r_mepc()
{
  reg_t x;
  asm volatile("csrr %0, mepc"
               : "=r"(x));
  return x;
}

// Machine Scratch register (used by early trap handler)
static inline void w_mscratch(reg_t x)
{
  asm volatile("csrw mscratch, %0"
               :
               : "r"(x));
}

// Machine-mode interrupt vector base address
static inline void w_mtvec(reg_t x)
{
  asm volatile("csrw mtvec, %0"
               :
               : "r"(x));
}

// Machine-mode Interrupt Enable bits
#define MIE_MEIE (1L << 11) // External interrupt enable
#define MIE_MTIE (1L << 7)  // Timer interrupt enable
#define MIE_MSIE (1L << 3)  // Software interrupt enable

// Read mie CSR
static inline reg_t r_mie()
{
  reg_t x;
  asm volatile("csrr %0, mie"
               : "=r"(x));
  return x;
}

// Write mie CSR
static inline void w_mie(reg_t x)
{
  asm volatile("csrw mie, %0"
               :
               : "r"(x));
}

#endif

trap.c 這邊從process原本的搶佔式,變成協同模式,並新增一個irq 網卡收到封包後會觸發一個irq, 這邊我弄蠻久,每次切換task sp都會少0x20,多跑幾分鐘整個就會qemu crash,這邊先這樣

code
    else if (irq == VIRTIO_IRQ8)
    {
        lib_puts("Virtio net IRQ\n");
        virtio_net_interrupt_handler();
    }

    case 7:
        lib_puts("timer interruption!\n");
        w_mie(r_mie() & ~(1 << 7));
        timer_handler();       // Update timer
        need_resched = 1;     // Only set flag, do not directly switch context
        w_mie(r_mie() | MIE_MTIE); // Ensure timer interrupt is enabled
        reg_t sp;
        asm volatile("mv %0, sp" : "=r"(sp));
        lib_printf("Current sp in trap_handler: 0x%llx\n", (unsigned long long)sp);
        break;

這邊就交給 user.c 自動將cpu主控權交給os了 user.c

code
#include "os.h"

int shared_var = 500;
extern volatile int need_resched;

lock_t lock;

void user_task0(void)
{
    lib_puts("Task0: Created!\n");
    while (1)
    {
        lib_puts("Task0: Running...\n");
        lib_delay(1000);

 if (need_resched) {
            lib_puts("Task1: Yield to OS...\n");  // debug 用
            need_resched = 0;
            task_os();
        }
    }
    }

void user_task1(void)
{
    lib_puts("Task1: Created!\n");
    while (1)
    {
        lib_puts("Task1: Running...\n");
        lib_delay(1000);

 if (need_resched) {
            lib_puts("Task1: Yield to OS...\n");  // debug 用
            need_resched = 0;
            task_os();
        }
    }
    }

void user_task2(void)
{
    lib_puts("Task2: Created!\n");
    while (1)
    {
        for (int i = 0; i < 50; i++)
        {
            lock_acquire(&lock);
            shared_var++;
            lock_free(&lock);
            lib_delay(100);
        }
        lib_printf("The value of shared_var is: %d \n", shared_var);
    }
}

void user_task3(void)
{
    lib_puts("Task3: Created!\n");
    while (1)
    {
        lib_puts("Trying to get the lock... \n");
        lock_acquire(&lock);
        lib_puts("Get the lock!\n");
        lock_free(&lock);
        lib_delay(1000);

    }
}

void user_init()
{
    // lock_init(&lock);
    task_create(&user_task0);
    task_create(&user_task1);
    // task_create(&user_task2);
    // task_create(&user_task3);
}

現在進入到初始化網卡的部分, 這邊要注意的是分兩個步驟,第一是對virtio net網卡進行初始化,第二步驟再把網卡註冊進 lwip, 這邊是否一定要lwip才能接受網頁協定?,不一定,你完全可以透過控制rx tx,攔截你想要的封包,檢測到什麼發送你要的封包出去, 你可以註冊完網卡就開一個小型 httpserver 擬就可以透過curl 或者網頁連到。,理所當然也可以自己ping image image 還是說要開啟一個http server這邊都是沒問題的`,看你要直接polling 還是等isr 都ok

code
    simple_http_server_raw();
    net_init = 1 ;
    // It is recommended to run RX loop or call sys_check_timeouts() regularly here
    while (1) {
          virtio_net_rx_loop2();    // RX event loop, do not omit!
        sys_check_timeouts();     // TCP timeout/retransmit
    }
code
#define VIRTIO_NET_BASE   0x10008000UL
#define NET_QUEUE_SIZE    64
#define ETH_FRAME_SIZE    1600
#define PGSIZE           4096
#define RAM_BASE         0x80000000UL

#define R_NET(addr) ((volatile uint32_t *)(VIRTIO_NET_BASE + (addr)))

// Virtio MMIO Registers (non-legacy)
#define VIRTIO_MMIO_MAGIC_VALUE       0x000
#define VIRTIO_MMIO_VERSION           0x004
#define VIRTIO_MMIO_DEVICE_ID         0x008
#define VIRTIO_MMIO_VENDOR_ID         0x00c
#define VIRTIO_MMIO_DEVICE_FEATURES   0x010
#define VIRTIO_MMIO_DRIVER_FEATURES   0x020
#define VIRTIO_MMIO_FEATURES_SEL      0x014
#define VIRTIO_MMIO_QUEUE_SEL         0x030
#define VIRTIO_MMIO_QUEUE_NUM_MAX     0x034
#define VIRTIO_MMIO_QUEUE_NUM         0x038
#define VIRTIO_MMIO_QUEUE_READY       0x044
#define VIRTIO_MMIO_QUEUE_NOTIFY      0x050
#define VIRTIO_MMIO_INTERRUPT_STATUS  0x060
#define VIRTIO_MMIO_INTERRUPT_ACK     0x064
#define VIRTIO_MMIO_STATUS            0x070
#define VIRTIO_MMIO_GUEST_PAGE_SIZE   0x028

#define VRING_DESC_F_NEXT  1
#define VRING_DESC_F_WRITE 2

#define VIRTIO_CONFIG_S_ACKNOWLEDGE 0x01
#define VIRTIO_CONFIG_S_DRIVER      0x02
#define VIRTIO_CONFIG_S_DRIVER_OK   0x04
#define VIRTIO_CONFIG_S_FEATURES_OK 0x08
#define VIRTIO_CONFIG_S_FAILED      0x80

typedef struct {
    char pages[2 * PGSIZE] __attribute__((aligned(PGSIZE)));
    virtq_desc_t *desc;         // RX descriptors
    virtq_avail_t *avail;       // RX avail ring
    virtq_used_t *used;         // RX used ring
    uint16_t used_idx;

    // TX queue
    char tx_pages[2 * PGSIZE] __attribute__((aligned(PGSIZE)));
    virtq_desc_t *tx_desc;      // TX descriptors
    virtq_avail_t *tx_avail;    // TX avail ring
    virtq_used_t *tx_used;      // TX used ring
    uint16_t tx_free_idx;
    uint16_t tx_used_idx;

    uint8_t rx_buffers[NET_QUEUE_SIZE][ETH_FRAME_SIZE] __attribute__((aligned(16)));
    uint8_t tx_buffers[NET_QUEUE_SIZE][ETH_FRAME_SIZE] __attribute__((aligned(16)));
} virtio_net_t;

static virtio_net_t vnet;
#define VIRTIO_NET_F_MAC    5  // bit 5
#define VIRTIO_NET_CONFIG_MAC_OFFSET  0x0   // The first field of the config region is MAC
#define VIRTIO_MMIO_CONFIG  0x100  // Config space offset

// Define these constants according to your platform
#define NET_QUEUE_SIZE  8
#define ETH_FRAME_SIZE  1514
#define PGSIZE  4096

// QEMU RISC-V virtio-mmio base register, according to your linker settings
#define VIRTIO_MMIO_BASE   0x10008000UL
#define R_NET(offset)  ((volatile uint32_t *)(VIRTIO_MMIO_BASE + (offset)))

// legacy mmio register offset
#define VIRTIO_MMIO_MAGIC_VALUE      0x000
#define VIRTIO_MMIO_VERSION         0x004
#define VIRTIO_MMIO_DEVICE_ID       0x008
#define VIRTIO_MMIO_VENDOR_ID       0x00c
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028
#define VIRTIO_MMIO_QUEUE_SEL       0x030
#define VIRTIO_MMIO_QUEUE_NUM_MAX   0x034
#define VIRTIO_MMIO_QUEUE_NUM       0x038
#define VIRTIO_MMIO_QUEUE_ALIGN     0x03c
#define VIRTIO_MMIO_QUEUE_PFN       0x040
#define VIRTIO_MMIO_QUEUE_NOTIFY    0x050
#define VIRTIO_MMIO_STATUS          0x070

// Status bits
#define VIRTIO_CONFIG_S_ACKNOWLEDGE (1 << 0)
#define VIRTIO_CONFIG_S_DRIVER      (1 << 1)
#define VIRTIO_CONFIG_S_DRIVER_OK   (1 << 2)
#define VIRTIO_CONFIG_S_FEATURES_OK (1 << 3)
#define VIRTIO_CONFIG_S_FAILED      (1 << 7)

// --- Your queue, desc, avail, used structures and global buffer ----
// According to your previous definitions

static inline uintptr_t virt_to_phys(const void *addr) {
    return (uintptr_t)addr;
}
struct netif vnet_netif;

static int net_init = 0;
// netif initialization callback
err_t virtio_netif_init(struct netif *netif)
{
    uint8_t mac[6];
    volatile uint8_t *cfg_base = (volatile uint8_t *)(VIRTIO_MMIO_BASE + VIRTIO_MMIO_CONFIG);
    for (int i = 0; i < 6; ++i)
        mac[i] = cfg_base[i];

    netif->linkoutput = virtio_netif_linkoutput;
    netif->output = etharp_output;
    netif->hwaddr_len = 6;
    memcpy(netif->hwaddr, mac, 6);
    netif->mtu = 1500;
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
    return ERR_OK;
}

void virtio_net_input(uint8_t *eth_frame, uint32_t len)
{
    struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    if (!p) return;
    pbuf_take(p, eth_frame, len);

    if (vnet_netif.input(p, &vnet_netif) != ERR_OK)
        pbuf_free(p);
}

void virtio_net_rx_loop2(void)
{
    if(net_init== 1){
        // while (1) {
        virtio_net_tx_recycle();

        uint16_t cur_used_idx = vnet.used->idx;
        while (vnet.used_idx != cur_used_idx) {
            uint16_t ring_idx = vnet.used_idx % NET_QUEUE_SIZE;
            uint16_t desc_idx = vnet.used->ring[ring_idx].id;
            uint32_t len = vnet.used->ring[ring_idx].len;
            uint8_t *buf = vnet.rx_buffers[desc_idx];
            uint8_t *eth_frame = buf + 10;  // virtio header

            // Pass the whole packet to lwIP
            virtio_net_input(eth_frame, len - 10);

            // Refill RX buffer
            vnet.avail->ring[vnet.avail->idx % NET_QUEUE_SIZE] = desc_idx;
            asm volatile("fence w, w" ::: "memory");
            vnet.avail->idx++;
            vnet.used_idx++;
        }
        // If you use polling, you can add a small delay or sys_check_timeouts();
        sys_check_timeouts(); // lwIP timeout mechanism (necessary)
    }
}

static err_t http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
    if (p == NULL) {
        tcp_close(pcb);
        return ERR_OK;
    }

    // Simple parsing of the first line GET path
    char *req = (char *)p->payload;
    if (strstr(req, "GET /hello ") == req) {
        // Return HTTP/1.1 + JSON
        const char *html = "<html><body><h1>Hello World!</h1></body></html>";
        const int html_len = 46; // Please check this manually or use strlen(html)
        const char *resp_head =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/html\r\n"
            "Content-Length: 46\r\n"
            "Connection: close\r\n"
            "\r\n";

        char resp[256];
        int pos = 0;

        // Copy HTTP header
        for (int i = 0; resp_head[i]; ++i)
            resp[pos++] = resp_head[i];
        // Copy HTML content
        for (int i = 0; html[i]; ++i)
            resp[pos++] = html[i];
        resp[pos] = '\0'; // Not necessary for HTTP, but useful for debug print

        tcp_write(pcb, resp, strlen(resp), TCP_WRITE_FLAG_COPY);
        tcp_output(pcb);
    } else {
        // Other paths return 404
        const char *resp =
            "HTTP/1.1 404 Not Found\r\n"
            "Content-Type: text/plain\r\n"
            "Content-Length: 9\r\n"
            "Connection: close\r\n"
            "\r\n"
            "Not found";
        tcp_write(pcb, resp, strlen(resp), TCP_WRITE_FLAG_COPY);
        tcp_output(pcb);
    }

    tcp_recved(pcb, p->tot_len);
    pbuf_free(p);
    tcp_close(pcb); // Close after processing (short connection)
    return ERR_OK;
}

static err_t http_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    tcp_recv(newpcb, http_recv);
    return ERR_OK;
}

void simple_http_server_raw(void)
{
    struct tcp_pcb *pcb = tcp_new();
    if (!pcb) return;
    tcp_bind(pcb, IP_ADDR_ANY, 80);
    pcb = tcp_listen(pcb);
    tcp_accept(pcb, http_accept);
}

void virtio_net_init() {
    uint32_t status = 0;
    uint32_t magic = *(R_NET(VIRTIO_MMIO_MAGIC_VALUE));
    uint32_t version = *(R_NET(VIRTIO_MMIO_VERSION));
    uint32_t device_id = *(R_NET(VIRTIO_MMIO_DEVICE_ID));
    uint32_t vendor_id = *(R_NET(VIRTIO_MMIO_VENDOR_ID));

    lib_printf("[NET] Magic: 0x%x\n", magic);
    lib_printf("[NET] Version: %d\n", version);
    lib_printf("[NET] Device ID: %d\n", device_id);
    lib_printf("[NET] Vendor ID: 0x%x\n", vendor_id);

    if (magic != 0x74726976 || version != 1 || device_id != 1 || vendor_id != 0x554d4551) {
        panic("[NET] virtio-net legacy device not found or incompatible");
    }

    // 1. Reset
    *(R_NET(VIRTIO_MMIO_STATUS)) = 0;
    status = 0;

    // 2. Acknowledge
    status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
    *(R_NET(VIRTIO_MMIO_STATUS)) = status;

    // 3. Driver
    status |= VIRTIO_CONFIG_S_DRIVER;
    *(R_NET(VIRTIO_MMIO_STATUS)) = status;

    // 4. Feature negotiation (only use low 32 bits)
    uint32_t features = *(R_NET(VIRTIO_MMIO_DEVICE_FEATURES));
    uint32_t supported_features = 0; // No extra features supported
    features &= supported_features;
    *(R_NET(VIRTIO_MMIO_DRIVER_FEATURES)) = features;

    // 5. Set guest page size
    *(R_NET(VIRTIO_MMIO_GUEST_PAGE_SIZE)) = PGSIZE;

    // 6. Initialize RX queue (queue 0)
    *(R_NET(VIRTIO_MMIO_QUEUE_SEL)) = 0;
    uint32_t maxq = *(R_NET(VIRTIO_MMIO_QUEUE_NUM_MAX));
    if (maxq == 0 || maxq < NET_QUEUE_SIZE) {
        panic("[NET] queue too small");
    }
    *(R_NET(VIRTIO_MMIO_QUEUE_NUM)) = NET_QUEUE_SIZE;

    memset(vnet.pages, 0, sizeof(vnet.pages));
    vnet.desc = (virtq_desc_t *)vnet.pages;
    vnet.avail = (virtq_avail_t *)(vnet.pages + NET_QUEUE_SIZE * sizeof(virtq_desc_t));
    vnet.used = (virtq_used_t *)(vnet.pages + PGSIZE);
    vnet.used_idx = 0;

    for (int i = 0; i < NET_QUEUE_SIZE; i++) {
        vnet.desc[i].addr = virt_to_phys(&vnet.rx_buffers[i]);
        vnet.desc[i].len = ETH_FRAME_SIZE;
        vnet.desc[i].flags = VRING_DESC_F_WRITE;
        vnet.avail->ring[i] = i;
    }
    vnet.avail->idx = NET_QUEUE_SIZE;

    uintptr_t pfn_rx = virt_to_phys(vnet.pages) / PGSIZE;
    *(R_NET(VIRTIO_MMIO_QUEUE_PFN)) = pfn_rx;
    *(R_NET(VIRTIO_MMIO_QUEUE_READY)) = 1;

    lib_printf("desc @ VA:%p PA:0x%lx\n", vnet.desc, virt_to_phys(vnet.desc));
    lib_printf("avail @ VA:%p PA:0x%lx\n", vnet.avail, virt_to_phys(vnet.avail));
    lib_printf("used @ VA:%p PA:0x%lx\n", vnet.used, virt_to_phys(vnet.used));
    for (int i=0; i<NET_QUEUE_SIZE; i++) {
        lib_printf("desc[%d] buf VA:%p PA:0x%lx\n", i, &vnet.rx_buffers[i], virt_to_phys(&vnet.rx_buffers[i]));
    }

    // 7. Initialize TX queue (queue 1)
    static char tx_pages[2 * PGSIZE] __attribute__((aligned(PGSIZE)));
    memset(tx_pages, 0, sizeof(tx_pages));
    vnet.tx_desc = (virtq_desc_t *)tx_pages;
    vnet.tx_avail = (virtq_avail_t *)(tx_pages + NET_QUEUE_SIZE * sizeof(virtq_desc_t));
    vnet.tx_used = (virtq_used_t *)(tx_pages + PGSIZE);
    vnet.tx_free_idx = 0;

    for (int i = 0; i < NET_QUEUE_SIZE; i++) {
        vnet.tx_desc[i].addr = virt_to_phys(&vnet.tx_buffers[i]);
        vnet.tx_desc[i].len = 0;
        vnet.tx_desc[i].flags = 0;
    }
    vnet.tx_avail->idx = 0;

    *(R_NET(VIRTIO_MMIO_QUEUE_SEL)) = 1;
    *(R_NET(VIRTIO_MMIO_QUEUE_NUM)) = NET_QUEUE_SIZE;

    uintptr_t pfn_tx = virt_to_phys(tx_pages) / PGSIZE;
    *(R_NET(VIRTIO_MMIO_QUEUE_PFN)) = pfn_tx;
    *(R_NET(VIRTIO_MMIO_QUEUE_READY)) = 1;

    // 8. Driver OK
    status |= VIRTIO_CONFIG_S_DRIVER_OK;
    *(R_NET(VIRTIO_MMIO_STATUS)) = status;

    uint8_t mac[6];
    volatile uint8_t *cfg_base = (volatile uint8_t *)(VIRTIO_MMIO_BASE + VIRTIO_MMIO_CONFIG);
    for (int i = 0; i < 6; ++i)
        mac[i] = cfg_base[i];
    lib_printf("[NET] Device MAC: %x:%x:%x:%x:%x:%x\n",
        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    lib_printf("pages VA: %p, PA: 0x%lx\n", vnet.pages, virt_to_phys(vnet.pages));
    for (int i=0; i<NET_QUEUE_SIZE; ++i)
        lib_printf("desc[%d] buf VA: %p, PA: 0x%lx\n", i, &vnet.rx_buffers[i], virt_to_phys(&vnet.rx_buffers[i]));

    // virtio_net_rx_loop();
    lwip_init();
    // 3. Register lwIP netif
    ip4_addr_t ipaddr, netmask, gw;
    IP4_ADDR(&ipaddr, 192,168,123,1);
    IP4_ADDR(&netmask, 255,255,255,0);
    IP4_ADDR(&gw, 192,168,123,1);

    netif_add(&vnet_netif, &ipaddr, &netmask, &gw, NULL, virtio_netif_init, ethernet_input);
    netif_set_default(&vnet_netif);
    netif_set_up(&vnet_netif);
    virtio_net_tx_init_free_list();
    // virtio_net_rx_loop2();
    simple_http_server_raw();
    net_init = 1 ;
    // It is recommended to run RX loop or call sys_check_timeouts() regularly here
    // while (1) {
    //       virtio_net_rx_loop2();    // RX event loop, do not omit!
    //     sys_check_timeouts();     // TCP timeout/retransmit
    // }
    // virtio_net_rx_loop();
}

void print_packet(uint8_t *buf, uint32_t len) {
    lib_printf("[NET] Packet len=%d\n", len);
    for (uint32_t i=0; i<len && i<64; i++) {
        lib_printf("%x ", buf[i]);
        if ((i+1)%16==0) lib_printf("\n");
    }
    lib_printf("\n");
}
void print_arp_reply_packet(uint8_t *tx_eth_frame) {
    lib_printf("ARP Reply Packet Content:\n");

    lib_printf("Dst MAC: ");
    for (int i = 0; i < 6; i++) {
        lib_printf("%x ", tx_eth_frame[i]);
    }
    lib_printf("\n");

    lib_printf("Src MAC: ");
    for (int i = 6; i < 12; i++) {
        lib_printf("%x ", tx_eth_frame[i]);
    }
    lib_printf("\n");

    lib_printf("EtherType: %x %x\n", tx_eth_frame[12], tx_eth_frame[13]);

    lib_printf("Hardware Type: %x %x\n", tx_eth_frame[14], tx_eth_frame[15]);
    lib_printf("Protocol Type: %x %x\n", tx_eth_frame[16], tx_eth_frame[17]);
    lib_printf("Hardware Size: %x\n", tx_eth_frame[18]);
    lib_printf("Protocol Size: %x\n", tx_eth_frame[19]);

    lib_printf("Opcode: %x %x\n", tx_eth_frame[20], tx_eth_frame[21]);

    lib_printf("Sender MAC: ");
    for (int i = 22; i < 28; i++) {
        lib_printf("%x ", tx_eth_frame[i]);
    }
    lib_printf("\n");

    lib_printf("Sender IP: ");
    for (int i = 28; i < 32; i++) {
        lib_printf("%d.", tx_eth_frame[i]);
    }
    lib_printf("\b \n");

    lib_printf("Target MAC: ");
    for (int i = 32; i < 38; i++) {
        lib_printf("%x ", tx_eth_frame[i]);
    }
    lib_printf("\n");

    lib_printf("Target IP: ");
    for (int i = 38; i < 42; i++) {
        lib_printf("%d.", tx_eth_frame[i]);
    }
    lib_printf("\b \n");
}

// Example ICMP checksum calculation function
uint16_t icmp_checksum(uint8_t *data, int len) {
    uint32_t sum = 0;
    for (int i = 0; i < len; i += 2) {
        uint16_t word = data[i] << 8;
        if (i + 1 < len)
            word |= data[i + 1];
        else
            word |= 0;
        sum += word;
    }
    // Carry wrap-around
    while (sum >> 16) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }
    return (uint16_t)(~sum);
}

static uint16_t tx_free_list[NET_QUEUE_SIZE];
static uint16_t tx_free_head = 0, tx_free_tail = 0;
void virtio_net_tx_init_free_list(void) {
    for (int i = 0; i < NET_QUEUE_SIZE; i++) {
        tx_free_list[i] = i;
    }
    tx_free_head = 0;
    tx_free_tail = NET_QUEUE_SIZE;
}

void virtio_net_tx_recycle(void) {
    while (vnet.tx_used_idx != vnet.tx_used->idx) {
        uint16_t used_ring_idx = vnet.tx_used_idx % NET_QUEUE_SIZE;
        uint16_t desc_idx = vnet.tx_used->ring[used_ring_idx].id;

        // Return to free_list
        tx_free_list[tx_free_tail % NET_QUEUE_SIZE] = desc_idx;
        tx_free_tail = (tx_free_tail + 1) % (2 * NET_QUEUE_SIZE); // Prevent overflow

        lib_printf("[NET] Recycle TX descriptor %d (head=%d tail=%d)\n", desc_idx, tx_free_head, tx_free_tail);
        vnet.tx_used_idx++;
    }
}

err_t virtio_netif_linkoutput(struct netif *netif, struct pbuf *p)
{
    if (tx_free_head == tx_free_tail) {
        // No available TX desc
        return ERR_MEM;
    }

    uint16_t tx_idx = tx_free_list[tx_free_head % NET_QUEUE_SIZE];
    tx_free_head = (tx_free_head + 1) % (2 * NET_QUEUE_SIZE); // Prevent overflow

    uint8_t *tx_buf = vnet.tx_buffers[tx_idx];
    memset(tx_buf, 0, 10);
    pbuf_copy_partial(p, tx_buf + 10, p->tot_len, 0);

    vnet.tx_desc[tx_idx].addr = virt_to_phys(tx_buf);
    vnet.tx_desc[tx_idx].len = p->tot_len + 10;
    vnet.tx_desc[tx_idx].flags = 0;

    vnet.tx_avail->ring[vnet.tx_avail->idx % NET_QUEUE_SIZE] = tx_idx;
    asm volatile("fence w, w" ::: "memory");
    vnet.tx_avail->idx++;

    *(R_NET(VIRTIO_MMIO_QUEUE_NOTIFY)) = 1;

    return ERR_OK;
}

// Interrupt handler for virtio-net
void virtio_net_interrupt_handler(void) {
    // Read ISR to clear interrupt (legacy virtio-mmio)
    // uint32_t isr = *(R_NET(VIRTIO_MMIO_ISR));
    // (void)isr; // You can check bits to determine RX/TX
    // Process new packets in the used ring
    // virtio_net_rx_loop();
    virtio_net_rx_loop2(); // Handler used for polling
    // Replenish descriptor, update avail, notify device if needed
}

當然這邊virtio_net_rx_loop2 這邊有看到兩個版本,virtio_net_rx_loop這部分是還沒引入 lwip,你要自己實做arp 和 icmp 協議假設你要全部自己來的話 image 也是沒問題的,這部分要叫chatgpt debug 最好的方式就是把封包全印,這樣chatgpt 才能取得更好的context 去推理

code
// Interrupt handler for virtio-net
void virtio_net_interrupt_handler(void) {
    // Read ISR to clear interrupt (legacy virtio-mmio)
    // uint32_t isr = *(R_NET(VIRTIO_MMIO_ISR));
    // (void)isr; // You can check bits to determine RX/TX
    // Process new packets in the used ring
    // virtio_net_rx_loop();
    virtio_net_rx_loop(); // Handler used for polling
    // Replenish descriptor, update avail, notify device if needed
}
code
// Interrupt handler for virtio-net
static uint16_t tx_used_idx = 0;  
static uint16_t tx_free_idx = 0; 

// The main RX event loop for polling mode
void virtio_net_rx_loop() {
    uint8_t my_mac[6] = {0x52,0x54,0x00,0x12,0x34,0x56};
    uint8_t my_ip[4] = {192,168,123,1};

    while (1) {
        virtio_net_tx_recycle();  // Recycle completed TX descriptors

        uint16_t cur_used_idx = vnet.used->idx;
        while (vnet.used_idx != cur_used_idx) {
            uint16_t ring_idx = vnet.used_idx % NET_QUEUE_SIZE;
            uint16_t desc_idx = vnet.used->ring[ring_idx].id;
            uint32_t len = vnet.used->ring[ring_idx].len;
            uint8_t *buf = vnet.rx_buffers[desc_idx];
            uint8_t *eth_frame = buf + 10;  // Skip virtio-net header

            // Print the packet content (for debugging)
            print_packet(eth_frame, len - 10);

            // ----- ARP Request check and reply -----
            if (len >= 42 &&
                eth_frame[12] == 0x08 && eth_frame[13] == 0x06 &&      // EtherType ARP
                eth_frame[20] == 0x00 && eth_frame[21] == 0x01 &&      // Opcode request
                eth_frame[38] == my_ip[0] && eth_frame[39] == my_ip[1] &&
                eth_frame[40] == my_ip[2] && eth_frame[41] == my_ip[3]) { // Target IP matches

                uint16_t next_free = (tx_free_idx + 1) % NET_QUEUE_SIZE;
                if (next_free == tx_used_idx) {
                    // No available descriptor, refill RX buffer and drop packet
                    vnet.avail->ring[vnet.avail->idx % NET_QUEUE_SIZE] = desc_idx;
                    asm volatile("fence w, w" ::: "memory");
                    vnet.avail->idx++;
                    vnet.used_idx++;
                    continue;
                }

                uint16_t tx_idx = tx_free_idx;
                uint8_t *tx_buf = vnet.tx_buffers[tx_idx];
                uint8_t *tx_eth_frame = tx_buf + 10;

                // Copy packet and modify as ARP Reply
                memcpy(tx_eth_frame, eth_frame, len - 10);

                // Modify ARP Reply fields
                for (int i = 0; i < 6; i++) tx_eth_frame[i] = eth_frame[6 + i];    // dst MAC = original src MAC
                for (int i = 0; i < 6; i++) tx_eth_frame[6 + i] = my_mac[i];      // src MAC = our MAC
                tx_eth_frame[20] = 0x00; tx_eth_frame[21] = 0x02;                 // opcode = reply
                for (int i = 0; i < 6; i++) tx_eth_frame[22 + i] = my_mac[i];     // sender MAC = us
                for (int i = 0; i < 4; i++) tx_eth_frame[28 + i] = my_ip[i];      // sender IP = us
                for (int i = 0; i < 6; i++) tx_eth_frame[32 + i] = eth_frame[6 + i];  // target MAC = original sender MAC
                for (int i = 0; i < 4; i++) tx_eth_frame[38 + i] = eth_frame[28 + i]; // target IP = original sender IP

                print_arp_reply_packet(tx_eth_frame);

                // Set TX descriptor
                vnet.tx_desc[tx_idx].addr = virt_to_phys(tx_buf);
                vnet.tx_desc[tx_idx].len = 52;
                vnet.tx_desc[tx_idx].flags = 0;

                // Place into TX avail ring and notify device
                vnet.tx_avail->ring[vnet.tx_avail->idx % NET_QUEUE_SIZE] = tx_idx;
                asm volatile("fence w, w" ::: "memory");
                vnet.tx_avail->idx++;

                *(R_NET(VIRTIO_MMIO_QUEUE_NOTIFY)) = 1;

                tx_free_idx = next_free;

                // Refill RX buffer
                vnet.avail->ring[vnet.avail->idx % NET_QUEUE_SIZE] = desc_idx;
                asm volatile("fence w, w" ::: "memory");
                vnet.avail->idx++;

                lib_puts("[NET] Sent ARP Reply via TX queue!\n");
                vnet.used_idx++;
                continue;
            }

            // ----- ICMP Echo Request (Ping) check and reply -----
            uint8_t ip_header_len = (eth_frame[14] & 0x0f) * 4;
            uint8_t icmp_type = eth_frame[14 + ip_header_len];

            lib_printf("Check EtherType: %x  %x \n", eth_frame[12], eth_frame[13]);
            lib_printf("Check Protocol: %x \n", eth_frame[23]);
            lib_printf("Check ICMP Type: %x \n", icmp_type);
            lib_printf("Check Target IP: %d.%d.%d.%d\n", eth_frame[14 + 16], eth_frame[14 + 17], eth_frame[14 + 18], eth_frame[14 + 19]);

            if (len >= 42 &&
                eth_frame[12] == 0x08 && eth_frame[13] == 0x00 &&              // EtherType IPv4
                eth_frame[23] == 0x01 &&                                       // Protocol ICMP
                icmp_type == 0x08 &&                                           // ICMP Echo Request
                eth_frame[14 + 16] == my_ip[0] && eth_frame[14 + 17] == my_ip[1] &&
                eth_frame[14 + 18] == my_ip[2] && eth_frame[14 + 19] == my_ip[3]) { // Target IP matches

                uint16_t next_free = (tx_free_idx + 1) % NET_QUEUE_SIZE;
                if (next_free == tx_used_idx) {
                    // No available descriptor, drop packet
                    vnet.avail->ring[vnet.avail->idx % NET_QUEUE_SIZE] = desc_idx;
                    asm volatile("fence w, w" ::: "memory");
                    vnet.avail->idx++;
                    vnet.used_idx++;
                    continue;
                }

                uint16_t tx_idx = tx_free_idx;
                uint8_t *tx_buf = vnet.tx_buffers[tx_idx];
                uint8_t *tx_eth_frame = tx_buf + 10;

                memcpy(tx_eth_frame, eth_frame, len - 10);

                // Modify Ethernet header (src/dst MAC)
                for (int i = 0; i < 6; i++) tx_eth_frame[i] = eth_frame[6 + i];      // dst MAC = original src MAC
                for (int i = 0; i < 6; i++) tx_eth_frame[6 + i] = my_mac[i];        // src MAC = our MAC

                // IP header offset 14, swap src/dst IP
                for (int i = 0; i < 4; i++) {
                    tx_eth_frame[14 + 16 + i] = eth_frame[14 + 12 + i];       // dst IP = original src IP
                    tx_eth_frame[14 + 12 + i] = my_ip[i];                     // src IP = our IP
                }

                // ICMP header: offset 14+20
                tx_eth_frame[14 + 20] = 0x00;   // ICMP type = Echo Reply
                tx_eth_frame[14 + 22] = 0;
                tx_eth_frame[14 + 23] = 0;
                uint16_t icmp_len = len - 10 - 14 - 20;
                uint16_t checksum = icmp_checksum(tx_eth_frame + 14 + 20, icmp_len);
                tx_eth_frame[14 + 22] = (checksum >> 8) & 0xFF;
                tx_eth_frame[14 + 23] = checksum & 0xFF;

                vnet.tx_desc[tx_idx].addr = virt_to_phys(tx_buf);
                vnet.tx_desc[tx_idx].len = len ;
                vnet.tx_desc[tx_idx].flags = 0;

                vnet.tx_avail->ring[vnet.tx_avail->idx % NET_QUEUE_SIZE] = tx_idx;
                asm volatile("fence w, w" ::: "memory");
                vnet.tx_avail->idx++;

                *(R_NET(VIRTIO_MMIO_QUEUE_NOTIFY)) = 1;

                tx_free_idx = next_free;

                vnet.avail->ring[vnet.avail->idx % NET_QUEUE_SIZE] = desc_idx;
                asm volatile("fence w, w" ::: "memory");
                vnet.avail->idx++;

                lib_puts("[NET] Sent ICMP Echo Reply via TX queue!\n");
                vnet.used_idx++;
                continue;
            }

            // Not ARP or ICMP Echo Request, just recycle RX buffer
            vnet.avail->ring[vnet.avail->idx % NET_QUEUE_SIZE] = desc_idx;
            asm volatile("fence w, w" ::: "memory");
            vnet.avail->idx++;
            vnet.used_idx++;
        }
    }
}