Commit 52916982 authored by Logan Gunthorpe's avatar Logan Gunthorpe Committed by Bjorn Helgaas

PCI/P2PDMA: Support peer-to-peer memory

Some PCI devices may have memory mapped in a BAR space that's intended for
use in peer-to-peer transactions.  To enable such transactions the memory
must be registered with ZONE_DEVICE pages so it can be used by DMA
interfaces in existing drivers.

Add an interface for other subsystems to find and allocate chunks of P2P
memory as necessary to facilitate transfers between two PCI peers:

  struct pci_dev *pci_p2pmem_find[_many]();
  int pci_p2pdma_distance[_many]();
  void *pci_alloc_p2pmem();

The new interface requires a driver to collect a list of client devices
involved in the transaction then call pci_p2pmem_find() to obtain any
suitable P2P memory.  Alternatively, if the caller knows a device which
provides P2P memory, they can use pci_p2pdma_distance() to determine if it
is usable.  With a suitable p2pmem device, memory can then be allocated
with pci_alloc_p2pmem() for use in DMA transactions.

Depending on hardware, using peer-to-peer memory may reduce the bandwidth
of the transfer but can significantly reduce pressure on system memory.
This may be desirable in many cases: for example a system could be designed
with a small CPU connected to a PCIe switch by a small number of lanes
which would maximize the number of lanes available to connect to NVMe
devices.

The code is designed to only utilize the p2pmem device if all the devices
involved in a transfer are behind the same PCI bridge.  This is because we
have no way of knowing whether peer-to-peer routing between PCIe Root Ports
is supported (PCIe r4.0, sec 1.3.1).  Additionally, the benefits of P2P
transfers that go through the RC is limited to only reducing DRAM usage
and, in some cases, coding convenience.  The PCI-SIG may be exploring
adding a new capability bit to advertise whether this is possible for
future hardware.

