Lab 10 Networking Part 1
Complete the implementation of the E1000 networking driver
Background
We will be using a virtual network device called the E1000 to handle network communication. To xv6 (and the driver you write), the E1000 looks like a real piece of hardware connected to a real Ethernet local area network (LAN). But in reality, the E1000 your driver will talk to is an emulation provided by qemu, connected to a LAN that is also emulated by qemu. On this LAN, xv6 (the “guest”) has an IP address of 10.0.2.15. The only other (emulated) computer on the LAN has IP address 10.0.2.2. qemu arranges that when xv6 uses the E1000 to send a packet to 10.0.2.2, it’s really delivered to the appropriate application on the (real) computer on which you’re running qemu (the “host”).
Testing code is setup sending packets to:
Task: Network Device Driver
Lab: networking In this part of the assignment, you will complete the implementation of the E1000 networking driver. So far, code has been provided to discover and initialize the device, and to handle interrupts, but not to send and receive packets. Browse Intel’s Software Developer's Manual for the E1000. This manual covers several closely related Ethernet controllers. QEMU emulates the 82540EM. You should skim over chapter 2 now to get a feel for the device. To write your driver, you’ll need to be familiar with chapters 3 and 14, as well as 4.1 (though not 4.1’s subsections). You’ll also need to use chapter 13 as reference. The other chapters mostly cover components of the E1000 that your driver won’t have to interact with. Don’t worry about the details right now; just get a feel for how the document is structured so you can find things later. Keep in mind that the E1000 has many advanced features, but you can ignore most of these. Only a small set of basic features is needed to complete the lab assignment.
Your Job
Your job is to implement support for sending and receiving packets. You’ll need to fill in the missing section in e1000_recv() and e1000_transmit(), both in kernel/e1000.c.
Both sending and receiving packets is managed by a queue of descriptors that is shared between xv6 and the E1000 in memory. These queues provide pointers to memory locations for the E1000 to DMA (i.e. transfer) packet data. They are implemented as circular arrays, meaning that when the card or the driver reach the end of the array, it wraps back around to the beginning. A common abbreviation is to refer to the receive data structures as RX and the transmit data structures as TX.
The E1000 generates an interrupt whenever new packets are received. Your receive code must scan the RX queue to handle each packet that has arrived and deliver its mbuf to the protocol layer by calling net_rx(). struct rx_desc describes the descriptor format. You will then need to allocate a new mbuf and program it into the descriptor so that the E1000 knows where to place the next payload when it eventually reaches the same location in the array at a later time.
Packet sends are requested by the protocol layer when it calls e1000_transmit(). Your transmit code must enqueue the mbuf into the TX queue. This includes extracting the payload’s location in memory and its length, and encoding this information into a descriptor in the TX queue. struct tx_desc describes the descriptor format. You will need to ensure that mbufs are eventually freed, but only after the transmission has finished (the NIC can encode a notification bit in the descriptor to indicate this).
In addition to reading and writing to the circular arrays of descriptors, you’ll need to interact with the E1000 through memory mapped I/O to detect when new descriptors are available on the receive path and to inform the E1000 that new descriptors have been provided on the transmit path. A pointer to the device’s I/O is stored in regs, and it can be accessed as an array of control registers. You’ll need to use indices E1000_RDT and E1000_TDT in particular.
Solution
How to discover and initialize the hardware device?
DMA for E1000
When creating a direct-map page table for the kernel, map this address for E1000.
In PCI-Express initialization, all E1000 registers start from this base address.
What is PCI Express
PCI Express - Wikipedia It is the common motherboard interface for personal computers’ graphics cards , hard drives , SSDs , Wi-Fi and Ethernet hardware connections.
E1000 hardware definitions
All registers are in E1000.
DMA ring format
PCI init
In pci.c
Tell the e1000 to reveal its registers at physical address 0x40000000.
E1000 initialization
e1000_init(uint32 *xregs)
is called by pci_init
. xregs
is the memory address at which the e1000’s registers are mapped. The flows are: 1. Reset the device 2. [E1000 14.5] Transmit initialization 3. [E1000 14.4] Receive initialization 4. transmitter, receiver control bits 5. Ask E1000 for receive interrupts. So E1000 sends interrupts for every packet.
How Transmit work in hardware?
[Head, Tail)
contains all the packets that are about to sent to outside by the hardware (E1000).
Tail
points to the 1st already sent but not yet recycled by software (OS driver) buffers.
Transmit Implementation in Driver
Our logic is when our driver about to send a new package, we: 1. Get current Tail position from hardware register. 2. Get the first not recycled buffer starting from Tail position. 3. Free the old buffer. 4. Save the new packet buffer in this location. Update the length, and other metadata. 5. Tell hardware there is a new ready-to-send packet, by move down the tail position.
How Receiving work in hardware?
Hardware maintains a circular ring of descriptors. Between Head and Tail, are the empty buffers that hardware writes when new packets arrive. As packets arrive, they are stored in memory and the head pointer is incremented by hardware.
The tail
points to the last empty buffer that hardware owns for receiving new packets.
The tail + 1
points to the 1st fully received buffer that software (OS driver) can consume.
After consumption, the tail is moved down by 1, since OS driver wants hardware to know that there is a new empty buffer to use for receiving.
WARNING
The image tail
is WRONG. It should be 1 entry above. (See code below)
Receiving Implementation in Driver
Get the received packet at
tail + 1
position.Ensure it is ready to be consumed.
Update buffer length from description length.
Deliver this buffer to protocal layer.
Allocate a new buffer and set at current position.
Since we now have 1 more empty buffer for receiving, tell hardware about this.
Last updated