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.
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++;
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);
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; }
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.