Inside Linux Kernel: From Boot to Intel I350 NIC Driver Initialization
This article walks through the Linux kernel boot sequence, the initialization of the kernel networking stack, the registration and initialization steps of the Intel I350 (IGB) network driver, and demonstrates how to inspect the resulting network interface using ifconfig and ethtool commands.
Kernel Boot Process
Power On : The hardware power is turned on.
Firmware : BIOS/UEFI loads, performs hardware self‑test and initialization.
BIOS/UEFI : Finds bootable media, reads MBR/GPT, and starts the bootloader.
Bootloader : GRUB2 loads the kernel image (e.g., /boot/vmlinuz-3.10.0-1160.83.1.el7.x86_64) into memory according to /boot/grub2/grub.cfg.
Initramfs : Loads /boot/initramfs-3.10.0-1160.83.1.el7.x86_64.img, which mounts the root filesystem after loading necessary drivers such as XFS.
Init System : After the root partition is mounted, init or systemd runs, reading /etc/inittab to start system services.
The entry point is start_kernel() in linux/init/main.c, which calls a series of initialization functions for CPU, memory, interrupts, scheduling, TCP/IP stack, etc.
asmlinkage __visible void __init start_kernel(void) {
...
rest_init(); // final function
}rest_init() creates process 0 and enters the idle loop.
Kernel Networking Stack Initialization
During start_kernel(), sock_init() in linux/net/socket.c initializes the socket subsystem.
static int __init sock_init(void) {
...
err = net_sysctl_init();
...
skb_init();
...
init_inodecache();
...
err = register_filesystem(&sock_fs_type);
...
sock_mnt = kern_mount(&sock_fs_type);
...
netfilter_init();
...
skb_timestamping_init();
...
}sock_init() : Creates the sk_buff slab cache and registers the socket filesystem.
proto_init() : Creates protocol files under /proc/net/ and registers their operations.
dev_init() :
Creates Ethernet device and TCP/IP protocol entries under /proc/sys/net/.
Enables hardware Rx/Tx interrupts.
Initializes per‑CPU Rx queues and registers soft‑IRQ handlers.
Registers the loopback device.
inet_proto_init : Registers INET socket handlers (TCP, UDP, ICMP, IGMP, etc.).
unix_proto_init : Registers UNIX socket handlers.
Network Driver Registration Process
The driver registers its initialization routine with module_init(). When the kernel probes devices, it calls the driver’s igb_init_module() for the Intel I350 IGB driver.
/**
* igb_init_module - Driver Registration Routine
*
* igb_init_module is the first routine called when the driver is loaded.
* It registers the driver with the PCI subsystem.
*/
static int __init igb_init_module(void) {
int ret;
pr_info("%s
", igb_driver_string);
pr_info("%s
", igb_copyright);
#ifdef CONFIG_IGB_DCA
dca_register_notify(&dca_notifier);
#endif
ret = pci_register_driver(&igb_driver);
return ret;
}
module_init(igb_init_module); pci_register_driver()matches the device’s Vendor ID and Device ID from the PCI configuration space to the driver’s igb_pci_tbl table.
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID */
__u32 subvendor, subdevice;/* Subsystem IDs */
__u32 class, class_mask; /* (class,subclass,prog-if) */
kernel_ulong_t driver_data;/* Driver‑private data */
__u32 override_only;
};
static const struct pci_device_id igb_pci_tbl[] = {
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_COPPER), board_82575 },
{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_COPPER), board_82575 },
...
};The driver’s callbacks are stored in a pci_driver structure.
static struct pci_driver igb_driver = {
.name = igb_driver_name,
.id_table = igb_pci_tbl,
.probe = igb_probe,
.remove = igb_remove,
#ifdef CONFIG_PM
.driver.pm = &igb_pm_ops,
#endif
.shutdown = igb_shutdown,
.sriov_configure = igb_pci_sriov_configure,
.err_handler = &igb_err_handler
};Network Driver Initialization Flow
The igb_probe() function (found in drivers/net/ethernet/intel/igb/igb_main.c) performs the following steps:
Allocate DMA memory and I/O ports with dma_set_mask().
Reserve PCI resources (Memory BAR, I/O BAR, MSI‑X BAR) via pci_request_selected_regions().
Instantiate net_device and private igb_adapter structures with alloc_etherdev_mq() / alloc_netdev_mq().
Create net_device (standard network device) and igb_adapter (driver‑specific data).
Create netdev_queue structures and link them to net_device.
Initialize the net_device instance.
Set netdev_ops and ethtool_ops function tables.
Initialize interrupt scheme:
Configure queue counts and enable MSI‑X with pci_enable_msix().
Allocate igb_q_vector structures and register NAPI.
Allocate Tx and Rx rings and bind them to vectors.
Set hardware timer, SR‑IOV features, and disable interrupts.
Configure device feature flags and VLAN features.
Read and store the MAC address.
Register the device with register_netdev(), which creates the network interface (e.g., ethX).
Key Data Structures
net_device structure (simplified):
struct net_device {
char name[IFNAMSIZ];
const struct net_device_ops *netdev_ops;
struct device dev;
struct net_device_stats stats;
unsigned int flags;
unsigned short gflags;
struct net_device *master;
struct net_device *real_dev;
struct netdev_hw_addr_list hw_addr_list;
unsigned int ifindex;
struct hlist_node name_hlist;
struct list_head napi_list;
struct netdev_queue *tx_queue;
struct Qdisc *qdisc;
struct netdev_rx_queue *rx_cpu_rmap;
struct rps_map *rps_cpu_map;
struct xps_map *xps_cpu_map;
struct netdev_features *features;
struct net_device_ops *ethtool_ops;
const struct ethtool_link_ksettings *link_ksettings;
struct phy_device *phydev;
struct netdev_adjacent *adj_list;
};net_device_ops example used by the IGB driver:
static const struct net_device_ops igb_netdev_ops = {
.ndo_init = igb_init_netdev,
.ndo_uninit = igb_uninit_netdev,
.ndo_start_xmit = igb_xmit_frame,
.ndo_rx_handler = igb_rx_handler,
.ndo_rx_callback = igb_rx_cleanup,
.ndo_tx_timeout = igb_tx_timeout,
.ndo_get_stats64 = igb_get_stats64,
...
};Creating the Network Interface
After register_netdev() is called, the kernel creates a network interface (e.g., eth0) that can be inspected with standard tools.
extern int register_netdev(struct net_device *dev);ifconfig Command
Running ifconfig eth0 displays the interface name, MAC address, MTU, IP configuration, and packet statistics such as RX/TX errors, drops, and overruns.
$ ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.3 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::a6d6:97f7:7a9a:4c71 prefixlen 64 scopeid 0x20<link>
ether 52:54:00:08:ea:e0 txqueuelen 1000 (Ethernet)
RX packets 1072779 bytes 591984204 (564.5 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 308802 bytes 80454446 (76.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0Key error counters:
RX errors : total receive errors (frame too long, CRC, FIFO overruns, etc.).
RX dropped : packets dropped due to insufficient skb buffers.
RX overruns : FIFO overruns caused by the CPU not handling interrupts quickly enough.
RX frame : misaligned frames.
ethtool Command
ethtoolqueries the driver for detailed NIC capabilities, link settings, and statistics.
$ ethtool enp2s0
Settings for enp2s0:
Supported ports: [ TP MII ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Half 1000baseT/Full
Supported pause frame use: No
Supports auto-negotiation: Yes
Advertised link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Half 1000baseT/Full
Speed: 100Mb/s
Duplex: Full
Port: MII
PHYAD: 0
Transceiver: internal
Auto-negotiation: on
Link detected: yes $ ethtool -S enp2s0
NIC statistics:
tx_packets: 3255
rx_packets: 39837
tx_errors: 0
rx_errors: 0
rx_missed: 0
align_errors: 0
tx_single_collisions: 0
tx_multi_collisions: 0
unicast: 3094
broadcast: 7873
multicast: 36743
tx_aborted: 0
tx_underrun: 0These tools help monitor NIC health, diagnose errors, and verify that the driver correctly registers and configures the network device.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
