18.3 Sending Packets to the Network Card

A network card device driver is usually started either when the kernel inserts a packet in its transmit queue (as described in the previous section), or when a packet is received from the communication channel. Let's focus here on packet transmission.

As we have seen, the qdisc_run( ) function is invoked whenever the kernel wishes to activate a network card device driver; it is also executed by the NET_TX_SOFTIRQ softirq, which is implemented by the net_tx_action( ) function (see Section 4.7).

Essentially, the qdisc_run( ) function checks whether the network card device is idle and can thus transmit the packets in the queue. If the device cannot do this — for instance, because the card is already busy in transmitting or receiving a packet, the queue has been stopped to avoid flooding the communication channel, or for whatever other reason — the NET_TX_SOFTIRQ softirq is activated and the current execution of qdisc_run( ) is terminated. At a later time, when the scheduler selects a ksoftirqd_CPUn kernel thread, the net_tx_action( ) function invokes qdisc_run( ) again to retry the packet transmission.

In particular, qdisc_run( ) performs the following actions:

1.       Checks whether the packet queue is "stopped" — that is, whether a suitable bit in the state field of the net_device network card object is set. If it is stopped, the function returns immediately.

2.       Invokes the qdisc_restart( ) function, which in turn performs the following actions:

a.       Invokes the dequeue method of the Qdisc packet queue to extract a packet from the queue. If the queue is empty, it terminates.

b.       Checks whether a packet sniffing policy is enforced on the kernel, telling it to pass a copy of each outgoing packet to a local socket; in this case, the function invokes the dev_queue_xmit_nit( ) function to do the job. We won't discuss this further.

c.       Invokes the hard_start_xmit method of the net_device object that describes the network card device.

d.       If the hard_start_xmit method fails in transmitting the packet, it reinserts the packet in the queue and invokes cpu_raise_softirq( ) to schedule the activation of the NET_TX_SOFTIRQ softirq.

3.       If the queue is now empty, or the hard_start_xmit method fails in transmitting the packet, the function terminates. Otherwise, it jumps to Step 1 to process another packet in the queue.

The hard_start_xmit method is specific to the network card device and takes care of transferring the packet from the socket buffer to the device's memory. Typically, the method limits itself to activate a DMA transfer. In PCI-based network cards, moreover, a small number of DMA transfers may usually be booked in advance: they are automatically activated by the card whenever it finishes the ongoing DMA transfers. If the card is not able to accept further packets because the device's memory is full, the method stops the packet queue by setting the proper bit in the state field of the net_device object. Therefore, the qdisc_run( ) function terminates and is presumably executed again later by the softirq.

When a DMA transfer ends, the card raises an interrupt. The corresponding interrupt handler, in turn, performs the following actions:

1.       Acknowledges the interrupt issued by the card.

2.       Checks for transmission errors, updates driver statistics, and so on.

3.       Invokes, if necessary, the cpu_raise_softirq( ) function to schedule the activation of the softirq.

4.       If the queue is stopped, resets the bit in the state field of the net_device object and restarts packet processing.

As you see, network card device drivers work like disk device drivers: the real work is mostly done in interrupt handlers and deferrable functions, so that usual processes are not blocked waiting for packet transmissions.