Network drivers - Part 2
Filling the gaps

By the end of chapter three we had stubbed out five functions which are called by tg3_init_one() . They are:

static int tg3_halt(struct tg3 *tp, int kind, int silent); static int tg3_get_invariants(struct tg3 *tp); static int tg3_get_device_address(struct tg3 *tp); static int tg3_test_dma(struct tg3 *tp); static PCI_Info_s * tg3_find_peer(struct tg3 *tp);

We skipped them either because they were very large, or because they in turn called another series of functions. Now we'll have to start porting the code for these functions. We'll start with the tg3_halt() function. It calls a series of other functions, namely:

tg3_stop_fw() tg3_write_sig_pre_reset() tg3_abort_hw() tg3_write_sig_legacy() tg3_write_sig_post_reset() tg3_chip_reset()

There's nothing for it: we'll also have to port each of these functions, too.

Luckily for us, most of these functions are actually fairly simple. They mostly modify structures we already have, or use functions we have already ported, or rely on Linux functions which are provided by linux_compat.h In fact, we can pretty much copy and paste the entire functions into our driver without having to modify anything other than to change the printk() functions into kerndbg() macros. The only function that requires much attention is tg3_chip_reset(), where we'll change the Linux style PCI functions to Syllable style calls to the bus manager.

Once we've completed the six functions called by tg3_halt() , we need to know if there are any other functions that are called but are not yet ported. That's easy to work out: we'll try to build the driver and see if it fails:

[user@machine:~/src]make gcc -kernel -fno-PIC -c -D__ENABLE_DEBUG__ -I. tg3.c -o objs/tg3.o gcc -kernel objs/tg3.o -o objs/tg3 objs/tg3.o: In function `tg3_abort_hw': tg3.c:(.text+0x737): undefined reference to `tg3_disable_ints' objs/tg3.o: In function `tg3_write_sig_pre_reset': tg3.c:(.text+0xbe1): undefined reference to `tg3_write_mem' tg3.c:(.text+0xc22): undefined reference to `tg3_write_mem' tg3.c:(.text+0xc39): undefined reference to `tg3_write_mem' tg3.c:(.text+0xc50): undefined reference to `tg3_write_mem' objs/tg3.o: In function `tg3_write_sig_post_reset': tg3.c:(.text+0xc96): undefined reference to `tg3_write_mem' objs/tg3.o:tg3.c:(.text+0xcb0): more undefined references to `tg3_write_mem' follow objs/tg3.o: In function `tg3_chip_reset': tg3.c:(.text+0xd3e): undefined reference to `tg3_nvram_lock' tg3.c:(.text+0x140b): undefined reference to `tg3_read_mem' tg3.c:(.text+0x151d): undefined reference to `tg3_read_mem' tg3.c:(.text+0x153e): undefined reference to `tg3_read_mem' objs/tg3.o: In function `tg3_stop_fw': tg3.c:(.text+0x15c6): undefined reference to `tg3_write_mem' collect2: ld returned 1 exit status make: *** [objs/tg3] Error 1 [user@machine:~/src]

So we can see that we'll also need to port the following functions:

tg3_write_mem() tg3_read_mem() tg3_disable_ints() tg3_nvram_lock()

The first two are simple enough. Again, we'll just change the Linux style PCI functions to Syllable code. Both tg3_disable_ints() and tg3_nvram_lock() are also very simple and require no changes. While we're here, we'll also go ahead and port tg3_nvram_unlock() because it's small and we can be fairly certain that we're going to need it at some point, so we may as well do it now.

That should be enough to satisfy the linker, so lets try compiling the driver again:

[user@machine:~/src]make gcc -kernel -fno-PIC -c -D__ENABLE_DEBUG__ -I. tg3.c -o objs/tg3.o gcc -kernel objs/tg3.o -o objs/tg3 [user@machine:~/src]

So that's tg3_halt() implemented, which was the first function on our original list. You can see what the driver looks like here .

One down, four to go

There are still four more stub functions which we must implement. They can be ported in the same manner as tg3_halt() , by porting the function and then porting any functions which is relies upon. There are no shortcuts here, just lots of cutting, pasting and porting! Still, as long as we stick to one of the stub functions at a time we shouldn't end up bogged down in the code.

