The most important tasks performed by network interfaces are data transmission and reception. We’ll start with transmission because it is slightly easier to understand.
Whenever the kernel needs to transmit a data packet, it calls the
hard_start_transmit method to put the data on an
outgoing queue. Each packet handled by the kernel is contained in a
socket buffer structure (struct sk_buff
), whose
definition is found in <linux/skbuff.h>
. The
structure gets its name from the Unix abstraction used to represent a
network connection, the socket. Even if the
interface has nothing to do with sockets, each network packet belongs
to a socket in the higher network layers, and the input/output buffers
of any socket are lists of struct sk_buff
structures. The same sk_buff
structure is used to
host network data throughout all the Linux network subsystems, but a
socket buffer is just a packet as far as the interface is concerned.
A pointer to sk_buff
is usually called
skb
, and we follow this practice both in the sample
code and in the text.
The socket buffer is a complex structure, and the kernel offers a
number of functions to act on it. The functions are described later in
Section 14.9; for now a few basic facts about
sk_buff
are enough for us to write a working
driver.
The socket buffer passed to hard_start_xmit
contains the physical
packet as it should appear on the media, complete with the
transmission-level headers. The interface doesn’t need to modify the
data being transmitted. skb->data
points to the
packet being transmitted, and skb->len
is its
length, in octets.
The snull packet transmission code is follows; the physical transmission machinery has been isolated in another function because every interface driver must implement it according to the specific hardware being driven.
int snull_tx(struct sk_buff *skb, struct net_device *dev) { int len; char *data; struct snull_priv *priv = (struct snull_priv *) dev->priv; len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; data = skb->data; dev->trans_start = jiffies; /* save the timestamp */ /* Remember the skb, so we can free it at interrupt time */ priv->skb = skb; /* actual delivery of data is device specific, and not shown here */ snull_hw_tx(data, len, dev); return 0; /* Our simple device cannot fail */ }
The transmission function thus performs only some sanity checks on the packet and transmits the data through the hardware-related function. That function (snull_hw_tx) is omitted here since it is entirely occupied with implementing the trickery of the snull device (including manipulating the source and destination addresses) and has little of interest to authors of real network drivers. It is present, of course, in the sample source for those who want to go in and see how it works.
The hard_start_xmit function is protected from
concurrent calls by a spinlock (xmit_lock
) in the
net_device
structure. As soon as the function
returns, however, it may be called again. The function returns when
the software is done instructing the hardware about packet
transmission, but hardware transmission will likely not have been
completed. This is not an issue with
snull, which does all of its work using the
CPU, so packet transmission is complete before the transmission
function returns.
Real hardware interfaces, on the other hand, transmit packets asynchronously and have a limited amount of memory available to store outgoing packets. When that memory is exhausted (which, for some hardware, will happen with a single outstanding packet to transmit), the driver will need to tell the networking system not to start any more transmissions until the hardware is ready to accept new data.
This notification is accomplished by calling netif_stop_queue, the function introduced earlier to stop the queue. Once your driver has stopped its queue, it must arrange to restart the queue at some point in the future, when it is again able to accept packets for transmission. To do so, it should call:
void netif_wake_queue(struct net_device *dev);
This function is just like netif_start_queue, except that it also pokes the networking system to make it start transmitting packets again.
Most modern network interfaces maintain an internal queue with multiple packets to transmit; in this way they can get the best performance from the network. Network drivers for these devices support having multiple transmisions outstanding at any given time, but device memory can fill up whether or not the hardware supports multiple outstanding transmission. Whenever device memory fills to the point that there is no room for the largest possible packet, the driver should stop the queue until space becomes available again.
Most drivers that deal with real hardware have to be prepared for that hardware to fail to respond occasionally. Interfaces can forget what they are doing, or the system can lose an interrupt. This sort of problem is common with some devices designed to run on personal computers.
Many drivers handle this problem by setting timers; if the operation has not completed by the time the timer expires, something is wrong. The network system, as it happens, is essentially a complicated assembly of state machines controlled by a mass of timers. As such, the networking code is in a good position to detect transmission timeouts automatically.
Thus, network drivers need not worry about detecting such problems
themselves. Instead, they need only set a timeout period, which goes
in the watchdog_timeo
field of the
net_device
structure. This period, which is in
jiffies, should be long enough to account for normal transmission
delays (such as collisions caused by congestion on the network media).
If the current system time exceeds the device’s
trans_start
time by at least the timeout period,
the networking layer will eventually call the driver’s
tx_timeout method. That method’s job is to do
whatever is needed to clear up the problem and to ensure the proper
completion of any transmissions that were already in progress. It is
important, in particular, that the driver not lose track of any socket
buffers that have been entrusted to it by the networking code.
snull has the ability to simulate transmitter lockups, which is controlled by two load-time parameters:
static int lockup = 0; MODULE_PARM(lockup, "i"); #ifdef HAVE_TX_TIMEOUT static int timeout = SNULL_TIMEOUT; MODULE_PARM(timeout, "i"); #endif
If the driver is loaded with the parameter
lockup=n
, a lockup will be simulated once every
n
packets transmitted, and the
watchdog_timeo
field will be set to the given
timeout
value. When simulating lockups,
snull also calls
netif_stop_queue to prevent other transmission
attempts from occurring.
The snull transmission timeout handler looks like this:
void snull_tx_timeout (struct net_device *dev) { struct snull_priv *priv = (struct snull_priv *) dev->priv; PDEBUG("Transmit timeout at %ld, latency %ld\n", jiffies, jiffies - dev->trans_start); priv->status = SNULL_TX_INTR; snull_interrupt(0, dev, NULL); priv->stats.tx_errors++; netif_wake_queue(dev); return; }
When a transmission timeout happens, the driver must mark the error in the interface statistics and arrange for the device to be reset to a sane state so that new packets can be transmitted. When a timeout happens in snull, the driver calls snull_interrupt to fill in the “missing” interrupt and restarts the transmit queue with netif_wake_queue.
Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.