Viewing: e1000.c
📄 e1000.c (Read Only) ⬅ To go back
#include "e1000.h"
#include "hal/driver.h"
#include "arch/x86/kernel_va_map.h"
#include "pci.h"
#include "vmm.h"
#include "pmm.h"
#include "interrupts.h"
#include "console.h"
#include "utils.h"
#include "io.h"
#include "sync.h"

#include <stddef.h>

/* Kernel VA layout for E1000 DMA buffers — see include/kernel_va_map.h */
#define E1000_MMIO_VA      KVA_E1000_MMIO
#define E1000_MMIO_PAGES   KVA_E1000_MMIO_PAGES
#define E1000_TX_DESC_VA   KVA_E1000_TX_DESC
#define E1000_RX_DESC_VA   KVA_E1000_RX_DESC
#define E1000_TX_BUF_VA    KVA_E1000_TX_BUF
#define E1000_RX_BUF_VA    KVA_E1000_RX_BUF

static volatile uint32_t* e1000_mmio = 0;
static uint8_t e1000_mac[6];
static int e1000_ready = 0;

/* Physical addresses for DMA */
static uint32_t tx_desc_phys;
static uint32_t rx_desc_phys;
static uint32_t tx_buf_phys[E1000_NUM_TX_DESC];
static uint32_t rx_buf_phys[E1000_NUM_RX_DESC];

/* Ring indices */
static volatile uint32_t tx_tail = 0;
static volatile uint32_t rx_tail = 0;

/* RX semaphore — signaled by IRQ handler, waited on by RX thread */
ksem_t e1000_rx_sem;

/* Cached PCI device info */
static uint8_t e1000_bus, e1000_slot, e1000_func;

/* ------------------------------------------------------------------ */
/* MMIO helpers                                                       */
/* ------------------------------------------------------------------ */

static inline uint32_t e1000_read(uint32_t reg) {
    return e1000_mmio[reg / 4];
}

static inline void e1000_write(uint32_t reg, uint32_t val) {
    e1000_mmio[reg / 4] = val;
}

/* ------------------------------------------------------------------ */
/* EEPROM                                                             */
/* ------------------------------------------------------------------ */

static uint16_t e1000_eeprom_read(uint8_t addr) {
    e1000_write(E1000_EERD, ((uint32_t)addr << 8) | E1000_EERD_START);
    uint32_t val;
    for (int i = 0; i < 1000; i++) {
        val = e1000_read(E1000_EERD);
        if (val & E1000_EERD_DONE)
            return (uint16_t)(val >> 16);
    }
    return 0;
}

static void e1000_read_mac(void) {
    uint16_t w0 = e1000_eeprom_read(0);
    uint16_t w1 = e1000_eeprom_read(1);
    uint16_t w2 = e1000_eeprom_read(2);
    e1000_mac[0] = (uint8_t)(w0 & 0xFF);
    e1000_mac[1] = (uint8_t)(w0 >> 8);
    e1000_mac[2] = (uint8_t)(w1 & 0xFF);
    e1000_mac[3] = (uint8_t)(w1 >> 8);
    e1000_mac[4] = (uint8_t)(w2 & 0xFF);
    e1000_mac[5] = (uint8_t)(w2 >> 8);
}

/* ------------------------------------------------------------------ */
/* DMA memory allocation                                              */
/* ------------------------------------------------------------------ */

static uint32_t alloc_dma_page(uintptr_t va) {
    void* phys = pmm_alloc_page();
    if (!phys) return 0;
    vmm_map_page((uint64_t)(uintptr_t)phys, (uint64_t)va,
                 VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE);
    memset((void*)va, 0, 4096);
    return (uint32_t)(uintptr_t)phys;
}

/* ------------------------------------------------------------------ */
/* TX ring setup                                                      */
/* ------------------------------------------------------------------ */

static int e1000_init_tx(void) {
    tx_desc_phys = alloc_dma_page(E1000_TX_DESC_VA);
    if (!tx_desc_phys) return -1;

    struct e1000_tx_desc* txd = (struct e1000_tx_desc*)E1000_TX_DESC_VA;

    /* Allocate TX buffers: 2 buffers per page (2048 each) */
    for (int i = 0; i < E1000_NUM_TX_DESC; i++) {
        int page_idx = i / 2;
        int buf_off  = (i % 2) * E1000_TX_BUF_SIZE;
        uintptr_t va = E1000_TX_BUF_VA + (uintptr_t)page_idx * 4096;

        if (buf_off == 0) {
            /* First buffer on this page — allocate it */
            uint32_t phys = alloc_dma_page(va);
            if (!phys) return -1;
            tx_buf_phys[i] = phys;
        } else {
            tx_buf_phys[i] = tx_buf_phys[i - 1] + E1000_TX_BUF_SIZE;
        }

        txd[i].buffer_addr = (uint64_t)tx_buf_phys[i];
        txd[i].cmd = 0;
        txd[i].status = E1000_TXD_STAT_DD; /* Mark as done so first send works */
    }

    e1000_write(E1000_TDBAL, tx_desc_phys);
    e1000_write(E1000_TDBAH, 0);
    e1000_write(E1000_TDLEN, E1000_NUM_TX_DESC * (uint32_t)sizeof(struct e1000_tx_desc));
    e1000_write(E1000_TDH, 0);
    e1000_write(E1000_TDT, 0);
    tx_tail = 0;

    /* Enable transmitter */
    e1000_write(E1000_TCTL, E1000_TCTL_EN | E1000_TCTL_PSP |
                (15U << E1000_TCTL_CT_SHIFT) |
                (64U << E1000_TCTL_COLD_SHIFT));

    /* Inter-packet gap: recommended 10, 8, 6 for copper */
    e1000_write(E1000_TIPG, 10 | (8 << 10) | (6 << 20));

    return 0;
}

/* ------------------------------------------------------------------ */
/* RX ring setup                                                      */
/* ------------------------------------------------------------------ */

static int e1000_init_rx(void) {
    rx_desc_phys = alloc_dma_page(E1000_RX_DESC_VA);
    if (!rx_desc_phys) return -1;

    struct e1000_rx_desc* rxd = (struct e1000_rx_desc*)E1000_RX_DESC_VA;

    for (int i = 0; i < E1000_NUM_RX_DESC; i++) {
        int page_idx = i / 2;
        int buf_off  = (i % 2) * E1000_RX_BUF_SIZE;
        uintptr_t va = E1000_RX_BUF_VA + (uintptr_t)page_idx * 4096;

        if (buf_off == 0) {
            uint32_t phys = alloc_dma_page(va);
            if (!phys) return -1;
            rx_buf_phys[i] = phys;
        } else {
            rx_buf_phys[i] = rx_buf_phys[i - 1] + E1000_RX_BUF_SIZE;
        }

        rxd[i].buffer_addr = (uint64_t)rx_buf_phys[i];
        rxd[i].status = 0;
    }

    /* Set receive address (MAC filter) */
    uint32_t ral = (uint32_t)e1000_mac[0] | ((uint32_t)e1000_mac[1] << 8) |
                   ((uint32_t)e1000_mac[2] << 16) | ((uint32_t)e1000_mac[3] << 24);
    uint32_t rah = (uint32_t)e1000_mac[4] | ((uint32_t)e1000_mac[5] << 8) |
                   (1U << 31); /* Address Valid */
    e1000_write(E1000_RAL0, ral);
    e1000_write(E1000_RAH0, rah);

    /* Clear multicast table */
    for (int i = 0; i < 128; i++) {
        e1000_write(E1000_MTA + (uint32_t)i * 4, 0);
    }

    e1000_write(E1000_RDBAL, rx_desc_phys);
    e1000_write(E1000_RDBAH, 0);
    e1000_write(E1000_RDLEN, E1000_NUM_RX_DESC * (uint32_t)sizeof(struct e1000_rx_desc));
    e1000_write(E1000_RDH, 0);
    e1000_write(E1000_RDT, E1000_NUM_RX_DESC - 1);
    rx_tail = 0;

    /* Enable receiver: accept broadcast, 2048-byte buffers, strip CRC */
    e1000_write(E1000_RCTL, E1000_RCTL_EN | E1000_RCTL_BAM |
                E1000_RCTL_BSIZE_2048 | E1000_RCTL_SECRC);

    return 0;
}