It takes around four hours before we have ported enough code that the driver compiles and links again. To get an idea of how much work we have done, this is the finished article . We have ported over 4000 lines of additional code: the driver is now 5623 lines compared to 1351 before we started. We quite literally now have half a driver: the complete Linux driver is a total of 11831 lines!

One function worth noting is tg3_setup_phy() , which at first glance looks small, but in the end it required a whole series of other functions which comprised over a thousand lines of code. However, it is not quite as bad as it may at first look. Many of the functions we have ported required no modification at all.

The best way to tackle four thousand lines of code is to copy and paste one or two functions at a time. Then look for any obvious changes that you will need to make. Then save your changes and attempt to recompile. You may still have link errors, but you should fix any compiler errors before moving on to the next couple of functions. What you want to avoid is to copy and paste a huge block of code, only to have to then work your way through hundreds of compiler errors!

There are still a handful of functions we have chosen to stub out, and also a few peices of code that are enclosed in #if blocks. You can find these by searching the code for XXXKV , which I have used to mark out these temporary changes to make them easier to find later.

It would be tempting at this point to install the driver on a machine and see if the current code works. However we are still not quite there yet; there are a few functions we need to revisit.

Up to this point we've simply provided empty macros for pci_save_state() and pci_restore_state() , which are Linux functions which Syllable does not have an equivalent of. To implement the functionality correctly, we'll need to make a few small changes and add an extra parameter to both functions. We can then implement a simple inline function in linux_compat.h . We've also added a new member to the tg3 structure at line 2109 of the file tg3.h .

We'll also add a simple implementation for device_uninit() which attempts to release resources and clean up when the driver is unloaded. Our current implementation will only work for one card, but we can fix that later. For now, it is enough. Many drivers do not implement anything at all for device_uninit() on the basis that the kernel is shutting down at the point that the function is called, hence there is no need to worry about freeing resources. However this may not always work for all hardware, especially hardware that relies on ACPI or loadable firmware, which may require being cleanly shut-down by the driver before they will function correctly again after a warm boot. Be careful with your own drivers.


We're almost ready to test the code that we have ported so far. We'll ensure that all debugging messages are enabled first, by adding:


At the top of the tg3.c file, just below the #include directives. This will ensure that every kerndbg() macro will print it's output, which will be useful as we test the driver.

We'll also need a machine to test the driver with. Obviously, it will have to have access to a device for our driver. It is also a good idea to have two seperate installations of Syllable on different partitions. You can install your new driver into one of them. If it crashes, you can boot from the second installation and delete or change the driver on the first, and then try again. If you only have one installation and your driver means the system is unable to boot, you'll be stuck. You may also want to invest in a "null modem" cable, which will allow you to connect a second computer to your test machine and capture the kernel log.

Amazingly, the driver works the first time:

0:init::init(-1) : tg3: Tigon3 [partno(BCM95705A50) rev 3001 PHY(5705)] (PCI:33MHz:32-bit) 10/100/1000BaseT Ethernet 0:init::init(-1) : 00:0:init::init(-1) : 0f: 0:init::init(-1) : 1f:0:init::init(-1) : bb: 0:init::init(-1) : fc:0:init::init(-1) : 4c 0:init::init(-1) : tg3: RXcsums[0] LinkChgREG[1] MIirq[1] ASF[0] Split[0] WireSpeed[1] TSOcap[0] 0:init::init(-1) : tg3: dma_rwctrl[763f0000] 0:init::init(-1) : tg3_probe(): Create node net/eth/tg3-0

We'll fix the poorly formatted debug output. But the output shows that the driver was loaded by the kernel, device_init() was called which detected the hardware (a BCM5705) and then successfully configured the hardware before creating the device node /dev/net/eth/tg3-0

This is fortunate for us, but things are not always this simple. If your driver crashes when you first test it, you will need to inspect the debug messages and stack trace from the kernel log to try and find the point at which the driver crashes. You may need to insert additional debugging output into the driver to do this. Like all debugging, there is no easy way to go about it. If you're stuck, sending the relevant section of your kernel log and perhaps also a copy of the source code for your driver to the syllable-kernel or syllable-developer mailing lists may help you find someone who can help you debug the problem.


Part 3: We'll continue to add functionality to the driver.