The HyperNews Linux KHG Discussion Pages

More: network driver info

Forum: Device Drivers
Re: Note Re: Network Device Drivers (Paul Gortmaker)
Re: More Re: Network Device Drivers (Neal Tucker)
Keywords: network driver functions
Date: Sat, 15 Jun 1996 03:33:21 GMT
From: Neal Tucker <ntucker@adobe.com>

Earlier, I posted a pointer to a bit of info on network device drivers, and the site that the web page is on is going away, so I am including what was there here...

How a Network Device Gets Added to the Kernel

There is a global variable called dev_base which points to a linked list of "device" structures. Each record represents a network device, and contains a pointer to the device driver's initialization function. The initialization function is the first code from the driver to ever get executed, and is responsible for setting up the hooks to the other driver code.

At boot time, the function device_setup (drivers/block/genhd.c) calls a function called net_dev_init (net/core/dev.c) which walks through the linked list pointed to by dev_base, calling each device's init function. If the init indicates failure (by returning a nonzero result), net_dev_init removes the device from the linked list and continues on.

This brings up the question of how the devices get added to the linked list of devices before any of their code is executed. That is accomplished by a clever piece of C preprocessor work in drivers/net/Space.c. This file has the static declarations for each device's "device" struct, including the pointer to the next device in the list. How can we define these links statically without knowing which devices are going to be included? Here's how it's done (from drivers/net/Space.c):

    #define NEXT_DEV        NULL
    
    #if defined(CONFIG_SLIP)
    static struct device slip_dev =
    {
      device name and some other info goes here
      ...
      NEXT_DEV,    /* <- link to previously listed */
                   /*    device struct (NULL here) */
      slip_init,   /* <- pointer to init function */
    };
    
    #undef NEXT_DEV
    #define NEXT_DEV (&slip_dev)
    #endif
    
    #if defined(CONFIG_PPP)
    static struct device ppp_dev =
    {
      device name and some other info goes here
      ...
      NEXT_DEV,   /* <- link to previously listed   */
                  /*    device struct, which is now *
                  /*    defined as &slip_dev        */
      ppp_init,   /* <- pointer to init function */
    };
    
    #undef NEXT_DEV
    #define NEXT_DEV (&ppp_dev)
    #endif
    
    struct device loopback_dev =
    {
      device name and some other info goes here
      ...
      NEXT_DEV,        /* <- link to previously listed   */
                       /*    device struct, which is now */
                       /*    defined as &ppp_dev         */
      loopback_init,   /* <- pointer to init function */
    };
    
    /* And finally, the head of the list, which points   */
    /* to the most recently defined device struct,       */
    /* loopback_dev.  This (dev_base) is the pointer the */
    /* kernel uses to access all the devices.            */
    
    struct device *dev_base = &loopback_dev;
    

There is a constant, NEXT_DEV, defined to always point at the last device record declared. When each device record gets declared, it puts the value of NEXT_DEV in itself as the "next" pointer and then redefines NEXT_DEV to point to itself. This is how the linked list is built. Note that NEXT_DEV starts out NULL so that the first device structure is the end of the list, and at the end, the global dev_base, which is the head of the list, gets the value of the last device structure.

Ethernet devices
Ethernet devices are a bit of a special case in how they get called at initialization time, probably due to the fact that there are so many different types of ethernet devices that we'd like to be able to refer to them by just calling them ethernet devices (ie "eth0", "eth1", etc), rather than calling them by name (ie "NE2000", "3C509", etc).

In the linked list mentioned above, there is a single entry for all ethernet devices, whose initialization function is set to the function ethif_probe (also defined in drivers/net/Space.c). This function simply calls each ethernet device's init function until it finds one that succeeds. This is done with a huge expression made up of the ANDed results of the calls to the initialization functions (note that with the ethernet devices, the init function is conventionally called xxx_probe). Here is an abridged version of that function:

    static int ethif_probe(struct device *dev)
    {
        u_long base_addr = dev->base_addr;
    
        if ((base_addr == 0xffe0)  ||  (base_addr == 1))
            return 1;
    
        if (1                  /* note start of expression here */
    #ifdef CONFIG_DGRS
            && dgrs_probe(dev)
    #endif
    #ifdef CONFIG_VORTEX
            && tc59x_probe(dev)
    #endif
    #ifdef CONFIG_NE2000
            && ne_probe(dev)
    #endif
            && 1 ) {          /* end of expression here         */
            return 1;
        }
        return 0;
    }
    

The result is that the if statement bails out as false if any of the probe calls returns zero (success), and only one ethernet card is initialized and used, no matter how many drivers you have installed. For the drivers that aren't installed, the #ifdef removes the code completely, and the expression gets a bit smaller. The implications of this scheme are that supporting multiple ethernet cards is now a special case, and requires providing command line parameters to the kernel which cause ethif_probe to be executed multiple times.


Messages

1. Idea: Network Driver Desprately Needed by Paul Atkinson