Network drivers - Part 1
The hardware

We've chosen to port the tg3 driver from Linux 2.6.18. This is the driver for the Broadcom "Tigon 3" range of gigabit ethernet chips. The Tigon 3 is also known as the Broadcom 57xx range, which is a very popular chip used in a wide range of desktop and laptop computers. The driver also covers the SysKonnect 9Dxx and 9Mxx chips and the Altima AC1xxx and AC9100 chips.

The driver is quite large but typical of many ethernet drivers. We will not cover every line of code in the driver, but we will port it step by step, highlighting differences between Linux and Syllable and putting theory into practice.

A skeletal driver

We'll start with a copy of the example driver we created in chapter 1. With a few modifications it is the basis of our tg3 driver for Syllable. We'll simply strip out most of the existing code and rename the device operations to fit the driver. The result can be seen here.

The first thing any driver has to do is try to find any compatable hardware. The tg3 driver supports many different devices, each with different PCI device ID's. The supported devices are stored in the tg3_pci_tbl[] array. The driver requests the device information for each device on the PCI bus from the bus manager and checks the PCI vendor ID and device ID against the values in tg3_pci_tbl[] . If the IDs match, the driver has found a supported device and can perform the necessary driver and hardware initialisation.

You can see this here. We have added the function tg3_probe() which is called from device_init() . The function loops through the connected PCI devices until it finds one whos vendor and device IDs match one of the entries in tg3_pci_tbl[] , which it then attempts to claim with claim_device() .

Linux compatibility

Quite a few drivers in Syllable have been ported from Linux. To make the process easier, the system header <atheos/linux_compat.h> provides some macros, structures and functions that help to make the Syllable kernel API more like Linux. Using linux_compat.h avoids having to re-write more code than is necessary. Using it is optional and you can choose which parts to use and which you would rather not. In our driver we're going to use linux_compat.h , but we do not want to use one specific feature which creates a Linux-like series of debugging macros which are used with Linux's printk() function. The header is included with:

#define NO_DEBUG_STUBS 1 #include <atheos/linux_compat.h>

linux_compat.h is the header where the pci_device_id structure is defined.

Net device and private data

Our driver will need to keep track of various bits of information. There are two structures we will use for this. The first is the net_device structure. In previous versions of Syllable every driver defined their own net_device structure, but from Syllable 0.6.3 on a generic version is provided in the system header <net/net_device.h> .

Each driver also has their own structure which is used to store additional information which is specific to the driver. Our driver declares the structure " tg3 " to store this data. The " priv " field in the net_device structure is then used to point to an instance of the tg3 structure, which means we only need to keep one pointer to a copy of the net_device instance. If we need to access data in the tg3 structure we can dereference the priv field.

For the tg3 driver we're going to use the original tg3.h private header file from Linux, with only four small alterations:

  1. On lines 2107 and 2294 we change struct pci_dev to PCI_Info_s
  2. On line 2232 we change struct timer_list to the Syllable type ktimer_t
  3. We have removed struct work_struct reset_task; from line 2310. This will be handled differently on Syllable.
  4. On line 2338 we have removed struct ethtool_coalesce coal; as Syllable does not support ethertool.

Other than that, the linux_compat.h header will take care of the rest of the Linux types and structures that are used within the file. We have also added the member:

area_id reg_area;

at line 2108.

We'll go ahead now and add the code to our driver to create and initalise a net_device instance and the rest of the code to tg3_probe() that creates a device node for every device we find. You can see what the driver looks like now.


Once we've detected a device, we need to initialise the driver and prepare the hardware. This is done by the function tg3_init_one() . This is the point where you will actually start to port code from the Linux driver, although this particular function will require more changes from the Linux code than most of the rest of the driver.

The first part of the function deals with the PCI bus. Rather than port the code directly we'll write the equivalent code for Syllable. So for example the following piece of code in the Linux version of tg3_init_one() :


is written in the Syllable driver as:

g_psBus->enable_pci_master( pDev->nBus, pDev->nDevice, pDev->nFunction );

Not all of the Linux PCI functions have direct equivalents in Syllable, so for example the following code from the Linux driver:

err = pci_enable_device(pdev); if (err) { printk(KERN_ERR PFX "Cannot enable PCI device, " "aborting.\n"); return err; }

is more verbose in the Syllable driver but does the same thing:

uint32 nOldCommand, nNewCommand; nOldCommand = g_psBus->read_pci_config( pDev->nBus, pDev->nDevice, pDev->nFunction, PCI_COMMAND, 2 ); nNewCommand = nOldCommand | ( PCI_COMMAND_MEMORY & 7); if( nOldCommand != nNewCommand ) g_psBus->write_pci_config( pDev->nBus, pDev->nDevice, pDev->nFunction, PCI_COMMAND, 2, nNewCommand );

You can see the completed implementation of tg3_init_one() here. The most noticable thing is that the file has grown considerably. We've started to pull in various macros at the top of the file, and some of the functions that are called during tg3_init_one() have also been copied across. Rather than try to port every function that is called by tg3_init_one() we've only provided stub functions at this point. The functions we've stubbed 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've also ignored the function tg3_init_coal() , which is not going to be needed on Syllable.

We have also ported the register read and write functions, which are used throughout the driver.

Also worth noting is that we've added a call to set_driver_data() in tg3_probe() . This function associates a pointer to the private data for the device with the PCI device handle. We can retrieve this pointer elsewhere in the driver with a call to get_driver_data() , and it is also passed to the ACPI power-management functions if they are called. We will cover power management at a later stage.

What's important?

There are some general guidelines you can follow that will help you to know which parts of the code should be ported and which parts can be ignored:

  1. Ignore code which relates to big-endian systems. Syllable is currently an x86 operating system only.
  2. Likewise, any code for non-x86 platforms or 64bit support that is contained within #if or #ifdef blocks can probably be ignored.
  3. Code for VLAN and ethertool support can be ignored as Syllable does not support either of these.
  4. Specific code for IPv6 or hardware IP checksum support may be ignored, but Syllable could gain support for these in the future so it may be worth the small additional effort to port code for these, or at least contain it within #ifdef blocks so that it can be enabled at a later date.
  5. Any macros or tables that directly relate to the Linux kernel module format. This also includes additional type prefixes and suffixes such as __iomem.

Almost everything else will be relevant, but you must keep a close eye on exactly what the code is doing. If you're not certain that something is required, comment it out or enclose it in an #if block and revisit it at a later point during development; its purpose may be made clearer by the rest of the code.

At this point we have something that is begining to look like a driver, but is still very incomplete. If you were to attempt to install the driver in its current state, it will simply crash. We'll have to port the code for the functions we have currently stubbed out before the driver will be complete enough to initialise the hardware correctly.


Part 2: Completing the card initialisation and the device API.