506 lines
12 KiB
C
506 lines
12 KiB
C
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/kassert.h>
|
|
#include <sys/kdebug.h>
|
|
#include <sys/kmem.h>
|
|
#include <sys/semaphore.h>
|
|
#include <sys/pci.h>
|
|
#include <sys/irq.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/nic.h>
|
|
|
|
#include <machine/pmap.h>
|
|
|
|
typedef struct E1000Device
|
|
{
|
|
uint32_t device;
|
|
const char *name;
|
|
uint32_t flags;
|
|
} E1000Device;
|
|
|
|
static E1000Device deviceList[] =
|
|
{
|
|
{ 0x8086100e, "E1000", 0 }, // EERD is not supported
|
|
// { 0x80861209, "i82551", 0 }, // Doesn't seem to work on my qemu build
|
|
{ 0, "", 0 },
|
|
};
|
|
|
|
void E1000_Configure(PCIDevice dev);
|
|
|
|
/*
|
|
* Hardware Structures
|
|
*/
|
|
|
|
#define E1000_REG_CTRL 0x0000
|
|
#define E1000_REG_STATUS 0x0008
|
|
#define E1000_REG_EECD 0x0010
|
|
#define E1000_REG_EERD 0x0014
|
|
#define E1000_REG_ICR 0x00C0
|
|
#define E1000_REG_ICS 0x00C8
|
|
#define E1000_REG_IMS 0x00D0
|
|
#define E1000_REG_IMC 0x00D8
|
|
#define E1000_REG_RCTL 0x0100
|
|
#define E1000_REG_TCTL 0x0400
|
|
|
|
#define E1000_REG_RDBAL 0x2800
|
|
#define E1000_REG_RDBAH 0x2804
|
|
#define E1000_REG_RDLEN 0x2808
|
|
#define E1000_REG_RDH 0x2810
|
|
#define E1000_REG_RDT 0x2818
|
|
#define E1000_REG_RDTR 0x2820
|
|
|
|
#define E1000_REG_TDBAL 0x3800
|
|
#define E1000_REG_TDBAH 0x3804
|
|
#define E1000_REG_TDLEN 0x3808
|
|
#define E1000_REG_TDH 0x3810
|
|
#define E1000_REG_TDT 0x3818
|
|
#define E1000_REG_TIDV 0x3820
|
|
#define E1000_REG_TADV 0x382C
|
|
|
|
#define E1000_REG_MTABASE 0x5200
|
|
|
|
// EEPROM Offsets
|
|
#define NVM_MAC_ADDR 0x0000
|
|
#define NVM_DEVICE_ID 0x000D
|
|
#define NVM_VENDOR_ID 0x000E
|
|
|
|
// Control Flags
|
|
#define CTRL_SLU (1 << 6)
|
|
|
|
// Recieve Control Flags
|
|
#define RCTL_EN (1 << 1)
|
|
#define RCTL_SBP (1 << 2)
|
|
#define RCTL_UPE (1 << 3)
|
|
#define RCTL_MPE (1 << 4)
|
|
#define RCTL_LPE (1 << 5)
|
|
#define RCTL_BSIZE_1K (0x1 << 16)
|
|
#define RCTL_BSIZE_2K (0x0 << 16)
|
|
#define RCTL_BSIZE_4K ((1 << 25) | (0x3 << 16))
|
|
#define RCTL_BSIZE_8K ((1 << 25) | (0x2 << 16))
|
|
#define RCTL_BSIZE_16K ((1 << 25) | (0x1 << 16))
|
|
|
|
// Transmit Control Flags
|
|
#define TCTL_EN (1 << 1)
|
|
#define TCTL_PSP (1 << 3)
|
|
|
|
// Interrupt Control Register
|
|
#define ICR_TXDW (1 << 0)
|
|
#define ICR_LSC (1 << 2)
|
|
#define ICR_RXO (1 << 6)
|
|
#define ICR_RXT0 (1 << 7)
|
|
|
|
typedef struct PACKED E1000RXDesc {
|
|
volatile uint64_t addr; // Address
|
|
volatile uint16_t len; // Length
|
|
volatile uint16_t csum; // Checksum
|
|
volatile uint8_t status; // Status
|
|
volatile uint8_t errors; // Errors
|
|
volatile uint16_t special;
|
|
} E1000RXDesc;
|
|
|
|
#define RDESC_STATUS_EOP (1 << 1)
|
|
#define RDESC_STATUS_DD (1 << 0)
|
|
|
|
#define RDESC_ERROR_RDE (1 << 7) /* Receive Data Error */
|
|
#define RDESC_ERROR_CE (1 << 0) /* CRC Error */
|
|
|
|
typedef struct PACKED E1000TXDesc {
|
|
volatile uint64_t addr; // Address
|
|
volatile uint16_t len; // Length
|
|
volatile uint8_t cso; // Checksum Offset
|
|
volatile uint8_t cmd; // Command
|
|
volatile uint8_t status; // Status
|
|
volatile uint8_t css; // Checksum start
|
|
volatile uint16_t special;
|
|
} E1000TXDesc;
|
|
|
|
#define TDESC_CMD_RS (1 << 3) /* Report Status */
|
|
#define TDESC_CMD_IC (1 << 2) /* Insert Checksum */
|
|
#define TDESC_CMD_IFCS (1 << 1) /* Insert Ethernet FCS/CRC */
|
|
#define TDESC_CMD_EOP (1 << 0) /* End-of-Packet */
|
|
|
|
/*
|
|
* Driver Configuration and Structures
|
|
*/
|
|
|
|
typedef struct E1000Dev {
|
|
NIC nic; // Must be first
|
|
PCIDevice dev;
|
|
IRQHandler irqHandle;
|
|
uint8_t *mmiobase;
|
|
E1000RXDesc *rxDesc;
|
|
E1000TXDesc *txDesc;
|
|
uint32_t rxTail;
|
|
uint32_t txTail;
|
|
Spinlock lock;
|
|
Semaphore ioSema;
|
|
} E1000Dev;
|
|
|
|
#define E1000_TX_QLEN 128
|
|
#define E1000_RX_QLEN 128
|
|
|
|
#define E1000_MAX_MTU 9000
|
|
|
|
// Memory Resources
|
|
static bool runOnce = false;
|
|
static Slab rxPool;
|
|
static Slab rxDescPool;
|
|
static Slab txDescPool;
|
|
DEFINE_SLAB(E1000RXDesc, &rxDescPool);
|
|
DEFINE_SLAB(E1000TXDesc, &txDescPool);
|
|
|
|
void *
|
|
RXPOOL_Alloc()
|
|
{
|
|
return Slab_Alloc(&rxPool);
|
|
}
|
|
|
|
void
|
|
RXPOOL_Free(void *buf)
|
|
{
|
|
Slab_Free(&rxPool, buf);
|
|
}
|
|
|
|
void
|
|
E1000_Init(uint32_t bus, uint32_t slot, uint32_t func)
|
|
{
|
|
PCIDevice dev;
|
|
|
|
dev.bus = bus;
|
|
dev.slot = slot;
|
|
dev.func = func;
|
|
dev.vendor = PCI_GetVendorID(&dev);
|
|
dev.device = PCI_GetDeviceID(&dev);
|
|
|
|
uint32_t device = dev.vendor << 16 | dev.device;
|
|
|
|
int deviceIdx = 0;
|
|
while (deviceList[deviceIdx].device != 0x0) {
|
|
if (deviceList[deviceIdx].device == device) {
|
|
kprintf("E1000: Found %s\n", deviceList[deviceIdx].name);
|
|
// Configure and add disks
|
|
E1000_Configure(dev);
|
|
}
|
|
|
|
deviceIdx++;
|
|
}
|
|
}
|
|
|
|
static inline uint32_t
|
|
MMIO_Read32(E1000Dev *dev, uint64_t addr)
|
|
{
|
|
return *(uint32_t volatile *)(dev->mmiobase + addr);
|
|
}
|
|
|
|
static inline void
|
|
MMIO_Write32(E1000Dev *dev, uint64_t addr, uint32_t val)
|
|
{
|
|
*(uint32_t *)(dev->mmiobase + addr) = val;
|
|
}
|
|
|
|
static uint16_t
|
|
E1000_EEPROM_Read(E1000Dev *dev, uint8_t addr)
|
|
{
|
|
uint16_t val;
|
|
|
|
// Write Address
|
|
MMIO_Write32(dev, E1000_REG_EERD, ((uint32_t)addr << 8) | 1);
|
|
|
|
// Wait for ready bit
|
|
while (1) {
|
|
val = MMIO_Read32(dev, E1000_REG_EERD);
|
|
if (val & 0x10)
|
|
break;
|
|
}
|
|
|
|
kprintf("%08x\n", val);
|
|
|
|
return (uint16_t)((val >> 16) & 0x0000FFFF);
|
|
}
|
|
|
|
void
|
|
E1000_TXPoll(E1000Dev *dev)
|
|
{
|
|
// Free memory
|
|
kprintf("TXPOLL\n");
|
|
}
|
|
|
|
void
|
|
E1000_RXPoll(E1000Dev *dev)
|
|
{
|
|
while (dev->rxDesc[dev->rxTail].status & RDESC_STATUS_DD) {
|
|
//void *data = (void *)DMPA2VA(dev->rxDesc[dev->rxTail].addr);
|
|
//uint16_t len = dev->rxDesc[dev->rxTail].len;
|
|
|
|
// Look for packet fragments up to EOP set in status
|
|
if ((dev->rxDesc[dev->rxTail].status & RDESC_STATUS_EOP) &&
|
|
(dev->rxDesc[dev->rxTail].errors == 0)) {
|
|
// Process packet
|
|
/*
|
|
* XXX: Need to create a queue and copyout packets here, because
|
|
* I'm seeing multiple interrupts.
|
|
*
|
|
* kprintf("E1000: Int\n");
|
|
*/
|
|
Semaphore_Release(&dev->ioSema);
|
|
}
|
|
|
|
if (dev->rxDesc[dev->rxTail].errors) {
|
|
kprintf("E1000: Error in RX Queue %x\n",
|
|
dev->rxDesc[dev->rxTail].errors);
|
|
dev->rxDesc[dev->rxTail].status = 0;
|
|
dev->rxDesc[dev->rxTail].errors = 0;
|
|
MMIO_Write32(dev, E1000_REG_RDT, dev->rxTail);
|
|
dev->rxTail = (dev->rxTail + 1) % E1000_RX_QLEN;
|
|
}
|
|
|
|
/*
|
|
dev->rxDesc[dev->rxTail].status = 0;
|
|
dev->rxDesc[dev->rxTail].errors = 0;
|
|
// XXX: Should write this only once
|
|
MMIO_Write32(dev, E1000_REG_RDT, dev->rxTail);
|
|
dev->rxTail = (dev->rxTail + 1) % E1000_RX_QLEN;
|
|
*/
|
|
}
|
|
}
|
|
|
|
void
|
|
E1000_Interrupt(void *arg)
|
|
{
|
|
E1000Dev *dev = (E1000Dev *)arg;
|
|
|
|
kprintf("E1000 (%d:%d) Interrupt\n",
|
|
dev->dev.bus, dev->dev.slot);
|
|
|
|
uint32_t cause = MMIO_Read32(dev, E1000_REG_ICR);
|
|
|
|
// Link Status
|
|
if (cause & ICR_LSC) {
|
|
cause &= ~ICR_LSC;
|
|
MMIO_Write32(dev, E1000_REG_CTRL, MMIO_Read32(dev, E1000_REG_CTRL) | CTRL_SLU);
|
|
}
|
|
|
|
// Transmit Queue Empty
|
|
if (cause & 3) {
|
|
cause &= ~3;
|
|
E1000_TXPoll(dev);
|
|
}
|
|
|
|
// Receive Overrun
|
|
if (cause & ICR_RXO) {
|
|
cause &= ~ICR_RXO;
|
|
kprintf("underrun %u %u\n", MMIO_Read32(dev, E1000_REG_RDH), dev->rxTail);
|
|
|
|
E1000_RXPoll(dev);
|
|
}
|
|
|
|
// Receive Timer
|
|
if (cause & ICR_RXT0) {
|
|
cause &= ~ICR_RXT0;
|
|
E1000_RXPoll(dev);
|
|
}
|
|
|
|
if (cause != 0) {
|
|
kprintf("E1000: Unhandled cause %08x\n", cause);
|
|
}
|
|
|
|
MMIO_Read32(dev, E1000_REG_ICR);
|
|
}
|
|
|
|
/*
|
|
* Read packets from NIC
|
|
*/
|
|
int
|
|
E1000_Dequeue(NIC *nic, MBuf *mbuf, NICCB cb, void *arg)
|
|
{
|
|
E1000Dev *dev = (E1000Dev *)nic;
|
|
|
|
retry:
|
|
Semaphore_Acquire(&dev->ioSema);
|
|
|
|
Spinlock_Lock(&dev->lock);
|
|
if ((dev->rxDesc[dev->rxTail].status & RDESC_STATUS_EOP) &&
|
|
(dev->rxDesc[dev->rxTail].errors == 0)) {
|
|
void *data = (void *)DMPA2VA(dev->rxDesc[dev->rxTail].addr);
|
|
uint16_t len = dev->rxDesc[dev->rxTail].len;
|
|
|
|
//Debug_PrintHex(data, len, 0, len);
|
|
// Use CB instead
|
|
memcpy((void *)mbuf->vaddr, data, len);
|
|
|
|
dev->rxDesc[dev->rxTail].status = 0;
|
|
dev->rxDesc[dev->rxTail].errors = 0;
|
|
// XXX: Should write this only once
|
|
MMIO_Write32(dev, E1000_REG_RDT, dev->rxTail);
|
|
dev->rxTail = (dev->rxTail + 1) % E1000_RX_QLEN;
|
|
} else {
|
|
/*
|
|
* XXX: Need to remove this once I do the copyout inside the interrupt
|
|
* handler.
|
|
*/
|
|
/*kprintf("Packet not ready %d %x %x\n",
|
|
dev->rxTail,
|
|
dev->rxDesc[dev->rxTail].status,
|
|
dev->rxDesc[dev->rxTail].errors);*/
|
|
Spinlock_Unlock(&dev->lock);
|
|
goto retry;
|
|
}
|
|
Spinlock_Unlock(&dev->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Transmit packets
|
|
*/
|
|
int
|
|
E1000_Enqueue(NIC *nic, MBuf *mbuf, NICCB cb, void *arg)
|
|
{
|
|
E1000Dev *dev = (E1000Dev *)nic;
|
|
void *data = (void *)DMPA2VA(dev->txDesc[dev->txTail].addr);
|
|
|
|
// Copy data
|
|
memcpy(data, (void *)mbuf->vaddr, mbuf->len);
|
|
dev->txDesc[dev->txTail].len = mbuf->len;
|
|
|
|
dev->txDesc[dev->txTail].cmd = TDESC_CMD_EOP | TDESC_CMD_IFCS | TDESC_CMD_RS;
|
|
MMIO_Write32(dev, E1000_REG_TDT, dev->txTail);
|
|
dev->txTail = (dev->txTail + 1) % E1000_TX_QLEN;
|
|
|
|
// Wait for transmit to complete
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
E1000_RXInit(E1000Dev *dev)
|
|
{
|
|
int i;
|
|
|
|
// Zero out Multicast Table Array
|
|
for (i = 0; i < 128; i++) {
|
|
MMIO_Write32(dev, E1000_REG_MTABASE + (i * 4), 0);
|
|
}
|
|
|
|
dev->rxDesc = (E1000RXDesc *)E1000RXDesc_Alloc();
|
|
for (i = 0; i < E1000_RX_QLEN; i++) {
|
|
dev->rxDesc[i].addr = VA2PA((uintptr_t)PAlloc_AllocPage()); // LOOKUP IN PMAP
|
|
dev->rxDesc[i].status = 0;
|
|
}
|
|
|
|
// Setup base and length of recieve descriptors
|
|
uintptr_t base = VA2PA((uintptr_t)dev->rxDesc);
|
|
MMIO_Write32(dev, E1000_REG_RDBAH, (uint32_t)(base >> 32));
|
|
MMIO_Write32(dev, E1000_REG_RDBAL, (uint32_t)(base & 0xFFFFFFFF));
|
|
MMIO_Write32(dev, E1000_REG_RDLEN, E1000_RX_QLEN * 16);
|
|
|
|
MMIO_Write32(dev, E1000_REG_RDH, 0);
|
|
MMIO_Write32(dev, E1000_REG_RDT, E1000_RX_QLEN);
|
|
dev->rxTail = 0;
|
|
|
|
MMIO_Write32(dev, E1000_REG_RCTL,
|
|
(RCTL_EN | RCTL_UPE | RCTL_MPE | RCTL_BSIZE_4K));
|
|
}
|
|
|
|
void
|
|
E1000_TXInit(E1000Dev *dev)
|
|
{
|
|
int i;
|
|
|
|
dev->txDesc = (E1000TXDesc *)E1000RXDesc_Alloc();
|
|
for (i = 0; i < E1000_TX_QLEN; i++) {
|
|
dev->txDesc[i].addr = VA2PA((uintptr_t)PAlloc_AllocPage()); // LOOKUP IN PMAP
|
|
dev->txDesc[i].cmd = 0;
|
|
}
|
|
|
|
// Setup base and length of recieve descriptors
|
|
uintptr_t base = VA2PA((uintptr_t)dev->txDesc);
|
|
MMIO_Write32(dev, E1000_REG_TDBAH, (uint32_t)(base >> 32));
|
|
MMIO_Write32(dev, E1000_REG_TDBAL, (uint32_t)(base & 0xFFFFFFFF));
|
|
MMIO_Write32(dev, E1000_REG_TDLEN, E1000_TX_QLEN * 16);
|
|
|
|
MMIO_Write32(dev, E1000_REG_TDH, 0);
|
|
MMIO_Write32(dev, E1000_REG_TDT, 0);
|
|
dev->txTail = 0;
|
|
|
|
// Interrupt Delay Timers
|
|
MMIO_Write32(dev, E1000_REG_TIDV, 1);
|
|
MMIO_Write32(dev, E1000_REG_TADV, 1);
|
|
|
|
MMIO_Write32(dev, E1000_REG_TCTL, TCTL_EN | TCTL_PSP);
|
|
}
|
|
|
|
void
|
|
E1000_Configure(PCIDevice dev)
|
|
{
|
|
E1000Dev *ethDev = (E1000Dev *)PAlloc_AllocPage();
|
|
PCI_Configure(&dev);
|
|
|
|
// Ensure that the NIC structure is the first thing inside E1000Dev
|
|
ASSERT((void *)ethDev == (void *)ðDev->nic);
|
|
|
|
if (!runOnce) {
|
|
runOnce = true;
|
|
|
|
//E1000_MAX_MTU + 14 + 4, 4096);
|
|
Slab_Init(&rxPool, "E1000 RX Pool", 4096, 4096);
|
|
Slab_Init(&rxDescPool, "E1000 RX Descriptors", E1000_RX_QLEN*sizeof(E1000RXDesc), 16);
|
|
Slab_Init(&txDescPool, "E1000 TX Descriptors", E1000_TX_QLEN*sizeof(E1000TXDesc), 16);
|
|
}
|
|
|
|
// Copy PCIDevice structure
|
|
memcpy(ðDev->dev, &dev, sizeof(dev));
|
|
|
|
// MMIO
|
|
int bar;
|
|
for (bar = 0; bar < PCI_MAX_BARS; bar++) {
|
|
if (dev.bars[bar].size == 0)
|
|
continue;
|
|
|
|
kprintf("E1000: BAR%d base=%08x size=%08x %s\n",
|
|
bar, dev.bars[bar].base, dev.bars[bar].size,
|
|
dev.bars[bar].type == PCIBAR_TYPE_IO ? "IO" : "Mem");
|
|
}
|
|
|
|
ethDev->mmiobase = (uint8_t *)DMPA2VA(dev.bars[0].base);
|
|
|
|
// Enable Link
|
|
MMIO_Write32(ethDev, E1000_REG_CTRL, MMIO_Read32(ethDev, E1000_REG_CTRL) | CTRL_SLU);
|
|
|
|
// Register IRQs
|
|
kprintf("E1000: IRQ %d\n", dev.irq);
|
|
ethDev->irqHandle.irq = dev.irq;
|
|
ethDev->irqHandle.cb = &E1000_Interrupt;
|
|
ethDev->irqHandle.arg = ethDev;
|
|
IRQ_Register(dev.irq, ðDev->irqHandle);
|
|
|
|
kprintf("E1000: MAC XX:XX:XX:XX:XX:XX\n");
|
|
for (int i = 0; i < 3; i++) {
|
|
E1000_EEPROM_Read(ethDev, NVM_MAC_ADDR + 2*i);
|
|
}
|
|
|
|
// Device lock
|
|
Spinlock_Init(ðDev->lock, "E1000 Spinlock", SPINLOCK_TYPE_NORMAL);
|
|
|
|
// IO Semaphore
|
|
Semaphore_Init(ðDev->ioSema, 0, "E1000 Semaphore");
|
|
ethDev->nic.tx = &E1000_Enqueue;
|
|
ethDev->nic.rx = &E1000_Dequeue;
|
|
|
|
// Enable interrupts
|
|
//MMIO_Write32(ethDev, E1000_REG_IMC, ~0);
|
|
MMIO_Write32(ethDev, E1000_REG_IMS, 0x1F6DC); //ICR_TXDW | ICR_RXO | ICR_RXT0);
|
|
MMIO_Read32(ethDev, E1000_REG_ICR); // Clear pending interrupts
|
|
|
|
E1000_RXInit(ethDev);
|
|
E1000_TXInit(ethDev);
|
|
|
|
ethDev->nic.handle = ethDev;
|
|
// XXX: Fill in callbacks
|
|
NIC_AddNIC(ðDev->nic);
|
|
}
|
|
|