Week 1 [ Mon 24 May - Sun 30 May 2010 ]

Mon 24 May 2010

Git commit: 0e64b6cd060808571a97d2ed26361952ac9c513d

Today's goal was to have the new pcnet32 driver skeleton in place. I took the exact structure that the r8169.[ch] implementation has, and modified it accordingly. Made the first commit/push after that.

Until now I've been testing gpxe by creating an .iso image and using that from VMware. After finding this page, I've decided to give the ROM “burning” a try. At first it didn't work because the new ethernetX interface I've created in the .vmx file was automatically set in bridged mode. I didn't immediately realize that gpxe would not be able to get to my local DHCP/TFTP servers this way. After fiddling with the .vmx file, I eventually set the NAT option in VMware and it worked. This test was done using the old pcnet32 driver.

Compiled using:

$ make NO_WERROR=1 bin/pcnet32.rom

since my driver's routines are empty and I had to ignore the unused parameter warnings temporarily.

VMware now greets me with a No more network devices message since my .probe routine does nothing.

Tue 25 May 2010

Git commit: 7bb4f10c31b18f3ed78537c83dbd9d4db10033bb

Today wasn't so productive in terms of coding but I started to delve into the details of the pcnet32 NIC I'm working on. Since I wanted to do a commit/push every day but I didn't want to taint my branch with unfinished implementations I created a separate branch where I'll push every day no matter what. This way, people can tell me if I am heading down the wrong path right away.

First, let's get all the acronyms out of the way:

  • RAP - Register Address Pointer
  • RDP - Register Data Port
  • BDP - Bus configuration register Data Port
  • BCR - Bus Configuration Register
  • CSR - Control and Status Register

Naturally, what we want to do in an initial stage is to gain access to those Control and Status Registers so we can bring the NIC in a well-known state. Suppose we want to read the chip version present in CSR88 and CSR89. To do that we have to write 88 in RAP so it “points” to CSR88 (or BCR88, since RAP is shared by BCRs and CSRs). After we've done that we read from RDP to obtain the CSR88's value. Repeat for CSR89. Here it is in pseudo-code form:

outw(88, ioaddr + 0x12); // 0x12 is RAP's offset from base
inw(ioaddr + 0x10);      // 0x10 is RDP's offset from base
outw(89, ioaddr + 0x12);
inw(ioaddr + 0x10);

Something similar has to be done in case we want to access the BCRs.

It's all fine and dandy until you realize that pcnet has two modes for accessing those registers:

  • WIO - Word I/O
  • DWIO - Double Word I/O

In WIO outw/inw have to be used and in DWIO outl/inl. The datasheet says that doing otherwise may cause “unexpected reprogramming of the […] control registers”. This means we have to define two sets of routines for accesing the registers, one for each mode. Bleh.

Also got a look at gpxe's structures and how they relate to each other:

  • struct pci_device - Contains information found in the PCI configuration space of a device.
  • struct net_device - Represents a generic network device. Create one such structure in the .probe routine and link the priv field to any private data your driver might store (alloc_etherdev). Link your driver's defined ops to this struct (netdev_init)

Finally, linke the pci_device to the net_device by setting pci_device's priv field to point to the net_device (pci_set_drvdata). Also you might want to make sure both structure point to the same physical device (the dev field).

Wed 26 May 2010

Git commit: 4c3a07b19fa0c70847df48141cba49e1e37662f8

Biggest lesson learned today was that writing a driver that supports all the possible chipsets of a certain type of NIC is a daunting task.

I spent most of the time comparing the Linux code with the old pcnet driver and trying to understand what each line does. While gpxe's API allows for a straight-forward port of the old code, I think it's better if I understand everything even though it might take some time to port the driver. This will pay off if I'll write another driver after this one's done.

Some stuff to note:

  • The pcnet32 NIC has an initialization block that is read by a BMU (Buffer Management Unit). The setup of this block is the driver's responsibility.
  • Based on the chip version, you can tell the capabilities of different chips, such as: duplex mode, media independent interface presence, and other bits of information that allow chip-specific code to be inserted.
  • PORTSEL - Port Select - allows software controlled selection of the network medium
  • ASEL - Auto Select - automatically select the operating media interface port

Thu 27 May 2010

Git commit: eb08022a85cf8c63fe9de240cb2d00679d942006

Heh. Today was fun. I kept on going with the .probe routine and the first two lines of code

