1, Experimental preparation
In this experiment, you will create a network interface card( network interface card)Write one xv6 Device driver. Get this experiment xv6 Source code, and check out net Branch: git fetch,git checkout net,make clean
Before writing code, you will find: revisit xv6 book "Chapter 5: interrupts and device drivers" is helpful. You will use a called E1000 Network devices to handle network communications. yes xv6 And the driver you wrote, E1000 Like a real hardware (connected to a real Ethernet local area network) Ethernet Local Area Net). In fact, your driver will be communicating E1000 By qemu Simulated, it is connected to LAN So is it qemu Simulated. In this simulation LAN Up, xv6()There is one IP Address 10.0.2.15. qemu Also arrange one IP For 10.0.2.2,function qemu Your computer appears in LAN Yes. When xv6 use E1000 Send a packet to 10.0.2.2，qemu Deliver the package to the corresponding computer (running) qemu)Application. You will use qemu User mode network stack( user-mode network stack). qemu The documentation for has more details about the user mode stack. https://qemu-project.gitlab.io/qemu/system/net.html#using-the-user-mode-network-stack. We have updated Makefile to enable QEMU's user mode network stack and E1000 network card. Makefile to configure qemu Record all input and output packages to files packets.cap，In your lab catalog. Look through these records for confirmation xv6 It is helpful to deliver and receive the package you expect. Package showing records: tcpdump -XXnr packets.pcap We have added many files to this experiment xv6 Warehouse. file kernel/e1000.c contain E1000 The initialization code and the empty function of transmitting and receiving packets (you need to fill in). kernel/e1000_dev.h Including register definition E1000 Defined flag bit (at Intel) E1000 Description in https://pdos.csail.mit.edu/6.828/2020/readings/8254x_GBe_SDM.pdf). kernel/net.c and kernel / net H contains a simple network stack (implementing IP/UDP/ARP Protocol). These files also contain flexible data structure code (hold package), called mbuf. Finally, kernel / PCI C contains code: when xv6 starts, look for E1000 card on PCI bus.
2. Your work
Your job is to finish e1000_transmit()and e1000_recv()，All in kernel/e1000.c，Therefore, the driver can transmit and receive packets. When make grade When you are prompted that your scheme has passed all tests, you are right. When writing code, you will refer to E1000 Software development manual( https://pdos.csail.mit.edu/6.828/2020/readings/8254x_GBe_SDM.pdf). The following sections may be helpful. Paragraph 2 is particularly important to give you an overview of the whole equipment. Paragraph 3.2 Give an overview of packet reception. Paragraph 3.3 Give an overview of package delivery. Given in paragraph 13 E1000 Overview of using registers. Paragraph 14 can help you understand the initial code we provide. browse E1000 Software development manual( https://pdos.csail.mit.edu/6.828/2020/readings/8254x_GBe_SDM.pdf). This manual contains several controllers that are very relevant to Ethernet. qemu simulates 82540EM. Browse Chapter 2 to learn about devices. To write drivers, you will need to be familiar with chapters 3, 14, and 4.1 (not a subsection of 4.1). You will also need to use Chapter 13 as a reference. Other chapters contain E1000 components that drive you without interaction. Don't worry about details at first; Just understand the document structure so that you can find it later. E1000 has some advanced features, most of which can be ignored. Only a small number of basic features need to be used to complete this experiment. e1000.c Provided to you in e1000_init()Functions, configuring E1000 from RAM Read the package to be delivered and write the received package to RAM. This technology is called DMA，Direct memory access, reference facts: E1000 Hardware directly from RAM Write and read packages. because bursts of packets It may be faster than the drive processing speed, e1000_init()to E1000 Provide multiple buffers，E1000 You can write the package to buffers. E1000 Need these buffers Described as RAM A set of descriptors in; Each descriptor contains a RAM Address in, E1000 The received packet can be written to it. struct rx_desc Describes the descriptor format. An array of descriptors is called a receive ring or a receive queue. This is a ring: when the card or driver reaches the bottom of the array, it goes to the beginning. e1000_init()use mbufalloc()by E1000 distribution mbuf package buffers，be used for DMA. There is also a transfer ring to drive the need E1000 Put the sent package inside. e1000_init()The size of two rings is RX_RING_SIZE and TX_RING_SIZE. When net.c When the network stack in needs to send a packet, call e1000_transmit()，Use one mbuf(Hold the package to send). Your pass through code must place a pointer (pointing to the package data) to TX(transmit)Ring. struct tx_desc Descriptor format description. You will need to ensure that each mbuf Eventually released, only in E1000 After the transfer package is finished( E1000 Set in descriptor E1000_TXD_STAT_DD Bit representation). When E1000 When receiving each packet from Ethernet, it first DMA Package to mbuf(Be next RX ring The descriptor points to), and then generates an interrupt. Yours e1000_recv()Code must be scanned RX ring，And call net_rx()Pass the of each new package mbuf To network stack( in net.c). You will need to assign a new mbuf，And put it into the descriptor, so when E1000 Arrive again RX When the point in the ring, it will find a new one buffer，DMA A new package to the buffer. Besides reading and writing RAM Outside the descriptor ring in, your driver will need to work with E1000 Memory mapping controls register interaction to detect when received packets are available and notify E1000: The driver has filled some with packets to send TX Descriptor. global variable regs Hold a pointer to E1000 The first control register); Your driver can be indexed like an array regs Get other registers. You will need to use indexes in particular E1000_RDT and E1000_TDT. To test your driver, execute in a window make server，Another window executes make qemu，Then in xv6 implement nettests. nettests Your first test attempts to send a UDP Package to host Send to operating system make server Procedures to be performed. If you don't finish the experiment, E1000 The driver will not actually send the packet and nothing will happen. When you finish this experiment, E1000 The driver will send the packet, qemu Pass it on to your host computer, make server You will see it, it will send a response packet, and then to E1000 Drive, then nettests You will see the response package. stay host Before sending a reply, it sends a ARP Request package to xv6 To find its 48 bit Ethernet address, expect xv6 yes ARP Respond. Once you've finished E1000 Driven work, kernel/net.c This will be handled. If everything is normal, nettests Will print testing ping: OK，make server Will print a message from xv6!. tcpdump -XXnr packets.pcap The following outputs should be generated:
reading from file packets.pcap, link-type EN10MB (Ethernet)
15:27:40.861988 IP 10.0.2.15.2000 > 10.0.2.2.25603: UDP, length 19
0x0000: ffff ffff ffff 5254 0012 3456 0800 4500 ...RT...4V...E.
0x0010: 002f 0000 0000 6411 3eae 0a00 020f 0a00 ./...d.>...
0x0020: 0202 07d0 6403 001b 0000 6120 6d65 7373 ...d...a.mess
0x0030: 6167 6520 6672 6f6d 2078 7636 21 age.from.xv6!
15:27:40.862370 ARP, Request who-has 10.0.2.15 tell 10.0.2.2, length 28
0x0000: ffff ffff ffff 5255 0a00 0202 0806 0001 ...RU...
0x0010: 0800 0604 0001 5255 0a00 0202 0a00 0202 ...RU...
0x0020: 0000 0000 0000 0a00 020f ...
15:27:40.862844 ARP, Reply 10.0.2.15 is-at 52:54:00:12:34:56, length 28
0x0000: ffff ffff ffff 5254 0012 3456 0806 0001 ...RT...4V...
0x0010: 0800 0604 0002 5254 0012 3456 0a00 020f ...RT...4V...
0x0020: 5255 0a00 0202 0a00 0202 RU...
15:27:40.863036 IP 10.0.2.2.25603 > 10.0.2.15.2000: UDP, length 17
0x0000: 5254 0012 3456 5255 0a00 0202 0800 4500 RT...4VRU...E.
0x0010: 002d 0000 0000 4011 62b0 0a00 0202 0a00 .-...@.b...
0x0020: 020f 6403 07d0 0019 3406 7468 6973 2069 ...d...4.this.i
0x0030: 7320 7468 6520 686f 7374 21 s.the.host!
Your output will look a little different, but it should contain the strings "ARP, Request", "ARP, Reply", "UDP", "a.message.from.xv6" and "this.is.the.host".
nettests performs some other tests, eventually a DNS request (sent to a Google name server via the real Internet). You should make sure your code passes all these tests, and then you will see this output:
nettests running on port 25603
testing ping: OK
testing single-process pings: OK
testing multi-process pings: OK
DNS arecord for pdos.csail.mit.edu. is 184.108.40.206
all tests passed.
You should make sure that make grade passes your plan.
At the beginning, add a print statement to e1000_transmit()and e1000_recv()，implement make server and nettests. You should see from your printed statement: nettests Generate a pair e1000_transmit Call of. Some implementation e1000_transmit Tips for:
(1) First, ask for the TX ring index from E1000, and look forward to the next package on the index by reading E1000_TDT control register.
(2) Check whether the ring overflows. If E1000_TXD_STAT_DD is not set by E1000_ On the descriptor of TDT index, E1000 does not end the previous delivery request, so an error is returned.
(3) In addition, mbuffree() is used to release the last mbuf (passed from that descriptor).
(4) Fill descriptor. M - > head refers to the contents of the package in memory, and M - > len is the length of the package. Set the necessary cmd flag bit (see section 3.3 in E1000 manual) and temporarily store a pointer to mbuf for later release.
(5) Finally, add 1 to E1000_TDT, for TX_RING_SIZE modulo operation to update the ring position.
(6) If e1000_transmit() successfully added mbuf to the ring and returned 0. Failed (no descriptor is available to pass the mbuf) and returned - 1, so the caller knows to release the mbuf.
Some implementations e1000_recv tips:
(1) First, ask for the ring index from E1000, and the next packet waiting to be received is located in the index_ Value of RDT control register, plus 1 pair of RX_RING_SIZE take mold.
(2) By checking E1000 in the status section of the descriptor_ RXD_ STAT_ DD bit, check whether the new package is available. If not available, stop.
(3) Update m - > len of mbuf to length in descriptor. Using net_rx() passes mbuf to the network stack.
(4) Use mbufalloc() to allocate a new mbuf instead of the one just allocated to net_rx(). Put its data pointer (M - > head) into the descriptor. The status bit of the clear descriptor is 0.
(5) Finally, update E1000_ The RDT register is the last processed index of the ring descriptor.
(6) e1000_init() initializes the RX ring with mbufs. You will need to know how it does this, and maybe borrow code.
(7) At some points, the total number of packets reached will exceed the ring size (16); Make sure your code is available for processing.
You will need locks to handle the possibility that xv6 more than one process uses E1000, or that E1000 is being used in the kernel thread when the interrupt occurs.