]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Provide DMA operations for EFI PCI devices
authorMichael Brown <mcb30@ipxe.org>
Wed, 4 Nov 2020 15:23:14 +0000 (15:23 +0000)
committerMichael Brown <mcb30@ipxe.org>
Thu, 5 Nov 2020 20:18:27 +0000 (20:18 +0000)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/dma.h
src/include/ipxe/pci.h
src/interface/efi/efi_pci.c

index d3db061f7babc3ef2e3998dcf28b1e781f9f87a4..878e03f117ba03f7ff98131cba79ec702a17008e 100644 (file)
@@ -32,6 +32,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 struct dma_mapping {
        /** Device-side address */
        physaddr_t addr;
+       /** Platform mapping token */
+       void *token;
 };
 
 /** A DMA-capable device */
index 272c4c06f433059ffd598c2b534114a252cfc196..6632c574d4a2a4405fb5391a701d8ed1f42df761 100644 (file)
@@ -12,6 +12,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #include <stdint.h>
 #include <ipxe/device.h>
 #include <ipxe/tables.h>
+#include <ipxe/dma.h>
 #include <ipxe/pci_io.h>
 
 /** PCI vendor ID */
@@ -187,6 +188,8 @@ struct pci_class_id {
 struct pci_device {
        /** Generic device */
        struct device dev;
+       /** DMA device */
+       struct dma_device dma;
        /** Memory base
         *
         * This is the physical address of the first valid memory BAR.
index 27ab61733f282065e6b9bc33c455375b931471ca..6c6ac5c77896b346b7c49fc0e54ca677aab9f7ab 100644 (file)
@@ -304,6 +304,240 @@ PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_word );
 PROVIDE_PCIAPI_INLINE ( efi, pci_write_config_dword );
 PROVIDE_PCIAPI ( efi, pci_ioremap, efipci_ioremap );
 
+/******************************************************************************
+ *
+ * EFI PCI DMA mappings
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Map buffer for DMA
+ *
+ * @v dma              DMA device
+ * @v addr             Buffer address
+ * @v len              Length of buffer
+ * @v flags            Mapping flags
+ * @v map              DMA mapping to fill in
+ * @ret rc             Return status code
+ */
+static int efipci_dma_map ( struct dma_device *dma, physaddr_t addr, size_t len,
+                           int flags, struct dma_mapping *map ) {
+       struct efi_pci_device *efipci =
+               container_of ( dma, struct efi_pci_device, pci.dma );
+       struct pci_device *pci = &efipci->pci;
+       EFI_PCI_IO_PROTOCOL *pci_io = efipci->io;
+       EFI_PCI_IO_PROTOCOL_OPERATION op;
+       EFI_PHYSICAL_ADDRESS bus;
+       UINTN count;
+       VOID *mapping;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Sanity check */
+       assert ( map->addr == 0 );
+       assert ( map->token == NULL );
+
+       /* Determine operation */
+       switch ( flags ) {
+       case DMA_TX:
+               op = EfiPciIoOperationBusMasterRead;
+               break;
+       case DMA_RX:
+               op = EfiPciIoOperationBusMasterWrite;
+               break;
+       default:
+               op = EfiPciIoOperationBusMasterCommonBuffer;
+               break;
+       }
+
+       /* Map buffer */
+       count = len;
+       if ( ( efirc = pci_io->Map ( pci_io, op, phys_to_virt ( addr ), &count,
+                                    &bus, &mapping ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( pci, "EFIPCI " PCI_FMT " cannot map %08lx+%zx: %s\n",
+                      PCI_ARGS ( pci ), addr, len, strerror ( rc ) );
+               goto err_map;
+       }
+
+       /* Check that full length was mapped.  The UEFI specification
+        * allows for multiple mappings to be required, but even the
+        * EDK2 PCI device drivers will fail if a platform ever
+        * requires this.
+        */
+       if ( count != len ) {
+               DBGC ( pci, "EFIPCI " PCI_FMT " attempted split mapping for "
+                      "%08lx+%zx\n", PCI_ARGS ( pci ), addr, len );
+               rc = -ENOTSUP;
+               goto err_len;
+       }
+
+       /* Populate mapping */
+       map->addr = bus;
+       map->token = mapping;
+
+       /* Increment mapping count (for debugging) */
+       if ( DBG_LOG )
+               dma->mapped++;
+
+       return 0;
+
+ err_len:
+       pci_io->Unmap ( pci_io, mapping );
+ err_map:
+       return rc;
+}
+
+/**
+ * Unmap buffer
+ *
+ * @v dma              DMA device
+ * @v map              DMA mapping
+ */
+static void efipci_dma_unmap ( struct dma_device *dma,
+                              struct dma_mapping *map ) {
+       struct efi_pci_device *efipci =
+               container_of ( dma, struct efi_pci_device, pci.dma );
+       EFI_PCI_IO_PROTOCOL *pci_io = efipci->io;
+
+       /* Sanity check */
+       assert ( map->token != NULL );
+
+       /* Unmap buffer */
+       pci_io->Unmap ( pci_io, map->token );
+
+       /* Clear mapping */
+       map->addr = 0;
+       map->token = NULL;
+
+       /* Decrement mapping count (for debugging) */
+       if ( DBG_LOG )
+               dma->mapped--;
+}
+
+/**
+ * Allocate and map DMA-coherent buffer
+ *
+ * @v dma              DMA device
+ * @v len              Length of buffer
+ * @v align            Physical alignment
+ * @v map              DMA mapping to fill in
+ * @ret addr           Buffer address, or NULL on error
+ */
+static void * efipci_dma_alloc ( struct dma_device *dma, size_t len,
+                                size_t align __unused,
+                                struct dma_mapping *map ) {
+       struct efi_pci_device *efipci =
+               container_of ( dma, struct efi_pci_device, pci.dma );
+       struct pci_device *pci = &efipci->pci;
+       EFI_PCI_IO_PROTOCOL *pci_io = efipci->io;
+       unsigned int pages;
+       VOID *addr;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Calculate number of pages */
+       pages = ( ( len + EFI_PAGE_SIZE - 1 ) / EFI_PAGE_SIZE );
+
+       /* Allocate (page-aligned) buffer */
+       if ( ( efirc = pci_io->AllocateBuffer ( pci_io, AllocateAnyPages,
+                                               EfiBootServicesData, pages,
+                                               &addr, 0 ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( pci, "EFIPCI " PCI_FMT " could not allocate %zd bytes: "
+                      "%s\n", PCI_ARGS ( pci ), len, strerror ( rc ) );
+               goto err_alloc;
+       }
+
+       /* Map buffer */
+       if ( ( rc = efipci_dma_map ( dma, virt_to_phys ( addr ), len, DMA_BI,
+                                    map ) ) != 0 )
+               goto err_map;
+
+       /* Increment allocation count (for debugging) */
+       if ( DBG_LOG )
+               dma->allocated++;
+
+       return addr;
+
+       efipci_dma_unmap ( dma, map );
+ err_map:
+       pci_io->FreeBuffer ( pci_io, pages, addr );
+ err_alloc:
+       return NULL;
+}
+
+/**
+ * Unmap and free DMA-coherent buffer
+ *
+ * @v dma              DMA device
+ * @v addr             Buffer address
+ * @v len              Length of buffer
+ * @v map              DMA mapping
+ */
+static void efipci_dma_free ( struct dma_device *dma, void *addr, size_t len,
+                             struct dma_mapping *map ) {
+       struct efi_pci_device *efipci =
+               container_of ( dma, struct efi_pci_device, pci.dma );
+       EFI_PCI_IO_PROTOCOL *pci_io = efipci->io;
+       unsigned int pages;
+
+       /* Calculate number of pages */
+       pages = ( ( len + EFI_PAGE_SIZE - 1 ) / EFI_PAGE_SIZE );
+
+       /* Unmap buffer */
+       efipci_dma_unmap ( dma, map );
+
+       /* Free buffer */
+       pci_io->FreeBuffer ( pci_io, pages, addr );
+
+       /* Decrement allocation count (for debugging) */
+       if ( DBG_LOG )
+               dma->allocated--;
+}
+
+/**
+ * Set addressable space mask
+ *
+ * @v dma              DMA device
+ * @v mask             Addressable space mask
+ */
+static void efipci_dma_set_mask ( struct dma_device *dma, physaddr_t mask ) {
+       struct efi_pci_device *efipci =
+               container_of ( dma, struct efi_pci_device, pci.dma );
+       struct pci_device *pci = &efipci->pci;
+       EFI_PCI_IO_PROTOCOL *pci_io = efipci->io;
+       EFI_PCI_IO_PROTOCOL_ATTRIBUTE_OPERATION op;
+       UINT64 attrs;
+       int is64;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Set dual address cycle attribute for 64-bit capable devices */
+       is64 = ( ( ( ( uint64_t ) mask ) + 1 ) == 0 );
+       op = ( is64 ? EfiPciIoAttributeOperationEnable :
+              EfiPciIoAttributeOperationDisable );
+       attrs = EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE;
+       if ( ( efirc = pci_io->Attributes ( pci_io, op, attrs, NULL ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( pci, "EFIPCI " PCI_FMT " could not %sable DAC: %s\n",
+                      PCI_ARGS ( pci ), ( is64 ? "en" : "dis" ),
+                      strerror ( rc ) );
+               /* Ignore failure: errors will manifest in mapping attempts */
+               return;
+       }
+}
+
+/** EFI PCI DMA operations */
+static struct dma_operations efipci_dma_operations = {
+       .map = efipci_dma_map,
+       .unmap = efipci_dma_unmap,
+       .alloc = efipci_dma_alloc,
+       .free = efipci_dma_free,
+       .set_mask = efipci_dma_set_mask,
+};
+
 /******************************************************************************
  *
  * EFI PCI device instantiation
@@ -353,6 +587,7 @@ int efipci_open ( EFI_HANDLE device, UINT32 attributes,
        }
        busdevfn = PCI_BUSDEVFN ( pci_segment, pci_bus, pci_dev, pci_fn );
        pci_init ( &efipci->pci, busdevfn );
+       dma_init ( &efipci->pci.dma, &efipci_dma_operations );
        DBGCP ( device, "EFIPCI " PCI_FMT " is %s\n",
                PCI_ARGS ( &efipci->pci ), efi_handle_name ( device ) );
 
@@ -533,6 +768,8 @@ static void efipci_stop ( struct efi_device *efidev ) {
 
        pci_remove ( &efipci->pci );
        list_del ( &efipci->pci.dev.siblings );
+       assert ( efipci->pci.dma.mapped == 0 );
+       assert ( efipci->pci.dma.allocated == 0 );
        efipci_close ( device );
        free ( efipci );
 }