]> git.proxmox.com Git - mirror_edk2.git/commitdiff
OvmfPkg/VirtioFsDxe: add a scatter-gather list data type
authorLaszlo Ersek <lersek@redhat.com>
Wed, 16 Dec 2020 21:10:42 +0000 (22:10 +0100)
committermergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Mon, 21 Dec 2020 17:16:23 +0000 (17:16 +0000)
In preparation for the variously structured FUSE request/response
exchanges that virtio-fs uses, introduce a scatter-gather list data type.
This will let us express FUSE request-response pairs flexibly.

Add a function for validating whether a (request buffer list, response
buffer list) pair is well-formed, and supported by the Virtio Filesystem
device's queue depth.

Add another function for mapping and submitting a validated pair of
scatter-gather lists to the Virtio Filesystem device.

Cc: Ard Biesheuvel <ard.biesheuvel@arm.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=3097
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20201216211125.19496-6-lersek@redhat.com>
Acked-by: Ard Biesheuvel <ard.biesheuvel@arm.com>
[lersek@redhat.com: suppress useless VS2019 warning about signed/unsigned
 comparison in VirtioFsSgListsValidate()]

OvmfPkg/VirtioFsDxe/Helpers.c
OvmfPkg/VirtioFsDxe/VirtioFsDxe.h

index 7b4906c541845dde7c260285336db59067c5df34..c493f6068f7ecf7f06a3cd67de4124685fb53c36 100644 (file)
@@ -297,3 +297,404 @@ VirtioFsExitBoot (
     VirtioFsAsVoid, VirtioFs->Label));\r
   VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, 0);\r
 }\r
