Syllable
Network drivers - Part 5
Power management

ACPI power management is still fairly new in Syllable, and few drivers implement support for it. For most modern hardware it's fairly simple to add support though.

There are two additional functions our driver must export to support power management. They are defined in the system header file <atheos/device.h> :

status_t device_suspend( int nDeviceID, int nDeviceHandle, void* pPrivateData ); status_t device_resume( int nDeviceID, int nDeviceHandle, void* pPrivateData );

These functions are similar to device_init() and device_uninit() functions. The pPrivateData argument in both of these functions is the pointer that was previously set by calling the function set_device_data() . In our case, it is a pointer to our instance of the net_device structure.

It's a fairly simple task to port the tg3_suspend() and tg3_resume() functions to Syllable. Apart from changing the timer handling to fit Syllable we also have to change the call to tg3_set_power_state() in device_suspend() from:

err = tg3_set_power_state(tp, pci_choose_state(pdev, state));

to

err = tg3_set_power_state(tp, PCI_D3hot );

The effect is the same. It seems we do also need the function tg3_restart_hw() , which was previously unused and removed from the driver. Again, it is a simple task to re-add the function to the driver.

Tasks

We have ignored tasks upto now. The Linux kernel implements the concept of "tasklets", which are essentially kernel threads that can be scheduled by the driver to perform a small piece of work that is required. The tg3 driver has one task, which is performed by the function tg3_reset_task() . That task may be scheduled at two points in the driver with the following function call:

schedule_work(&tp->reset_task);

Tasklets are not something Syllable implements. If we wanted too, we could always emulate them with a kernel thread. Instead of calling schedule_work() we would instead unlock a mutex that the thread was waiting on, causing it to run.

For now we'll adopt a simpler aproach of simply calling tg3_reset_task() directly. This has the disadvantage that the task is performed from "inside" the interrupt handler, but it is unlikely to be a problem.

Release vs uninit

When the driver is unloaded by the kernel it will call the device_uninit() function as a way of informing the driver. Syllable 0.6.2 added a new function:

void device_release( int nDeviceID, int nDeviceHandle, void* pPrivateData );

device_release() is called by the kernel before device_uninit() . It is passed the same data as device_suspend() and device_resume() , that is the drivers DeviceID, the handle returned by claim_device() and the private data set with set_device_data() .

We can use device_release() to shut down the card and clean up instead of device_uninit() . If we do that, we no longer need to store the device handle in a global variable. This means that the driver will able to work with multiple devices in one machine.

Changing our driver is simple. We take the code from device_uninit() and move it to our new device_release() function, with a few additional changes to take advantage of the additional device parameters. The device_release() function looks like this:

void device_release( int nDeviceID, int nDeviceHandle, void* pPrivateData ) { struct net_device *pNetDev = pPrivateData; struct tg3 *pPrivate = netdev_priv( pNetDev ); set_device_data( nDeviceHandle, NULL ); release_irq( pNetDev->irq, pNetDev->irq_handle ); delete_timer( pPrivate->timer ); delete_area( pPrivate->reg_area ); pPrivate->regs = NULL; kfree( pPrivate ); kfree( pNetDev ); release_device( nDeviceHandle ); }

We can remove all references to the global g_nDeviceHandle variable.

The device_uninit() function is still required by Syllable but now it has little to do:

status_t device_uninit( int nDeviceID ) { return 0; }
Smaller changes

We can also make a few smaller tweaks and changes. We'll clean up the defines for DEBUG_LIMIT at the top of the file and enclose them in an #if block so that we can easily "switch on" debugging again should we need to.

We'll also change the name that is passed to claim_device(). Currently the device is claimed as a "Broadcom Tigon3", but most Broadcom users would probably know these cards by the name "Broadcom NetExtreme", so that's what we'll use.

It's also probably a good idea if we move the small piece of code we added to set an MTU value when we were debugging the Rx codepath. We'll move it into tg3_probe() where some other default values are set in our net_device instance.

Leftovers

There are still a handful of places in the driver where we have been forced to comment out blocks of code inside #if blocks. Most of this code deals with scanning the PCI bus using Linux-specific functions. Right now we will not be implementing any support for this code on Syllable. The downside is that the driver may not work correctly on certain motherboard chipsets or with certain configurations, but those are limited. We can revisit these blocks of code at a later date, when we have a suitable solution that will work with Syllable.

Recap

We've now ported a network driver to Syllable. We've covered some important topics and shown how to debug your driver in practical terms. Here are some things to remember as you work on your own code: