6.7 About Network Device Drivers

Network device drivers receive and transmit data packets on hardware interface that connect to external systems, and provide a uniform interface that network protocols can access. In Oracle Linux, these drivers are abstracted from the hardware implementation of the network adapters themselves, whose implementation hides the underlying layer-1 and layer-2 protocols.

Like a block driver's driver_request() routine, most callback routines of a network driver must be registered with the kernel to allow the kernel to execute them when data arrives on a network port or when the kernel needs to send data out on a network port. Like a character driver, a network driver does not access physically addressable storage media or support file system access. Unlike block and character device drivers, network drivers do not have device files under /dev. Instead, you use a net_device structure to define the capabilities of a network interface and the kernel updates the members of this structure when you use a command such as ip to configure the interface.

The implementation of network device drivers is likely to differ in many respects between Linux and UNIX operating systems, with most differences arising from the functions that the driver uses to handle network requests, the structures that are used to represent network driver methods and to define the properties of network interfaces, and the kernel interface routines for handling network queues.

The driver_int() and driver_svr() routines that UNIX network device drivers use to handle device interrupts and the transfer incoming data from a network device to the upstream protocol module are usually named driver_interrupt() and driver_rx() on Linux systems. The driver_rx() method should call netif_rx() to pass the socket buffer to the upstream module.

The driver_put() routine that UNIX network device drivers use to send data out on a network device corresponds to the ndo_start_xmit() method on Linux systems. The method can use the following utility functions to control upstream queueing of socket buffers for transmission:

netif_queue_stopped()

Asks the kernel whether a queue is currently stopped.

netif_start_queue()

Informs the kernel that the driver is ready to start sending packets.

netif_stop_queue()

Asks the kernel to stop sending packets when transmission buffers are full or for driver cleanup when closing the device.

netif_wake_queue()

Asks the kernel to wake up a queue that is currently stopped and resume sending packets.

After a network packet has been sent out successfully, driver_interrupt() calls a driver_tx routine that deletes the driver's copy of the socket buffer data.

The net_device_ops structure for a network device, defined in <linux/netdevice.h>, contains a set of method pointers that specify how the system interacts with the device.

struct net_device_ops {
        int                     (*ndo_init)(struct net_device *dev);
        void                    (*ndo_uninit)(struct net_device *dev);
        int                     (*ndo_open)(struct net_device *dev);
        int                     (*ndo_stop)(struct net_device *dev);
        netdev_tx_t             (*ndo_start_xmit) (struct sk_buff *skb,
                                                   struct net_device *dev);
        u16                     (*ndo_select_queue)(struct net_device *dev,
                                                    struct sk_buff *skb);
        void                    (*ndo_change_rx_flags)(struct net_device *dev,
                                                       int flags);
        void                    (*ndo_set_rx_mode)(struct net_device *dev);
        void                    (*ndo_set_multicast_list)(struct net_device *dev);
        int                     (*ndo_set_mac_address)(struct net_device *dev,
                                                       void *addr);
        int                     (*ndo_validate_addr)(struct net_device *dev);
        int                     (*ndo_do_ioctl)(struct net_device *dev,
                                                struct ifreq *ifr, int cmd);
        int                     (*ndo_set_config)(struct net_device *dev,
                                                  struct ifmap *map);
        int                     (*ndo_change_mtu)(struct net_device *dev,
                                                  int new_mtu);
        int                     (*ndo_neigh_setup)(struct net_device *dev,
                                                   struct neigh_parms *);
        void                    (*ndo_tx_timeout) (struct net_device *dev);

        struct rtnl_link_stats64* (*ndo_get_stats64)(struct net_device *dev,
                                                     struct rtnl_link_stats64 *storage);
        struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);

        void                    (*ndo_vlan_rx_register)(struct net_device *dev,
                                                        struct vlan_group *grp);
        void                    (*ndo_vlan_rx_add_vid)(struct net_device *dev,
                                                       unsigned short vid);
        void                    (*ndo_vlan_rx_kill_vid)(struct net_device *dev,
                                                        unsigned short vid);
        /* Several lines omitted */
};

A simple network device driver might only need to implement a subset of the functions defined for this structure, for example:

struct net_device_ops driver_netdevops = {
    .ndo_init       = driver_init,
    .ndo_open       = driver_open,
    .ndo_stop       = foo_close,
    .ndo_start_xmit = foo_start_xmit,
    .ndo_do_ioctl   = foo_ioctl,
    .ndo_tx_timeout = foo_tx_timeout,
};

The kernel calls ndo_open() when you bring up and assign an address to a network interface, ndo_stop() when you shut down the interface, and ndo_start_xmit() when it wants to transmit a packet.

The net_device structure for a character device, defined in <linux/netdevice.h>, defines the properties of the network interface.

The most important members of the net_device structure are:

dev_addr

The hardware media access control (MAC) address.

features

A bit mask that describes the features that the device supports.

flags

Describes the current properties of the device, such as the following:

IFF_BROADCAST

Broadcast mode is enabled.

IFF_LOOPBACK

The interface corresponds to a loopback device.

IFF_MULTICAST

Multicast mode is enabled.

IFF_NOARP

The interface does not use ARP to perform address resolution. This flag is usually set by point-to-point interfaces.

IFF_PROMISC

Promiscuous mode is enabled.

IFF_UP

The interface is enabled. The driver does not set this flag. Using an application such as ip to bring an interface up or down causes the kernel to invoke the ndo_open() or ndo_stop() method and set or unset the flag to indicate the state of the interface.

irq

The interrupt request queue (IRQ) number.

mtu

The maximum transmission unit (MTU), which is the maximum frame size that the device can transmit.

name

The name of the interface, for example, eth0 or lo0.

netdev_ops

A pointer to a net_device_ops structure that defines the methods for the interface.

promiscuity

A counter of how many client applications have placed the interface in promiscuous mode.

stats

A net_device_stats structure that contains interface usage statistics.

As well as driver methods, driver_interrupt(), and related helper routines, it is usual to define module_init() initialization and module_exit() cleanup routines for the driver that are called when the driver is loaded and unloaded. These routines should call register_netdev() and unregister_netdev() to register and unregister the device, and alloc_netdev() and free_netdev() to allocate and delete the kernel's representation of the device. For Ethernet devices, it is usual to call alloc_etherdev() instead of alloc_netdev(). When initializing the driver, use request_irq() to install the interrupt handler, remembering to specify shared interrupts by setting the SA_SHIRQ bit in the flags argument as well as a unique dev_id argument that the handler can use to identify interrupts that it should process. The cleanup routine should call free_irq() to unregister the handler.