/* ------------------------------------------------------------------ */
/* Interrupt handler                                                  */
/* ------------------------------------------------------------------ */

static void e1000_irq_handler(struct registers* regs) {
    (void)regs;
    uint32_t icr = e1000_read(E1000_ICR);
    /* Wake the RX thread if a receive event occurred */
    if (icr & (E1000_ICR_RXT0 | E1000_ICR_RXDMT0 | E1000_ICR_RXO)) {
        ksem_signal(&e1000_rx_sem);
    }
}

/* ------------------------------------------------------------------ */
/* Public API                                                         */
/* ------------------------------------------------------------------ */

int e1000_init(void) {
    const struct pci_device* dev = pci_find_device(E1000_VENDOR_ID, E1000_DEVICE_ID);
    if (!dev) {
        kprintf("[E1000] Device not found.\n");
        return -1;
    }

    e1000_bus  = dev->bus;
    e1000_slot = dev->slot;
    e1000_func = dev->func;

    /* Read BAR0 (MMIO base) */
    uint32_t bar0 = dev->bar[0];
    if (bar0 & 1) {
        kprintf("[E1000] BAR0 is I/O (unsupported).\n");
        return -1;
    }
    uint32_t mmio_phys = bar0 & 0xFFFFFFF0U;

    /* Map E1000 MMIO region (128KB) */
    for (int i = 0; i < E1000_MMIO_PAGES; i++) {
        vmm_map_page((uint64_t)(mmio_phys + (uint32_t)i * 4096),
                     (uint64_t)(E1000_MMIO_VA + (uint32_t)i * 4096),
                     VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE);
    }
    e1000_mmio = (volatile uint32_t*)E1000_MMIO_VA;

    /* Enable PCI bus mastering + memory space */
    uint32_t cmd = pci_config_read(e1000_bus, e1000_slot, e1000_func, 0x04);
    cmd |= (1U << 2) | (1U << 1); /* Bus Master | Memory Space */
    pci_config_write(e1000_bus, e1000_slot, e1000_func, 0x04, cmd);

    /* Reset the device */
    uint32_t ctrl = e1000_read(E1000_CTRL);
    e1000_write(E1000_CTRL, ctrl | E1000_CTRL_RST);
    /* Wait for reset to complete (spec says ~1us, be generous) */
    for (volatile int i = 0; i < 100000; i++) { }

    /* Init RX semaphore before enabling interrupts */
    ksem_init(&e1000_rx_sem, 0);

    /* Disable interrupts during setup */
    e1000_write(E1000_IMC, 0xFFFFFFFF);
    e1000_read(E1000_ICR);  /* Clear pending */

    /* Set link up */
    ctrl = e1000_read(E1000_CTRL);
    e1000_write(E1000_CTRL, ctrl | E1000_CTRL_SLU | E1000_CTRL_ASDE);

    /* Read MAC address from EEPROM */
    e1000_read_mac();

    kprintf("[E1000] MAC: %x:%x:%x:%x:%x:%x\n",
            (unsigned)e1000_mac[0], (unsigned)e1000_mac[1],
            (unsigned)e1000_mac[2], (unsigned)e1000_mac[3],
            (unsigned)e1000_mac[4], (unsigned)e1000_mac[5]);

    /* Init TX and RX rings */
    if (e1000_init_tx() < 0) {
        kprintf("[E1000] Failed to init TX ring.\n");
        return -1;
    }
    if (e1000_init_rx() < 0) {
        kprintf("[E1000] Failed to init RX ring.\n");
        return -1;
    }

    /* Register interrupt handler */
    uint8_t irq = dev->irq_line;
    if (irq < 16) {
        register_interrupt_handler((uint8_t)(32 + irq), e1000_irq_handler);
    }

    /* Enable RX interrupts */
    e1000_write(E1000_IMS, E1000_ICR_RXT0 | E1000_ICR_LSC |
                E1000_ICR_RXDMT0 | E1000_ICR_RXO);

    e1000_ready = 1;

    kprintf("[E1000] Initialized, IRQ=%u, MMIO=0x%x\n",
            (unsigned)irq, (unsigned)mmio_phys);

    return 0;
}

