Skip to content

Instantly share code, notes, and snippets.

@pdumais
Created September 28, 2015 00:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pdumais/f5a2aff025fc7a3fe6bc to your computer and use it in GitHub Desktop.
Save pdumais/f5a2aff025fc7a3fe6bc to your computer and use it in GitHub Desktop.
rtl8139 bare metal netcard driver
#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