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. Personally, I 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.2 in Chapter 9. The way a network driver is registered by its init_module function is different from char and block drivers. Instead of asking for a major number, 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 device item. The structures for sn0 and sn1, the two snull interfaces, are declared like this:

char snull_names[16]; /* two eight-byte buffers */
struct device snull_devs[2] = {
    {
        snull_names, /* name--set at load time */
        0, 0, 0, 0,  /* shmem addresses */
        0x000,       /* ioport */
        0,           /* irq line */
        0, 0, 0,     /* various flags: init to 0 */
        NULL,        /* next ptr */
        snull_init,  /* init function, fill other fields with NULLs */
    },
    {
        snull_names+8,/* name--set at load time */
        0, 0, 0, 0,  /* shmem addresses */
        0x000,       /* ioport */
        0,           /* irq line */
        0, 0, 0,     /* various flags: init to 0 */
        NULL,        /* next ptr */
        snull_init,  /* init function, fill other fields with NULLs */
    }
};

Note that the first field, the name, points to a static buffer, which will be filled at load time. In this way, the interface name can be chosen later, as explained below. In case you are tempted to use an explicit buffer in the structure, like "01234567", I warn you that the code won’t work reliably. This is because the compiler collapses duplicate strings; you end up with a single buffer and two pointers to it. Moreover, the compiler could even choose to store constant strings in read-only memory: not what you want.

I won’t completely describe struct device until the next section, because it is a huge structure, and it won’t help to have it dissected so early. I prefer to use the structure in the driver and explain each field as it is used.

The previous code fragment makes explicit use of the name and init fields of struct device. name, the first struct device field, 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 first character of the name is either the null character or a blank, device registration uses the first available eth n name. Thus, 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, init_module uses dynamic assignment. The default names are assigned by init_module:

if (!snull_eth) { /* call them "sn0" and "sn1" */
    memcpy(snull_devs[0].name, "sn0", 4);
    memcpy(snull_devs[1].name, "sn1", 4);
} else { /* use automatic assignment */
    snull_devs[0].name[0] = snull_devs[1].name[0] = ' ';
}

The init field is 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 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. This is different from char and block drivers, which are organized as a two-level tree, indexed by major and minor numbers.

Since the real initialization is performed elsewhere, init_module 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"
",
               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, while 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 rules for probing char devices apply: check the I/O ports before using them and don’t write to them during the probe. Also, you should avoid registering I/O ports and interrupt lines at this point. Real 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.''

Actually, device probing at load time is discouraged for ISA devices because it is potentially dangerous--the ISA architecture is notoriously fault-intolerant. For this reason, most network drivers refuse to probe for their hardware when loaded as modules, and the kernel proper probes only for the first network interface, without performing any hardware tests after one network device has been detected. Usually dev->base_addr, the base I/O address for the current device, determines what to do:

  • If dev->base_addr is a valid I/O address for the device, that value should be used without probing any other I/O locations. This happens, for example, when the value is assigned at load time.

  • If dev->base_addr is zero, probing for the device is acceptable. A user can thus request a probe by setting the I/O address to zero at load time.

  • Otherwise, no probing should be performed. The kernel uses a value of 0xffe0 to prevent probing, but any invalid address will do. It’s up to the driver to silently reject invalid addresses in base_addr. A module should, by default, set the address to an impossible value in order to prevent undesired probing. Note that looking for PCI devices is always safe because it does not involve probing (see Chapter 15).

As you may have noticed, this way of controlling probing using a load-time setting is the same technique that we used in skull.

On exit from dev->init, the dev structure should be filled with correct values. Filling the structure is the main role of the initialization routine. Fortunately, the kernel takes care of some Ethernet-wide defaults, through the function ether_setup, which fills struct device.

The core of snull_init is:

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;
/* keep the default flags, just add NOARP */
dev->flags           |= IFF_NOARP;

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; every real Ethernet interface is ARP-aware and therefore won’t set this flag. It’s interesting to note that an interface can work without ARP. For example, the plip interface is an Ethernet-like interface without ARP support, like snull. This topic is discussed in detail later in Section 14.9, while the device structure is dissected in the next section.

I’d like to introduce now another struct 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 includes the statistics for the interface. It’s important that statistical information is always 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. The structure includes struct enet_statistics, which is the standard place to hold interface statistics.

The following lines in snull_init allocate 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));

Module Unloading

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

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


Modularized and Non-Modularized Drivers

While there is no notable difference between modularized and non-modularized char and block drivers, that’s not the case for network drivers.

When a driver is distributed as part of the mainstream Linux kernel, it doesn’t declare its own 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 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 only needs to add a call to the driver’s initialization function to ethif_probe. Authors of non-eth drivers, on the other hand, insert their 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 device structures and calls their probing (dev->init) functions by passing them a pointer to the device itself. If the probe function succeeds, Space.c initializes the device structure. 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 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. This introduction to driver setup is meant only to stress the importance of the init device method. If a driver module were to contain a pre-filled device structure, it wouldn’t fit the initialization technique of the mainstream kernel and wouldn’t be forward-compatible if some new field were introduced in struct device.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.126.74