Poster of Linux kernelThe best gift for a Linux geek
 Linux kernel map 
⇦ prev ⇱ home next ⇨

17.2. Connecting to the Kernel

We 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 e100.c, in order of increasing complexity. All these files live in drivers/net, within the kernel source tree.

17.2.1. Device Registration

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 not register them—as described in Section 10.2. 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, which is defined in <linux/netdevice.h>. The snull driver keeps pointers to two of these structures (for sn0 and sn1) in a simple array:

struct net_device *snull_devs[2];

The net_device structure, like many other kernel structures, contains a kobject and is, therefore, reference-counted and exported via sysfs. As with other such structures, it must be allocated dynamically. The kernel function provided to perform this allocation is alloc_netdev, which has the following prototype:

struct net_device *alloc_netdev(int sizeof_priv, 
                                const char *name,
                                void (*setup)(struct net_device *));

Here, sizeof_priv is the size of the driver's "private data" area; with network devices, that area is allocated along with the net_device structure. In fact, the two are allocated together in one large chunk of memory, but driver authors should pretend that they don't know that. name is the name of this interface, as is seen by user space; this name can have a printf-style %d in it. The kernel replaces the %d with the next available interface number. Finally, setup is a pointer to an initialization function that is called to set up the rest of the net_device structure. We get to the initialization function shortly, but, for now, suffice it to say that snull allocates its two device structures in this way:

snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
        snull_init);
snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
        snull_init);
if (snull_devs[0] =  = NULL || snull_devs[1] =  = NULL)
    goto out;

As always, we must check the return value to ensure that the allocation succeeded.

The networking subsystem provides a number of helper functions wrapped around alloc_netdev for various types of interfaces. The most common is alloc_etherdev, which is defined in <linux/etherdevice.h>:

struct net_device *alloc_etherdev(int sizeof_priv);

This function allocates a network device using eth%d for the name argument. It provides its own initialization function (ether_setup) that sets several net_device fields with appropriate values for Ethernet devices. Thus, there is no driver-supplied initialization function for alloc_etherdev; the driver should simply do its required initialization directly after a successful allocation. Writers of drivers for other types of devices may want to take advantage of one of the other helper functions, such as alloc_fcdev (defined in <linux/fcdevice.h>) for fiber-channel devices, alloc_fddidev (<linux/fddidevice.h>) for FDDI devices, or alloc_trdev (<linux/trdevice.h>) for token ring devices.

snull could use alloc_etherdev without trouble; we chose to use alloc_netdev instead, as a way of demonstrating the lower-level interface and to give us control over the name assigned to the interface.

Once the net_device structure has been initialized, completing the process is just a matter of passing the structure to register_netdev. In snull, the call looks as follows:

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);

The usual cautions apply here: as soon as you call register_netdev, your driver may be called to operate on the device. Thus, you should not register the device until everything has been completely initialized.

17.2.2. Initializing Each Device

We have looked at the allocation and registration of net_device structures, but we passed over the intermediate step of completely initializing that structure. Note that struct net_device is always put together at runtime; it cannot be set up at compile time in the same manner as a file_operations or block_device_operations structure. This initialization must be complete before calling register_netdev. The net_device structure is large and complicated; fortunately, the kernel takes care of some Ethernet-wide defaults through the ether_setup function (which is called by alloc_etherdev).

Since snull uses alloc_netdev , it has a separate initialization function. The core of this function (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;
dev->tx_timeout      = snull_tx_timeout;
dev->watchdog_timeo = timeout;
/* keep the default flags, just add NOARP */
dev->flags           |= IFF_NOARP;
dev->features        |= NETIF_F_NO_CSUM;
dev->hard_header_cache = NULL;      /* Disable caching */

The above code is a fairly routine initialization of the net_device structure; it is mostly a matter of storing pointers to our various driver functions. The single unusual feature of the code is setting IFF_NOARP in the flags. This specifies that the interface cannot use the Address Resolution Protocol (ARP). 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 in Section 17.11 later in this chapter.

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

We 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 along with the net_device structure. Direct access to the priv field is also discouraged, for performance and flexibility reasons. When a driver needs to get access to the private data pointer, it should use the netdev_priv function. Thus, the snull driver is full of declarations such as:

struct snull_priv *priv = netdev_priv(dev);

The snull module declares a snull_priv data structure to be used for priv:

struct snull_priv {
    struct net_device_stats stats;
    int status;
    struct snull_packet *ppool;
    struct snull_packet *rx_queue;  /* List of incoming packets */
    int rx_int_enabled;
    int tx_packetlen;
    u8 *tx_packetdata;
    struct sk_buff *skb;
    spinlock_t lock;
};

The structure includes, among other things, 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:

priv = netdev_priv(dev);
memset(priv, 0, sizeof(struct snull_priv));
spin_lock_init(&priv->lock);
snull_rx_ints(dev, 1);      /* enable receive interrupts */

17.2.3. Module Unloading

Nothing special happens when the module is unloaded. The module cleanup function simply unregisters the interfaces, performs whatever internal cleanup is required, and releases the net_device structure back to the system:

void snull_cleanup(void)
{
    int i;
    
    for (i = 0; i < 2;  i++) {
        if (snull_devs[i]) {
            unregister_netdev(snull_devs[i]);
            snull_teardown_pool(snull_devs[i]);
            free_netdev(snull_devs[i]);
        }
    }
    return;
}

The call to unregister_netdev removes the interface from the system; free_netdev returns the net_device structure to the kernel. If a reference to that structure exists somewhere, it may continue to exist, but your driver need not care about that. Once you have unregistered the interface, the kernel no longer calls its methods.

Note that our internal cleanup (done in snull_teardown_pool) cannot happen until the device has been unregistered. It must, however, happen before we return the net_device structure to the system; once we have called free_netdev , we cannot make any further references to the device or our private area.

    ⇦ prev ⇱ home next ⇨
    Poster of Linux kernelThe best gift for a Linux geek