Page 1 of 1

QEMU RTL8139 Driver Issues (Buffer is zero)

Posted: Sat Jan 17, 2015 9:46 am
by zhiayang
Hello. I recently (not really that recent) rewrote a large part of my OS, and in the process left out some non-important things in the rewrite, specifically my whole network stack.

Unfortunately, when it came time to reassemble the pieces, I discovered a problem in the RTL8139 driver (I only have drivers for that one card). Note that it has worked before, and did not change in the year in which i did not touch it. (that's no indication of much though)

Specifically, the problem is that the NIC will issue an IRQ with an Rx OK, but when I check the buffer, it's only zeroes (verified by checking memory in QEMU's monitor). This is the very first packet received, so most of the things below like ReadOffset and SeenOfs are zero. Therefore I'm just reading from the beginning of the buffer for 0x248 bytes (which is the value of RxBufAddr). 0x248 bytes of zeroes, that is...

I can send packets just fine -- I sent a DHCP Discover broadcast, and using wireshark I see that indeed I get a DHCP offer, but that I can't actually 'receive' it due to this problem.

Code: Select all

void RTL8139::HandlePacket()
{
	uint64_t ReadOffset = 0;
	uint64_t EndOffset = 0;
	uint64_t PacketCount = 0;
	uint64_t length = 0;

	EndOffset = IOPort::Read16(this->ioaddr + Registers::RxBufAddr);
	ReadOffset = this->SeenOfs;

	if(ReadOffset > EndOffset)
	{
		while(ReadOffset < RxBufferSize)
		{
			PacketCount++;
			length = *(uint16_t*) &this->ReceiveBuffer[ReadOffset + 2];
			Ethernet::HandlePacket(this, &this->ReceiveBuffer[ReadOffset + 4], length - 4);

			if(length > 2000)
				HALT("What");

			ReadOffset += length + 4;
			ReadOffset = (ReadOffset + 3) & ~3;	// align to 4 bytes
		}
		ReadOffset -= RxBufferSize;
	}
	while(ReadOffset < EndOffset)
	{
		PacketCount++;
		Ethernet::HandlePacket(this, &this->ReceiveBuffer[ReadOffset + 4], *(uint16_t*) &this->ReceiveBuffer[ReadOffset + 2] - 4);

		ReadOffset += *(uint16_t*) &this->ReceiveBuffer[ReadOffset + 2] + 4;
		ReadOffset = (ReadOffset + 3) & ~3;	// align to 4 bytes
	}
	this->SeenOfs = ReadOffset;
}

And here is the initialisation code:

Code: Select all


// enable bus mastering
uint32_t f = this->pcidev->GetRegisterData(0x4, 0, 2);
this->pcidev->WriteRegisterData(0x4, 0, 2, (f | 0x4) & ~0x400);

uint32_t mmio = (uint32_t) this->pcidev->GetBAR(0);
assert(this->pcidev->IsBARIOPort(0));
Log("Initialised Busmastering RTL8139 NIC with BaseIO %x", mmio);

this->ioaddr = (uint16_t) mmio;

Interrupts::InstallIRQHandler(this->pcidev->GetInterruptLine(), StaticHandleInterrupt, this);


// power on
IOPort::WriteByte(this->ioaddr + Registers::Config1, 0x0);

this->Reset();
Log("RTL8139 Software reset complete.");

// get mac address
Memory::Set(this->MAC, 0, sizeof(this->MAC));
for(uint8_t i = 0; i < 6; i++)
{
	this->MAC[i] = IOPort::ReadByte(this->ioaddr + Registers::MAC0 + i);
}

Log("Initialised RTL8139 NIC with MAC address %#02x:%#02x:%#02x:%#02x:%#02x:%#02x", this->MAC[0], this->MAC[1], this->MAC[2], this->MAC[3], this->MAC[4], this->MAC[5]);

// init rx buffer.
// setup 8K + 1500 + 16
// 3 pages contiguous

this->ReceiveBuffer = (uint8_t*) MemoryManager::Physical::AllocateDMA(3);
Log("Configured 12kb buffer for RX at %x", this->ReceiveBuffer);

IOPort::Write32(this->ioaddr + Registers::RxBuf, (uint32_t) (uint64_t) this->ReceiveBuffer);
IOPort::Write16(this->ioaddr + Registers::RxBufPtr, 0);
IOPort::Write16(this->ioaddr + Registers::RxBufAddr, 0);
IOPort::Write16(this->ioaddr + Registers::IntrMask, 0xF);


// setup transmit buffers.
// 1500 bytes each, 4 buffers
// 2 pages, non contiguous.

this->TransmitBuffers[0] = MemoryManager::Physical::AllocateDMA(1);
this->TransmitBuffers[1] = this->TransmitBuffers[0] + 0x800;

this->TransmitBuffers[2] = MemoryManager::Physical::AllocateDMA(1);
this->TransmitBuffers[3] = this->TransmitBuffers[2] + 0x800;

// send data to the card
IOPort::Write32(this->ioaddr + Registers::TxAddr0, (uint32_t) this->TransmitBuffers[0]);
IOPort::Write32(this->ioaddr + Registers::TxAddr1, (uint32_t) this->TransmitBuffers[1]);
IOPort::Write32(this->ioaddr + Registers::TxAddr2, (uint32_t) this->TransmitBuffers[2]);
IOPort::Write32(this->ioaddr + Registers::TxAddr3, (uint32_t) this->TransmitBuffers[3]);


// set size (max, 8K + 15 + WRAP = 1)
IOPort::Write32(this->ioaddr + Registers::RxConfig, 0x8F);

// enable things.
IOPort::WriteByte(this->ioaddr + Registers::Command, 0x0C);


Help is much appreciated, thanks!

Re: QEMU RTL8139 Driver Issues (Buffer is zero)

Posted: Sat Jan 17, 2015 11:01 pm
by zhiayang
Alright, after some deeper investigation, the NIC is doing something weird...
I fill the entire buffer with 0xAA bytes. After examining the memory after the IRQ, I find that most of the buffer is zeroed...
Except the last few hundred bytes, which are filled with seemingly random bytes and some odd recurrences. These bytes have no immediate correlation to Wireshark's indication of the packet contents...

Re: QEMU RTL8139 Driver Issues (Buffer is zero)

Posted: Sun Jan 18, 2015 8:54 am
by jnc100
I don't have any specific experience with the rtl8139, but the hardware device code in qemu is generally heavy on debug statements. From a quick look at the source, you can recompile qemu with DEBUG_RTL8139 defined and it will output a wealth of information about every packet received which may offer a further clue.

Regards,
John.