Network Virtio and RTL8139 NIC support

This commit is contained in:
boreddevnl 2026-03-14 16:52:52 +01:00
parent 95c465ca39
commit b05b221c41
16 changed files with 674 additions and 52 deletions

View file

@ -157,7 +157,7 @@ run: $(ISO_IMAGE)
qemu-system-x86_64 -m 4G -serial stdio -cdrom $< -boot d \
-smp 4 \
-audiodev coreaudio,id=audio0 -machine pcspk-audiodev=audio0 \
-netdev user,id=net0,hostfwd=udp::12346-:12345 -device e1000,netdev=net0 \
-netdev user,id=net0,hostfwd=udp::12346-:12345 -device virtio-net-pci,netdev=net0 \
-vga std -global VGA.xres=1920 -global VGA.yres=1080 \
-display cocoa,show-cursor=off \
-drive file=disk.img,format=raw,file.locking=off \

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -130,6 +130,14 @@ e1000_device_t* e1000_get_device(void) {
return &e1000_dev;
}
int e1000_get_mac(uint8_t* mac_out) {
if (!e1000_initialized) return -1;
for (int i = 0; i < 6; i++) {
mac_out[i] = e1000_dev.mac_address.bytes[i];
}
return 0;
}
int e1000_send_packet(const void* data, size_t length) {
if (!e1000_initialized || !e1000_dev.initialized) return -1;
if (length > 2048) return -1;

View file

@ -1,9 +0,0 @@
#ifndef E1000_NETIF_H
#define E1000_NETIF_H
#include "lwip/netif.h"
err_t e1000_netif_init(struct netif *netif);
void e1000_netif_poll(struct netif *netif);
#endif

View file

@ -10,12 +10,13 @@
#include "lwip/raw.h"
#include "lwip/sys.h"
#include "netif/ethernet.h"
#include "e1000_netif.h"
#include "nic_netif.h"
#include "kutils.h"
#include "pci.h"
#include "e1000.h"
#include "nic.h"
static struct netif e1000_netif;
static struct netif nic_netif;
static int lwip_initialized = 0;
static struct tcp_pcb *current_tcp_pcb = NULL;
@ -58,14 +59,9 @@ static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)
int network_init(void) {
if (lwip_initialized) return 0;
// First, find and initialize the E1000 device if not already done
if (!e1000_get_device()) {
pci_device_t pci_dev;
if (pci_find_device(E1000_VENDOR_ID, E1000_DEVICE_ID_82540EM, &pci_dev)) {
if (e1000_init(&pci_dev) != 0) return -1;
} else {
return -1; // No E1000 found
}
// First, find and initialize the generic NIC device if not already done
if (nic_init() != 0) {
return -1; // No supported NIC found
}
lwip_init();
@ -76,12 +72,12 @@ int network_init(void) {
ip4_addr_set_zero(&netmask);
ip4_addr_set_zero(&gw);
if (netif_add(&e1000_netif, &ipaddr, &netmask, &gw, NULL, e1000_netif_init, ethernet_input) == NULL) {
if (netif_add(&nic_netif, &ipaddr, &netmask, &gw, NULL, nic_netif_init, ethernet_input) == NULL) {
return -1;
}
netif_set_default(&e1000_netif);
netif_set_up(&e1000_netif);
netif_set_default(&nic_netif);
netif_set_up(&nic_netif);
lwip_initialized = 1;
@ -113,13 +109,13 @@ int network_init(void) {
int network_get_mac_address(mac_address_t* mac) {
if (!lwip_initialized) return -1;
for (int i = 0; i < 6; i++) mac->bytes[i] = e1000_netif.hwaddr[i];
for (int i = 0; i < 6; i++) mac->bytes[i] = nic_netif.hwaddr[i];
return 0;
}
int network_get_ipv4_address(ipv4_address_t* ip) {
if (!lwip_initialized) return -1;
u32_t addr = ip4_addr_get_u32(netif_ip4_addr(&e1000_netif));
u32_t addr = ip4_addr_get_u32(netif_ip4_addr(&nic_netif));
ip->bytes[0] = (addr >> 0) & 0xFF;
ip->bytes[1] = (addr >> 8) & 0xFF;
ip->bytes[2] = (addr >> 16) & 0xFF;
@ -131,13 +127,13 @@ int network_set_ipv4_address(const ipv4_address_t* ip) {
if (!lwip_initialized) return -1;
ip4_addr_t ipaddr;
IP4_ADDR(&ipaddr, ip->bytes[0], ip->bytes[1], ip->bytes[2], ip->bytes[3]);
netif_set_ipaddr(&e1000_netif, &ipaddr);
netif_set_ipaddr(&nic_netif, &ipaddr);
return 0;
}
static volatile int network_processing = 0;
static void network_poll_internal(void) {
e1000_netif_poll(&e1000_netif);
nic_netif_poll(&nic_netif);
sys_check_timeouts();
}
@ -174,14 +170,14 @@ int network_dhcp_acquire(void) {
}
network_processing = 1;
serial_write("[DHCP] Starting...\n");
dhcp_start(&e1000_netif);
dhcp_start(&nic_netif);
uint32_t start = sys_now();
asm volatile("sti");
int loops = 0;
while (sys_now() - start < 10000) { // 10 second timeout
network_poll_internal();
if (dhcp_supplied_address(&e1000_netif)) {
if (dhcp_supplied_address(&nic_netif)) {
asm volatile("cli");
serial_write("[DHCP] Bound!\n");
network_processing = 0;
@ -405,7 +401,7 @@ int network_set_dns_server(const ipv4_address_t *ip) {
int network_get_gateway_ip(ipv4_address_t *ip) {
if (!lwip_initialized) return -1;
u32_t addr = ip4_addr_get_u32(netif_ip4_gw(&e1000_netif));
u32_t addr = ip4_addr_get_u32(netif_ip4_gw(&nic_netif));
ip->bytes[0] = (addr >> 0) & 0xFF;
ip->bytes[1] = (addr >> 8) & 0xFF;
ip->bytes[2] = (addr >> 16) & 0xFF;
@ -424,10 +420,10 @@ int network_get_dns_ip(ipv4_address_t *ip) {
}
int network_is_initialized(void) { return lwip_initialized; }
int network_has_ip(void) { return lwip_initialized && !ip4_addr_isany_val(*netif_ip4_addr(&e1000_netif)); }
int network_has_ip(void) { return lwip_initialized && !ip4_addr_isany_val(*netif_ip4_addr(&nic_netif)); }
int network_send_frame(const void* data, size_t length) { return e1000_send_packet(data, length); }
int network_receive_frame(void* buffer, size_t buffer_size) { return e1000_receive_packet(buffer, buffer_size); }
int network_send_frame(const void* data, size_t length) { return nic_send_packet(data, length); }
int network_receive_frame(void* buffer, size_t buffer_size) { return nic_receive_packet(buffer, buffer_size); }
static u16_t icmp_cksum(void *data, int len) {
u32_t sum = 0;

View file

@ -6,7 +6,7 @@
#include <stdint.h>
#include <stddef.h>
#include "e1000.h"
#include "nic.h"
#define ETH_FRAME_MAX_SIZE 1518
#define ETH_HEADER_SIZE 14

123
src/kernel/nic.c Normal file
View file

@ -0,0 +1,123 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "nic.h"
#include "pci.h"
#include "kutils.h"
// Forward declarations for driver inits
extern int e1000_init(pci_device_t* pci_dev);
extern int rtl8139_init(pci_device_t* pci_dev);
extern int virtio_net_init(pci_device_t* pci_dev);
extern int e1000_send_packet(const void* data, size_t length);
extern int e1000_receive_packet(void* buffer, size_t buffer_size);
extern int e1000_get_mac(uint8_t* mac_out);
extern int rtl8139_send_packet(const void* data, size_t length);
extern int rtl8139_receive_packet(void* buffer, size_t buffer_size);
extern int rtl8139_get_mac(uint8_t* mac_out);
extern int virtio_net_send_packet(const void* data, size_t length);
extern int virtio_net_receive_packet(void* buffer, size_t buffer_size);
extern int virtio_net_get_mac(uint8_t* mac_out);
static nic_driver_t active_nic_driver = {0};
static int nic_initialized = 0;
static int register_e1000(pci_device_t* dev) {
if (e1000_init(dev) == 0) {
active_nic_driver.name = "e1000";
active_nic_driver.init = e1000_init;
active_nic_driver.send_packet = e1000_send_packet;
active_nic_driver.receive_packet = e1000_receive_packet;
active_nic_driver.get_mac_address = e1000_get_mac;
return 0;
}
return -1;
}
static int register_rtl8139(pci_device_t* dev) {
if (rtl8139_init(dev) == 0) {
active_nic_driver.name = "rtl8139";
active_nic_driver.init = rtl8139_init;
active_nic_driver.send_packet = rtl8139_send_packet;
active_nic_driver.receive_packet = rtl8139_receive_packet;
active_nic_driver.get_mac_address = rtl8139_get_mac;
return 0;
}
return -1;
}
static int register_virtio_net(pci_device_t* dev) {
if (virtio_net_init(dev) == 0) {
active_nic_driver.name = "virtio-net";
active_nic_driver.init = virtio_net_init;
active_nic_driver.send_packet = virtio_net_send_packet;
active_nic_driver.receive_packet = virtio_net_receive_packet;
active_nic_driver.get_mac_address = virtio_net_get_mac;
return 0;
}
return -1;
}
int nic_init(void) {
if (nic_initialized) return 0;
pci_device_t pci_dev;
// Try finding RTL8139 (Vendor: 0x10EC, Device: 0x8139)
if (pci_find_device(0x10EC, 0x8139, &pci_dev)) {
if (register_rtl8139(&pci_dev) == 0) {
nic_initialized = 1;
return 0;
}
}
// Try finding Virtio-Net (Vendor: 0x1AF4, Device: 0x1000 for legacy or 0x1041 for modern)
// Here we mainly support legacy VirtIO Network (0x1000)
if (pci_find_device(0x1AF4, 0x1000, &pci_dev)) {
if (register_virtio_net(&pci_dev) == 0) {
nic_initialized = 1;
return 0;
}
}
// Modern VirtIO NET is 0x1041
if (pci_find_device(0x1AF4, 0x1041, &pci_dev)) {
if (register_virtio_net(&pci_dev) == 0) {
nic_initialized = 1;
return 0;
}
}
// Try finding E1000 (Vendor: 0x8086, Device: 0x100E)
if (pci_find_device(0x8086, 0x100E, &pci_dev)) {
if (register_e1000(&pci_dev) == 0) {
nic_initialized = 1;
return 0;
}
}
return -1; // No supported NIC found
}
nic_driver_t* nic_get_driver(void) {
if (!nic_initialized) return NULL;
return &active_nic_driver;
}
int nic_send_packet(const void* data, size_t length) {
if (!nic_initialized || !active_nic_driver.send_packet) return -1;
return active_nic_driver.send_packet(data, length);
}
int nic_receive_packet(void* buffer, size_t buffer_size) {
if (!nic_initialized || !active_nic_driver.receive_packet) return 0;
return active_nic_driver.receive_packet(buffer, buffer_size);
}
int nic_get_mac_address(uint8_t* mac_out) {
if (!nic_initialized || !active_nic_driver.get_mac_address) return -1;
return active_nic_driver.get_mac_address(mac_out);
}

25
src/kernel/nic.h Normal file
View file

@ -0,0 +1,25 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef NIC_H
#define NIC_H
#include <stdint.h>
#include <stddef.h>
#include "pci.h"
typedef struct nic_driver {
const char* name;
int (*init)(pci_device_t* pci_dev);
int (*send_packet)(const void* data, size_t length);
int (*receive_packet)(void* buffer, size_t buffer_size);
int (*get_mac_address)(uint8_t* mac_out);
} nic_driver_t;
int nic_init(void);
nic_driver_t* nic_get_driver(void);
int nic_send_packet(const void* data, size_t length);
int nic_receive_packet(void* buffer, size_t buffer_size);
int nic_get_mac_address(uint8_t* mac_out);
#endif

View file

@ -7,28 +7,24 @@
#include "lwip/ethip6.h"
#include "lwip/etharp.h"
#include "netif/etharp.h"
#include "e1000.h"
#include "nic.h"
#include "network.h"
#include "kutils.h"
#define IFNAME0 'e'
#define IFNAME1 'n'
struct e1000_netif_state {
// Any extra state if needed
};
static err_t e1000_low_level_output(struct netif *netif, struct pbuf *p) {
static err_t nic_low_level_output(struct netif *netif, struct pbuf *p) {
(void)netif;
if (p->next == NULL) {
if (e1000_send_packet(p->payload, p->len) != 0) {
if (nic_send_packet(p->payload, p->len) != 0) {
return ERR_IF;
}
} else {
u8_t buffer[2048];
u16_t copied = pbuf_copy_partial(p, buffer, 2048, 0);
if (e1000_send_packet(buffer, copied) != 0) {
if (nic_send_packet(buffer, copied) != 0) {
return ERR_IF;
}
}
@ -37,11 +33,11 @@ static err_t e1000_low_level_output(struct netif *netif, struct pbuf *p) {
return ERR_OK;
}
static void e1000_low_level_input(struct netif *netif) {
static void nic_low_level_input(struct netif *netif) {
u8_t buffer[2048];
int len;
while ((len = e1000_receive_packet(buffer, sizeof(buffer))) > 0) {
while ((len = nic_receive_packet(buffer, sizeof(buffer))) > 0) {
struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
@ -55,19 +51,17 @@ static void e1000_low_level_input(struct netif *netif) {
}
}
err_t e1000_netif_init(struct netif *netif) {
err_t nic_netif_init(struct netif *netif) {
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
netif->output = etharp_output;
netif->linkoutput = e1000_low_level_output;
netif->linkoutput = nic_low_level_output;
e1000_device_t* dev = e1000_get_device();
nic_driver_t* dev = nic_get_driver();
if (!dev) return ERR_IF;
netif->hwaddr_len = 6;
for (int i = 0; i < 6; i++) {
netif->hwaddr[i] = dev->mac_address.bytes[i];
}
nic_get_mac_address(netif->hwaddr);
netif->mtu = 1500;
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
@ -78,6 +72,6 @@ err_t e1000_netif_init(struct netif *netif) {
return ERR_OK;
}
void e1000_netif_poll(struct netif *netif) {
e1000_low_level_input(netif);
void nic_netif_poll(struct netif *netif) {
nic_low_level_input(netif);
}

9
src/kernel/nic_netif.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef NIC_NETIF_H
#define NIC_NETIF_H
#include "lwip/netif.h"
err_t nic_netif_init(struct netif *netif);
void nic_netif_poll(struct netif *netif);
#endif

186
src/kernel/rtl8139.c Normal file
View file

@ -0,0 +1,186 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "rtl8139.h"
#include "io.h"
#include "kutils.h"
#include "platform.h"
#define RTL8139_MAC_0 0x00
#define RTL8139_TSD0 0x10
#define RTL8139_TSAD0 0x20
#define RTL8139_RBSTART 0x30
#define RTL8139_CR 0x37
#define RTL8139_CAPR 0x38
#define RTL8139_CBR 0x3A
#define RTL8139_IMR 0x3C
#define RTL8139_ISR 0x3E
#define RTL8139_TCR 0x40
#define RTL8139_RCR 0x44
#define RTL8139_CONFIG_1 0x52
#define RTL8139_RCR_AAP (1 << 0) // Accept All Packets
#define RTL8139_RCR_APM (1 << 1) // Accept Physical Match Packets
#define RTL8139_RCR_AM (1 << 2) // Accept Multicast Packets
#define RTL8139_RCR_AB (1 << 3) // Accept Broadcast Packets
#define RTL8139_RCR_WRAP (1 << 7) // WRAP
#define RTL8139_CR_TE (1 << 2) // Transmitter Enable
#define RTL8139_CR_RE (1 << 3) // Receiver Enable
#define RTL8139_CR_RST (1 << 4) // Reset
static int rtl8139_initialized = 0;
static uint64_t mmio_base_addr = 0;
static uint8_t mac_addr[6];
// Receive buffer must be 8K + 16 bytes + 1.5K
// Let's use 32K buffer for safety, which is standard.
static uint8_t rx_buffer[32768 + 16 + 1500] __attribute__((aligned(4096)));
static uint16_t rx_ptr = 0; // Current read position
// Transmit buffers (4 descriptors in RTL8139)
static uint8_t tx_buffers[4][4096] __attribute__((aligned(4096)));
static uint8_t tx_curr = 0;
static inline uint8_t rtl8139_inb(uint16_t offset) { return *(volatile uint8_t*)(uintptr_t)(mmio_base_addr + offset); }
static inline uint16_t rtl8139_inw(uint16_t offset) { return *(volatile uint16_t*)(uintptr_t)(mmio_base_addr + offset); }
static inline uint32_t rtl8139_inl(uint16_t offset) { return *(volatile uint32_t*)(uintptr_t)(mmio_base_addr + offset); }
static inline void rtl8139_outb(uint16_t offset, uint8_t value) { *(volatile uint8_t*)(uintptr_t)(mmio_base_addr + offset) = value; }
static inline void rtl8139_outw(uint16_t offset, uint16_t value) { *(volatile uint16_t*)(uintptr_t)(mmio_base_addr + offset) = value; }
static inline void rtl8139_outl(uint16_t offset, uint32_t value) { *(volatile uint32_t*)(uintptr_t)(mmio_base_addr + offset) = value; }
int rtl8139_init(pci_device_t* pci_dev) {
if (rtl8139_initialized) return 0;
// Enable bus mastering
uint32_t command = pci_read_config(pci_dev->bus, pci_dev->device, pci_dev->function, 0x04);
pci_write_config(pci_dev->bus, pci_dev->device, pci_dev->function, 0x04, command | (1 << 2) | (1 << 1));
uint32_t bar1 = pci_read_config(pci_dev->bus, pci_dev->device, pci_dev->function, 0x14); // BAR1: MMIO
if (bar1 == 0 || bar1 == 0xFFFFFFFF) return -1;
if (bar1 & 1) return -1; // Should not be I/O space
mmio_base_addr = p2v(bar1 & ~0xF);
extern void serial_write(const char *str);
serial_write("[RTL8139] MMIO Base: 0x");
char hex_buf[32]; k_itoa_hex(mmio_base_addr, hex_buf); serial_write(hex_buf); serial_write("\n");
// Power on (CONFIG1)
rtl8139_outb(RTL8139_CONFIG_1, 0x00);
// Software Reset
rtl8139_outb(RTL8139_CR, RTL8139_CR_RST);
while (rtl8139_inb(RTL8139_CR) & RTL8139_CR_RST) {
// Wait
}
// Read MAC
uint32_t mac_low = rtl8139_inl(RTL8139_MAC_0);
uint32_t mac_high = rtl8139_inw(RTL8139_MAC_0 + 4);
mac_addr[0] = (mac_low >> 0) & 0xFF;
mac_addr[1] = (mac_low >> 8) & 0xFF;
mac_addr[2] = (mac_low >> 16) & 0xFF;
mac_addr[3] = (mac_low >> 24) & 0xFF;
mac_addr[4] = (mac_high >> 0) & 0xFF;
mac_addr[5] = (mac_high >> 8) & 0xFF;
serial_write("[RTL8139] MAC: ");
for(int i=0; i<6; i++) {
char buf[4];
k_itoa_hex(mac_addr[i], buf);
serial_write(buf);
if(i<5) serial_write(":");
}
serial_write("\n");
// Init RX buffer
uint32_t rx_phys = v2p((uint64_t)(uintptr_t)rx_buffer);
rtl8139_outl(RTL8139_RBSTART, rx_phys);
// Set IMR / ISR
rtl8139_outw(RTL8139_IMR, 0x0005); // TOK and ROK
// Set RCR (Receive Configuration Register)
// Accept Broadcast/Multicast/Physical Match + WRAP
rtl8139_outl(RTL8139_RCR, RTL8139_RCR_AB | RTL8139_RCR_AM | RTL8139_RCR_APM | RTL8139_RCR_WRAP | (3 << 11)); // 32k rx buffer
// Enable Transmitter and Receiver
rtl8139_outb(RTL8139_CR, RTL8139_CR_RE | RTL8139_CR_TE);
// Config TCR
rtl8139_outl(RTL8139_TCR, (0x03 << 24)); // IFG
rtl8139_initialized = 1;
return 0;
}
int rtl8139_send_packet(const void* data, size_t length) {
if (!rtl8139_initialized) return -1;
if (length > 1792) return -1;
// Use current tx buffer
uint8_t* tx_buf = tx_buffers[tx_curr];
uint8_t* src = (uint8_t*)data;
for (size_t i = 0; i < length; i++) tx_buf[i] = src[i];
uint32_t phys = v2p((uint64_t)(uintptr_t)tx_buf);
rtl8139_outl(RTL8139_TSAD0 + (tx_curr * 4), phys);
// Status is length | clear bit 13 to send
rtl8139_outl(RTL8139_TSD0 + (tx_curr * 4), length);
tx_curr = (tx_curr + 1) % 4;
return 0;
}
int rtl8139_receive_packet(void* buffer, size_t buffer_size) {
if (!rtl8139_initialized) return 0;
uint8_t cmd = rtl8139_inb(RTL8139_CR);
if ((cmd & 1) == 1) { // Buffer empty
return 0;
}
uint16_t* rx_head = (uint16_t*)(rx_buffer + rx_ptr);
uint16_t rx_status = rx_head[0];
uint16_t rx_length = rx_head[1]; // includes 4 bytes CRC at end
if (rx_status & (1 << 5)) { // Bad packet
// Bad packet received. We need to skip it or reset.
}
if (rx_length > 0 && rx_length <= buffer_size + 4) {
uint8_t* pkt = (uint8_t*)(rx_head) + 4;
uint16_t net_len = rx_length - 4; // Strip CRC
uint8_t* dest = (uint8_t*)buffer;
for (int i = 0; i < net_len; i++) {
dest[i] = pkt[i];
}
// Update rx_ptr
rx_ptr = (rx_ptr + rx_length + 4 + 3) & ~3; // Align up to 4 bytes
if (rx_ptr > 32768) {
rx_ptr -= 32768; // Wrap around
}
rtl8139_outw(RTL8139_CAPR, rx_ptr - 16);
return net_len;
}
// Default error handling, skip it
rx_ptr = (rx_ptr + rx_length + 4 + 3) & ~3;
if (rx_ptr > 32768) rx_ptr -= 32768;
rtl8139_outw(RTL8139_CAPR, rx_ptr - 16);
return 0;
}
int rtl8139_get_mac(uint8_t* mac_out) {
if (!rtl8139_initialized) return -1;
for (int i = 0; i < 6; i++) {
mac_out[i] = mac_addr[i];
}
return 0;
}

16
src/kernel/rtl8139.h Normal file
View file

@ -0,0 +1,16 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef RTL8139_H
#define RTL8139_H
#include <stdint.h>
#include <stddef.h>
#include "pci.h"
int rtl8139_init(pci_device_t* pci_dev);
int rtl8139_send_packet(const void* data, size_t length);
int rtl8139_receive_packet(void* buffer, size_t buffer_size);
int rtl8139_get_mac(uint8_t* mac_out);
#endif

258
src/kernel/virtio_net.c Normal file
View file

@ -0,0 +1,258 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "virtio_net.h"
#include "io.h"
#include "kutils.h"
#include "platform.h"
#define VIRTIO_PCI_HOST_FEATURES 0x00
#define VIRTIO_PCI_GUEST_FEATURES 0x04
#define VIRTIO_PCI_QUEUE_PFN 0x08
#define VIRTIO_PCI_QUEUE_SIZE 0x0C
#define VIRTIO_PCI_QUEUE_SEL 0x0E
#define VIRTIO_PCI_QUEUE_NOTIFY 0x10
#define VIRTIO_PCI_STATUS 0x12
#define VIRTIO_PCI_ISR 0x13
#define VIRTIO_PCI_CONFIG 0x14
#define VRING_DESC_F_NEXT 1
#define VRING_DESC_F_WRITE 2
struct vq_desc {
uint64_t addr;
uint32_t len;
uint16_t flags;
uint16_t next;
} __attribute__((packed));
struct vq_avail {
uint16_t flags;
uint16_t idx;
uint16_t ring[256];
} __attribute__((packed));
struct vq_used_elem {
uint32_t id;
uint32_t len;
} __attribute__((packed));
struct vq_used {
uint16_t flags;
uint16_t idx;
struct vq_used_elem ring[256];
} __attribute__((packed));
struct virtqueue {
struct vq_desc* desc;
struct vq_avail* avail;
struct vq_used* used;
uint16_t q_size;
uint16_t last_used_idx;
uint16_t last_avail_idx; // Software tracking of idx
};
// Virtio Network header (typically 12 bytes if VIRTIO_NET_F_MRG_RXBUF is NOT set, 10 bytes legacy without MRG)
struct virtio_net_hdr {
uint8_t flags;
uint8_t gso_type;
uint16_t hdr_len;
uint16_t gso_size;
uint16_t csum_start;
uint16_t csum_offset;
// uint16_t num_buffers; // if MRG_RXBUF enabled
} __attribute__((packed));
static uint16_t io_base = 0;
static int virtio_initialized = 0;
static uint8_t mac_addr[6];
static struct virtqueue rx_vq;
static struct virtqueue tx_vq;
// Memory buffers for queues
static uint8_t rx_ring_mem[16384] __attribute__((aligned(4096)));
static uint8_t tx_ring_mem[16384] __attribute__((aligned(4096)));
static uint8_t rx_buffers[256][2048];
static struct virtio_net_hdr tx_hdr[256];
static uint8_t tx_buffers[256][2048];
static void virtqueue_init(struct virtqueue* vq, uint8_t* mem, uint16_t qsize) {
vq->q_size = qsize;
vq->last_used_idx = 0;
vq->last_avail_idx = 0;
vq->desc = (struct vq_desc*)mem;
vq->avail = (struct vq_avail*)(mem + qsize * sizeof(struct vq_desc));
// used ring is aligned to 4096 after avail
uintptr_t avail_end = (uintptr_t)vq->avail + sizeof(struct vq_avail) + sizeof(uint16_t); // avail_event
uintptr_t used_start = (avail_end + 4095) & ~4095;
vq->used = (struct vq_used*)used_start;
k_memset(mem, 0, 16384);
}
int virtio_net_init(pci_device_t* pci_dev) {
if (virtio_initialized) return 0;
uint32_t bar0 = pci_read_config(pci_dev->bus, pci_dev->device, pci_dev->function, 0x10);
if (!(bar0 & 1)) {
// We only support legacy I/O virtio
return -1;
}
// Enable Bus Master + I/O Space
uint32_t command = pci_read_config(pci_dev->bus, pci_dev->device, pci_dev->function, 0x04);
pci_write_config(pci_dev->bus, pci_dev->device, pci_dev->function, 0x04, command | (1 << 2) | (1 << 0));
io_base = bar0 & ~3;
extern void serial_write(const char *str);
serial_write("[VIRTIO-NET] I/O Base: 0x");
char hex_buf[32]; k_itoa_hex(io_base, hex_buf); serial_write(hex_buf); serial_write("\n");
// Reset device
outb(io_base + VIRTIO_PCI_STATUS, 0);
// Acknowledge + Driver
outb(io_base + VIRTIO_PCI_STATUS, 1 | 2);
// Read features (negotiate MAC)
uint32_t features = inl(io_base + VIRTIO_PCI_HOST_FEATURES);
outl(io_base + VIRTIO_PCI_GUEST_FEATURES, features & 0x01000020); // MAC + STATUS
// Setup RX Virtqueue (Index 0)
outw(io_base + VIRTIO_PCI_QUEUE_SEL, 0);
uint16_t rx_qsize = inw(io_base + VIRTIO_PCI_QUEUE_SIZE);
if (rx_qsize == 0) return -1;
if (rx_qsize > 256) rx_qsize = 256;
virtqueue_init(&rx_vq, rx_ring_mem, rx_qsize);
outl(io_base + VIRTIO_PCI_QUEUE_PFN, v2p((uint64_t)(uintptr_t)rx_ring_mem) / 4096);
// Pre-fill RX ring with buffers
for (int i = 0; i < rx_qsize; i++) {
rx_vq.desc[i].addr = v2p((uint64_t)(uintptr_t)rx_buffers[i]);
rx_vq.desc[i].len = 2048;
rx_vq.desc[i].flags = VRING_DESC_F_WRITE;
rx_vq.desc[i].next = 0;
rx_vq.avail->ring[i] = i;
}
rx_vq.avail->idx = rx_qsize;
rx_vq.last_avail_idx = rx_qsize;
// Setup TX Virtqueue (Index 1)
outw(io_base + VIRTIO_PCI_QUEUE_SEL, 1);
uint16_t tx_qsize = inw(io_base + VIRTIO_PCI_QUEUE_SIZE);
if (tx_qsize > 256) tx_qsize = 256;
virtqueue_init(&tx_vq, tx_ring_mem, tx_qsize);
outl(io_base + VIRTIO_PCI_QUEUE_PFN, v2p((uint64_t)(uintptr_t)tx_ring_mem) / 4096);
// Read MAC
for (int i = 0; i < 6; i++) {
mac_addr[i] = inb(io_base + VIRTIO_PCI_CONFIG + i);
}
serial_write("[VIRTIO-NET] MAC: ");
for(int i=0; i<6; i++) {
char buf[4];
k_itoa_hex(mac_addr[i], buf);
serial_write(buf);
if(i<5) serial_write(":");
}
serial_write("\n");
// Device OK
outb(io_base + VIRTIO_PCI_STATUS, 1 | 2 | 4);
// Kick RX Queue
outw(io_base + VIRTIO_PCI_QUEUE_NOTIFY, 0);
virtio_initialized = 1;
return 0;
}
int virtio_net_send_packet(const void* data, size_t length) {
if (!virtio_initialized) return -1;
if (length > 1514) return -1;
uint16_t head = tx_vq.last_avail_idx % tx_vq.q_size;
uint16_t d_idx = head * 2; // We use 2 descriptors per packet (Header, Data)
// Setup header
k_memset(&tx_hdr[head], 0, sizeof(struct virtio_net_hdr));
tx_vq.desc[d_idx].addr = v2p((uint64_t)(uintptr_t)&tx_hdr[head]);
tx_vq.desc[d_idx].len = sizeof(struct virtio_net_hdr);
tx_vq.desc[d_idx].flags = VRING_DESC_F_NEXT;
tx_vq.desc[d_idx].next = d_idx + 1;
// Setup data
uint8_t* src = (uint8_t*)data;
for (size_t i = 0; i < length; i++) tx_buffers[head][i] = src[i];
tx_vq.desc[d_idx + 1].addr = v2p((uint64_t)(uintptr_t)tx_buffers[head]);
tx_vq.desc[d_idx + 1].len = length;
tx_vq.desc[d_idx + 1].flags = 0; // Not NEXT, Not WRITE
tx_vq.avail->ring[tx_vq.avail->idx % tx_vq.q_size] = d_idx;
__asm__ __volatile__ ("mfence"); // Memory barrier
tx_vq.avail->idx++;
__asm__ __volatile__ ("mfence");
tx_vq.last_avail_idx++;
// Notify device
outw(io_base + VIRTIO_PCI_QUEUE_NOTIFY, 1);
return 0;
}
int virtio_net_receive_packet(void* buffer, size_t buffer_size) {
if (!virtio_initialized) return 0;
if (rx_vq.last_used_idx == rx_vq.used->idx) {
return 0; // No new packets
}
uint16_t u_idx = rx_vq.last_used_idx % rx_vq.q_size;
uint32_t d_idx = rx_vq.used->ring[u_idx].id;
uint32_t len = rx_vq.used->ring[u_idx].len;
// The length includes the virtio_net_hdr (10 bytes)
// We must strip it for the caller
uint16_t net_len = 0;
if (len > sizeof(struct virtio_net_hdr)) {
net_len = len - sizeof(struct virtio_net_hdr);
if (net_len > buffer_size) net_len = buffer_size;
uint8_t* pkt = rx_buffers[d_idx] + sizeof(struct virtio_net_hdr);
uint8_t* dest = (uint8_t*)buffer;
for (int i = 0; i < net_len; i++) {
dest[i] = pkt[i];
}
}
// Put buffer back directly in avail ring
rx_vq.avail->ring[rx_vq.avail->idx % rx_vq.q_size] = d_idx;
__asm__ __volatile__ ("mfence");
rx_vq.avail->idx++;
rx_vq.last_used_idx++;
__asm__ __volatile__ ("mfence");
outw(io_base + VIRTIO_PCI_QUEUE_NOTIFY, 0);
return net_len;
}
int virtio_net_get_mac(uint8_t* mac_out) {
if (!virtio_initialized) return -1;
for (int i = 0; i < 6; i++) {
mac_out[i] = mac_addr[i];
}
return 0;
}

16
src/kernel/virtio_net.h Normal file
View file

@ -0,0 +1,16 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef VIRTIO_NET_H
#define VIRTIO_NET_H
#include <stdint.h>
#include <stddef.h>
#include "pci.h"
int virtio_net_init(pci_device_t* pci_dev);
int virtio_net_send_packet(const void* data, size_t length);
int virtio_net_receive_packet(void* buffer, size_t buffer_size);
int virtio_net_get_mac(uint8_t* mac_out);
#endif