priv->tx_ring_size = TX_RING_SIZE;
priv->rx_ring_size = RX_RING_SIZE;

led me to read about the descriptor rings. They contain information about data buffers scattered in memory, such as: address, length, status. At this point I don't know if a frame can be split across multiple buffers or not, but I'll cross that bridge when I get to it. Today was about allocating the memory necessary for these descriptors and it turns out this is easy to do with malloc_dma and free_dma which allocates aligned memory (as the specifications required). The old driver didn't do this, so I browsed the r8169 code to see how it's done. Also it seems that the old one worked with only one Tx descriptor.

The old driver had 4 Tx and 16 Rx buffers by default, but the new one has 16 Tx and 32 Rx buffers. I'm not sure how these numbers are chosen. Empirically, perhaps (observe which combination performs better)?

The next lines involved setting up the MII (Media Independent Interface). I wasn't quite sure what that was so I started browsing. Turns out modern NICs use this to separate layer 2 from layer 1 basically. To be more specific, the chip that implements the MAC logic is separated from the PHY chip (which, from what I can tell, is the closest thing to the wires from the NIC). Since the physical medium can range from copper to fiber optics to quantum carrier pigeons, the MII allows the MAC to remain oblivious to this diversity.

Eventually the initialization block was all done and after switching to 32bit mode I could tell the NIC where to find it.

At stefanha's suggestion i started refactoring the .probe routine a bit, since it was quickly becoming >4 screens long. Even though I've moved around some large code blocks, the routine still has 3 screens because of everything else that I've added. It needs more cowbell.

Fri 28 May 2010

Git commit: f77ad7eee94a3d161a2ddf0c94a8906a75169d24

Didn't manage to do as much as I wanted today. The .probe implementation is almost done, there are a few missing pieces such as the debugging code for 0x2620 and 0x2624 and the options settings. The debugging code isn't mandatory for now since I don't have any of those specific chips. I want to keep the .probe implementation as clean as possible since I'll probably be referring to it a lot to see if I forgot to initialize stuff. So for now I'll keep that debugging code out. When all is done, I'll bring the code in. The options are used later in the code, so I'll tackle those when I reach the specific sections. Also, the TX and RX descriptors aren't fully initialized yet, I'll do that when I actually implement tx and rx. Other than that :), the .probe routine is pretty much done.

Did a test to see if .probe runs all the way through and this gave me an oportunity to fix some bugs, mostly related to the fact that I performed certain initializations after they were required. Gpxe now tells me that the .open routine is unsupported, so I guess I'll tackle that next.

Sat 29 May 2010

Git commit: ad2ec93a52650947ce04e55f765456b6e22dcb36

Today I ran into the first piece of code that I just couldn't understand. Starting the .open implementation, I had to set up the options field. This field describes some of the NICs capabilities, such as: duplex mode, auto negotiation, 10/100Base-T, etc. This is all good but the way these options are selected in both the Linux driver and the old driver don't make any sense to me. I spent several hours browsing the source code and the Internet trying to find some answers but this didn't yield any results. After a while I went on with the .open implementation, I understand what it does, but the way the options are set is all wrong so nothing I've written so far works how its supposed to.

For example, my current chip is an AM79C970A which can do full-duplex. Because of the options, the old driver's code ends up taking a path leading to the access of BCR32 which the chip doesn't have. I'm not sure if the NIC silently fails when you try to access an inexistent register or something really evil happens :)

If I don't figure this out by tomorrow, I'll set the options as they should be for my current chip and after the driver works, I'll go through each other chips' datasheet and figure out the rest.

Some other stuff to note:

  • AUI - Attachment Unit Interface
  • GPSI - General Purpose Serial Interface

Sun 30 May 2010

Git commit: 598996546cb8a9838dfb982331bc5af7c328a00e

Today went pretty well, the .open implementation is almost done, plus I've tackled the .remove and .close since they're easy both in concept and in implementation.

There's a small problem in that the AM79C970 chips don't have autonegotiation possibilities which both the old driver and the Linux driver assume. I deduced this only from looking at the Linux code and running the old driver. Stefanha suggested that I do a test run with the Linux code to see if it takes that path. It's on my list for tomorrow, along with a bit of code-cleanup and some code tree walking :). After that, tx and rx await.

No major problems until now, I guess I'll have to wait until the driver is all done to see if everything fits together or not :)

This concludes the first week, it's been a lot of fun so far.

QR Code
QR Code soc:2010:andreif:journal:week1 (generated for current page)