Our driver can be loaded by the kernel and successfully initialises the hardware. The logical next step is to add functionality to the device API in the tg3_dev_open() , tg3_dev_close() , tg3_dev_ioctl() and tg3_dev_write() functions. They are quite simple functions but will lead us onto porting the rest of the driver, including the interrupt handling code and the transmit and receive code.
The implementation of these functions is almost identical across all of the ethernet drivers. tg3_dev_write() for example, looks like this:
All it does is to copy the raw data from the pBuffer argument into a new PacketBuf_s structure and call tg3_start_xmit() , which is currently a stub function. tg3_dev_ioctl() is similarly just as simple, requiring code for just four ioctl commands. It calls two new functions, tg3_open() and tg3_close() . Do not confuse these with the existing tg3_dev_open() and tg3_dev_close() functions. We will cover tg3_open() and tg3_close() later.
One thing to note is the use of the pNode argument in both tg3_dev_ioctl() and tg3_dev_write() . Both functions have:
at the top of the function. The pNode argument is passed to the driver by the kernel, and comes from the pData argument we gave the kernel when we called create_device_node():
The last two functions, tg3_dev_open() and tg3_dev_close() are the simplest of the four: they don't have to do anything at all! As long as they return successfully, nothing else has to happen. The real work is done when tg3_open() or tg3_close() are called in response to the appropriate ioctl commands in tg3_dev_ioctl() .
You can see what the driver looks like now, with stubs for the three functions tg3_start_xmit() , tg3_open() and tg3_close() here .
We now have a driver with three stub functions. They are:
Nothing much can happen until the device has been successfully opened, so we'll start with that.
One section of code that deserves a special mention is the timer code. The Linux code is:
In Syllable, timers are managed differently so we have changed those four lines to:
The effect is identical: a timer will call the function tg3_timer() after the timer period expires. We'll provide a stub for tg3_timer() for now.
With a few more tweaks, the driver compiles but we now have a whole new list of functions that need to be ported:
The functions tg3_full_lock() and tg3_full_unlock() look like good candidates to do first. In fact they are single-line functions that simply take a spinlock. We've modified them slightly for Syllable; the Linux version uses the function spin_lock_bh() and spin_unlock_bh() , but Syllable has no concept of "bottom halves" as Linux does so there are no special spinlock functions.
That leaves us with seven other functions to port:
We also still have tg3_timer() , tg3_close() and tg3_xmit() . We'll go ahead and port the seven in our list above first, though.
One place where we run into differences between Syllable and Linux is the use of the Linux NAPI functions such as netif_rx_schedule() . NAPI is quite different to how Syllable works, so we will need to change calls to these functions to act in a more compatible way.
With NAPI the driver calls netif_rx_schedule() to tell the kernel that work is required and the kernel then calls a poll() function in the driver when it is ready. We can not easily emulate this scheme in Syllable, so we will have to change it.
If you want more information, the Linux NAPI documentation describes how netif_rx_schedule() works. For Syllable, we will make the following changes:
For now, we'll provide stubs for tg3_rx() and tg3_tx() .
Our changes may require some further tweaking when we come to test the driver, but for now these simple changes should be sufficient.
Once we have all of the functions ported the driver compiles again. We've still got some code enclosed in #if blocks and some stubs (the timer handling code and the Rx and Tx functions), but we now have code to open the device and initialise the hardware and ring buffers, code to handle interrupts and code to close the device and return it to a sensible state.
We've added just over 2000 lines of code to the driver. You can see how the driver looks here .
The following functions are currently stubs:
So we clearly do not yet have enough to be able to test our driver properly, but can at least try the load it and see what happens when the interface layer attempts to open the device. This will cause tg3_dev_open() to be called and run much of the code we have ported since we last tested the driver.
Again when we test the driver it does not crash, which would seem to indicate that most of the new code is working as we would expect. We can add some additional debugging output to check. We'll add some debugging to tg3_open() to see if the new code paths are being followed.
When we test we discover that tg3_open() is called but exits early:
tg3_open() has several places where it may return early, so we'll add debugging statements at each of those points to find out what the likely problem is:
So tg3_open() returns at the second possible early return point, in this block of code:
Apparently, we could not successfully claim the desired IRQ. We can also see from the kernel output that at least part of tg3_request_irq() worked:
So we'll have to investigate a little further and try to find out what has gone wrong.
tg3_request_irq() is quite simple:
The problem is that we have changed the call to request_irq() but not payed attention the way it is used. On Linux request_irq() returns 0 to indicate success and a positive value to indicate failure. On Syllable request_irq() returns a positive value as a handle that is used with other kernel functions, and less than 0 to indicate failure. This is the opposite of what is expected. So we'll re-write this to fit Syllable:
We store the handle returned by request_irq() and return 0 to indicate success and 1 if request_irq() failed. This should satisfy any code in the driver that calls tg3_request_irq() .
Now tg3_open() appears to work:
At this point there is not much more we can do to test the driver. However, we can at least be moderately certain that the device initialisation code is now complete.
Part 4: We'll port the rest of the code for the Rx and Tx code paths, and test our driver.