This commit includes significant rework and feedback from Christoph
Hellwig.
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
Signed-off-by: default avatarLogan Gunthorpe <logang@deltatee.com>
[bhelgaas: fold in fix from Keith Busch <keith.busch@intel.com>:
https://lore.kernel.org/linux-pci/20181012155920.15418-1-keith.busch@intel.com,
to address comment from Dan Carpenter <dan.carpenter@oracle.com>, fold in
https://lore.kernel.org/linux-pci/20181017160510.17926-1-logang@deltatee.com]
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
parent 7876320f
...@@ -132,6 +132,23 @@ config PCI_PASID ...@@ -132,6 +132,23 @@ config PCI_PASID
If unsure, say N. If unsure, say N.
config PCI_P2PDMA
bool "PCI peer-to-peer transfer support"
depends on PCI && ZONE_DEVICE
select GENERIC_ALLOCATOR
help
Enableѕ drivers to do PCI peer-to-peer transactions to and from
BARs that are exposed in other devices that are the part of
the hierarchy where peer-to-peer DMA is guaranteed by the PCI
specification to work (ie. anything below a single PCI bridge).
Many PCIe root complexes do not support P2P transactions and
it's hard to tell which support it at all, so at this time,
P2P DMA transations must be between devices behind the same root
port.
If unsure, say N.
config PCI_LABEL config PCI_LABEL
def_bool y if (DMI || ACPI) def_bool y if (DMI || ACPI)
depends on PCI depends on PCI
......
...@@ -26,6 +26,7 @@ obj-$(CONFIG_PCI_SYSCALL) += syscall.o ...@@ -26,6 +26,7 @@ obj-$(CONFIG_PCI_SYSCALL) += syscall.o
obj-$(CONFIG_PCI_STUB) += pci-stub.o obj-$(CONFIG_PCI_STUB) += pci-stub.o
obj-$(CONFIG_PCI_PF_STUB) += pci-pf-stub.o obj-$(CONFIG_PCI_PF_STUB) += pci-pf-stub.o
obj-$(CONFIG_PCI_ECAM) += ecam.o obj-$(CONFIG_PCI_ECAM) += ecam.o
obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
# Endpoint library must be initialized before its users # Endpoint library must be initialized before its users
......
This diff is collapsed.
...@@ -53,11 +53,16 @@ struct vmem_altmap { ...@@ -53,11 +53,16 @@ struct vmem_altmap {
* wakeup event whenever a page is unpinned and becomes idle. This * wakeup event whenever a page is unpinned and becomes idle. This
* wakeup is used to coordinate physical address space management (ex: * wakeup is used to coordinate physical address space management (ex:
* fs truncate/hole punch) vs pinned pages (ex: device dma). * fs truncate/hole punch) vs pinned pages (ex: device dma).
*
* MEMORY_DEVICE_PCI_P2PDMA:
* Device memory residing in a PCI BAR intended for use with Peer-to-Peer
* transactions.
*/ */
enum memory_type { enum memory_type {
MEMORY_DEVICE_PRIVATE = 1, MEMORY_DEVICE_PRIVATE = 1,
MEMORY_DEVICE_PUBLIC, MEMORY_DEVICE_PUBLIC,
MEMORY_DEVICE_FS_DAX, MEMORY_DEVICE_FS_DAX,
MEMORY_DEVICE_PCI_P2PDMA,
}; };
/* /*
......
...@@ -890,6 +890,19 @@ static inline bool is_device_public_page(const struct page *page) ...@@ -890,6 +890,19 @@ static inline bool is_device_public_page(const struct page *page)
page->pgmap->type == MEMORY_DEVICE_PUBLIC; page->pgmap->type == MEMORY_DEVICE_PUBLIC;
} }
#ifdef CONFIG_PCI_P2PDMA
static inline bool is_pci_p2pdma_page(const struct page *page)
{
return is_zone_device_page(page) &&
page->pgmap->type == MEMORY_DEVICE_PCI_P2PDMA;
}
#else /* CONFIG_PCI_P2PDMA */
static inline bool is_pci_p2pdma_page(const struct page *page)
{
return false;
}
#endif /* CONFIG_PCI_P2PDMA */
#else /* CONFIG_DEV_PAGEMAP_OPS */ #else /* CONFIG_DEV_PAGEMAP_OPS */
static inline void dev_pagemap_get_ops(void) static inline void dev_pagemap_get_ops(void)
{ {
...@@ -913,6 +926,11 @@ static inline bool is_device_public_page(const struct page *page) ...@@ -913,6 +926,11 @@ static inline bool is_device_public_page(const struct page *page)
{ {
return false; return false;
} }
static inline bool is_pci_p2pdma_page(const struct page *page)
{
return false;
}
#endif /* CONFIG_DEV_PAGEMAP_OPS */ #endif /* CONFIG_DEV_PAGEMAP_OPS */
static inline void get_page(struct page *page) static inline void get_page(struct page *page)
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* PCI Peer 2 Peer DMA support.
*
* Copyright (c) 2016-2018, Logan Gunthorpe
* Copyright (c) 2016-2017, Microsemi Corporation
* Copyright (c) 2017, Christoph Hellwig
* Copyright (c) 2018, Eideticom Inc.
*/
#ifndef _LINUX_PCI_P2PDMA_H
#define _LINUX_PCI_P2PDMA_H
#include <linux/pci.h>
struct block_device;
struct scatterlist;
#ifdef CONFIG_PCI_P2PDMA
int pci_p2pdma_add_resource(struct pci_dev *pdev, int bar, size_t size,
u64 offset);
int pci_p2pdma_distance_many(struct pci_dev *provider, struct device **clients,
int num_clients, bool verbose);
bool pci_has_p2pmem(struct pci_dev *pdev);
struct pci_dev *pci_p2pmem_find_many(struct device **clients, int num_clients);
void *pci_alloc_p2pmem(struct pci_dev *pdev, size_t size);
void pci_free_p2pmem(struct pci_dev *pdev, void *addr, size_t size);
pci_bus_addr_t pci_p2pmem_virt_to_bus(struct pci_dev *pdev, void *addr);
struct scatterlist *pci_p2pmem_alloc_sgl(struct pci_dev *pdev,
unsigned int *nents, u32 length);
void pci_p2pmem_free_sgl(struct pci_dev *pdev, struct scatterlist *sgl);
void pci_p2pmem_publish(struct pci_dev *pdev, bool publish);
#else /* CONFIG_PCI_P2PDMA */
static inline int pci_p2pdma_add_resource(struct pci_dev *pdev, int bar,
size_t size, u64 offset)
{
return -EOPNOTSUPP;
}
static inline int pci_p2pdma_distance_many(struct pci_dev *provider,
struct device **clients, int num_clients, bool verbose)
{
return -1;
}
static inline bool pci_has_p2pmem(struct pci_dev *pdev)
{
return false;
}
static inline struct pci_dev *pci_p2pmem_find_many(struct device **clients,
int num_clients)
{
return NULL;
}
static inline void *pci_alloc_p2pmem(struct pci_dev *pdev, size_t size)
{
return NULL;
}
static inline void pci_free_p2pmem(struct pci_dev *pdev, void *addr,
size_t size)
{
}
static inline pci_bus_addr_t pci_p2pmem_virt_to_bus(struct pci_dev *pdev,
void *addr)
{
return 0;
}
static inline struct scatterlist *pci_p2pmem_alloc_sgl(struct pci_dev *pdev,
unsigned int *nents, u32 length)
{
return NULL;
}
static inline void pci_p2pmem_free_sgl(struct pci_dev *pdev,
struct scatterlist *sgl)
{
}
static inline void pci_p2pmem_publish(struct pci_dev *pdev, bool publish)
{
}
#endif /* CONFIG_PCI_P2PDMA */
static inline int pci_p2pdma_distance(struct pci_dev *provider,
struct device *client, bool verbose)
{
return pci_p2pdma_distance_many(provider, &client, 1, verbose);
}
static inline struct pci_dev *pci_p2pmem_find(struct device *client)
{
return pci_p2pmem_find_many(&client, 1);
}
#endif /* _LINUX_PCI_P2P_H */
...@@ -281,6 +281,7 @@ struct pcie_link_state; ...@@ -281,6 +281,7 @@ struct pcie_link_state;
struct pci_vpd; struct pci_vpd;
struct pci_sriov; struct pci_sriov;
struct pci_ats; struct pci_ats;
struct pci_p2pdma;
/* The pci_dev structure describes PCI devices */ /* The pci_dev structure describes PCI devices */
struct pci_dev { struct pci_dev {
...@@ -438,6 +439,9 @@ struct pci_dev { ...@@ -438,6 +439,9 @@ struct pci_dev {
#endif #endif
#ifdef CONFIG_PCI_PASID #ifdef CONFIG_PCI_PASID
u16 pasid_features; u16 pasid_features;
#endif
#ifdef CONFIG_PCI_P2PDMA
struct pci_p2pdma *p2pdma;
#endif #endif
phys_addr_t rom; /* Physical address if not from BAR */ phys_addr_t rom; /* Physical address if not from BAR */
size_t romlen; /* Length if not from BAR */ size_t romlen; /* Length if not from BAR */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment