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, /* <- USES KERNEL LISTED RECENTLY LIST, NEXT_DEV, DEFINED PPP_INIT, DEVICE #IF ACCESS WHICH SLIP_INIT, LOOPBACK_DEV. */ ALL FINALLY, &SLIP_DEV OTHER TO HERE STRUCT (&PPP_DEV) STATIC * LOOPBACK_INIT, (DEV_BASE) GOES LINK
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.
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.