mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 18:58:40 +00:00
248 lines
7.2 KiB
C
248 lines
7.2 KiB
C
// 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;
|
|
};
|
|
|
|
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;
|
|
} __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;
|
|
|
|
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));
|
|
|
|
uintptr_t avail_end = (uintptr_t)vq->avail + sizeof(struct vq_avail) + sizeof(uint16_t);
|
|
uintptr_t used_start = (avail_end + 4095) & ~4095;
|
|
vq->used = (struct vq_used*)used_start;
|
|
|
|
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)) {
|
|
return -1;
|
|
}
|
|
|
|
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]; itoa_hex(io_base, hex_buf); serial_write(hex_buf); serial_write("\n");
|
|
|
|
outb(io_base + VIRTIO_PCI_STATUS, 0);
|
|
|
|
outb(io_base + VIRTIO_PCI_STATUS, 1 | 2);
|
|
|
|
uint32_t features = inl(io_base + VIRTIO_PCI_HOST_FEATURES);
|
|
outl(io_base + VIRTIO_PCI_GUEST_FEATURES, features & 0x01000020);
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
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];
|
|
itoa_hex(mac_addr[i], buf);
|
|
serial_write(buf);
|
|
if(i<5) serial_write(":");
|
|
}
|
|
serial_write("\n");
|
|
|
|
outb(io_base + VIRTIO_PCI_STATUS, 1 | 2 | 4);
|
|
|
|
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;
|
|
|
|
size_t send_len = length;
|
|
if (send_len < 60) send_len = 60;
|
|
|
|
volatile struct vq_used* used = tx_vq.used;
|
|
uint32_t timeout = 10000000;
|
|
while ((uint16_t)(tx_vq.last_avail_idx - used->idx) >= (tx_vq.q_size / 2)) {
|
|
if (--timeout == 0) return -1;
|
|
__asm__ __volatile__ ("pause");
|
|
}
|
|
|
|
uint16_t head = tx_vq.last_avail_idx % (tx_vq.q_size / 2);
|
|
uint16_t d_idx = head * 2;
|
|
|
|
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;
|
|
|
|
uint8_t* src = (uint8_t*)data;
|
|
for (size_t i = 0; i < length; i++) tx_buffers[head][i] = src[i];
|
|
for (size_t i = length; i < send_len; i++) tx_buffers[head][i] = 0;
|
|
|
|
tx_vq.desc[d_idx + 1].addr = v2p((uint64_t)(uintptr_t)tx_buffers[head]);
|
|
tx_vq.desc[d_idx + 1].len = send_len;
|
|
tx_vq.desc[d_idx + 1].flags = 0;
|
|
|
|
tx_vq.avail->ring[tx_vq.avail->idx % tx_vq.q_size] = d_idx;
|
|
|
|
__asm__ __volatile__ ("mfence");
|
|
tx_vq.avail->idx++;
|
|
__asm__ __volatile__ ("mfence");
|
|
|
|
tx_vq.last_avail_idx++;
|
|
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;
|
|
|
|
volatile struct vq_used* used = rx_vq.used;
|
|
if (rx_vq.last_used_idx == used->idx) {
|
|
return 0;
|
|
}
|
|
|
|
uint16_t u_idx = rx_vq.last_used_idx % rx_vq.q_size;
|
|
uint32_t d_idx = used->ring[u_idx].id;
|
|
uint32_t len = used->ring[u_idx].len;
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|