]> git.proxmox.com Git - mirror_edk2.git/commitdiff
OvmfPkg: VirtioNetDxe: add technical notes
authorLaszlo Ersek <lersek@redhat.com>
Fri, 14 Jun 2013 07:39:25 +0000 (07:39 +0000)
committerjljusten <jljusten@6f19259b-4bc3-4df7-8a09-765794883524>
Fri, 14 Jun 2013 07:39:25 +0000 (07:39 +0000)
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
git-svn-id: https://svn.code.sf.net/p/edk2/code/trunk/edk2@14404 6f19259b-4bc3-4df7-8a09-765794883524

OvmfPkg/VirtioNetDxe/TechNotes.txt [new file with mode: 0644]

diff --git a/OvmfPkg/VirtioNetDxe/TechNotes.txt b/OvmfPkg/VirtioNetDxe/TechNotes.txt
new file mode 100644 (file)
index 0000000..9c1dfe6
--- /dev/null
@@ -0,0 +1,355 @@
+## @file\r
+#\r
+# Technical notes for the virtio-net driver.\r
+#\r
+# Copyright (C) 2013, Red Hat, Inc.\r
+#\r
+# This program and the accompanying materials are licensed and made available\r
+# under the terms and conditions of the BSD License which accompanies this\r
+# distribution. The full text of the license may be found at\r
+# http://opensource.org/licenses/bsd-license.php\r
+#\r
+# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+# WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+#\r
+##\r
+\r
+Disclaimer\r
+----------\r
+\r
+All statements concerning standards and specifications are informative and not\r
+normative. They are made in good faith. Corrections are most welcome on the\r
+edk2-devel mailing list.\r
+\r
+The following documents have been perused while writing the driver and this\r
+document:\r
+- Unified Extensible Firmware Interface Specification, Version 2.3.1, Errata C;\r
+  June 27, 2012\r
+- Driver Writer's Guide for UEFI 2.3.1, 03/08/2012, Version 1.01;\r
+- Virtio PCI Card Specification, v0.9.5 DRAFT, 2012 May 7.\r
+\r
+\r
+Summary\r
+-------\r
+\r
+The VirtioNetDxe UEFI_DRIVER implements the Simple Network Protocol for\r
+virtio-net devices. Higher level protocols are automatically installed on top\r
+of it by the DXE Core / the ConnectController() boot service, enabling for\r
+virtio-net devices eg. DHCP configuration, TCP transfers with edk2 StdLib\r
+applications, and PXE booting in OVMF.\r
+\r
+\r
+UEFI driver structure\r
+---------------------\r
+\r
+A driver instance, belonging to a given virtio-net device, can be in one of\r
+four states at any time. The states stack up as follows below. The state\r
+transitions are labeled with the primary function (and its important callees\r
+faithfully indented) that implement the transition.\r
+\r
+                               |  ^\r
+                               |  |\r
+   [DriverBinding.c]           |  | [DriverBinding.c]\r
+   VirtioNetDriverBindingStart |  | VirtioNetDriverBindingStop\r
+     VirtioNetSnpPopulate      |  |   VirtioNetSnpEvacuate\r
+       VirtioNetGetFeatures    |  |\r
+                               v  |\r
+                   +-------------------------+\r
+                   | EfiSimpleNetworkStopped |\r
+                   +-------------------------+\r
+                               |  ^\r
+                [SnpStart.c]   |  | [SnpStop.c]\r
+                VirtioNetStart |  | VirtioNetStop\r
+                               |  |\r
+                               v  |\r
+                   +-------------------------+\r
+                   | EfiSimpleNetworkStarted |\r
+                   +-------------------------+\r
+                               |  ^\r
+  [SnpInitialize.c]            |  | [SnpShutdown.c]\r
+  VirtioNetInitialize          |  | VirtioNetShutdown\r
+    VirtioNetInitRing {Rx, Tx} |  |   VirtioNetShutdownRx [SnpSharedHelpers.c]\r
+      VirtioRingInit           |  |   VirtioNetShutdownTx [SnpSharedHelpers.c]\r
+    VirtioNetInitTx            |  |   VirtioRingUninit {Tx, Rx}\r
+    VirtioNetInitRx            |  |\r
+                               v  |\r
+                  +-----------------------------+\r
+                  | EfiSimpleNetworkInitialized |\r
+                  +-----------------------------+\r
+\r
+The state at the top means "nonexistent" and is hence unnamed on the diagram --\r
+a driver instance actually doesn't exist at that point. The transition\r
+functions out of and into that state implement the Driver Binding Protocol.\r
+\r
+The lower three states characterize an existent driver instance and are all\r
+states defined by the Simple Network Protocol. The transition functions between\r
+them are member functions of the Simple Network Protocol.\r
+\r
+Each transition function validates its expected source state and its\r
+parameters. For example, VirtioNetDriverBindingStop will refuse to disconnect\r
+from the controller unless it's in EfiSimpleNetworkStopped.\r
+\r
+\r
+Driver instance states (Simple Network Protocol)\r
+------------------------------------------------\r
+\r
+In the EfiSimpleNetworkStopped state, the virtio-net device is (has been)\r
+re-set. No resources are allocated for networking / traffic purposes. The MAC\r
+address and other device attributes have been retrieved from the device (this\r
+is necessary for completing the VirtioNetDriverBindingStart transition).\r
+\r
+The EfiSimpleNetworkStarted is completely identical to the\r
+EfiSimpleNetworkStopped state for virtio-net, in the functional and\r
+resource-usage sense. This state is mandated / provided by the Simple Network\r
+Protocol for flexibility that the virtio-net driver doesn't exploit.\r
+\r
+In particular, the EfiSimpleNetworkStarted state is the target of the Shutdown\r
+SNP member function, and must therefore correspond to a hardware configuration\r
+where "[it] is safe for another driver to initialize". (Clearly another UEFI\r
+driver could not do that due to the exclusivity of the driver binding that\r
+VirtioNetDriverBindingStart() installs, but a later OS driver might qualify.)\r
+\r
+The EfiSimpleNetworkInitialized state is the live state of the virtio NIC / the\r
+driver instance. Virtio and other resources required for network traffic have\r
+been allocated, and the following SNP member functions are available (in\r
+addition to VirtioNetShutdown which leaves the state):\r
+\r
+- VirtioNetReceive [SnpReceive.c]: poll the virtio NIC for an Rx packet that\r
+  may have arrived asynchronously;\r
+\r
+- VirtioNetTransmit [SnpTransmit.c]: queue a Tx packet for asynchronous\r
+  transmission (meant to be used together with VirtioNetGetStatus);\r
+\r
+- VirtioNetGetStatus [SnpGetStatus.c]: query link status and status of pending\r
+  Tx packets;\r
+\r
+- VirtioNetMcastIpToMac [SnpMcastIpToMac.c]: transform a multicast IPv4/IPv6\r
+  address into a multicast MAC address;\r
+\r
+- VirtioNetReceiveFilters [SnpReceiveFilters.c]: emulate unicast / multicast /\r
+  broadcast filter configuration (not their actual effect -- a more liberal\r
+  filter setting than requested is allowed by the UEFI specification).\r
+\r
+The following SNP member functions are not supported [SnpUnsupported.c]:\r
+\r
+- VirtioNetReset: reinitialize the virtio NIC without shutting it down (a loop\r
+  from/to EfiSimpleNetworkInitialized);\r
+\r
+- VirtioNetStationAddress: assign a new MAC address to the virtio NIC,\r
+\r
+- VirtioNetStatistics: collect statistics,\r
+\r
+- VirtioNetNvData: access non-volatile data on the virtio NIC.\r
+\r
+Missing support for these functions is allowed by the UEFI specification and\r
+doesn't seem to trip up higher level protocols.\r
+\r
+\r
+Events and task priority levels\r
+-------------------------------\r
+\r
+The UEFI specification defines a sophisticated mechanism for asynchronous\r
+events / callbacks (see "6.1 Event, Timer, and Task Priority Services" for\r
+details). Such callbacks work like software interrupts, and some notion of\r
+locking / masking is important to implement critical sections (atomic or\r
+exclusive access to data or a device). This notion is defined as Task Priority\r
+Levels.\r
+\r
+The virtio-net driver for OVMF must concern itself with events for two reasons:\r
+\r
+- The Simple Network Protocol provides its clients with a (non-optional) WAIT\r
+  type event called WaitForPacket: it allows them to check or wait for Rx\r
+  packets by polling or blocking on this event. (This functionality overlaps\r
+  with the Receive member function.) The event is available to clients starting\r
+  with EfiSimpleNetworkStopped (inclusive).\r
+\r
+  The virtio-net driver is informed about such client polling or blockage by\r
+  receiving an asynchronous callback (a software interrupt). In the callback\r
+  function the driver must interrogate the driver instance state, and if it is\r
+  EfiSimpleNetworkInitialized, access the Rx queue and see if any packets are\r
+  available for consumption. If so, it must signal the WaitForPacket WAIT type\r
+  event, waking the client.\r
+\r
+  For simplicity and safety, all parts of the virtio-net driver that access any\r
+  bit of the driver instance (data or device) run at the TPL_CALLBACK level.\r
+  This is the highest level allowed for an SNP implementation, and all code\r
+  protected in this manner satisfies even stricter non-blocking requirements\r
+  than what's documented for TPL_CALLBACK.\r
+\r
+  The task priority level for the WaitForPacket callback too is set by the\r
+  driver, the choice is TPL_CALLBACK again. This in effect serializes  the\r
+  WaitForPacket callback (VirtioNetIsPacketAvailable [Events.c]) with "normal"\r
+  parts of the driver.\r
+\r
+- According to the Driver Writer's Guide, a network driver should install a\r
+  callback function for the global EXIT_BOOT_SERVICES event (a special NOTIFY\r
+  type event). When the ExitBootServices() boot service has cleaned up internal\r
+  firmware state and is about to pass control to the OS, any network driver has\r
+  to stop any in-flight DMA transfers, lest it corrupts OS memory. For this\r
+  reason EXIT_BOOT_SERVICES is emitted and the network driver must abort\r
+  in-flight DMA transfers.\r
+\r
+  This callback (VirtioNetExitBoot) is synchronized with the rest of the driver\r
+  code just the same as explained for WaitForPacket. In\r
+  EfiSimpleNetworkInitialized state it resets the virtio NIC, halting all data\r
+  transfer. After the callback returns, no further driver code is expected to\r
+  be scheduled.\r
+\r
+\r
+Virtio internals -- Rx\r
+----------------------\r
+\r
+Requests (Rx and Tx alike) are always submitted by the guest and processed by\r
+the host. For Tx, processing means transmission. For Rx, processing means\r
+filling in the request with an incoming packet. Submitted requests exist on the\r
+"Available Ring", and answered (processed) requests show up on the "Used Ring".\r
+\r
+Packet data includes the media (Ethernet) header: destination MAC, source MAC,\r
+and Ethertype (14 bytes total).\r
+\r
+The following structures implement packet reception. Most of them are defined\r
+in the Virtio specification, the only driver-specific trait here is the static\r
+pre-configuration of the two-part descriptor chains, in VirtioNetInitRx. The\r
+diagram is simplified.\r
+\r
+                     Available Index       Available Index\r
+                     last processed          incremented\r
+                       by the host           by the guest\r
+                           v       ------->        v\r
+Available  +-------+-------+-------+-------+-------+\r
+Ring       |DescIdx|DescIdx|DescIdx|DescIdx|DescIdx|\r
+           +-------+-------+-------+-------+-------+\r
+                              =D6     =D2\r
+\r
+       D2         D3          D4         D5          D6         D7\r
+Descr. +----------+----------++----------+----------++----------+----------+\r
+Table  |Adr:Len:Nx|Adr:Len:Nx||Adr:Len:Nx|Adr:Len:Nx||Adr:Len:Nx|Adr:Len:Nx|\r
+       +----------+----------++----------+----------++----------+----------+\r
+        =A2    =D3 =A3         =A4    =D5 =A5         =A6    =D7 =A7\r
+\r
+\r
+            A2        A3     A4       A5     A6       A7\r
+Receive     +---------------+---------------+---------------+\r
+Destination |vnet hdr:packet|vnet hdr:packet|vnet hdr:packet|\r
+Area        +---------------+---------------+---------------+\r
+\r
+                Used Index                               Used Index incremented\r
+        last processed by the guest                            by the host\r
+                    v                    ------->                   v\r
+Used    +-----------+-----------+-----------+-----------+-----------+\r
+Ring    |DescIdx:Len|DescIdx:Len|DescIdx:Len|DescIdx:Len|DescIdx:Len|\r
+        +-----------+-----------+-----------+-----------+-----------+\r
+                                     =D4\r
+\r
+In VirtioNetInitRx, the guest allocates the fixed size Receive Destination\r
+Area, which accommodates all packets delivered asynchronously by the host. To\r
+each packet, a slice of this area is dedicated; each slice is further\r
+subdivided into virtio-net request header and network packet data. The\r
+(guest-physical) addresses of these sub-slices are denoted with A2, A3, A4 and\r
+so on. Importantly, an even-subscript "A" always belongs to a virtio-net\r
+request header, while an odd-subscript "A" always belongs to a packet\r
+sub-slice.\r
+\r
+Furthermore, the guest lays out a static pattern in the Descriptor Table. For\r
+each packet that can be in-flight or already arrived from the host,\r
+VirtioNetInitRx sets up a separate, two-part descriptor chain. For packet N,\r
+the Nth descriptor chain is set up as follows:\r
+\r
+- the first (=head) descriptor, with even index, points to the fixed-size\r
+  sub-slice receiving the virtio-net request header,\r
+\r
+- the second descriptor (with odd index) points to the fixed (1514 byte) size\r
+  sub-slice receiving the packet data,\r
+\r
+- a link from the first (head) descriptor in the chain is established to the\r
+  second (tail) descriptor in the chain.\r
+\r
+Finally, the guest populates the Available Ring with the indices of the head\r
+descriptors. All descriptor indices on both the Available Ring and the Used\r
+Ring are even.\r
+\r
+Packet reception occurs as follows:\r
+\r
+- The host consumes a descriptor index off the Available Ring. This index is\r
+  even (=2*N), and fingers the head descriptor of the chain belonging to packet\r
+  N.\r
+\r
+- The host reads the descriptors D(2*N) and -- following the Next link there\r
+  --- D(2*N+1), and stores the virtio-net request header at A(2*N), and the\r
+  packet data at A(2*N+1).\r
+\r
+- The host places the index of the head descriptor, 2*N, onto the Used Ring,\r
+  and sets the Len field in the same Used Ring Element to the total number of\r
+  bytes transferred for the entire descriptor chain. This enables the guest to\r
+  identify the length of Rx packets.\r
+\r
+- VirtioNetReceive polls the Used Ring. If a new Used Ring Element shows up, it\r
+  copies the data out to the caller, and recycles the index of the head\r
+  descriptor (ie. 2*N) to the Available Ring.\r
+\r
+- Because the host can process (answer) Rx requests in any order theoretically,\r
+  the order of head descriptor indices on each of the Available Ring and the\r
+  Used Ring is virtually random. (Except right after the initial population in\r
+  VirtioNetInitRx, when the Available Ring is full and increasing, and the Used\r
+  Ring is empty.)\r
+\r
+- If the Available Ring is empty, the host is forced to drop packets. If the\r
+  Used Ring is empty, VirtioNetReceive returns EFI_NOT_READY (no packet\r
+  available).\r
+\r
+\r
+Virtio internals -- Tx\r
+----------------------\r
+\r
+The transmission structure erected by VirtioNetInitTx is similar, it differs\r
+in the following:\r
+\r
+- There is no Receive Destination Area.\r
+\r
+- Each head descriptor, D(2*N), points to a read-only virtio-net request header\r
+  that is shared by all of the head descriptors. This virtio-net request header\r
+  is never modified by the host.\r
+\r
+- Each tail descriptor is re-pointed to the caller-supplied packet buffer\r
+  whenever VirtioNetTransmit places the corresponding head descriptor on the\r
+  Available Ring. The caller is responsible to hang on to the unmodified buffer\r
+  until it is reported transmitted by VirtioNetGetStatus.\r
+\r
+Steps of packet transmission:\r
+\r
+- Client code calls VirtioNetTransmit. VirtioNetTransmit tracks free descriptor\r
+  chains by keeping the indices of their head descriptors in a stack that is\r
+  private to the driver instance. All elements of the stack are even.\r
+\r
+- If the stack is empty (that is, each descriptor chain, in isolation, is\r
+  either pending transmission, or has been processed by the host but not\r
+  yet recycled by a VirtioNetGetStatus call), then VirtioNetTransmit returns\r
+  EFI_NOT_READY.\r
+\r
+- Otherwise the index of a free chain's head descriptor is popped from the\r
+  stack. The linked tail descriptor is re-pointed as discussed above. The head\r
+  descriptor's index is pushed on the Available Ring.\r
+\r
+- The host moves the head descriptor index from the Available Ring to the Used\r
+  Ring when it transmits the packet.\r
+\r
+- Client code calls VirtioNetGetStatus. In case the Used Ring is empty, the\r
+  function reports no Tx completion. Otherwise, a head descriptor's index is\r
+  consumed from the Used Ring and recycled to the private stack. The client\r
+  code's original packet buffer address is fetched from the tail descriptor\r
+  (where it has been stored at VirtioNetTransmit time) and returned to the\r
+  caller.\r
+\r
+- The Len field of the Used Ring Element is not checked. The host is assumed to\r
+  have transmitted the entire packet -- VirtioNetTransmit had forced it below\r
+  1514 bytes (inclusive). The Virtio specification suggests this packet size is\r
+  always accepted (and a lower MTU could be encountered on any later hop as\r
+  well). Additionally, there's no good way to report a short transmit via\r
+  VirtioNetGetStatus; EFI_DEVICE_ERROR seems too serious from the specification\r
+  and higher level protocols could interpret it as a fatal condition.\r
+\r
+- The host can theoretically reorder head descriptor indices when moving them\r
+  from the Available Ring to the Used Ring (out of order transmission). Because\r
+  of this (and the choice of a stack over a list for free descriptor chain\r
+  tracking) the order of head descriptor indices on either Ring is\r
+  unpredictable.\r