Chroma/chroma/lainlib/ethernet/e1000/E1000Driver.c
Curle 2c95714d2f
Working on ethernet.
Includes a prototype E1000 driver.
2021-03-19 02:18:39 +00:00

352 lines
12 KiB
C

/*************************
*** Team Kitty, 2021 ***
*** Lainlib ***
************************/
#include <lainlib/ethernet/e1000/e1000.h>
#include <kernel/chroma.h>
#include <kernel/system/memory.h>
#include <kernel/system/interrupts.h>
/**
* This file handles all the logic for interfacing with the E1000 networking device.
* This card is labelled either the Intel I217, or Intel Gigabit 82577LM.
* These cards are identical, and this driver will work identically for both of them.
*
* To use this driver, allocate an e1000_device struct and pass it to the E1000Init() function,
* along with its' PCI device header.
*
* TODO: usage information
*/
/**
* Write data to the device's command registers.
* If we use BAR type 0, we use MMIO, otherwise ports.
*
* @param Device The device to which we write the data.
* @param Address The address to write the data at. For MMIO, the offset from base.
* @param Data The data to write into the register.
*/
void E1000WriteCommandRegister(e1000_device_t* Device, uint16_t Address, uint32_t Data) {
if(Device->BARType == 0)
WriteMMIO(Device->MemoryBase + Address, Data, 4);
else {
WritePort(Device->IOBase, Address, 4);
WritePort(Device->IOBase + 4, Data, 4);
}
}
/**
* Read data from the device's command registers.
* If we use BAR type 0, we read from MMIO. Otherwise, ports.
*
* @param Device The device to read the data from
* @param Address The address we expect the data to be at. For MMIO, the offset from base.
* @return uint32_t The data contained in the register.
*/
uint32_t E1000ReadCommandRegister(e1000_device_t* Device, uint16_t Address) {
if(Device->BARType == 0)
return ReadMMIO(Device->MemoryBase + Address, 4);
else {
WritePort(Device->IOBase, Address, 4);
return ReadPort(Device->IOBase + 4, 4);
}
}
/**
* Attempt to detect the presence of an EEPROM in the E1000.
* It sometimes doesn't like revealing its secrets, so we try it around 1000 times.
*
* @param Device The device to attempt to detect an EEPROM inside.
* @return true The given device has an EEPROM
* @return false The given device does not have an EEPROM
*/
bool E1000DetectEEPROM(e1000_device_t* Device) {
uint32_t Res = 0;
E1000WriteCommandRegister(Device, REG_EEPROM, 0x1);
Device->HasEEPROM = false;
for(size_t i = 0; i < 1000 && !Device->HasEEPROM; i++) {
Res = E1000ReadCommandRegister(Device, REG_EEPROM);
if(Res & 0x10)
Device->HasEEPROM = true;
}
return Res;
}
/**
* Read data from the E1000's EEPROM, if it has one.
* TODO: Unify
* @param Device The device to read
* @param Address The address we expect the data to be at
* @return uint32_t 32 bits of data in the given address of the EEPROM. 0 if not.
*/
uint32_t E1000ReadEEPROM(e1000_device_t* Device, uint8_t Address) {
uint32_t Temp = 0;
if(Device->HasEEPROM) {
// Tell the device we want the data at given address.
E1000WriteCommandRegister(Device, REG_EEPROM, (((uint32_t) Address) << 8) | 1);
// Spinlock until we get the result we expect
// TODO: Timeout?
while(!((Temp = E1000ReadCommandRegister(Device, REG_EEPROM)) & (1 << 4)));
} else {
// The E1000, if it does not have an EEPROM, instead stores it in internal ROM.
// So the same thing applies, but with different bits.
E1000WriteCommandRegister(Device, REG_EEPROM, (((uint32_t) Address) << 2) | 1);
while(!((Temp = E1000ReadCommandRegister(Device, REG_EEPROM)) & (1 << 1)));
}
return (uint16_t)((Temp >> 16) & 0xFFFF);
}
/**
* Read the E1000's MAC address into the internal buffer.
*
* @param Device The device to read from.
* @return true The read finished successfully
* @return false The MMIO base address is non-zero - this is invalid.
*/
bool E1000ReadMAC(e1000_device_t* Device) {
if(Device->HasEEPROM) {
uint32_t Temp;
Temp = E1000ReadEEPROM(Device, 0);
Device->MAC[0] = Temp & 0xff;
Device->MAC[1] = Temp >> 8;
Temp = E1000ReadEEPROM(Device, 1);
Device->MAC[2] = Temp & 0xff;
Device->MAC[3] = Temp >> 8;
Temp = E1000ReadEEPROM(Device, 2);
Device->MAC[4] = Temp & 0xff;
Device->MAC[5] = Temp >> 8;
} else {
uint8_t* MACBaseChar = (uint8_t*) (Device->MemoryBase + REG_MAC);
uint32_t* MACBaseLong = (uint32_t*) (Device->MemoryBase + REG_MAC);
if(MACBaseLong[0] != 0)
for(size_t i = 0; i < 6; i++)
Device->MAC[i] = MACBaseChar[i];
else
return false;
return true;
}
}
/**
* Prepare the receive buffers, tell the device how to handle incoming packets.
*
* @param Device The device to prepare
*/
void E1000InitRX(e1000_device_t* Device) {
uint8_t* Ptr;
struct e1000_receive_packet* Packets;
Ptr = (uint8_t*) (kmalloc(sizeof(struct e1000_receive_packet) * E1000_NUM_RX_DESC + 16));
Packets = (struct e1000_receive_packet*) Ptr;
for(size_t i = 0; i < E1000_NUM_RX_DESC; i++) {
Device->ReceivePackets[i] = (struct e1000_receive_packet*) ((uint8_t*)Packets + i*16);
Device->ReceivePackets[i]->address = (size_t) ((uint8_t*) kmalloc((PAGE_SIZE * 2) + 16));
Device->ReceivePackets[i]->Status = 0;
}
E1000WriteCommandRegister(Device, REG_TXDESCLO, (uint32_t) ((size_t)Ptr >> 32));
E1000WriteCommandRegister(Device, REG_TXDESCHI, (uint32_t) ((size_t)Ptr & 0xFFFFFFFF));
E1000WriteCommandRegister(Device, REG_RXDESCLO, (size_t) Ptr);
E1000WriteCommandRegister(Device, REG_RXDESCHI, 0);
E1000WriteCommandRegister(Device, REG_RXDESCLEN, E1000_NUM_RX_DESC * 16);
E1000WriteCommandRegister(Device, REG_RXDESCHEAD, 0);
E1000WriteCommandRegister(Device, REG_RXDESCTAIL, E1000_NUM_RX_DESC - 1);
Device->CurrentReceivePacket = 0;
E1000WriteCommandRegister(Device, REG_RCTRL,
RCTL_EN | // ENable
RCTL_SBP | // Store Bad Packets
RCTL_UPE | // Unicast Promiscuous Enable
RCTL_MPE | // Multicast Promiscuous Enable
RCTL_LBM_NONE | // LoopBack Mode
RCTL_RDMTS_HALF | // Receive Descriptor Minimum Threshold Size - throw interrupts when the buffer gets half filled
RCTL_BAM | // Broadcast Accept Mode
RCTL_SECRC | // Strip Ethernet CRC
RCTL_BSIZE_8192 // 8192 byte long buffer.
);
}
/**
* Prepare the transmit buffers, tell the device how to handle outgoing packets.
*
* @param Device The device to prepare
*/
void E1000InitTX(e1000_device_t* Device) {
uint8_t* Ptr;
struct e1000_transmit_packet* Packets;
Ptr = (uint8_t*) (kmalloc(sizeof(struct e1000_transmit_packet) * E1000_NUM_TX_DESC + 16));
Packets = (struct e1000_transmit_packet*) Ptr;
for(int i = 0; i < E1000_NUM_TX_DESC; i++) {
Device->TransmitPackets[i] = (struct e1000_transmit_packet*) ((uint8_t*) Packets + (i * 16));
Device->TransmitPackets[i]->Address = 0;
Device->TransmitPackets[i]->Command = 0;
Device->TransmitPackets[i]->Status = TSTA_DD;
}
E1000WriteCommandRegister(Device, REG_TXDESCHI, (uint32_t) ((size_t)Ptr >> 32));
E1000WriteCommandRegister(Device, REG_TXDESCLO, (uint32_t) ((size_t)Ptr & 0xFFFFFFFF));
E1000WriteCommandRegister(Device, REG_TXDESCLEN, E1000_NUM_TX_DESC * 16);
E1000WriteCommandRegister(Device, REG_TXDESCHEAD, 0);
E1000WriteCommandRegister(Device, REG_TXDESCTAIL, 0);
Device->CurrentTransmitPacket = 0;
E1000WriteCommandRegister(Device, REG_TCTRL,
TCTL_EN | // ENable
TCTL_PSP | // Pad Short Packets
(15 << TCTL_CT_SHIFT) | // Collision Threshold - Attempt to re-send the packet 15 times.
(0x3F << TCTL_COLD_SHIFT) | // Collision Distance
(0x3 << TCTL_RRTHRES_SHIFT) // Read Request Threshold - infinity.
);
E1000WriteCommandRegister(Device, REG_TIPG,
0x0060200A);
}
/**
* Tell the device that it may send interrupts to inform us of what's going on
*
* @param Device The device to notify
*/
void E1000InitInt(e1000_device_t* Device) {
E1000WriteCommandRegister(Device, REG_IMASK, 0x1F6DC);
E1000WriteCommandRegister(Device, REG_IMASK, 0xFF & ~4);
E1000ReadCommandRegister(Device, 0xC0);
}
/**
* Handle interrupts fired by the E1000.
* It will either:
* - Help initialise the device
* - Handle an incoming packet
*
* @param InterruptContext The interrupt metadata.
*/
void E1000InterruptFired(INTERRUPT_FRAME* InterruptContext) {
e1000_device_t* NIC = E1000NIC; // TODO: Find device from interrupt frame?
E1000WriteCommandRegister(NIC, REG_IMASK, 1);
uint32_t NICStatus = E1000ReadCommandRegister(NIC, 0xC0);
if(NICStatus & 0x4)
E1000Uplink(NIC);
else if(NICStatus & 0x80)
E1000Receive(NIC);
}
/**
* Initialise the state of the device, prepare it for further initialization steps.
*
* @param Device The device to initialise
* @param PCIHeader The device's PCI headers.
*/
void E1000Init(e1000_device_t* Device, pci_device_t* PCIHeader) {
pci_bar_t BAR = PCIHeader->BARs[0];
InstallIRQ(11, E1000InterruptFired);
if(BAR.MMIO) {
Device->BARType = 0;
Device->MemoryBase = DecodeVirtualAddress(&KernelAddressSpace, BAR.Address);
SerialPrintf("[E1000] Device is memory mapped - 0x%p x 0x%d\r\n", Device->MemoryBase, BAR.Length);
// UpdatePaging
} else {
Device->BARType = 1;
Device->IOBase = BAR.Port;
SerialPrintf("[E1000] Device is port mapped - 0x%p\r\n", Device->IOBase);
}
if(E1000DetectEEPROM(Device))
SerialPrintf("[E1000] Device has EEPROM\r\n");
E1000ReadMAC(Device);
SerialPrintf("[E1000] Device's MAC is %d:%d:%d:%d:%d:%d\r\n", Device->MAC[0], Device->MAC[1], Device->MAC[2], Device->MAC[3], Device->MAC[4], Device->MAC[5]);
// Setup multicast
for(size_t i = 0; i < 0x80; i++)
WritePort(0x5200 + i * 4, 0, 4);
E1000InitRX(Device);
E1000InitTX(Device);
E1000InitInt(Device);
SerialPrintf("[E1000] Device ready.\r\n");
}
/**
* Tell the device that it may connect itself to any networks it finds itself on.
*
* @param Device The device to notify
*/
void E1000Uplink(e1000_device_t* Device) {
uint32_t Flags = E1000ReadCommandRegister(Device, 0);
E1000WriteCommandRegister(Device, 0, Flags | 0x40);
}
/**
* Handle a received packet, placing it in the buffer for other apps to consume.
*
* @param Device The device which received this packet.
*/
void E1000Receive(e1000_device_t* Device) {
uint16_t Temp;
while(Device->ReceivePackets[Device->CurrentReceivePacket]->Status & 0x1) {
Device->ReceivePackets[Device->CurrentReceivePacket]->Status = 0;
Temp = Device->CurrentReceivePacket;
Device->CurrentReceivePacket = (Device->CurrentReceivePacket + 1) % E1000_NUM_RX_DESC;
E1000WriteCommandRegister(Device, REG_RXDESCTAIL, Temp);
}
}
/**
* Retrieve the E1000's MAC address.
* Only valid after E1000ReadMAC is called.
*
* @param Device The device to read
* @return uint8_t* A pointer to the MAC data.
*/
uint8_t* E1000GetMAC(e1000_device_t* Device) {
return (uint8_t*) Device->MAC;
}
/**
* Send a packet of specified length, via the given Device.
*
* @param Device The NIC to send the packet from
* @param Data The data to send
* @param Length The length of the data
* @return int 0 if successful. No other returns.
*/
int E1000Send(e1000_device_t* Device, const void* Data, uint16_t Length) {
struct e1000_transmit_packet* Target = Device->TransmitPackets[Device->CurrentTransmitPacket];
Target->Address = (size_t) Data;
Target->Length = Length;
Target->Command = CMD_EOP | CMD_IFCS | CMD_RS;
Target->Status = 0;
uint8_t temp = Device->CurrentTransmitPacket;
Device->CurrentTransmitPacket = (Device->CurrentTransmitPacket + 1) % E1000_NUM_TX_DESC;
E1000WriteCommandRegister(Device, REG_TXDESCTAIL, Device->CurrentTransmitPacket);
while(!(Device->TransmitPackets[temp]->Status & 0xFF));
return 0;
}