Connecting to the Kernel

We’ll start looking at the structure of network drivers by dissecting the snull source. Keeping the source code for several drivers handy might help you follow the discussion and to see how real-world Linux network drivers operate. As a place to start, we suggest loopback.c, plip.c, and 3c509.c, in order of increasing complexity. Keeping skeleton.c handy might help as well, although this sample driver doesn’t actually run. All these files live in drivers/net, within the kernel source tree.

Module Loading

When a driver module is loaded into a running kernel, it requests resources and offers facilities; there’s nothing new in that. And there’s also nothing new in the way resources are requested. The driver should probe for its device and its hardware location (I/O ports and IRQ line)—but without registering them—as described in Section 9.3 in Chapter 9. The way a network driver is registered by its module initialization function is different from char and block drivers. Since there is no equivalent of major and minor numbers for network interfaces, a network driver does not request such a number. Instead, the driver inserts a data structure for each newly detected interface into a global list of network devices.

Each interface is described by a struct net_device item. The structures for sn0 and sn1, the two snull interfaces, are declared like this:

struct net_device snull_devs[2] = {
    { init: snull_init, },  /* init, nothing more */
    { init: snull_init, }
};

The initialization shown seems quite simple—it sets only one field. In fact, the net_device structure is huge, and we will be filling in other pieces of it later on. But it is not helpful to cover the entire structure at this point; instead, we will explain each field as it is used. For the interested reader, the definition of the structure may be found in <linux/netdevice.h>.

The first struct net_device field we will look at is name, which holds the interface name (the string identifying the interface). The driver can hardwire a name for the interface or it can allow dynamic assignment, which works like this: if the name contains a %d format string, the first available name found by replacing that string with a small integer is used. Thus, eth%d is turned into the first available eth n name; the first Ethernet interface is called eth0, and the others follow in numeric order. The snull interfaces are called sn0 and sn1 by default. However, if eth=1 is specified at load time (causing the integer variable snull_eth to be set to 1), snull_init uses dynamic assignment, as follows:

if (!snull_eth) { /* call them "sn0" and "sn1" */
    strcpy(snull_devs[0].name, "sn0");
    strcpy(snull_devs[1].name, "sn1");
} else { /* use automatic assignment */
    strcpy(snull_devs[0].name, "eth%d");
    strcpy(snull_devs[1].name, "eth%d");
}

The other field we initialized is init, a function pointer. Whenever you register a device, the kernel asks the driver to initialize itself. Initialization means probing for the physical interface and filling the net_device structure with the proper values, as described in the following section. If initialization fails, the structure is not linked to the global list of network devices. This peculiar way of setting things up is most useful during system boot; every driver tries to register its own devices, but only devices that exist are linked to the list.

Because the real initialization is performed elsewhere, the initialization function has little to do, and a single statement does it:

for (i=0; i<2;  i++)
    if ( (result = register_netdev(snull_devs + i)) )
        printk("snull: error %i registering device \"%s\"\n",
               result, snull_devs[i].name);
    else device_present++;

Initializing Each Device

Probing for the device should be performed in the init function for the interface (which is often called the “probe” function). The single argument received by init is a pointer to the device being initialized; its return value is either 0 or a negative error code, usually -ENODEV.

No real probing is performed for the snull interface, because it is not bound to any hardware. When you write a real driver for a real interface, the usual rules for probing devices apply, depending on the peripheral bus you are using. Also, you should avoid registering I/O ports and interrupt lines at this point. Hardware registration should be delayed until device open time; this is particularly important if interrupt lines are shared with other devices. You don’t want your interface to be called every time another device triggers an IRQ line just to reply “no, it’s not mine.”

The main role of the initialization routine is to fill in the dev structure for this device. Note that for network devices, this structure is always put together at runtime. Because of the way the network interface probing works, the dev structure cannot be set up at compile time in the same manner as a file_operations or block_device_operations structure. So, on exit from dev->init, the dev structure should be filled with correct values. Fortunately, the kernel takes care of some Ethernet-wide defaults through the function ether_setup, which fills several fields in struct net_device.

The core of snull_init is as follows:

ether_setup(dev); /* assign some of the fields */