int e1000_send(const void* data, uint16_t len) {
    if (!e1000_ready || !data || len == 0 || len > E1000_TX_BUF_SIZE)
        return -1;

    struct e1000_tx_desc* txd = (struct e1000_tx_desc*)E1000_TX_DESC_VA;
    uint32_t idx = tx_tail;

    /* Non-blocking: if descriptor not ready, return immediately */
    if (!(txd[idx].status & E1000_TXD_STAT_DD))
        return -1;

    /* Copy data to TX buffer */
    uintptr_t buf_va = E1000_TX_BUF_VA + (uintptr_t)(idx / 2) * 4096 +
                       (uintptr_t)(idx % 2) * E1000_TX_BUF_SIZE;
    memcpy((void*)buf_va, data, len);

    /* Set up descriptor */
    txd[idx].buffer_addr = (uint64_t)tx_buf_phys[idx];
    txd[idx].length = len;
    txd[idx].cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_IFCS | E1000_TXD_CMD_RS;
    txd[idx].status = 0;

    /* Advance tail */
    tx_tail = (idx + 1) % E1000_NUM_TX_DESC;
    e1000_write(E1000_TDT, tx_tail);

    return 0;
}

int e1000_recv(void* buf, uint16_t buf_len) {
    if (!e1000_ready || !buf || buf_len == 0)
        return 0;

    struct e1000_rx_desc* rxd = (struct e1000_rx_desc*)E1000_RX_DESC_VA;
    uint32_t idx = rx_tail;

    if (!(rxd[idx].status & E1000_RXD_STAT_DD))
        return 0;  /* No packet available */

    uint16_t pkt_len = rxd[idx].length;
    if (pkt_len > buf_len) pkt_len = buf_len;

    /* Copy data from RX buffer */
    uintptr_t buf_va = E1000_RX_BUF_VA + (uintptr_t)(idx / 2) * 4096 +
                       (uintptr_t)(idx % 2) * E1000_RX_BUF_SIZE;
    memcpy(buf, (const void*)buf_va, pkt_len);

    /* Reset descriptor and advance tail */
    rxd[idx].status = 0;
    uint32_t old_tail = rx_tail;
    rx_tail = (idx + 1) % E1000_NUM_RX_DESC;
    e1000_write(E1000_RDT, old_tail);

    return (int)pkt_len;
}

void e1000_get_mac(uint8_t mac[6]) {
    for (int i = 0; i < 6; i++) mac[i] = e1000_mac[i];
}

int e1000_link_up(void) {
    if (!e1000_ready) return 0;
    return (e1000_read(E1000_STATUS) & (1U << 1)) ? 1 : 0;
}

/* HAL driver registration */
static int e1000_drv_probe(void) {
    return pci_find_device(0x8086, 0x100E) ? 0 : -1;
}

static const struct hal_driver e1000_hal_driver = {
    .name     = "e1000",
    .type     = HAL_DRV_NET,
    .priority = 20,
    .ops      = { .probe = e1000_drv_probe, .init = e1000_init, .shutdown = NULL }
};

void e1000_driver_register(void) {
    hal_driver_register(&e1000_hal_driver);
}