+\r
+/**\r
+  Validate two VIRTIO_FS_SCATTER_GATHER_LIST objects -- list of request\r
+  buffers, list of response buffers -- together.\r
+\r
+  On input, the caller is required to populate the following fields:\r
+  - VIRTIO_FS_IO_VECTOR.Buffer,\r
+  - VIRTIO_FS_IO_VECTOR.Size,\r
+  - VIRTIO_FS_SCATTER_GATHER_LIST.IoVec,\r
+  - VIRTIO_FS_SCATTER_GATHER_LIST.NumVec.\r
+\r
+  On output (on successful return), the following fields will be\r
+  zero-initialized:\r
+  - VIRTIO_FS_IO_VECTOR.Mapped,\r
+  - VIRTIO_FS_IO_VECTOR.MappedAddress,\r
+  - VIRTIO_FS_IO_VECTOR.Mapping,\r
+  - VIRTIO_FS_IO_VECTOR.Transferred.\r
+\r
+  On output (on successful return), the following fields will be calculated:\r
+  - VIRTIO_FS_SCATTER_GATHER_LIST.TotalSize.\r
+\r
+  The function may only be called after VirtioFsInit() returns successfully and\r
+  before VirtioFsUninit() is called.\r
+\r
+  @param[in] VirtioFs            The Virtio Filesystem device that the\r
+                                 request-response exchange, expressed via\r
+                                 RequestSgList and ResponseSgList, will be\r
+                                 submitted to.\r
+\r
+  @param[in,out] RequestSgList   The scatter-gather list that describes the\r
+                                 request part of the exchange -- the buffers\r
+                                 that should be sent to the Virtio Filesystem\r
+                                 device in the virtio transfer.\r
+\r
+  @param[in,out] ResponseSgList  The scatter-gather list that describes the\r
+                                 response part of the exchange -- the buffers\r
+                                 that the Virtio Filesystem device should\r
+                                 populate in the virtio transfer. May be NULL\r
+                                 if the exchange with the Virtio Filesystem\r
+                                 device consists of a request only, with the\r
+                                 response part omitted altogether.\r
+\r
+  @retval EFI_SUCCESS            RequestSgList and ResponseSgList have been\r
+                                 validated, output fields have been set.\r
+\r
+  @retval EFI_INVALID_PARAMETER  RequestSgList is NULL.\r
+\r
+  @retval EFI_INVALID_PARAMETER  On input, a\r
+                                 VIRTIO_FS_SCATTER_GATHER_LIST.IoVec field is\r
+                                 NULL, or a\r
+                                 VIRTIO_FS_SCATTER_GATHER_LIST.NumVec field is\r
+                                 zero.\r
+\r
+  @retval EFI_INVALID_PARAMETER  On input, a VIRTIO_FS_IO_VECTOR.Buffer field\r
+                                 is NULL, or a VIRTIO_FS_IO_VECTOR.Size field\r
+                                 is zero.\r
+\r
+  @retval EFI_UNSUPPORTED        (RequestSgList->NumVec +\r
+                                 ResponseSgList->NumVec) exceeds\r
+                                 VirtioFs->QueueSize, meaning that the total\r
+                                 list of buffers cannot be placed on the virtio\r
+                                 queue in a single descriptor chain (with one\r
+                                 descriptor per buffer).\r
+\r
+  @retval EFI_UNSUPPORTED        One of the\r
+                                 VIRTIO_FS_SCATTER_GATHER_LIST.TotalSize fields\r
+                                 would exceed MAX_UINT32.\r
+**/\r
+EFI_STATUS\r
+VirtioFsSgListsValidate (\r
+  IN     VIRTIO_FS                     *VirtioFs,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL\r
+  )\r
+{\r
+  VIRTIO_FS_SCATTER_GATHER_LIST *SgListParam[2];\r
+  UINT16                        DescriptorsNeeded;\r
+  UINTN                         ListId;\r
+\r
+  if (RequestSgList == NULL) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  SgListParam[0] = RequestSgList;\r
+  SgListParam[1] = ResponseSgList;\r
+\r
+  DescriptorsNeeded = 0;\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    VIRTIO_FS_SCATTER_GATHER_LIST *SgList;\r
+    UINT32                        SgListTotalSize;\r
+    UINTN                         IoVecIdx;\r
+\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    //\r
+    // Sanity-check SgList -- it must provide at least one IO Vector.\r
+    //\r
+    if (SgList->IoVec == NULL || SgList->NumVec == 0) {\r
+      return EFI_INVALID_PARAMETER;\r
+    }\r
+    //\r
+    // Make sure that, for each IO Vector in this SgList, a virtio descriptor\r
+    // can be added to the virtio queue, after the other descriptors added\r
+    // previously.\r
+    //\r
+    if (SgList->NumVec > (UINTN)(MAX_UINT16 - DescriptorsNeeded) ||\r
+        DescriptorsNeeded + SgList->NumVec > VirtioFs->QueueSize) {\r
+      return EFI_UNSUPPORTED;\r
+    }\r
+    DescriptorsNeeded += (UINT16)SgList->NumVec;\r
+\r
+    SgListTotalSize = 0;\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      VIRTIO_FS_IO_VECTOR *IoVec;\r
+\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Sanity-check this IoVec -- it must describe a non-empty buffer.\r
+      //\r
+      if (IoVec->Buffer == NULL || IoVec->Size == 0) {\r
+        return EFI_INVALID_PARAMETER;\r
+      }\r
+      //\r
+      // Make sure the cumulative size of all IO Vectors in this SgList remains\r
+      // expressible as a UINT32.\r
+      //\r
+      if (IoVec->Size > MAX_UINT32 - SgListTotalSize) {\r
+        return EFI_UNSUPPORTED;\r
+      }\r
+      SgListTotalSize += (UINT32)IoVec->Size;\r
+\r
+      //\r
+      // Initialize those fields in this IO Vector that will be updated in\r
+      // relation to mapping / transfer.\r
+      //\r
+      IoVec->Mapped        = FALSE;\r
+      IoVec->MappedAddress = 0;\r
+      IoVec->Mapping       = NULL;\r
+      IoVec->Transferred   = 0;\r
+    }\r
+\r
+    //\r
+    // Store the cumulative size of all IO Vectors that we have calculated in\r
+    // this SgList.\r
+    //\r
+    SgList->TotalSize = SgListTotalSize;\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Submit a validated pair of (request buffer list, response buffer list) to the\r
+  Virtio Filesystem device.\r
+\r
+  On input, the pair of VIRTIO_FS_SCATTER_GATHER_LIST objects must have been\r
+  validated together, using the VirtioFsSgListsValidate() function.\r
+\r
+  On output (on successful return), the following fields will be re-initialized\r
+  to zero (after temporarily setting them to different values):\r
+  - VIRTIO_FS_IO_VECTOR.Mapped,\r
+  - VIRTIO_FS_IO_VECTOR.MappedAddress,\r
+  - VIRTIO_FS_IO_VECTOR.Mapping.\r
+\r
+  On output (on successful return), the following fields will be calculated:\r
+  - VIRTIO_FS_IO_VECTOR.Transferred.\r
+\r
+  The function may only be called after VirtioFsInit() returns successfully and\r
+  before VirtioFsUninit() is called.\r
+\r
+  @param[in,out] VirtioFs        The Virtio Filesystem device that the\r
+                                 request-response exchange, expressed via\r
+                                 RequestSgList and ResponseSgList, should now\r
+                                 be submitted to.\r
+\r
+  @param[in,out] RequestSgList   The scatter-gather list that describes the\r
+                                 request part of the exchange -- the buffers\r
+                                 that should be sent to the Virtio Filesystem\r
+                                 device in the virtio transfer.\r
+\r
+  @param[in,out] ResponseSgList  The scatter-gather list that describes the\r
+                                 response part of the exchange -- the buffers\r
+                                 that the Virtio Filesystem device should\r
+                                 populate in the virtio transfer. May be NULL\r
+                                 if and only if NULL was passed to\r
+                                 VirtioFsSgListsValidate() as ResponseSgList.\r
+\r
+  @retval EFI_SUCCESS       Transfer complete. The caller should investigate\r
+                            the VIRTIO_FS_IO_VECTOR.Transferred fields in\r
+                            ResponseSgList, to ensure coverage of the relevant\r
+                            response buffers. Subsequently, the caller should\r
+                            investigate the contents of those buffers.\r
+\r
+  @retval EFI_DEVICE_ERROR  The Virtio Filesystem device reported populating\r
+                            more response bytes than ResponseSgList->TotalSize.\r
+\r
+  @return                   Error codes propagated from\r
+                            VirtioMapAllBytesInSharedBuffer(), VirtioFlush(),\r
+                            or VirtioFs->Virtio->UnmapSharedBuffer().\r
+**/\r
+EFI_STATUS\r
+VirtioFsSgListsSubmit (\r
+  IN OUT VIRTIO_FS                     *VirtioFs,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL\r
+  )\r
+{\r
+  VIRTIO_FS_SCATTER_GATHER_LIST *SgListParam[2];\r
+  VIRTIO_MAP_OPERATION          SgListVirtioMapOp[ARRAY_SIZE (SgListParam)];\r
+  UINT16                        SgListDescriptorFlag[ARRAY_SIZE (SgListParam)];\r
+  UINTN                         ListId;\r
+  VIRTIO_FS_SCATTER_GATHER_LIST *SgList;\r
+  UINTN                         IoVecIdx;\r
+  VIRTIO_FS_IO_VECTOR           *IoVec;\r
+  EFI_STATUS                    Status;\r
+  DESC_INDICES                  Indices;\r
+  UINT32                        TotalBytesWrittenByDevice;\r
+  UINT32                        BytesPermittedForWrite;\r
+\r
+  SgListParam[0]          = RequestSgList;\r
+  SgListVirtioMapOp[0]    = VirtioOperationBusMasterRead;\r
+  SgListDescriptorFlag[0] = 0;\r
+\r
+  SgListParam[1]          = ResponseSgList;\r
+  SgListVirtioMapOp[1]    = VirtioOperationBusMasterWrite;\r
+  SgListDescriptorFlag[1] = VRING_DESC_F_WRITE;\r
+\r
+  //\r
+  // Map all IO Vectors.\r
+  //\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Map this IO Vector.\r
+      //\r
+      Status = VirtioMapAllBytesInSharedBuffer (\r
+                 VirtioFs->Virtio,\r
+                 SgListVirtioMapOp[ListId],\r
+                 IoVec->Buffer,\r
+                 IoVec->Size,\r
+                 &IoVec->MappedAddress,\r
+                 &IoVec->Mapping\r
+                 );\r
+      if (EFI_ERROR (Status)) {\r
+        goto Unmap;\r
+      }\r
+      IoVec->Mapped = TRUE;\r
+    }\r
+  }\r
+\r
+  //\r
+  // Compose the descriptor chain.\r
+  //\r
+  VirtioPrepare (&VirtioFs->Ring, &Indices);\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      UINT16 NextFlag;\r
+\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Set VRING_DESC_F_NEXT on all except the very last descriptor.\r
+      //\r
+      NextFlag = VRING_DESC_F_NEXT;\r
+      if (ListId == ARRAY_SIZE (SgListParam) - 1 &&\r
+          IoVecIdx == SgList->NumVec - 1) {\r
+        NextFlag = 0;\r
+      }\r
+      VirtioAppendDesc (\r
+        &VirtioFs->Ring,\r
+        IoVec->MappedAddress,\r
+        (UINT32)IoVec->Size,\r
+        SgListDescriptorFlag[ListId] | NextFlag,\r
+        &Indices\r
+        );\r
+    }\r
+  }\r
+\r
+  //\r
+  // Submit the descriptor chain.\r
+  //\r
+  Status = VirtioFlush (VirtioFs->Virtio, VIRTIO_FS_REQUEST_QUEUE,\r
+             &VirtioFs->Ring, &Indices, &TotalBytesWrittenByDevice);\r
+  if (EFI_ERROR (Status)) {\r
+    goto Unmap;\r
+  }\r
+\r
+  //\r
+  // Sanity-check: the Virtio Filesystem device should not have written more\r
+  // bytes than what we offered buffers for.\r
+  //\r
+  if (ResponseSgList == NULL) {\r
+    BytesPermittedForWrite = 0;\r
+  } else {\r
+    BytesPermittedForWrite = ResponseSgList->TotalSize;\r
+  }\r
+  if (TotalBytesWrittenByDevice > BytesPermittedForWrite) {\r
+    Status = EFI_DEVICE_ERROR;\r
+    goto Unmap;\r
+  }\r
+\r
+  //\r
+  // Update the transfer sizes in the IO Vectors.\r
+  //\r
+  for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) {\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) {\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      if (SgListVirtioMapOp[ListId] == VirtioOperationBusMasterRead) {\r
+        //\r
+        // We report that the Virtio Filesystem device has read all buffers in\r
+        // the request.\r
+        //\r
+        IoVec->Transferred = IoVec->Size;\r
+      } else {\r
+        //\r
+        // Regarding the response, calculate how much of the current IO Vector\r
+        // has been populated by the Virtio Filesystem device. In\r
+        // "TotalBytesWrittenByDevice", VirtioFlush() reported the total count\r
+        // across all device-writeable descriptors, in the order they were\r
+        // chained on the ring.\r
+        //\r
+        IoVec->Transferred = MIN ((UINTN)TotalBytesWrittenByDevice,\r
+                               IoVec->Size);\r
+        TotalBytesWrittenByDevice -= (UINT32)IoVec->Transferred;\r
+      }\r
+    }\r
+  }\r
+\r
+  //\r
+  // By now, "TotalBytesWrittenByDevice" has been exhausted.\r
+  //\r
+  ASSERT (TotalBytesWrittenByDevice == 0);\r
+\r
+  //\r
+  // We've succeeded; fall through.\r
+  //\r
+Unmap:\r
+  //\r
+  // Unmap all mapped IO Vectors on both the success and the error paths. The\r
+  // unmapping occurs in reverse order of mapping, in an attempt to avoid\r
+  // memory fragmentation.\r
+  //\r
+  ListId = ARRAY_SIZE (SgListParam);\r
+  while (ListId > 0) {\r
+    --ListId;\r
+    SgList = SgListParam[ListId];\r
+    if (SgList == NULL) {\r
+      continue;\r
+    }\r
+    IoVecIdx = SgList->NumVec;\r
+    while (IoVecIdx > 0) {\r
+      EFI_STATUS UnmapStatus;\r
+\r
+      --IoVecIdx;\r
+      IoVec = &SgList->IoVec[IoVecIdx];\r
+      //\r
+      // Unmap this IO Vector, if it has been mapped.\r
+      //\r
+      if (!IoVec->Mapped) {\r
+        continue;\r
+      }\r
+      UnmapStatus = VirtioFs->Virtio->UnmapSharedBuffer (VirtioFs->Virtio,\r
+                                        IoVec->Mapping);\r
+      //\r
+      // Re-set the following fields to the values they initially got from\r
+      // VirtioFsSgListsValidate() -- the above unmapping attempt is considered\r
+      // final, even if it fails.\r
+      //\r
+      IoVec->Mapped        = FALSE;\r
+      IoVec->MappedAddress = 0;\r
+      IoVec->Mapping       = NULL;\r
+\r
+      //\r
+      // If we are on the success path, but the unmapping failed, we need to\r
+      // transparently flip to the failure path -- the caller must learn they\r
+      // should not consult the response buffers.\r
+      //\r
+      // The branch below can be taken at most once.\r
+      //\r
+      if (!EFI_ERROR (Status) && EFI_ERROR (UnmapStatus)) {\r
+        Status = UnmapStatus;\r
+      }\r
+    }\r
+  }\r
+\r
+  return Status;\r
+}\r
index 2aae96ecd79ac3d0384d3b55e66efcb743fe4889..12acbd6dc35900a8b5d253e3dbf55de01f0c878d 100644 (file)
@@ -51,6 +51,52 @@ typedef struct {
 #define VIRTIO_FS_FROM_SIMPLE_FS(SimpleFsReference) \\r
   CR (SimpleFsReference, VIRTIO_FS, SimpleFs, VIRTIO_FS_SIG);\r
 \r
+//\r
+// Structure for describing a contiguous buffer, potentially mapped for Virtio\r
+// transfer.\r
+//\r
+typedef struct {\r
+  //\r
+  // The following fields originate from the owner of the buffer.\r
+  //\r
+  VOID  *Buffer;\r
+  UINTN Size;\r
+  //\r
+  // All of the fields below, until the end of the structure, are\r
+  // zero-initialized when the structure is initially validated.\r
+  //\r
+  // Mapped, MappedAddress and Mapping are updated when the buffer is mapped\r
+  // for VirtioOperationBusMasterRead or VirtioOperationBusMasterWrite. They\r
+  // are again updated when the buffer is unmapped.\r
+  //\r
+  BOOLEAN              Mapped;\r
+  EFI_PHYSICAL_ADDRESS MappedAddress;\r
+  VOID                 *Mapping;\r
+  //\r
+  // Transferred is updated after VirtioFlush() returns successfully:\r
+  // - for VirtioOperationBusMasterRead, Transferred is set to Size;\r
+  // - for VirtioOperationBusMasterWrite, Transferred is calculated from the\r
+  //   UsedLen output parameter of VirtioFlush().\r
+  //\r
+  UINTN Transferred;\r
+} VIRTIO_FS_IO_VECTOR;\r
+\r
+//\r
+// Structure for describing a list of IO Vectors.\r
+//\r
+typedef struct {\r
+  //\r
+  // The following fields originate from the owner of the buffers.\r
+  //\r
+  VIRTIO_FS_IO_VECTOR *IoVec;\r
+  UINTN               NumVec;\r
+  //\r
+  // TotalSize is calculated when the scatter-gather list is initially\r
+  // validated.\r
+  //\r
+  UINT32 TotalSize;\r
+} VIRTIO_FS_SCATTER_GATHER_LIST;\r
+\r
 //\r
 // Initialization and helper routines for the Virtio Filesystem device.\r
 //\r
@@ -72,6 +118,20 @@ VirtioFsExitBoot (
   IN VOID      *VirtioFsAsVoid\r
   );\r
 \r
+EFI_STATUS\r
+VirtioFsSgListsValidate (\r
+  IN     VIRTIO_FS                     *VirtioFs,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL\r
+  );\r
+\r
+EFI_STATUS\r
+VirtioFsSgListsSubmit (\r
+  IN OUT VIRTIO_FS                     *VirtioFs,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList,\r
+  IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL\r
+  );\r
+\r
 //\r
 // EFI_SIMPLE_FILE_SYSTEM_PROTOCOL member functions for the Virtio Filesystem\r
 // driver.\r