dev->open            = snull_open;
dev->stop            = snull_release;
dev->set_config      = snull_config;
dev->hard_start_xmit = snull_tx;
dev->do_ioctl        = snull_ioctl;
dev->get_stats       = snull_stats;
dev->rebuild_header  = snull_rebuild_header;
dev->hard_header     = snull_header;
#ifdef HAVE_TX_TIMEOUT
dev->tx_timeout     = snull_tx_timeout;
dev->watchdog_timeo = timeout;
#endif
/* keep the default flags, just add NOARP */
dev->flags           |= IFF_NOARP;
dev->hard_header_cache = NULL;      /* Disable caching */
SET_MODULE_OWNER(dev);

The single unusual feature of the code is setting IFF_NOARP in the flags. This specifies that the interface cannot use ARP, the Address Resolution Protocol. ARP is a low-level Ethernet protocol; its job is to turn IP addresses into Ethernet Medium Access Control (MAC) addresses. Since the “remote” systems simulated by snull do not really exist, there is nobody available to answer ARP requests for them. Rather than complicate snull with the addition of an ARP implementation, we chose to mark the interface as being unable to handle that protocol. The assignment to hard_header_cache is there for a similar reason: it disables the caching of the (nonexistent) ARP replies on this interface. This topic is discussed in detail later in this chapter in Section 14.10.

The initialization code also sets a couple of fields (tx_timeout and watchdog_timeo) that relate to the handling of transmission timeouts. We will cover this topic thoroughly later in this chapter in Section 14.5.2.

Finally, this code calls SET_MODULE_OWNER, which initializes the owner field of the net_device structure with a pointer to the module itself. The kernel uses this information in exactly the same way it uses the owner field of the file_operations structure—to maintain the module’s usage count.

We’ll look now at one more struct net_device field, priv. Its role is similar to that of the private_data pointer that we used for char drivers. Unlike fops->private_data, this priv pointer is allocated at initialization time instead of open time, because the data item pointed to by priv usually includes the statistical information about interface activity. It’s important that statistical information always be available, even when the interface is down, because users may want to display the statistics at any time by calling ifconfig. The memory wasted by allocating priv during initialization instead of on open is irrelevant because most probed interfaces are constantly up and running in the system. The snull module declares a snull_priv data structure to be used for priv:

struct snull_priv {
    struct net_device_stats stats;
    int status;
    int rx_packetlen;
    u8 *rx_packetdata;
    int tx_packetlen;
    u8 *tx_packetdata;
    struct sk_buff *skb;
    spinlock_t lock;
};

The structure includes an instance of struct net_device_stats, which is the standard place to hold interface statistics. The following lines in snull_init allocate and initialize dev->priv:

dev->priv = kmalloc(sizeof(struct snull_priv), GFP_KERNEL);
if (dev->priv == NULL)
    return -ENOMEM;
memset(dev->priv, 0, sizeof(struct snull_priv));
spin_lock_init(& ((struct snull_priv *) dev->priv)->lock);

Module Unloading

Nothing special happens when the module is unloaded. The module cleanup function simply unregisters the interfaces from the list after releasing memory associated with the private structure:

void snull_cleanup(void)
{
    int i;
   
    for (i=0; i<2;  i++) {
        kfree(snull_devs[i].priv);
        unregister_netdev(snull_devs + i);
    }
    return;
}

Modularized and Nonmodularized Drivers

Although char and block drivers are the same regardless of whether they’re modular or linked into the kernel, that’s not the case for network drivers.

When a driver is linked directly into the Linux kernel, it doesn’t declare its own net_device structures; the structures declared in drivers/net/Space.c are used instead. Space.c declares a linked list of all the network devices, both driver-specific structures like plip1 and general-purpose eth devices. Ethernet drivers don’t care about their net_device structures at all, because they use the general-purpose structures. Such general eth device structures declare ethif_probe as their init function. A programmer inserting a new Ethernet interface in the mainstream kernel needs only to add a call to the driver’s initialization function to ethif_probe. Authors of non-eth drivers, on the other hand, insert their net_device structures in Space.c. In both cases only the source file Space.c has to be modified if the driver must be linked to the kernel proper.

At system boot, the network initialization code loops through all the net_device structures and calls their probing (dev->init) functions by passing them a pointer to the device itself. If the probe function succeeds, the kernel initializes the next available net_device structure to use that interface. This way of setting up drivers permits incremental assignment of devices to the names eth0, eth1, and so on, without changing the name field of each device.

When a modularized driver is loaded, on the other hand, it declares its own net_device structures (as we have seen in this chapter), even if the interface it controls is an Ethernet interface.

The curious reader can learn more about interface initialization by looking at Space.c and net_init.c.

Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.