Generally speaking, the network card has two main important functions: receiving data and sending data.

Therefore, when the nic receives the packet, it notifies the Linux kernel that there is data to be processed. In addition, the nic driver should provide an interface for the Linux kernel to send data out.

Net_device structure is abstracted from Linux to adapt to different types of nic devices. Different NIC drivers only need to fill in various member variables of net_device structure according to Linux specifications, so that the Linux kernel can identify the NIC and work.

Next, we will analyze the implementation principle of receiving and sending data packets.

The net_device structure

The net_device structure is an abstraction of network interface card devices by the Linux kernel. However, due to historical reasons, the definition of net_device structure is very complicated.

However, this article focuses on the implementation of the network card device to send and receive data, so it will not analyze all members of the NET_device structure. Members related to sending and receiving data are listed as follows:

struct net_device { char name[IFNAMSIZ]; // Device name... unsigned int irq; // Interrupt number... int (*init)(struct net_device *dev); // The device initializes the device interface... int (*open)(struct net_device *dev); Int (*stop)(struct net_device *dev); Int (*hard_start_xmit)(struct sk_buff * SKB,struct net_device *dev); int (*hard_start_xmit)(struct sk_buff * SKB,struct net_device *dev); . };Copy the code

Here are the roles of each member:

  • Name: indicates the device name. Display the device name on the terminal or search for devices by device name.

  • Irq: indicates the interrupt number. When a NIC receives data packets from the network, it needs to generate an interrupt to notify the Linux kernel that data packets need to be processed. The IRQ is the interrupt number registered by the NIC driver with the kernel interrupt service.

  • Init, open, and stop are the initializing, enabling, and disabling interfaces respectively.

  • Hard_start_xmit: When data needs to be sent through the nic device, this interface can be called to send data.

Therefore, an NIC driver must perform the following two tasks:

  • The hard_start_xmit method implements the net_device structure to provide the ability to send data.

  • The kernel is notified to process packets received by the nic device by registering hardware interrupt services with the kernel.

That is, the ability to send data is provided by the net_device structure’s hard_start_xmit method, and the ability to notify the kernel to process the received packets is provided by the nic’s hardware interrupt.

Figure 1 shows the process of the network card receiving and sending data:

Figure 1 The process of receiving and sending data

The figure above shows the process of NS8390 nic receiving and sending data (red brackets are the receiving process and blue brackets are the sending process). From the figure above, it can be found that the NS8390 NIC driver does two things:

  • Set the hard_STARt_xmit method of the net_device structure to ei_STARt_xmit.

  • The EI_interrupt hardware interrupt service is registered with the Linux kernel.

So, when the nic receives a packet, it triggers the EI_interrupt interrupt service to notify the kernel that there is a packet to process. When data needs to be sent through the network card, the ei_STARt_xmit method is called to send the data.

Data receiving process

The ei_interrupt interrupt service is triggered when the nic receives a packet from the network. Let’s look at the implementation of ei_interrupt:

void ei_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct net_device *dev = dev_id; long e8390_base; int interrupts, nr_serviced = 0; struct ei_device *ei_local; e8390_base = dev->base_addr; ei_local = (struct ei_device *)dev->priv; spin_lock(&ei_local->page_lock); . While ((Interrupts = INb_p (E8390_BASE + EN0_ISR))! = 0 && ++nr_serviced < MAX_SERVICE) { ... If (interrupts & (ENISR_RX + ENISR_RX_ERR)) {ei_receive(dev); // (3) read data from the nic}... }... spin_unlock(&ei_local->page_lock); return; }Copy the code

The above code removes a lot of hardware-related operations because this article is not analyzing the implementation of the nic driver.

Ei_interrupt The ei_interrupt service first reads the interrupt type and stores it in the Interrupts variable. Then determine whether the interrupt type is received or not, and if so, call ei_receive to read the data from the nic.

Let’s continue with the implementation of the ei_receive function:

static void ei_receive(struct net_device *dev) { ... while (++rx_pkt_count < 10) { int pkt_len; // The length of the packet int pkt_stat; // The state of the packet... If ((pkt_stat & 0x0F) == ENRSR_RXOK) {struct sk_buff * SKB; skb = dev_alloc_skb(pkt_len + 2); If (SKB) {skb_reserve(SKB, 2); skb->dev = dev; // Set the device to receive packets skb_put(SKB, pkt_len); Ei_block_input (dev, pkt_len, SKB, current_offset+sizeof(rx_frame)); ei_block_input(dev, pkt_len, SKB, current_offset+sizeof(rx_frame)); skb->protocol = eth_type_trans(skb, dev); // Get the network layer protocol type netif_rx(SKB) from the Ethernet header; // Send the packet to the kernel protocol stack... }}... }... return; }Copy the code

The ei_receive function does the following:

  • Apply for an SK_buff packet object and set its dev field to the device receiving the packet.

  • Read the received data from the nic by calling the ei_block_INPUT function and save it to the newly applied SK_buff packet object. The ei_block_input function is implemented by the nic driver, so I won’t go into detail here.

  • Get the network-layer protocol type from the Ethernet header of the packet by calling eth_type_trans.

  • Call the netif_rx function to send the packet up to the kernel network stack.

When the packet is sent to the kernel network protocol stack, the kernel takes over the processing of the packet. Generally speaking, the kernel network protocol stack processes data packets through IP at the network layer and TCP or UDP at the transport layer, and then submits the data to the application layer for processing.

Data sending process

When the network protocol stack needs to send data through the nic device, the hard_start_xmit method of the net_device structure is called, while for the NS8390 nic, the hard_start_xmit method is set to the ei_STARt_xmit function.

That is, when data is sent using the NS8390 network card, the ei_STARt_xmit function is eventually called to send the data out. Let’s look at the implementation of the ei_start_xmit function:

static int ei_start_xmit(struct sk_buff *skb, struct net_device *dev) { ... length = skb->len; // The length of the packet... disable_irq_nosync(dev->irq); // Disable hardware interrupt spin_lock(&ei_local->page_lock); // Lock the device to avoid the use of multi-core cpus... Ei_block_output (dev, length, SKB ->data, ei_local->tx_start_page); . spin_unlock(&ei_local->page_lock); // Unlock the device enable_irq(dev->irq); // Open hardware interrupts... return 0; }Copy the code

The implementation of the ei_start_xmit function is very simple, stripped of hardware-related operations:

  • First, turn off the hardware interrupt of the network card to prevent the interference of hardware interrupt in the sending process.

  • The ei_block_output function is called to send the data of the packet. This function is implemented by the nic driver and will not be analyzed in detail here.

  • Turn on hardware interrupts for the nic so that the NIC can continue to inform the kernel.

conclusion

This article mainly introduces the process of receiving and sending data packets, but the initialization process of the network adapter is not involved. Of course, the initialization process of the network adapter device is also very important and may be covered in a later article.