Created
September 28, 2015 00:47
-
-
Save pdumais/f5a2aff025fc7a3fe6bc to your computer and use it in GitHub Desktop.
rtl8139 bare metal netcard driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "../memorymap.h" | |
#include "display.h" | |
#include "utils.h" | |
#include "netcard.h" | |
#define RX_BUFFER_SIZE 8192 | |
extern unsigned int pci_getBar(unsigned int dev,unsigned char bar); | |
extern unsigned short pci_getIRQ(unsigned int dev); | |
extern void pci_enableBusMastering(unsigned int dev); | |
extern void registerIRQ(void* handler, unsigned long irq); | |
extern struct NetworkConfig* net_getConfig(); | |
extern void setSoftIRQ(unsigned long); | |
/* | |
http://linuxgazette.net/156/jangir.html | |
WARNING: This driver has some limitations: | |
1) we are assuming that only one nic can exist | |
2) we assume that 1 IO base and 1 memory area exists (but that might be ok) | |
3) we assume that the BIOS has configured the IRQ and memory address for that device | |
*/ | |
struct RTL8139DeviceInfo | |
{ | |
unsigned char readIndex; | |
unsigned char writeIndex; | |
unsigned char currentTXDescriptor; | |
unsigned char transmittedDescriptor; | |
unsigned int deviceAddress; | |
unsigned short iobase; | |
unsigned long memoryAddress; | |
unsigned short irq; | |
unsigned long macAddress; | |
unsigned long rxBufferCount; | |
unsigned char* rxBuffers; | |
unsigned char txbuf[4][2048]; | |
unsigned char rxbuf[RX_BUFFER_SIZE+2048]; | |
unsigned short rxBufferIndex; | |
}; | |
struct RTL8139DeviceInfo devInfoList[32]={0}; | |
extern void memcpy64(char* source, char* dest, unsigned long size); | |
void handler() | |
{ | |
unsigned char deviceIndex; | |
for (deviceIndex = 0; deviceIndex < 32; deviceIndex++) | |
{ | |
struct RTL8139DeviceInfo* dev = &devInfoList[deviceIndex]; | |
if (dev->iobase==0) continue; | |
unsigned short isr; | |
INPORTW(isr,dev->iobase+0x3E); | |
OUTPORTW(0xFFFF,dev->iobase + 0x3E); | |
unsigned int status; | |
unsigned char cmd=0; | |
unsigned short size; | |
unsigned short i; | |
if (isr&1) // ROK | |
{ | |
// It is possible that we get here and CMD.BUFE is still set. So check it right now | |
INPORTB(cmd,dev->iobase+0x37); | |
while ((cmd&1) == 0) // check if CMD.BUFE == 1 | |
{ | |
// if last frame overflowed buffer, this won't will start at rxBufferIndex%RX_BUFFER_SIZE instead of zero | |
if (dev->rxBufferIndex>=RX_BUFFER_SIZE) dev->rxBufferIndex = (dev->rxBufferIndex%RX_BUFFER_SIZE); | |
status =*(unsigned int*)(dev->rxbuf+dev->rxBufferIndex); | |
size = status>>16; | |
memcpy64((char*)&dev->rxbuf[dev->rxBufferIndex],(char*)&dev->rxBuffers[dev->writeIndex*2048],size); | |
dev->rxBufferIndex = ((dev->rxBufferIndex+size+4+3)&0xFFFC); | |
// This is ridiculous, you need to substract 16 from CAPR. This is not mentionned anywhere. I had to look in | |
// some other driver's source code to find out. I don't understand where that comes from | |
OUTPORTW(dev->rxBufferIndex-16,dev->iobase+0x38); | |
dev->writeIndex = (dev->writeIndex+1)&(dev->rxBufferCount-1); | |
if (dev->writeIndex==dev->readIndex) | |
{ | |
pf("There is a buffer overrun in RTL8139 rcv buffer. Receive thread could read corrupted data\r\n"); | |
while(1); | |
// Buffer overrun | |
} | |
INPORTB(cmd,dev->iobase+0x37); | |
} | |
setSoftIRQ(SOFTIRQ_NET); | |
} | |
if (isr&0b100) //TOK | |
{ | |
unsigned long tsdCount = 0; | |
unsigned int tsdValue; | |
while (tsdCount <4) | |
{ | |
unsigned short tsd = 0x10 + (dev->transmittedDescriptor*4); | |
dev->transmittedDescriptor = (dev->transmittedDescriptor+1)&0b11; | |
INPORTL(tsdValue,dev->iobase+tsd); | |
if (tsd&0x2000) // OWN is set, so it means that the data was transmitted to FIFO | |
{ | |
if ((tsd&0x8000)==0) | |
{ | |
//TOK is false, so the packet transmission was bad. Ignore that for now. We will drop it. | |
} | |
} | |
else | |
{ | |
// this frame is pending transmission, we will get another interrupt. | |
break; | |
} | |
//OUTPORTL(0x2000,iobase+tsd); // set lenght to zero to clear the other flags but leave OWN to 1 | |
tsdCount++; | |
} | |
} | |
} | |
} | |
unsigned long rtl8139_getMACAddress(struct NetworkCard* netcard) | |
{ | |
struct RTL8139DeviceInfo *dev = (struct RTL8139DeviceInfo*)netcard->deviceInfo; | |
return dev->macAddress; | |
} | |
void* initrtl8139(unsigned int deviceAddr, char* buffer, unsigned int bufferSize) | |
{ | |
unsigned int templ; | |
unsigned short tempw; | |
unsigned long i; | |
unsigned long tempq; | |
struct RTL8139DeviceInfo *devInfo=0; | |
for (i=0;i<32;i++) | |
{ | |
if (devInfoList[i].iobase==0) | |
{ | |
devInfo = &devInfoList[i]; | |
break; | |
} | |
} | |
if (devInfo==0) return 0; | |
devInfo->rxBuffers = buffer; | |
devInfo->rxBufferCount = bufferSize; | |
devInfo->deviceAddress = deviceAddr; | |
if (devInfo->deviceAddress == 0xFFFFFFFF) | |
{ | |
pf("No network card found\r\n"); | |
return 0; | |
} | |
for (i=0;i<6;i++) | |
{ | |
unsigned int m = pci_getBar(devInfo->deviceAddress,i); | |
if (m==0) continue; | |
if (m&1) | |
{ | |
devInfo->iobase = m & 0xFFFC; | |
} | |
else | |
{ | |
devInfo->memoryAddress = m & 0xFFFFFFF0; | |
} | |
} | |
devInfo->irq = pci_getIRQ(devInfo->deviceAddress); | |
registerIRQ(&handler,devInfo->irq); | |
pci_enableBusMastering(devInfo->deviceAddress); | |
pf("RTL8139 netcard found. IRQ=0x%x, IO=0x%x\r\n",devInfo->irq,devInfo->iobase); | |
// Activate card | |
OUTPORTB(0,devInfo->iobase+0x52); | |
// reset | |
unsigned char v=0x10; | |
OUTPORTB(v,devInfo->iobase+0x37); | |
while ((v&0x10)!=0) | |
{ | |
INPORTB(v,devInfo->iobase+0x37); | |
} | |
INPORTL(templ,devInfo->iobase+4); | |
tempq = templ; | |
tempq = tempq <<32; | |
INPORTL(templ,devInfo->iobase); | |
tempq |= templ; | |
devInfo->macAddress = tempq; | |
return devInfo; | |
} | |
void rtl8139_start(struct NetworkCard* netcard) | |
{ | |
struct RTL8139DeviceInfo* dev = (struct RTL8139DeviceInfo*)netcard->deviceInfo; | |
dev->readIndex = 0; | |
dev->writeIndex = 0; | |
dev->transmittedDescriptor = 0; | |
dev->currentTXDescriptor = 0; | |
// Enable TX and RX: | |
OUTPORTB(0b00001100,dev->iobase+0x37); | |
// We need to uses physical addresses for the RX and TX buffers. In our case, we are fine since | |
// we are using identity mapping with virtual memory. | |
//Set the Receive Configuration Register (RCR) | |
OUTPORTL(0x8F, dev->iobase+0x44); | |
//Set the Transmit Configuration Register (TCR) | |
OUTPORTL(0x03000600, dev->iobase+0x40); | |
// set receive buffer address | |
OUTPORTL((unsigned char*)&dev->rxbuf[0], dev->iobase+0x30); | |
// set TX descriptors | |
OUTPORTL((unsigned char*)&dev->txbuf[0][0], dev->iobase+0x20); | |
OUTPORTL((unsigned char*)&dev->txbuf[1][0], dev->iobase+0x24); | |
OUTPORTL((unsigned char*)&dev->txbuf[2][0], dev->iobase+0x28); | |
OUTPORTL((unsigned char*)&dev->txbuf[3][0], dev->iobase+0x2C); | |
// enable Full duplex 100mpbs | |
OUTPORTB(0b00100001, dev->iobase+0x63); | |
//enable TX and RX interrupts: | |
OUTPORTW(0b101, dev->iobase+0x3C); | |
} | |
// checks if a frame is available and returns 0 if none are available | |
// If a frame is available, it is copied to the buffer (needs to be at least the size of an MTU) | |
// and returns the size of the data copied. | |
//WARNING: There is no locking mechanism here. Caller must make sure that this wont be executed by 2 threads | |
// at the same time | |
unsigned long rtl8139_receive(unsigned char** buffer, struct NetworkCard* netcard) | |
{ | |
struct RTL8139DeviceInfo* dev = (struct RTL8139DeviceInfo*)netcard->deviceInfo; | |
if (dev->readIndex != dev->writeIndex) | |
{ | |
unsigned short size; | |
unsigned short i; | |
unsigned char* p = (char*)&dev->rxBuffers[dev->readIndex*2048]; | |
size = p[2] | (p[3]<<8); | |
if (!(p[0]&1)) | |
{ | |
pf("what should we do??\r\n"); | |
return 0; // PacketHeader.ROK | |
} | |
*buffer = (char*)&p[4]; // skip header | |
return size; | |
} | |
else | |
{ | |
return 0; | |
} | |
} | |
void rtl8139_recvProcessed(struct NetworkCard* netcard) | |
{ | |
struct RTL8139DeviceInfo* dev = (struct RTL8139DeviceInfo*)netcard->deviceInfo; | |
dev->readIndex = (dev->readIndex+1) & (dev->rxBufferCount-1); // increment read index and wrap around 16 | |
} | |
unsigned long rtl8139_send(struct NetworkBuffer *netbuf, struct NetworkCard* netcard) | |
{ | |
struct RTL8139DeviceInfo* dev = (struct RTL8139DeviceInfo*)netcard->deviceInfo; | |
unsigned short size = netbuf->layer2Size+netbuf->layer3Size+netbuf->layer4Size+netbuf->payloadSize; | |
if (size>1792) | |
{ | |
pf("can't send. Frame too big: l2:%x, l3: %x, l4: %x payload size: %x\r\n",netbuf->layer2Size,netbuf->layer3Size,netbuf->layer4Size,netbuf->payloadSize); | |
return 0; | |
} | |
unsigned short tsd = 0x10 + (dev->currentTXDescriptor*4); | |
unsigned short tsdValue; | |
INPORTW(tsdValue,dev->iobase+tsd); | |
// it is also important to check the TOK bit. If we try to send something when | |
if (tsdValue & 0x2000 == 0) | |
{ | |
//the whole queue is pending packet sending | |
return 0; | |
} | |
else | |
{ | |
unsigned short i; | |
unsigned char *buf = dev->txbuf[dev->currentTXDescriptor]; | |
memcpy64((char*)&netbuf->layer2Data[0],(char*)&buf[0],netbuf->layer2Size); | |
memcpy64((char*)&netbuf->layer3Data[0],(char*)&buf[netbuf->layer2Size],netbuf->layer3Size); | |
memcpy64((char*)&netbuf->layer4Data[0],(char*)&buf[netbuf->layer2Size+netbuf->layer3Size],netbuf->layer4Size); | |
memcpy64((char*)&netbuf->payload[0],(char*)&buf[netbuf->layer2Size+netbuf->layer3Size+netbuf->layer4Size],netbuf->payloadSize); | |
tsdValue = size; | |
OUTPORTL(tsdValue,dev->iobase+tsd); | |
dev->currentTXDescriptor = (dev->currentTXDescriptor+1)&0b11; // wrap around 4 | |
return size; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment