* [PATCH v6 0/5] vfio/dma-buf: add TPH support for peer-to-peer access
@ 2026-06-08 18:56 Zhiping Zhang
2026-06-08 18:56 ` [PATCH v6 1/5] net/mlx5: free mlx5_st_idx_data on final dealloc Zhiping Zhang
` (4 more replies)
0 siblings, 5 replies; 17+ messages in thread
From: Zhiping Zhang @ 2026-06-08 18:56 UTC (permalink / raw)
To: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal,
Christian Konig
Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel,
Keith Busch, Yochai Cohen, Yishai Hadas, Zhiping Zhang
This series adds TLP Processing Hints (TPH) support to the VFIO dma-buf
export path, allowing importing drivers (e.g. mlx5) to use the
exporter's steering tag when performing peer-to-peer DMA into a
VFIO-owned device.
There is no separate in-tree vendor kernel driver for the target device:
vfio-pci is the in-tree driver and the targeted device is managed
from userspace via VFIO passthrough. That is why the ST has to flow
through a uAPI: userspace owns the device and its ST table, so it is the
entity that can publish a meaningful value for a given dma-buf. The
kernel-visible participants are still in-tree: vfio-pci exports the
dma-buf and mlx5 imports it.
On the effect: the endpoint's PCIe ingress block uses the 8-bit ST as
an in-band instruction for the incoming P2P TLP -- selecting a target
cache partition and, on writes, an in-flight operation on the data
before it lands. The dma-buf callback keeps this opaque to the
framework -- only the producer (userspace owner of the VFIO device)
and the consumer (endpoint block) need to interpret the value. The
dma-buf get_tph callback itself is optional for workloads that depend
on the endpoint's in-flight operation that fallback does not produce
the same result.
The dma-buf hook is intentionally generic and discoverable rather than
a private side channel. The exporter owns the completing address
space for the dma-buf and decides whether it can provide a meaningful
ST/PH tuple for that completer; the dma-buf core keeps the tuple opaque,
and importers merely request the namespace they support and place the
returned value on generated TLPs. Exporters that cannot derive a
meaningful tuple simply return -EOPNOTSUPP.
Patch 1 is a pre-existing fix split out from the series:
mlx5_st_dealloc_index() removed the xarray entry but never freed the
backing struct, so repeated alloc/dealloc cycles leaked memory.
Patch 2 exposes the enabled TPH requester type through a small PCI/TPH
helper, plus a pcie_tph_supported() helper so consumers don't reach
into pci_dev internals (and so callers in CONFIG_PCIE_TPH=n builds
get a clean -EOPNOTSUPP path).
Patch 3 adds the optional dma_buf_ops::get_tph callback to the dma-buf
framework so importers can fetch TPH metadata from an exporter.
Patch 4 implements get_tph in vfio-pci and adds the new uAPI
(VFIO_DEVICE_FEATURE_DMA_BUF_TPH) for userspace to attach the metadata.
Patch 5 wires up the mlx5 RDMA driver as a consumer.
Build-tested with both CONFIG_PCIE_TPH=y and CONFIG_PCIE_TPH=n.
Functional validation on the target topology: PCIe analyzer captures
on the P2P TLPs confirm the ST emitted by mlx5 matches the value
published through VFIO_DEVICE_FEATURE_DMA_BUF_TPH, and the end-to-end
P2P workload only produces results consistent with the endpoint's
ST-selected in-flight operation. For example, with userspace
publishing 8-bit ST=0xf0 and PH=2, an analyzer capture of a peer-to-
peer MWr64 shows "STP MWr64 TC=0 OHC=2 ..." followed by "OHC-B
ST=F0h PH=2 HV=1":
(TLP Captures)
08000260 -> STP MWr64 TC=0 OHC=2 TS=0 Attr=0 L=8
F0000004 -> RID=4h:0h.0h EP- Tag=F0h
E0200000 -> AddrH=000020E0h
00080006 -> AddrL=06000800h
90F00000 -> OHC-B ST=F0h PH=2 HV=1 AMA=0 AV-
Previous link:
v5: https://lore.kernel.org/dri-devel/20260526144401.1485788-1-zhipingz@meta.com/
v4: https://lore.kernel.org/linux-pci/20260519201401.1558410-1-zhipingz@meta.com/
v3: https://lore.kernel.org/linux-pci/20260512184755.4137227-1-zhipingz@meta.com/
v2: https://lore.kernel.org/linux-pci/20260430200704.352228-1-zhipingz@meta.com/
Zhiping Zhang (5):
net/mlx5: free mlx5_st_idx_data on final dealloc
PCI/TPH: expose the enabled TPH requester type and capability helpers
dma-buf: add optional get_tph() callback
vfio/pci: implement get_tph and DMA_BUF_TPH feature
RDMA/mlx5: get tph for p2p access when registering dma-buf mr
drivers/infiniband/core/frmr_pools.c | 20 ++-
drivers/infiniband/hw/mlx5/mr.c | 124 +++++++++++++++++-
.../net/ethernet/mellanox/mlx5/core/lib/st.c | 50 +++++--
drivers/pci/tph.c | 25 ++++
drivers/vfio/pci/vfio_pci_core.c | 3 +
drivers/vfio/pci/vfio_pci_dmabuf.c | 92 ++++++++++++-
drivers/vfio/pci/vfio_pci_priv.h | 12 ++
include/linux/dma-buf.h | 31 +++++
include/linux/mlx5/driver.h | 12 ++
include/linux/pci-tph.h | 7 +
include/rdma/frmr_pools.h | 5 +-
include/uapi/linux/vfio.h | 45 +++++++
12 files changed, 406 insertions(+), 20 deletions(-)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v6 1/5] net/mlx5: free mlx5_st_idx_data on final dealloc 2026-06-08 18:56 [PATCH v6 0/5] vfio/dma-buf: add TPH support for peer-to-peer access Zhiping Zhang @ 2026-06-08 18:56 ` Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 2/5] PCI/TPH: expose enabled requester type and capability helpers Zhiping Zhang ` (3 subsequent siblings) 4 siblings, 0 replies; 17+ messages in thread From: Zhiping Zhang @ 2026-06-08 18:56 UTC (permalink / raw) To: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas, Zhiping Zhang When the last reference to an ST table entry is dropped, mlx5_st_dealloc_index() removed the entry from idx_xa but leaked the backing mlx5_st_idx_data allocation. Repeated alloc/dealloc cycles therefore accumulate one struct mlx5_st_idx_data per cycle. Free idx_data after the xa_erase() so the lifetime of the bookkeeping struct matches the lifetime of the ST entry it tracks. Fixes: 888a7776f4fb ("net/mlx5: Add support for device steering tag") Signed-off-by: Zhiping Zhang <zhipingz@meta.com> --- drivers/net/ethernet/mellanox/mlx5/core/lib/st.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c index 997be91f0a13..7cedc348790d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c @@ -175,6 +175,7 @@ int mlx5_st_dealloc_index(struct mlx5_core_dev *dev, u16 st_index) if (refcount_dec_and_test(&idx_data->usecount)) { xa_erase(&st->idx_xa, st_index); + kfree(idx_data); /* We leave PCI config space as was before, no mkey will refer to it */ } -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v6 2/5] PCI/TPH: expose enabled requester type and capability helpers 2026-06-08 18:56 [PATCH v6 0/5] vfio/dma-buf: add TPH support for peer-to-peer access Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 1/5] net/mlx5: free mlx5_st_idx_data on final dealloc Zhiping Zhang @ 2026-06-08 18:56 ` Zhiping Zhang 2026-06-09 19:56 ` Bjorn Helgaas 2026-06-08 18:56 ` [PATCH v6 3/5] dma-buf: add optional get_tph() callback Zhiping Zhang ` (2 subsequent siblings) 4 siblings, 1 reply; 17+ messages in thread From: Zhiping Zhang @ 2026-06-08 18:56 UTC (permalink / raw) To: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas, Zhiping Zhang Add pcie_tph_enabled_req_type() so drivers can query the enabled TPH requester mode without reaching into pci_dev internals, and pcie_tph_supported() so they can test whether the device exposes the PCIe TPH Extended Capability without doing the same. This keeps pci_dev::tph_req_type and pci_dev::tph_cap inside the PCI/TPH code and provides !CONFIG_PCIE_TPH stubs for callers. Signed-off-by: Zhiping Zhang <zhipingz@meta.com> --- drivers/pci/tph.c | 25 +++++++++++++++++++++++++ include/linux/pci-tph.h | 7 +++++++ 2 files changed, 32 insertions(+) diff --git a/drivers/pci/tph.c b/drivers/pci/tph.c index 91145e8d9d95..aa09113c46d4 100644 --- a/drivers/pci/tph.c +++ b/drivers/pci/tph.c @@ -174,6 +174,31 @@ u32 pcie_tph_get_st_table_loc(struct pci_dev *pdev) } EXPORT_SYMBOL(pcie_tph_get_st_table_loc); +/** + * pcie_tph_enabled_req_type - Return the device's enabled TPH requester type + * @pdev: PCI device to query + * + * Return: PCI_TPH_REQ_DISABLE, PCI_TPH_REQ_TPH_ONLY or PCI_TPH_REQ_EXT_TPH. + */ +u8 pcie_tph_enabled_req_type(struct pci_dev *pdev) +{ + return pdev->tph_req_type; +} +EXPORT_SYMBOL(pcie_tph_enabled_req_type); + +/** + * pcie_tph_supported - Whether the device advertises the TPH Extended Cap + * @pdev: PCI device to query + * + * Return: true when the device exposes the PCIe TPH Extended Capability, + * false otherwise. + */ +bool pcie_tph_supported(struct pci_dev *pdev) +{ + return pdev->tph_cap != 0; +} +EXPORT_SYMBOL(pcie_tph_supported); + /* * Return the size of ST table. If ST table is not in TPH Requester Extended * Capability space, return 0. Otherwise return the ST Table Size + 1. diff --git a/include/linux/pci-tph.h b/include/linux/pci-tph.h index be68cd17f2f8..2e07bce77038 100644 --- a/include/linux/pci-tph.h +++ b/include/linux/pci-tph.h @@ -9,6 +9,8 @@ #ifndef LINUX_PCI_TPH_H #define LINUX_PCI_TPH_H +#include <linux/pci_regs.h> + /* * According to the ECN for PCI Firmware Spec, Steering Tag can be different * depending on the memory type: Volatile Memory or Persistent Memory. When a @@ -30,6 +32,8 @@ void pcie_disable_tph(struct pci_dev *pdev); int pcie_enable_tph(struct pci_dev *pdev, int mode); u16 pcie_tph_get_st_table_size(struct pci_dev *pdev); u32 pcie_tph_get_st_table_loc(struct pci_dev *pdev); +u8 pcie_tph_enabled_req_type(struct pci_dev *pdev); +bool pcie_tph_supported(struct pci_dev *pdev); #else static inline int pcie_tph_set_st_entry(struct pci_dev *pdev, unsigned int index, u16 tag) @@ -41,6 +45,9 @@ static inline int pcie_tph_get_cpu_st(struct pci_dev *dev, static inline void pcie_disable_tph(struct pci_dev *pdev) { } static inline int pcie_enable_tph(struct pci_dev *pdev, int mode) { return -EINVAL; } +static inline u8 pcie_tph_enabled_req_type(struct pci_dev *pdev) +{ return PCI_TPH_REQ_DISABLE; } +static inline bool pcie_tph_supported(struct pci_dev *pdev) { return false; } #endif #endif /* LINUX_PCI_TPH_H */ -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v6 2/5] PCI/TPH: expose enabled requester type and capability helpers 2026-06-08 18:56 ` [PATCH v6 2/5] PCI/TPH: expose enabled requester type and capability helpers Zhiping Zhang @ 2026-06-09 19:56 ` Bjorn Helgaas 2026-06-09 23:38 ` Zhiping Zhang 0 siblings, 1 reply; 17+ messages in thread From: Bjorn Helgaas @ 2026-06-09 19:56 UTC (permalink / raw) To: Zhiping Zhang Cc: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas On Mon, Jun 08, 2026 at 11:56:39AM -0700, Zhiping Zhang wrote: > Add pcie_tph_enabled_req_type() so drivers can query the enabled TPH > requester mode without reaching into pci_dev internals, and > pcie_tph_supported() so they can test whether the device exposes the > PCIe TPH Extended Capability without doing the same. s/exposes/advertises/ > This keeps pci_dev::tph_req_type and pci_dev::tph_cap inside the > PCI/TPH code and provides !CONFIG_PCIE_TPH stubs for callers. Update subject line to match capitalization of history (use "git log --oneline drivers/pci/tph.c") s/expose/Add/ in subject. ("Expose" suggests that the interfaces already exist and we're just exporting them, but these interfaces didn't exist at all before.) > Signed-off-by: Zhiping Zhang <zhipingz@meta.com> With the above, Acked-by: Bjorn Helgaas <bhelgaas@google.com> > --- > drivers/pci/tph.c | 25 +++++++++++++++++++++++++ > include/linux/pci-tph.h | 7 +++++++ > 2 files changed, 32 insertions(+) > > diff --git a/drivers/pci/tph.c b/drivers/pci/tph.c > index 91145e8d9d95..aa09113c46d4 100644 > --- a/drivers/pci/tph.c > +++ b/drivers/pci/tph.c > @@ -174,6 +174,31 @@ u32 pcie_tph_get_st_table_loc(struct pci_dev *pdev) > } > EXPORT_SYMBOL(pcie_tph_get_st_table_loc); > > +/** > + * pcie_tph_enabled_req_type - Return the device's enabled TPH requester type > + * @pdev: PCI device to query > + * > + * Return: PCI_TPH_REQ_DISABLE, PCI_TPH_REQ_TPH_ONLY or PCI_TPH_REQ_EXT_TPH. > + */ > +u8 pcie_tph_enabled_req_type(struct pci_dev *pdev) > +{ > + return pdev->tph_req_type; > +} > +EXPORT_SYMBOL(pcie_tph_enabled_req_type); > + > +/** > + * pcie_tph_supported - Whether the device advertises the TPH Extended Cap > + * @pdev: PCI device to query > + * > + * Return: true when the device exposes the PCIe TPH Extended Capability, > + * false otherwise. > + */ > +bool pcie_tph_supported(struct pci_dev *pdev) > +{ > + return pdev->tph_cap != 0; > +} > +EXPORT_SYMBOL(pcie_tph_supported); > + > /* > * Return the size of ST table. If ST table is not in TPH Requester Extended > * Capability space, return 0. Otherwise return the ST Table Size + 1. > diff --git a/include/linux/pci-tph.h b/include/linux/pci-tph.h > index be68cd17f2f8..2e07bce77038 100644 > --- a/include/linux/pci-tph.h > +++ b/include/linux/pci-tph.h > @@ -9,6 +9,8 @@ > #ifndef LINUX_PCI_TPH_H > #define LINUX_PCI_TPH_H > > +#include <linux/pci_regs.h> > + > /* > * According to the ECN for PCI Firmware Spec, Steering Tag can be different > * depending on the memory type: Volatile Memory or Persistent Memory. When a > @@ -30,6 +32,8 @@ void pcie_disable_tph(struct pci_dev *pdev); > int pcie_enable_tph(struct pci_dev *pdev, int mode); > u16 pcie_tph_get_st_table_size(struct pci_dev *pdev); > u32 pcie_tph_get_st_table_loc(struct pci_dev *pdev); > +u8 pcie_tph_enabled_req_type(struct pci_dev *pdev); > +bool pcie_tph_supported(struct pci_dev *pdev); > #else > static inline int pcie_tph_set_st_entry(struct pci_dev *pdev, > unsigned int index, u16 tag) > @@ -41,6 +45,9 @@ static inline int pcie_tph_get_cpu_st(struct pci_dev *dev, > static inline void pcie_disable_tph(struct pci_dev *pdev) { } > static inline int pcie_enable_tph(struct pci_dev *pdev, int mode) > { return -EINVAL; } > +static inline u8 pcie_tph_enabled_req_type(struct pci_dev *pdev) > +{ return PCI_TPH_REQ_DISABLE; } > +static inline bool pcie_tph_supported(struct pci_dev *pdev) { return false; } > #endif > > #endif /* LINUX_PCI_TPH_H */ > -- > 2.53.0-Meta > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 2/5] PCI/TPH: expose enabled requester type and capability helpers 2026-06-09 19:56 ` Bjorn Helgaas @ 2026-06-09 23:38 ` Zhiping Zhang 0 siblings, 0 replies; 17+ messages in thread From: Zhiping Zhang @ 2026-06-09 23:38 UTC (permalink / raw) To: Bjorn Helgaas Cc: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas On Tue, Jun 9, 2026 at 12:56 PM Bjorn Helgaas <helgaas@kernel.org> wrote: > > > > On Mon, Jun 08, 2026 at 11:56:39AM -0700, Zhiping Zhang wrote: > > Add pcie_tph_enabled_req_type() so drivers can query the enabled TPH > > requester mode without reaching into pci_dev internals, and > > pcie_tph_supported() so they can test whether the device exposes the > > PCIe TPH Extended Capability without doing the same. > > s/exposes/advertises/ > ack, will fix in v7. > > This keeps pci_dev::tph_req_type and pci_dev::tph_cap inside the > > PCI/TPH code and provides !CONFIG_PCIE_TPH stubs for callers. > > Update subject line to match capitalization of history > (use "git log --oneline drivers/pci/tph.c") > > s/expose/Add/ in subject. > > ("Expose" suggests that the interfaces already exist and we're just > exporting them, but these interfaces didn't exist at all before.) > ack, thanks for pointing this out! > > Signed-off-by: Zhiping Zhang <zhipingz@meta.com> > > With the above, > > Acked-by: Bjorn Helgaas <bhelgaas@google.com> > Thanks, Zhiping ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 3/5] dma-buf: add optional get_tph() callback 2026-06-08 18:56 [PATCH v6 0/5] vfio/dma-buf: add TPH support for peer-to-peer access Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 1/5] net/mlx5: free mlx5_st_idx_data on final dealloc Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 2/5] PCI/TPH: expose enabled requester type and capability helpers Zhiping Zhang @ 2026-06-08 18:56 ` Zhiping Zhang 2026-06-09 8:10 ` Christian König 2026-06-09 19:38 ` sashiko-bot 2026-06-08 18:56 ` [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 5/5] RDMA/mlx5: get tph for p2p access when registering dma-buf mr Zhiping Zhang 4 siblings, 2 replies; 17+ messages in thread From: Zhiping Zhang @ 2026-06-08 18:56 UTC (permalink / raw) To: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas, Zhiping Zhang Add an optional dma-buf get_tph callback so an exporter can return TPH (TLP Processing Hints) metadata to an importer. 8-bit ST and 16-bit Extended ST are distinct namespaces in the PCIe TPH ST table and may both be present with different values. The importer passes its supported steering-tag width and the exporter returns the matching value, or -EOPNOTSUPP if no metadata is available for that width. The callback is intentionally exporter-owned and optional. The exporter owns the completing address space for the dma-buf, so only it can decide whether it has meaningful TPH metadata for that completer. The dma-buf core keeps the returned ST/PH tuple opaque and simply provides a discoverable negotiation point between exporter and importer; exporters that cannot derive a useful tuple just return -EOPNOTSUPP. That keeps the kernel API generic rather than VFIO-specific. The first user is VFIO_DEVICE_FEATURE_DMA_BUF_TPH in vfio-pci, with the mlx5 RDMA driver as the first importer, but any future exporter that can derive a TPH tuple for its completing address space can reuse the same callback. Signed-off-by: Zhiping Zhang <zhipingz@meta.com> --- include/linux/dma-buf.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index d1203da56fc5..8437dbe4a83e 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -113,6 +113,37 @@ struct dma_buf_ops { */ void (*unpin)(struct dma_buf_attachment *attach); + /** + * @get_tph: + * @dmabuf: DMA buffer for which to retrieve TPH metadata + * @extended: false to request the 8-bit ST namespace, true to request + * the 16-bit Extended ST namespace + * @steering_tag: Returns the raw TPH steering tag for the requested + * namespace + * @ph: Returns the TPH processing hint (2-bit value) + * + * Return the TPH (TLP Processing Hints) metadata associated with this + * DMA buffer for the requested steering-tag namespace. 8-bit ST and + * 16-bit Extended ST are distinct namespaces in the PCIe TPH ST table + * and may both be present with different values, so the exporter must + * select the value that matches @extended and must not substitute one + * for the other. + * + * The exporter owns the completing address space for @dmabuf and + * therefore decides whether it can derive meaningful TPH metadata for + * that completer. The dma-buf core treats the returned ST/PH tuple as + * opaque transport metadata; importers that support TPH place it on + * outbound TLPs, while exporters that cannot derive a useful tuple + * simply return -EOPNOTSUPP. + * + * Return 0 on success, or -EOPNOTSUPP if no metadata is available for + * the requested namespace. + * + * This callback is optional. + */ + int (*get_tph)(struct dma_buf *dmabuf, bool extended, + u16 *steering_tag, u8 *ph); + /** * @map_dma_buf: * -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v6 3/5] dma-buf: add optional get_tph() callback 2026-06-08 18:56 ` [PATCH v6 3/5] dma-buf: add optional get_tph() callback Zhiping Zhang @ 2026-06-09 8:10 ` Christian König 2026-06-09 14:38 ` Zhiping Zhang 2026-06-09 19:38 ` sashiko-bot 1 sibling, 1 reply; 17+ messages in thread From: Christian König @ 2026-06-09 8:10 UTC (permalink / raw) To: Zhiping Zhang, Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas On 6/8/26 20:56, Zhiping Zhang wrote: > Add an optional dma-buf get_tph callback so an exporter can return TPH > (TLP Processing Hints) metadata to an importer. > > 8-bit ST and 16-bit Extended ST are distinct namespaces in the PCIe TPH > ST table and may both be present with different values. The importer > passes its supported steering-tag width and the exporter returns the > matching value, or -EOPNOTSUPP if no metadata is available for that > width. > > The callback is intentionally exporter-owned and optional. The exporter > owns the completing address space for the dma-buf, so only it can decide > whether it has meaningful TPH metadata for that completer. The dma-buf > core keeps the returned ST/PH tuple opaque and simply provides a > discoverable negotiation point between exporter and importer; exporters > that cannot derive a useful tuple just return -EOPNOTSUPP. > > That keeps the kernel API generic rather than VFIO-specific. The first > user is VFIO_DEVICE_FEATURE_DMA_BUF_TPH in vfio-pci, with the mlx5 RDMA > driver as the first importer, but any future exporter that can derive a > TPH tuple for its completing address space can reuse the same callback. > > Signed-off-by: Zhiping Zhang <zhipingz@meta.com> > --- > include/linux/dma-buf.h | 31 +++++++++++++++++++++++++++++++ > 1 file changed, 31 insertions(+) > > diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h > index d1203da56fc5..8437dbe4a83e 100644 > --- a/include/linux/dma-buf.h > +++ b/include/linux/dma-buf.h > @@ -113,6 +113,37 @@ struct dma_buf_ops { > */ > void (*unpin)(struct dma_buf_attachment *attach); > > + /** > + * @get_tph: > + * @dmabuf: DMA buffer for which to retrieve TPH metadata > + * @extended: false to request the 8-bit ST namespace, true to request > + * the 16-bit Extended ST namespace > + * @steering_tag: Returns the raw TPH steering tag for the requested > + * namespace > + * @ph: Returns the TPH processing hint (2-bit value) > + * > + * Return the TPH (TLP Processing Hints) metadata associated with this > + * DMA buffer for the requested steering-tag namespace. 8-bit ST and > + * 16-bit Extended ST are distinct namespaces in the PCIe TPH ST table > + * and may both be present with different values, so the exporter must > + * select the value that matches @extended and must not substitute one > + * for the other. > + * > + * The exporter owns the completing address space for @dmabuf and > + * therefore decides whether it can derive meaningful TPH metadata for > + * that completer. The dma-buf core treats the returned ST/PH tuple as > + * opaque transport metadata; importers that support TPH place it on > + * outbound TLPs, while exporters that cannot derive a useful tuple > + * simply return -EOPNOTSUPP. > + * > + * Return 0 on success, or -EOPNOTSUPP if no metadata is available for > + * the requested namespace. > + * > + * This callback is optional. > + */ > + int (*get_tph)(struct dma_buf *dmabuf, bool extended, > + u16 *steering_tag, u8 *ph); > + That needs a wrapper for importers to call which also handles if the callback isn't present. Regards, Christian. > /** > * @map_dma_buf: > * ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 3/5] dma-buf: add optional get_tph() callback 2026-06-09 8:10 ` Christian König @ 2026-06-09 14:38 ` Zhiping Zhang 0 siblings, 0 replies; 17+ messages in thread From: Zhiping Zhang @ 2026-06-09 14:38 UTC (permalink / raw) To: Christian König Cc: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas > > include/linux/dma-buf.h | 31 +++++++++++++++++++++++++++++++ > > 1 file changed, 31 insertions(+) > > > > diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h > > index d1203da56fc5..8437dbe4a83e 100644 > > --- a/include/linux/dma-buf.h > > +++ b/include/linux/dma-buf.h > > @@ -113,6 +113,37 @@ struct dma_buf_ops { > > */ > > void (*unpin)(struct dma_buf_attachment *attach); > > > > + /** > > + * @get_tph: > > + * @dmabuf: DMA buffer for which to retrieve TPH metadata > > + * @extended: false to request the 8-bit ST namespace, true to request > > + * the 16-bit Extended ST namespace > > + * @steering_tag: Returns the raw TPH steering tag for the requested > > + * namespace > > + * @ph: Returns the TPH processing hint (2-bit value) > > + * > > + * Return the TPH (TLP Processing Hints) metadata associated with this > > + * DMA buffer for the requested steering-tag namespace. 8-bit ST and > > + * 16-bit Extended ST are distinct namespaces in the PCIe TPH ST table > > + * and may both be present with different values, so the exporter must > > + * select the value that matches @extended and must not substitute one > > + * for the other. > > + * > > + * The exporter owns the completing address space for @dmabuf and > > + * therefore decides whether it can derive meaningful TPH metadata for > > + * that completer. The dma-buf core treats the returned ST/PH tuple as > > + * opaque transport metadata; importers that support TPH place it on > > + * outbound TLPs, while exporters that cannot derive a useful tuple > > + * simply return -EOPNOTSUPP. > > + * > > + * Return 0 on success, or -EOPNOTSUPP if no metadata is available for > > + * the requested namespace. > > + * > > + * This callback is optional. > > + */ > > + int (*get_tph)(struct dma_buf *dmabuf, bool extended, > > + u16 *steering_tag, u8 *ph); > > + > > That needs a wrapper for importers to call which also handles if the callback isn't present. > > Regards, > Christian. > agreed, will use a wrapper in next revision. Thanks, Zhiping ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 3/5] dma-buf: add optional get_tph() callback 2026-06-08 18:56 ` [PATCH v6 3/5] dma-buf: add optional get_tph() callback Zhiping Zhang 2026-06-09 8:10 ` Christian König @ 2026-06-09 19:38 ` sashiko-bot 1 sibling, 0 replies; 17+ messages in thread From: sashiko-bot @ 2026-06-09 19:38 UTC (permalink / raw) To: Zhiping Zhang; +Cc: kvm, dri-devel, linux-pci Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The `get_tph` callback takes `struct dma_buf *` instead of `struct dma_buf_attachment *`, failing to account for per-attachment mappings and dynamic buffer migrations. - [High] The new `get_tph` operation lacks a corresponding core API wrapper function in `dma-buf.c` and is omitted from the subsystem locking documentation. -- commit 4644c9f73b091302efc6ec2a805dcc7affb8aaf6 Author: Zhiping Zhang <zhipingz@meta.com> dma-buf: add optional get_tph() callback This commit adds an optional get_tph callback to the dma-buf ops so an exporter can return PCIe TPH (TLP Processing Hints) metadata to an importer. The API provides a discoverable negotiation point between exporter and importer. > diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h > index d1203da56fc5f..8437dbe4a83ec 100644 > --- a/include/linux/dma-buf.h > +++ b/include/linux/dma-buf.h > @@ -113,6 +113,37 @@ struct dma_buf_ops { > */ > void (*unpin)(struct dma_buf_attachment *attach); > > + /** > + * @get_tph: > + * @dmabuf: DMA buffer for which to retrieve TPH metadata > + * @extended: false to request the 8-bit ST namespace, true to request > + * the 16-bit Extended ST namespace > + * @steering_tag: Returns the raw TPH steering tag for the requested > + * namespace > + * @ph: Returns the TPH processing hint (2-bit value) > + * > + * Return the TPH (TLP Processing Hints) metadata associated with this > + * DMA buffer for the requested steering-tag namespace. 8-bit ST and > + * 16-bit Extended ST are distinct namespaces in the PCIe TPH ST table > + * and may both be present with different values, so the exporter must > + * select the value that matches @extended and must not substitute one > + * for the other. > + * > + * The exporter owns the completing address space for @dmabuf and > + * therefore decides whether it can derive meaningful TPH metadata for > + * that completer. The dma-buf core treats the returned ST/PH tuple as > + * opaque transport metadata; importers that support TPH place it on > + * outbound TLPs, while exporters that cannot derive a useful tuple > + * simply return -EOPNOTSUPP. > + * > + * Return 0 on success, or -EOPNOTSUPP if no metadata is available for > + * the requested namespace. > + * > + * This callback is optional. > + */ > + int (*get_tph)(struct dma_buf *dmabuf, bool extended, > + u16 *steering_tag, u8 *ph); [Severity: High] Does this callback need to take a struct dma_buf_attachment * instead of a struct dma_buf *? A dma-buf's physical backing storage is only stable in the context of a mapped attachment. Exporters might provide device memory to a peer-to-peer capable importer, but system memory to another importer. Furthermore, dynamic dma-bufs can migrate their backing storage between VRAM and system memory, which changes the completer and the correct Steering Tag. By querying TPH metadata on the global dma_buf object without an attachment context, the exporter cannot know which importer's mapping is being queried. Can this prevent the exporter from returning the correct completing address space's Steering Tag? [Severity: High] Should this new operation have a corresponding core API wrapper function exported in drivers/dma-buf/dma-buf.c and documented in the locking convention block? In the dma-buf architecture, ops are typically encapsulated behind core wrappers to enforce cross-subsystem locking, validate inputs, and handle tracing. By omitting the wrapper, importers are forced to bypass the core API and dereference the ops structure directly: drivers/infiniband/hw/mlx5/mr.c:get_tph_mr_dmabuf() { ... ret = dmabuf->ops->get_tph(dmabuf, extended, &steering_tag, &local_ph); ... } Does the lack of a wrapper function like dma_buf_get_tph() violate the expected API encapsulation for dma-buf operations? > /** > * @map_dma_buf: > * -- Sashiko AI review · https://sashiko.dev/#/patchset/20260608185646.4085127-1-zhipingz@meta.com?part=3 ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature 2026-06-08 18:56 [PATCH v6 0/5] vfio/dma-buf: add TPH support for peer-to-peer access Zhiping Zhang ` (2 preceding siblings ...) 2026-06-08 18:56 ` [PATCH v6 3/5] dma-buf: add optional get_tph() callback Zhiping Zhang @ 2026-06-08 18:56 ` Zhiping Zhang 2026-06-09 8:12 ` Christian König ` (2 more replies) 2026-06-08 18:56 ` [PATCH v6 5/5] RDMA/mlx5: get tph for p2p access when registering dma-buf mr Zhiping Zhang 4 siblings, 3 replies; 17+ messages in thread From: Zhiping Zhang @ 2026-06-08 18:56 UTC (permalink / raw) To: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas, Zhiping Zhang Implement the dma-buf get_tph callback for vfio-pci-exported dma-bufs and add VFIO_DEVICE_FEATURE_DMA_BUF_TPH so userspace can attach TPH metadata to such a dma-buf. 8-bit ST and 16-bit Extended ST are distinct PCIe TPH namespaces; the uAPI carries both with explicit validity flags, and get_tph() returns the value matching the importer's requested width (or -EOPNOTSUPP). The TPH descriptor is published and read under a new per-dma-buf mutex priv->tph_lock so a SET racing with a get_tph reader sees consistent fields. The mutex's only role is serialising the TPH state; priv->vdev and dmabuf lifetime are managed by the existing ioctl reference and dma_buf_get() ref, so the cleanup path does not need to take this mutex. The SET ioctl returns -EOPNOTSUPP if the underlying device does not expose the PCIe TPH Extended Capability (pdev->tph_cap == 0); setting ST metadata on a device that cannot act as a TPH completer is nonsensical and rejecting it early gives userspace a clear signal. The uAPI itself is not device-specific. It publishes the PCI SIG-defined ST/PH tuple for a VFIO-owned PCIe completer and keeps the tuple opaque to dma-buf; any importer simply requests the namespace it supports and places the returned value on generated TLPs. Any other userspace driver using vfio-pci for an endpoint that accepts inbound TPH can reuse the same interface. Signed-off-by: Zhiping Zhang <zhipingz@meta.com> --- Test plan: verified the kernel-side behavior by checking that an importer such as mlx5 emits the programmed ST/PH on outbound P2P TLPs after a successful VFIO_DEVICE_FEATURE_DMA_BUF_TPH set. drivers/vfio/pci/vfio_pci_core.c | 3 + drivers/vfio/pci/vfio_pci_dmabuf.c | 92 +++++++++++++++++++++++++++++- drivers/vfio/pci/vfio_pci_priv.h | 12 ++++ include/uapi/linux/vfio.h | 45 +++++++++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c index 050e7542952e..4fa36f2f7555 100644 --- a/drivers/vfio/pci/vfio_pci_core.c +++ b/drivers/vfio/pci/vfio_pci_core.c @@ -1569,6 +1569,9 @@ int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags, return vfio_pci_core_feature_token(vdev, flags, arg, argsz); case VFIO_DEVICE_FEATURE_DMA_BUF: return vfio_pci_core_feature_dma_buf(vdev, flags, arg, argsz); + case VFIO_DEVICE_FEATURE_DMA_BUF_TPH: + return vfio_pci_core_feature_dma_buf_tph(vdev, flags, arg, + argsz); default: return -ENOTTY; } diff --git a/drivers/vfio/pci/vfio_pci_dmabuf.c b/drivers/vfio/pci/vfio_pci_dmabuf.c index 1a177ce7de54..dd11a7db6b41 100644 --- a/drivers/vfio/pci/vfio_pci_dmabuf.c +++ b/drivers/vfio/pci/vfio_pci_dmabuf.c @@ -2,7 +2,9 @@ /* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. */ #include <linux/dma-buf-mapping.h> +#include <linux/mutex.h> #include <linux/pci-p2pdma.h> +#include <linux/pci-tph.h> #include <linux/dma-resv.h> #include "vfio_pci_priv.h" @@ -19,7 +21,14 @@ struct vfio_pci_dma_buf { u32 nr_ranges; struct kref kref; struct completion comp; - u8 revoked : 1; + /* @tph_lock serializes TPH SET vs get_tph on the TPH fields below. */ + struct mutex tph_lock; + u8 tph_st_valid:1; + u8 tph_st_ext_valid:1; + u8 tph_ph:2; + u8 tph_st; + u16 tph_st_ext; + u8 revoked:1; }; static int vfio_pci_dma_buf_attach(struct dma_buf *dmabuf, @@ -69,6 +78,25 @@ vfio_pci_dma_buf_map(struct dma_buf_attachment *attachment, return ret; } +static int vfio_pci_dma_buf_get_tph(struct dma_buf *dmabuf, bool extended, + u16 *steering_tag, u8 *ph) +{ + struct vfio_pci_dma_buf *priv = dmabuf->priv; + + guard(mutex)(&priv->tph_lock); + if (extended) { + if (!priv->tph_st_ext_valid) + return -EOPNOTSUPP; + *steering_tag = priv->tph_st_ext; + } else { + if (!priv->tph_st_valid) + return -EOPNOTSUPP; + *steering_tag = priv->tph_st; + } + *ph = priv->tph_ph; + return 0; +} + static void vfio_pci_dma_buf_unmap(struct dma_buf_attachment *attachment, struct sg_table *sgt, enum dma_data_direction dir) @@ -95,12 +123,14 @@ static void vfio_pci_dma_buf_release(struct dma_buf *dmabuf) up_write(&priv->vdev->memory_lock); vfio_device_put_registration(&priv->vdev->vdev); } + mutex_destroy(&priv->tph_lock); kfree(priv->phys_vec); kfree(priv); } static const struct dma_buf_ops vfio_pci_dmabuf_ops = { .attach = vfio_pci_dma_buf_attach, + .get_tph = vfio_pci_dma_buf_get_tph, .map_dma_buf = vfio_pci_dma_buf_map, .unmap_dma_buf = vfio_pci_dma_buf_unmap, .release = vfio_pci_dma_buf_release, @@ -265,6 +295,7 @@ int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, ret = -ENOMEM; goto err_free_ranges; } + mutex_init(&priv->tph_lock); priv->phys_vec = kzalloc_objs(*priv->phys_vec, get_dma_buf.nr_ranges); if (!priv->phys_vec) { ret = -ENOMEM; @@ -327,12 +358,71 @@ int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, err_free_phys: kfree(priv->phys_vec); err_free_priv: + mutex_destroy(&priv->tph_lock); kfree(priv); err_free_ranges: kfree(dma_ranges); return ret; } +int vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, + u32 flags, + struct vfio_device_feature_dma_buf_tph __user *arg, + size_t argsz) +{ + struct vfio_device_feature_dma_buf_tph set_tph; + struct vfio_pci_dma_buf *priv; + struct dma_buf *dmabuf; + int ret; + + if (!pcie_tph_supported(vdev->pdev)) + return -EOPNOTSUPP; + + ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_SET, + sizeof(set_tph)); + if (ret != 1) + return ret; + + if (copy_from_user(&set_tph, arg, sizeof(set_tph))) + return -EFAULT; + + if (set_tph.flags & ~(VFIO_DMA_BUF_TPH_ST | VFIO_DMA_BUF_TPH_ST_EXT)) + return -EINVAL; + + /* PCIe TLP Processing Hint is a 2-bit field. */ + if (set_tph.ph & ~0x3) + return -EINVAL; + + dmabuf = dma_buf_get(set_tph.dmabuf_fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + if (dmabuf->ops != &vfio_pci_dmabuf_ops) { + ret = -EINVAL; + goto out_put; + } + + priv = dmabuf->priv; + if (priv->vdev != vdev) { + ret = -EINVAL; + goto out_put; + } + + scoped_guard(mutex, &priv->tph_lock) { + priv->tph_st = set_tph.steering_tag; + priv->tph_st_ext = set_tph.steering_tag_ext; + priv->tph_ph = set_tph.ph; + priv->tph_st_valid = !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST); + priv->tph_st_ext_valid = + !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST_EXT); + } + ret = 0; + +out_put: + dma_buf_put(dmabuf); + return ret; +} + void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked) { struct vfio_pci_dma_buf *priv; diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h index fca9d0dfac90..c58f369be4b3 100644 --- a/drivers/vfio/pci/vfio_pci_priv.h +++ b/drivers/vfio/pci/vfio_pci_priv.h @@ -118,6 +118,10 @@ static inline bool vfio_pci_is_vga(struct pci_dev *pdev) int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, struct vfio_device_feature_dma_buf __user *arg, size_t argsz); +int vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, + u32 flags, + struct vfio_device_feature_dma_buf_tph __user *arg, + size_t argsz); void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev); void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked); #else @@ -128,6 +132,14 @@ vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, { return -ENOTTY; } + +static inline int +vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, u32 flags, + struct vfio_device_feature_dma_buf_tph __user *arg, + size_t argsz) +{ + return -ENOTTY; +} static inline void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev) { } diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index 5de618a3a5ee..0ca26721849b 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -1534,6 +1534,51 @@ struct vfio_device_feature_dma_buf { */ #define VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 12 +/** + * Upon VFIO_DEVICE_FEATURE_SET associate TPH (TLP Processing Hints) metadata + * with a vfio-exported dma-buf. The dma-buf must have been created by + * VFIO_DEVICE_FEATURE_DMA_BUF on this device, and the device must expose the + * TPH Extended Capability (otherwise the ioctl returns -EOPNOTSUPP). + * + * dmabuf_fd is the file descriptor returned by VFIO_DEVICE_FEATURE_DMA_BUF. + * + * 8-bit ST (steering_tag) and 16-bit Extended ST (steering_tag_ext) are + * distinct namespaces in the PCIe TPH ST table and may both be present with + * different values. Userspace should populate the value(s) it has from the + * firmware ST table for this device and set the matching VFIO_DMA_BUF_TPH_ST / + * VFIO_DMA_BUF_TPH_ST_EXT bit in @flags. An importer requests a specific + * width and receives the matching value; if the requested width is not + * present, the importer is told TPH is unavailable for this dma-buf. + * + * This publishes the PCI SIG-defined ST/PH tuple for a VFIO-owned PCIe + * completer. The dma-buf core treats the tuple as opaque completer-owned + * metadata; an importer simply requests the namespace it supports and places + * the returned value on generated TLPs. + * + * @flags == 0 clears any previously published metadata. + * + * ph is the 2-bit TLP Processing Hint and must be in the range [0, 3]. + * + * Userspace is responsible for setting TPH on the dma-buf before handing the + * fd to the importer. Calling SET again replaces the previously published + * values; racing a SET against an importer that is already consuming the + * dma-buf is a userspace ordering problem. + * + * Return: 0 on success, -errno on failure. + */ +#define VFIO_DEVICE_FEATURE_DMA_BUF_TPH 13 + +#define VFIO_DMA_BUF_TPH_ST (1 << 0) /* steering_tag valid */ +#define VFIO_DMA_BUF_TPH_ST_EXT (1 << 1) /* steering_tag_ext valid */ + +struct vfio_device_feature_dma_buf_tph { + __s32 dmabuf_fd; + __u32 flags; + __u16 steering_tag_ext; + __u8 steering_tag; + __u8 ph; +}; + /* -------- API for Type1 VFIO IOMMU -------- */ /** -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature 2026-06-08 18:56 ` [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature Zhiping Zhang @ 2026-06-09 8:12 ` Christian König 2026-06-09 14:39 ` Zhiping Zhang 2026-06-09 19:38 ` sashiko-bot 2026-06-09 21:46 ` Alex Williamson 2 siblings, 1 reply; 17+ messages in thread From: Christian König @ 2026-06-09 8:12 UTC (permalink / raw) To: Zhiping Zhang, Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas On 6/8/26 20:56, Zhiping Zhang wrote: > Implement the dma-buf get_tph callback for vfio-pci-exported dma-bufs > and add VFIO_DEVICE_FEATURE_DMA_BUF_TPH so userspace can attach TPH > metadata to such a dma-buf. > > 8-bit ST and 16-bit Extended ST are distinct PCIe TPH namespaces; the > uAPI carries both with explicit validity flags, and get_tph() returns > the value matching the importer's requested width (or -EOPNOTSUPP). > > The TPH descriptor is published and read under a new per-dma-buf mutex > priv->tph_lock so a SET racing with a get_tph reader sees consistent > fields. The mutex's only role is serialising the TPH state; priv->vdev > and dmabuf lifetime are managed by the existing ioctl reference and > dma_buf_get() ref, so the cleanup path does not need to take this > mutex. > > The SET ioctl returns -EOPNOTSUPP if the underlying device does not > expose the PCIe TPH Extended Capability (pdev->tph_cap == 0); setting > ST metadata on a device that cannot act as a TPH completer is > nonsensical and rejecting it early gives userspace a clear signal. > > The uAPI itself is not device-specific. It publishes the PCI SIG-defined > ST/PH tuple for a VFIO-owned PCIe completer and keeps the tuple opaque > to dma-buf; any importer simply requests the namespace it supports and > places the returned value on generated TLPs. Any other userspace driver > using vfio-pci for an endpoint that accepts inbound TPH can reuse the > same interface. > > Signed-off-by: Zhiping Zhang <zhipingz@meta.com> > --- > Test plan: verified the kernel-side behavior by checking that an > importer such as mlx5 emits the programmed ST/PH on outbound P2P TLPs > after a successful VFIO_DEVICE_FEATURE_DMA_BUF_TPH set. > > drivers/vfio/pci/vfio_pci_core.c | 3 + > drivers/vfio/pci/vfio_pci_dmabuf.c | 92 +++++++++++++++++++++++++++++- > drivers/vfio/pci/vfio_pci_priv.h | 12 ++++ > include/uapi/linux/vfio.h | 45 +++++++++++++++ > 4 files changed, 151 insertions(+), 1 deletion(-) > > diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c > index 050e7542952e..4fa36f2f7555 100644 > --- a/drivers/vfio/pci/vfio_pci_core.c > +++ b/drivers/vfio/pci/vfio_pci_core.c > @@ -1569,6 +1569,9 @@ int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags, > return vfio_pci_core_feature_token(vdev, flags, arg, argsz); > case VFIO_DEVICE_FEATURE_DMA_BUF: > return vfio_pci_core_feature_dma_buf(vdev, flags, arg, argsz); > + case VFIO_DEVICE_FEATURE_DMA_BUF_TPH: > + return vfio_pci_core_feature_dma_buf_tph(vdev, flags, arg, > + argsz); > default: > return -ENOTTY; > } > diff --git a/drivers/vfio/pci/vfio_pci_dmabuf.c b/drivers/vfio/pci/vfio_pci_dmabuf.c > index 1a177ce7de54..dd11a7db6b41 100644 > --- a/drivers/vfio/pci/vfio_pci_dmabuf.c > +++ b/drivers/vfio/pci/vfio_pci_dmabuf.c > @@ -2,7 +2,9 @@ > /* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. > */ > #include <linux/dma-buf-mapping.h> > +#include <linux/mutex.h> > #include <linux/pci-p2pdma.h> > +#include <linux/pci-tph.h> > #include <linux/dma-resv.h> > > #include "vfio_pci_priv.h" > @@ -19,7 +21,14 @@ struct vfio_pci_dma_buf { > u32 nr_ranges; > struct kref kref; > struct completion comp; > - u8 revoked : 1; > + /* @tph_lock serializes TPH SET vs get_tph on the TPH fields below. */ > + struct mutex tph_lock; Clear NO-GO. When that info is exposed through DMA-buf it must be protected by the DMA-buf resv lock. Christian. > + u8 tph_st_valid:1; > + u8 tph_st_ext_valid:1; > + u8 tph_ph:2; > + u8 tph_st; > + u16 tph_st_ext; > + u8 revoked:1; > }; > > static int vfio_pci_dma_buf_attach(struct dma_buf *dmabuf, > @@ -69,6 +78,25 @@ vfio_pci_dma_buf_map(struct dma_buf_attachment *attachment, > return ret; > } > > +static int vfio_pci_dma_buf_get_tph(struct dma_buf *dmabuf, bool extended, > + u16 *steering_tag, u8 *ph) > +{ > + struct vfio_pci_dma_buf *priv = dmabuf->priv; > + > + guard(mutex)(&priv->tph_lock); > + if (extended) { > + if (!priv->tph_st_ext_valid) > + return -EOPNOTSUPP; > + *steering_tag = priv->tph_st_ext; > + } else { > + if (!priv->tph_st_valid) > + return -EOPNOTSUPP; > + *steering_tag = priv->tph_st; > + } > + *ph = priv->tph_ph; > + return 0; > +} > + > static void vfio_pci_dma_buf_unmap(struct dma_buf_attachment *attachment, > struct sg_table *sgt, > enum dma_data_direction dir) > @@ -95,12 +123,14 @@ static void vfio_pci_dma_buf_release(struct dma_buf *dmabuf) > up_write(&priv->vdev->memory_lock); > vfio_device_put_registration(&priv->vdev->vdev); > } > + mutex_destroy(&priv->tph_lock); > kfree(priv->phys_vec); > kfree(priv); > } > > static const struct dma_buf_ops vfio_pci_dmabuf_ops = { > .attach = vfio_pci_dma_buf_attach, > + .get_tph = vfio_pci_dma_buf_get_tph, > .map_dma_buf = vfio_pci_dma_buf_map, > .unmap_dma_buf = vfio_pci_dma_buf_unmap, > .release = vfio_pci_dma_buf_release, > @@ -265,6 +295,7 @@ int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, > ret = -ENOMEM; > goto err_free_ranges; > } > + mutex_init(&priv->tph_lock); > priv->phys_vec = kzalloc_objs(*priv->phys_vec, get_dma_buf.nr_ranges); > if (!priv->phys_vec) { > ret = -ENOMEM; > @@ -327,12 +358,71 @@ int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, > err_free_phys: > kfree(priv->phys_vec); > err_free_priv: > + mutex_destroy(&priv->tph_lock); > kfree(priv); > err_free_ranges: > kfree(dma_ranges); > return ret; > } > > +int vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, > + u32 flags, > + struct vfio_device_feature_dma_buf_tph __user *arg, > + size_t argsz) > +{ > + struct vfio_device_feature_dma_buf_tph set_tph; > + struct vfio_pci_dma_buf *priv; > + struct dma_buf *dmabuf; > + int ret; > + > + if (!pcie_tph_supported(vdev->pdev)) > + return -EOPNOTSUPP; > + > + ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_SET, > + sizeof(set_tph)); > + if (ret != 1) > + return ret; > + > + if (copy_from_user(&set_tph, arg, sizeof(set_tph))) > + return -EFAULT; > + > + if (set_tph.flags & ~(VFIO_DMA_BUF_TPH_ST | VFIO_DMA_BUF_TPH_ST_EXT)) > + return -EINVAL; > + > + /* PCIe TLP Processing Hint is a 2-bit field. */ > + if (set_tph.ph & ~0x3) > + return -EINVAL; > + > + dmabuf = dma_buf_get(set_tph.dmabuf_fd); > + if (IS_ERR(dmabuf)) > + return PTR_ERR(dmabuf); > + > + if (dmabuf->ops != &vfio_pci_dmabuf_ops) { > + ret = -EINVAL; > + goto out_put; > + } > + > + priv = dmabuf->priv; > + if (priv->vdev != vdev) { > + ret = -EINVAL; > + goto out_put; > + } > + > + scoped_guard(mutex, &priv->tph_lock) { > + priv->tph_st = set_tph.steering_tag; > + priv->tph_st_ext = set_tph.steering_tag_ext; > + priv->tph_ph = set_tph.ph; > + priv->tph_st_valid = !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST); > + priv->tph_st_ext_valid = > + !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST_EXT); > + } > + ret = 0; > + > +out_put: > + dma_buf_put(dmabuf); > + return ret; > +} > + > void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked) > { > struct vfio_pci_dma_buf *priv; > diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h > index fca9d0dfac90..c58f369be4b3 100644 > --- a/drivers/vfio/pci/vfio_pci_priv.h > +++ b/drivers/vfio/pci/vfio_pci_priv.h > @@ -118,6 +118,10 @@ static inline bool vfio_pci_is_vga(struct pci_dev *pdev) > int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, > struct vfio_device_feature_dma_buf __user *arg, > size_t argsz); > +int vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, > + u32 flags, > + struct vfio_device_feature_dma_buf_tph __user *arg, > + size_t argsz); > void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev); > void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked); > #else > @@ -128,6 +132,14 @@ vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, > { > return -ENOTTY; > } > + > +static inline int > +vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, u32 flags, > + struct vfio_device_feature_dma_buf_tph __user *arg, > + size_t argsz) > +{ > + return -ENOTTY; > +} > static inline void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev) > { > } > diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h > index 5de618a3a5ee..0ca26721849b 100644 > --- a/include/uapi/linux/vfio.h > +++ b/include/uapi/linux/vfio.h > @@ -1534,6 +1534,51 @@ struct vfio_device_feature_dma_buf { > */ > #define VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 12 > > +/** > + * Upon VFIO_DEVICE_FEATURE_SET associate TPH (TLP Processing Hints) metadata > + * with a vfio-exported dma-buf. The dma-buf must have been created by > + * VFIO_DEVICE_FEATURE_DMA_BUF on this device, and the device must expose the > + * TPH Extended Capability (otherwise the ioctl returns -EOPNOTSUPP). > + * > + * dmabuf_fd is the file descriptor returned by VFIO_DEVICE_FEATURE_DMA_BUF. > + * > + * 8-bit ST (steering_tag) and 16-bit Extended ST (steering_tag_ext) are > + * distinct namespaces in the PCIe TPH ST table and may both be present with > + * different values. Userspace should populate the value(s) it has from the > + * firmware ST table for this device and set the matching VFIO_DMA_BUF_TPH_ST / > + * VFIO_DMA_BUF_TPH_ST_EXT bit in @flags. An importer requests a specific > + * width and receives the matching value; if the requested width is not > + * present, the importer is told TPH is unavailable for this dma-buf. > + * > + * This publishes the PCI SIG-defined ST/PH tuple for a VFIO-owned PCIe > + * completer. The dma-buf core treats the tuple as opaque completer-owned > + * metadata; an importer simply requests the namespace it supports and places > + * the returned value on generated TLPs. > + * > + * @flags == 0 clears any previously published metadata. > + * > + * ph is the 2-bit TLP Processing Hint and must be in the range [0, 3]. > + * > + * Userspace is responsible for setting TPH on the dma-buf before handing the > + * fd to the importer. Calling SET again replaces the previously published > + * values; racing a SET against an importer that is already consuming the > + * dma-buf is a userspace ordering problem. > + * > + * Return: 0 on success, -errno on failure. > + */ > +#define VFIO_DEVICE_FEATURE_DMA_BUF_TPH 13 > + > +#define VFIO_DMA_BUF_TPH_ST (1 << 0) /* steering_tag valid */ > +#define VFIO_DMA_BUF_TPH_ST_EXT (1 << 1) /* steering_tag_ext valid */ > + > +struct vfio_device_feature_dma_buf_tph { > + __s32 dmabuf_fd; > + __u32 flags; > + __u16 steering_tag_ext; > + __u8 steering_tag; > + __u8 ph; > +}; > + > /* -------- API for Type1 VFIO IOMMU -------- */ > > /** ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature 2026-06-09 8:12 ` Christian König @ 2026-06-09 14:39 ` Zhiping Zhang 0 siblings, 0 replies; 17+ messages in thread From: Zhiping Zhang @ 2026-06-09 14:39 UTC (permalink / raw) To: Christian König Cc: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas > > drivers/vfio/pci/vfio_pci_core.c | 3 + > > drivers/vfio/pci/vfio_pci_dmabuf.c | 92 +++++++++++++++++++++++++++++- > > drivers/vfio/pci/vfio_pci_priv.h | 12 ++++ > > include/uapi/linux/vfio.h | 45 +++++++++++++++ > > 4 files changed, 151 insertions(+), 1 deletion(-) > > > > diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c > > index 050e7542952e..4fa36f2f7555 100644 > > --- a/drivers/vfio/pci/vfio_pci_core.c > > +++ b/drivers/vfio/pci/vfio_pci_core.c > > @@ -1569,6 +1569,9 @@ int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags, > > return vfio_pci_core_feature_token(vdev, flags, arg, argsz); > > case VFIO_DEVICE_FEATURE_DMA_BUF: > > return vfio_pci_core_feature_dma_buf(vdev, flags, arg, argsz); > > + case VFIO_DEVICE_FEATURE_DMA_BUF_TPH: > > + return vfio_pci_core_feature_dma_buf_tph(vdev, flags, arg, > > + argsz); > > default: > > return -ENOTTY; > > } > > diff --git a/drivers/vfio/pci/vfio_pci_dmabuf.c b/drivers/vfio/pci/vfio_pci_dmabuf.c > > index 1a177ce7de54..dd11a7db6b41 100644 > > --- a/drivers/vfio/pci/vfio_pci_dmabuf.c > > +++ b/drivers/vfio/pci/vfio_pci_dmabuf.c > > @@ -2,7 +2,9 @@ > > /* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. > > */ > > #include <linux/dma-buf-mapping.h> > > +#include <linux/mutex.h> > > #include <linux/pci-p2pdma.h> > > +#include <linux/pci-tph.h> > > #include <linux/dma-resv.h> > > > > #include "vfio_pci_priv.h" > > @@ -19,7 +21,14 @@ struct vfio_pci_dma_buf { > > u32 nr_ranges; > > struct kref kref; > > struct completion comp; > > - u8 revoked : 1; > > > + /* @tph_lock serializes TPH SET vs get_tph on the TPH fields below. */ > > + struct mutex tph_lock; > > Clear NO-GO. > > When that info is exposed through DMA-buf it must be protected by the DMA-buf resv lock. > > Christian. > Understood, will fix in next revision. Thanks, Zhiping ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature 2026-06-08 18:56 ` [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature Zhiping Zhang 2026-06-09 8:12 ` Christian König @ 2026-06-09 19:38 ` sashiko-bot 2026-06-09 21:46 ` Alex Williamson 2 siblings, 0 replies; 17+ messages in thread From: sashiko-bot @ 2026-06-09 19:38 UTC (permalink / raw) To: Zhiping Zhang; +Cc: kvm, linux-pci, dri-devel Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Medium] Unconditional validation of the flag-gated `ph` parameter causes valid requests clearing metadata to be rejected. - [Low] Mixing `goto`-based error handling with scope-based cleanup helpers (`scoped_guard`) in the same function. - [Low] Unprotected read of `priv->vdev` races with concurrent write to `NULL`. -- commit f7be31969e4c92a551932ef245b8d9697e571272 Author: Zhiping Zhang <zhipingz@meta.com> vfio/pci: implement get_tph and DMA_BUF_TPH feature This commit implements the dma-buf get_tph callback for vfio-pci-exported dma-bufs. It also adds the VFIO_DEVICE_FEATURE_DMA_BUF_TPH feature to allow userspace to attach TPH metadata to a dma-buf. > diff --git a/drivers/vfio/pci/vfio_pci_dmabuf.c b/drivers/vfio/pci/vfio_pci_dmabuf.c > index 1a177ce7de546..f1bc3733d3db5 100644 > --- a/drivers/vfio/pci/vfio_pci_dmabuf.c > +++ b/drivers/vfio/pci/vfio_pci_dmabuf.c [ ... ] > @@ -327,12 +358,71 @@ int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, [ ... ] > +int vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, > + u32 flags, > + struct vfio_device_feature_dma_buf_tph __user *arg, > + size_t argsz) > +{ > + struct vfio_device_feature_dma_buf_tph set_tph; > + struct vfio_pci_dma_buf *priv; > + struct dma_buf *dmabuf; > + int ret; > + > + if (!pcie_tph_supported(vdev->pdev)) > + return -EOPNOTSUPP; > + > + ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_SET, > + sizeof(set_tph)); > + if (ret != 1) > + return ret; > + > + if (copy_from_user(&set_tph, arg, sizeof(set_tph))) > + return -EFAULT; > + > + if (set_tph.flags & ~(VFIO_DMA_BUF_TPH_ST | VFIO_DMA_BUF_TPH_ST_EXT)) > + return -EINVAL; > + > + /* PCIe TLP Processing Hint is a 2-bit field. */ > + if (set_tph.ph & ~0x3) > + return -EINVAL; [Severity: Medium] Will this unconditionally reject requests clearing the metadata? The documentation mentions that setting flags to 0 clears previously published metadata. However, because this check is evaluated unconditionally, userspace leaving the ph field uninitialized (containing stack garbage) when passing flags = 0 will cause the kernel to erroneously reject a valid clear request with -EINVAL. Should this validation only occur if the relevant flags are set? > + > + dmabuf = dma_buf_get(set_tph.dmabuf_fd); > + if (IS_ERR(dmabuf)) > + return PTR_ERR(dmabuf); > + > + if (dmabuf->ops != &vfio_pci_dmabuf_ops) { > + ret = -EINVAL; > + goto out_put; > + } > + > + priv = dmabuf->priv; > + if (priv->vdev != vdev) { [Severity: Low] Does this unprotected read of priv->vdev race with concurrent unbound operations? In vfio_pci_dma_buf_cleanup(), there is a concurrent write setting priv->vdev = NULL under the vdev->memory_lock semaphore. Since this read occurs without locks, can it race with the concurrent write and cause KCSAN warnings or load tearing? Would using READ_ONCE() here and WRITE_ONCE() in vfio_pci_dma_buf_cleanup() help prevent this? > + ret = -EINVAL; > + goto out_put; > + } > + > + scoped_guard(mutex, &priv->tph_lock) { > + priv->tph_st = set_tph.steering_tag; > + priv->tph_st_ext = set_tph.steering_tag_ext; > + priv->tph_ph = set_tph.ph; > + priv->tph_st_valid = !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST); > + priv->tph_st_ext_valid = > + !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST_EXT); > + } > + ret = 0; > + > +out_put: [Severity: Low] Is it okay to mix goto-based error handling and scope-based cleanup helpers in this function? The kernel cleanup guidelines discourage mixing goto labels for cleanup (like goto out_put here) and scoped_guard() in the same function to avoid confusing ownership semantics. Would it be better to either use a __free() wrapper for the dmabuf or stick to traditional mutex_lock()/mutex_unlock() here? > + dma_buf_put(dmabuf); > + return ret; > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260608185646.4085127-1-zhipingz@meta.com?part=4 ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature 2026-06-08 18:56 ` [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature Zhiping Zhang 2026-06-09 8:12 ` Christian König 2026-06-09 19:38 ` sashiko-bot @ 2026-06-09 21:46 ` Alex Williamson 2026-06-09 23:48 ` Zhiping Zhang 2 siblings, 1 reply; 17+ messages in thread From: Alex Williamson @ 2026-06-09 21:46 UTC (permalink / raw) To: Zhiping Zhang Cc: Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig, Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas, alex On Mon, 8 Jun 2026 11:56:41 -0700 Zhiping Zhang <zhipingz@meta.com> wrote: > @@ -327,12 +358,71 @@ int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, > err_free_phys: > kfree(priv->phys_vec); > err_free_priv: > + mutex_destroy(&priv->tph_lock); > kfree(priv); > err_free_ranges: > kfree(dma_ranges); > return ret; > } > > +int vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, > + u32 flags, > + struct vfio_device_feature_dma_buf_tph __user *arg, > + size_t argsz) > +{ > + struct vfio_device_feature_dma_buf_tph set_tph; > + struct vfio_pci_dma_buf *priv; > + struct dma_buf *dmabuf; > + int ret; > + > + if (!pcie_tph_supported(vdev->pdev)) > + return -EOPNOTSUPP; This tests for the TPH capability, but the TPH capability is only a requirement for functions that generate TLPs with TPH, ie. a requester. This feature is about providing TPH steering tags when the device is a completer. Bits 13:12 of the Device Capabilities 2 register indicate if the device is supported as a TPH completer. Additionally these bits indicate if the device supports standard and extended TPH, which means we should not only fail if the device reports 00b, but should reject extended steering tags unless the device reports 11b. > + > + ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_SET, > + sizeof(set_tph)); > + if (ret != 1) > + return ret; > + > + if (copy_from_user(&set_tph, arg, sizeof(set_tph))) > + return -EFAULT; > + > + if (set_tph.flags & ~(VFIO_DMA_BUF_TPH_ST | VFIO_DMA_BUF_TPH_ST_EXT)) > + return -EINVAL; > + > + /* PCIe TLP Processing Hint is a 2-bit field. */ > + if (set_tph.ph & ~0x3) > + return -EINVAL; Sashiko notes what appears to be a false positive here, the uAPI states ph is to be in the range [0, 3] and nowhere else says that it's allowed to be garbage for a clear operation. > + > + dmabuf = dma_buf_get(set_tph.dmabuf_fd); > + if (IS_ERR(dmabuf)) > + return PTR_ERR(dmabuf); > + > + if (dmabuf->ops != &vfio_pci_dmabuf_ops) { > + ret = -EINVAL; > + goto out_put; > + } > + > + priv = dmabuf->priv; > + if (priv->vdev != vdev) { > + ret = -EINVAL; > + goto out_put; > + } Sashiko notes this may need READ_ONCE()/WRITE_ONCE() semantics, but that may get fixed as part of the resv lock usage. > + > + scoped_guard(mutex, &priv->tph_lock) { > + priv->tph_st = set_tph.steering_tag; > + priv->tph_st_ext = set_tph.steering_tag_ext; > + priv->tph_ph = set_tph.ph; > + priv->tph_st_valid = !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST); > + priv->tph_st_ext_valid = > + !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST_EXT); > + } > + ret = 0; > + > +out_put: > + dma_buf_put(dmabuf); > + return ret; > +} > + > void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked) > { > struct vfio_pci_dma_buf *priv; ... > diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h > index 5de618a3a5ee..0ca26721849b 100644 > --- a/include/uapi/linux/vfio.h > +++ b/include/uapi/linux/vfio.h > @@ -1534,6 +1534,51 @@ struct vfio_device_feature_dma_buf { > */ > #define VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 12 > > +/** > + * Upon VFIO_DEVICE_FEATURE_SET associate TPH (TLP Processing Hints) metadata > + * with a vfio-exported dma-buf. The dma-buf must have been created by > + * VFIO_DEVICE_FEATURE_DMA_BUF on this device, and the device must expose the > + * TPH Extended Capability (otherwise the ioctl returns -EOPNOTSUPP). > + * > + * dmabuf_fd is the file descriptor returned by VFIO_DEVICE_FEATURE_DMA_BUF. > + * > + * 8-bit ST (steering_tag) and 16-bit Extended ST (steering_tag_ext) are > + * distinct namespaces in the PCIe TPH ST table and may both be present with > + * different values. Userspace should populate the value(s) it has from the > + * firmware ST table for this device and set the matching VFIO_DMA_BUF_TPH_ST / > + * VFIO_DMA_BUF_TPH_ST_EXT bit in @flags. An importer requests a specific > + * width and receives the matching value; if the requested width is not > + * present, the importer is told TPH is unavailable for this dma-buf. > + * > + * This publishes the PCI SIG-defined ST/PH tuple for a VFIO-owned PCIe > + * completer. The dma-buf core treats the tuple as opaque completer-owned > + * metadata; an importer simply requests the namespace it supports and places > + * the returned value on generated TLPs. > + * > + * @flags == 0 clears any previously published metadata. This is overselling the invalidation. It only flags the fields as invalid for future get_tph() requests, it does nothing to clear previously published metadata from importers. Thanks, Alex > + * > + * ph is the 2-bit TLP Processing Hint and must be in the range [0, 3]. > + * > + * Userspace is responsible for setting TPH on the dma-buf before handing the > + * fd to the importer. Calling SET again replaces the previously published > + * values; racing a SET against an importer that is already consuming the > + * dma-buf is a userspace ordering problem. > + * > + * Return: 0 on success, -errno on failure. > + */ > +#define VFIO_DEVICE_FEATURE_DMA_BUF_TPH 13 > + > +#define VFIO_DMA_BUF_TPH_ST (1 << 0) /* steering_tag valid */ > +#define VFIO_DMA_BUF_TPH_ST_EXT (1 << 1) /* steering_tag_ext valid */ > + > +struct vfio_device_feature_dma_buf_tph { > + __s32 dmabuf_fd; > + __u32 flags; > + __u16 steering_tag_ext; > + __u8 steering_tag; > + __u8 ph; > +}; > + > /* -------- API for Type1 VFIO IOMMU -------- */ > > /** ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature 2026-06-09 21:46 ` Alex Williamson @ 2026-06-09 23:48 ` Zhiping Zhang 0 siblings, 0 replies; 17+ messages in thread From: Zhiping Zhang @ 2026-06-09 23:48 UTC (permalink / raw) To: Alex Williamson Cc: Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig, Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas > > +int vfio_pci_core_feature_dma_buf_tph(struct vfio_pci_core_device *vdev, > > + u32 flags, > > + struct vfio_device_feature_dma_buf_tph __user *arg, > > + size_t argsz) > > +{ > > + struct vfio_device_feature_dma_buf_tph set_tph; > > + struct vfio_pci_dma_buf *priv; > > + struct dma_buf *dmabuf; > > + int ret; > > + > > + if (!pcie_tph_supported(vdev->pdev)) > > + return -EOPNOTSUPP; > > This tests for the TPH capability, but the TPH capability is only a > requirement for functions that generate TLPs with TPH, ie. a requester. > This feature is about providing TPH steering tags when the device is a > completer. Bits 13:12 of the Device Capabilities 2 register indicate > if the device is supported as a TPH completer. > > Additionally these bits indicate if the device supports standard and > extended TPH, which means we should not only fail if the device reports > 00b, but should reject extended steering tags unless the device reports > 11b. > You are right: pcie_tph_supported is not correct here. Let me use a helper function to return something like this: PCI_TPH_COMP_{NONE, TPH_ONLY, EXT_TPH}. > > + > > + ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_SET, > > + sizeof(set_tph)); > > + if (ret != 1) > > + return ret; > > + > > + if (copy_from_user(&set_tph, arg, sizeof(set_tph))) > > + return -EFAULT; > > + > > + if (set_tph.flags & ~(VFIO_DMA_BUF_TPH_ST | VFIO_DMA_BUF_TPH_ST_EXT)) > > + return -EINVAL; > > + > > + /* PCIe TLP Processing Hint is a 2-bit field. */ > > + if (set_tph.ph & ~0x3) > > + return -EINVAL; > > Sashiko notes what appears to be a false positive here, the uAPI states > ph is to be in the range [0, 3] and nowhere else says that it's allowed > to be garbage for a clear operation. > Agreed - i will leave it as is. > > + > > + dmabuf = dma_buf_get(set_tph.dmabuf_fd); > > + if (IS_ERR(dmabuf)) > > + return PTR_ERR(dmabuf); > > + > > + if (dmabuf->ops != &vfio_pci_dmabuf_ops) { > > + ret = -EINVAL; > > + goto out_put; > > + } > > + > > + priv = dmabuf->priv; > > + if (priv->vdev != vdev) { > > + ret = -EINVAL; > > + goto out_put; > > + } > > Sashiko notes this may need READ_ONCE()/WRITE_ONCE() semantics, but > that may get fixed as part of the resv lock usage. > Ack. > > + > > + scoped_guard(mutex, &priv->tph_lock) { > > + priv->tph_st = set_tph.steering_tag; > > + priv->tph_st_ext = set_tph.steering_tag_ext; > > + priv->tph_ph = set_tph.ph; > > + priv->tph_st_valid = !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST); > > + priv->tph_st_ext_valid = > > + !!(set_tph.flags & VFIO_DMA_BUF_TPH_ST_EXT); > > + } > > + ret = 0; > > + > > +out_put: > > + dma_buf_put(dmabuf); > > + return ret; > > +} > > + > > void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked) > > { > > struct vfio_pci_dma_buf *priv; > ... > > diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h > > index 5de618a3a5ee..0ca26721849b 100644 > > --- a/include/uapi/linux/vfio.h > > +++ b/include/uapi/linux/vfio.h > > @@ -1534,6 +1534,51 @@ struct vfio_device_feature_dma_buf { > > */ > > #define VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 12 > > > > +/** > > + * Upon VFIO_DEVICE_FEATURE_SET associate TPH (TLP Processing Hints) metadata > > + * with a vfio-exported dma-buf. The dma-buf must have been created by > > + * VFIO_DEVICE_FEATURE_DMA_BUF on this device, and the device must expose the > > + * TPH Extended Capability (otherwise the ioctl returns -EOPNOTSUPP). > > + * > > + * dmabuf_fd is the file descriptor returned by VFIO_DEVICE_FEATURE_DMA_BUF. > > + * > > + * 8-bit ST (steering_tag) and 16-bit Extended ST (steering_tag_ext) are > > + * distinct namespaces in the PCIe TPH ST table and may both be present with > > + * different values. Userspace should populate the value(s) it has from the > > + * firmware ST table for this device and set the matching VFIO_DMA_BUF_TPH_ST / > > + * VFIO_DMA_BUF_TPH_ST_EXT bit in @flags. An importer requests a specific > > + * width and receives the matching value; if the requested width is not > > + * present, the importer is told TPH is unavailable for this dma-buf. > > + * > > + * This publishes the PCI SIG-defined ST/PH tuple for a VFIO-owned PCIe > > + * completer. The dma-buf core treats the tuple as opaque completer-owned > > + * metadata; an importer simply requests the namespace it supports and places > > + * the returned value on generated TLPs. > > + * > > + * @flags == 0 clears any previously published metadata. > > This is overselling the invalidation. It only flags the fields as > invalid for future get_tph() requests, it does nothing to clear > previously published metadata from importers. Thanks, > > Alex > Good catch, let me re-word. Thanks, Zhiping ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 5/5] RDMA/mlx5: get tph for p2p access when registering dma-buf mr 2026-06-08 18:56 [PATCH v6 0/5] vfio/dma-buf: add TPH support for peer-to-peer access Zhiping Zhang ` (3 preceding siblings ...) 2026-06-08 18:56 ` [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature Zhiping Zhang @ 2026-06-08 18:56 ` Zhiping Zhang 2026-06-09 19:38 ` sashiko-bot 4 siblings, 1 reply; 17+ messages in thread From: Zhiping Zhang @ 2026-06-08 18:56 UTC (permalink / raw) To: Alex Williamson, Jason Gunthorpe, Leon Romanovsky, Sumit Semwal, Christian Konig Cc: Bjorn Helgaas, kvm, linux-rdma, linux-pci, netdev, dri-devel, Keith Busch, Yochai Cohen, Yishai Hadas, Zhiping Zhang Query dma-buf TPH metadata when registering a dma-buf MR for peer-to- peer access and translate the returned steering tag into an mlx5 ST index. Keep the DMAH path as the first priority and only fall back to DMA-buf metadata when no DMAH is supplied. Split the existing mlx5_st_alloc_index() into mlx5_st_alloc_index_by_tag() plus a tag-from-cpu wrapper so the dma-buf path can allocate an ST index directly from a raw steering tag without going through the per-CPU table. mlx5_st_alloc_index_by_tag() explicitly initialises 'ret' so the duplicate-tag fast path doesn't return an uninitialised value, which would otherwise be observed by callers when an MR re-uses a tag that already has an ST index allocated. For TPH-backed FRMRs, the extra ST-table reference belongs to the hardware mkey handle, not the transient MR object. Add mlx5_st_get_index() and extend the FRMR pool API so ib_frmr_pool_pop() reports whether a handle was reused and destroy_frmrs() receives the pool key. The DMAH and dma-buf paths take a provisional ST ref before pool lookup; reuse drops that provisional ref immediately, while newly created handles keep it and release it only when the FRMR handle is actually destroyed, either directly or through FRMR pool aging/cleanup. Also decode the PH bits stored in kernel_vendor_key when recreating pooled mkeys so the programmed requester hint matches the pool key. Signed-off-by: Zhiping Zhang <zhipingz@meta.com> --- drivers/infiniband/core/frmr_pools.c | 20 ++- drivers/infiniband/hw/mlx5/mr.c | 124 +++++++++++++++++- .../net/ethernet/mellanox/mlx5/core/lib/st.c | 49 +++++-- include/linux/mlx5/driver.h | 12 ++ include/rdma/frmr_pools.h | 5 +- 5 files changed, 191 insertions(+), 19 deletions(-) diff --git a/drivers/infiniband/core/frmr_pools.c b/drivers/infiniband/core/frmr_pools.c index 5e992ff3d7cf..61a77847118e 100644 --- a/drivers/infiniband/core/frmr_pools.c +++ b/drivers/infiniband/core/frmr_pools.c @@ -92,7 +92,8 @@ static void destroy_all_handles_in_queue(struct ib_device *device, u32 count; while (pop_frmr_handles_page(pool, queue, &page, &count)) { - pools->pool_ops->destroy_frmrs(device, page->handles, count); + pools->pool_ops->destroy_frmrs(device, &pool->key, + page->handles, count); kfree(page); } } @@ -136,7 +137,8 @@ static bool age_pinned_pool(struct ib_device *device, struct ib_frmr_pool *pool) spin_unlock(&pool->lock); if (destroyed) - pools->pool_ops->destroy_frmrs(device, handles, destroyed); + pools->pool_ops->destroy_frmrs(device, &pool->key, handles, + destroyed); kfree(handles); return has_work; } @@ -453,9 +455,11 @@ int ib_frmr_pools_set_pinned(struct ib_device *device, struct ib_frmr_key *key, } static int get_frmr_from_pool(struct ib_device *device, - struct ib_frmr_pool *pool, struct ib_mr *mr) + struct ib_frmr_pool *pool, struct ib_mr *mr, + bool *reused) { struct ib_frmr_pools *pools = device->frmr_pools; + bool local_reused = false; u32 handle; int err; @@ -464,6 +468,7 @@ static int get_frmr_from_pool(struct ib_device *device, if (pool->inactive_queue.ci > 0) { handle = pop_handle_from_queue_locked( &pool->inactive_queue); + local_reused = true; } else { spin_unlock(&pool->lock); err = pools->pool_ops->create_frmrs(device, &pool->key, @@ -474,6 +479,7 @@ static int get_frmr_from_pool(struct ib_device *device, } } else { handle = pop_handle_from_queue_locked(&pool->queue); + local_reused = true; } pool->in_use++; @@ -484,6 +490,8 @@ static int get_frmr_from_pool(struct ib_device *device, mr->frmr.pool = pool; mr->frmr.handle = handle; + if (reused) + *reused = local_reused; return 0; } @@ -493,10 +501,12 @@ static int get_frmr_from_pool(struct ib_device *device, * * @device: The device to pop the FRMR handle from. * @mr: The MR to pop the FRMR handle from. + * @reused: Optional output that reports whether the returned handle was + * reused from the pool instead of freshly created. * * Returns 0 on success, negative error code on failure. */ -int ib_frmr_pool_pop(struct ib_device *device, struct ib_mr *mr) +int ib_frmr_pool_pop(struct ib_device *device, struct ib_mr *mr, bool *reused) { struct ib_frmr_pools *pools = device->frmr_pools; struct ib_frmr_pool *pool; @@ -509,7 +519,7 @@ int ib_frmr_pool_pop(struct ib_device *device, struct ib_mr *mr) return PTR_ERR(pool); } - return get_frmr_from_pool(device, pool, mr); + return get_frmr_from_pool(device, pool, mr, reused); } EXPORT_SYMBOL(ib_frmr_pool_pop); diff --git a/drivers/infiniband/hw/mlx5/mr.c b/drivers/infiniband/hw/mlx5/mr.c index 3b6da45061a5..b56df39d3385 100644 --- a/drivers/infiniband/hw/mlx5/mr.c +++ b/drivers/infiniband/hw/mlx5/mr.c @@ -38,6 +38,7 @@ #include <linux/delay.h> #include <linux/dma-buf.h> #include <linux/dma-resv.h> +#include <linux/pci-tph.h> #include <rdma/frmr_pools.h> #include <rdma/ib_umem_odp.h> #include "dm.h" @@ -167,12 +168,39 @@ static int get_unchangeable_access_flags(struct mlx5_ib_dev *dev, #define MLX5_FRMR_POOLS_KERNEL_KEY_PH_MASK 0xFF0000 #define MLX5_FRMR_POOLS_KERNEL_KEY_ST_INDEX_MASK 0xFFFF +static int mlx5_ib_get_frmr_st_handle_ref(struct mlx5_ib_dev *dev, + u16 st_index) +{ + if (st_index == MLX5_MKC_PCIE_TPH_NO_STEERING_TAG_INDEX) + return 0; + + return mlx5_st_get_index(dev->mdev, st_index); +} + +static void mlx5_ib_put_st_index_ref(struct mlx5_ib_dev *dev, u16 st_index) +{ + if (st_index == MLX5_MKC_PCIE_TPH_NO_STEERING_TAG_INDEX) + return; + + mlx5_st_dealloc_index(dev->mdev, st_index); +} + +static void mlx5_ib_put_frmr_st_handle_ref(struct mlx5_ib_dev *dev, + u64 kernel_vendor_key) +{ + u16 st_index = kernel_vendor_key & + MLX5_FRMR_POOLS_KERNEL_KEY_ST_INDEX_MASK; + + mlx5_ib_put_st_index_ref(dev, st_index); +} + static struct mlx5_ib_mr * _mlx5_frmr_pool_alloc(struct mlx5_ib_dev *dev, struct ib_umem *umem, int access_flags, int access_mode, unsigned long page_size, u16 st_index, u8 ph) { struct mlx5_ib_mr *mr; + bool reused = false; int err; mr = kzalloc_obj(*mr); @@ -195,11 +223,14 @@ _mlx5_frmr_pool_alloc(struct mlx5_ib_dev *dev, struct ib_umem *umem, mr->ibmr.frmr.key.kernel_vendor_key = st_index | (ph << MLX5_FRMR_POOLS_KERNEL_KEY_PH_SHIFT); - err = ib_frmr_pool_pop(&dev->ib_dev, &mr->ibmr); + err = ib_frmr_pool_pop(&dev->ib_dev, &mr->ibmr, &reused); if (err) { kfree(mr); return ERR_PTR(err); } + if (reused) + mlx5_ib_put_frmr_st_handle_ref( + dev, mr->ibmr.frmr.key.kernel_vendor_key); mr->mmkey.key = mr->ibmr.frmr.handle; init_waitqueue_head(&mr->mmkey.wait); @@ -229,7 +260,7 @@ struct mlx5_ib_mr *mlx5_mr_cache_alloc(struct mlx5_ib_dev *dev, init_waitqueue_head(&mr->mmkey.wait); mr->ibmr.frmr.key = key; - ret = ib_frmr_pool_pop(&dev->ib_dev, &mr->ibmr); + ret = ib_frmr_pool_pop(&dev->ib_dev, &mr->ibmr, NULL); if (ret) { kfree(mr); return ERR_PTR(ret); @@ -273,7 +304,8 @@ static int mlx5r_create_mkeys(struct ib_device *device, struct ib_frmr_key *key, st_index = key->kernel_vendor_key & MLX5_FRMR_POOLS_KERNEL_KEY_ST_INDEX_MASK; - ph = key->kernel_vendor_key & MLX5_FRMR_POOLS_KERNEL_KEY_PH_MASK; + ph = (key->kernel_vendor_key & MLX5_FRMR_POOLS_KERNEL_KEY_PH_MASK) >> + MLX5_FRMR_POOLS_KERNEL_KEY_PH_SHIFT; if (ph) { /* Normalize ph: swap MLX5_IB_NO_PH for 0 */ if (ph == MLX5_IB_NO_PH) @@ -299,7 +331,8 @@ static int mlx5r_create_mkeys(struct ib_device *device, struct ib_frmr_key *key, return err; } -static void mlx5r_destroy_mkeys(struct ib_device *device, u32 *handles, +static void mlx5r_destroy_mkeys(struct ib_device *device, + const struct ib_frmr_key *key, u32 *handles, unsigned int count) { struct mlx5_ib_dev *dev = to_mdev(device); @@ -311,6 +344,9 @@ static void mlx5r_destroy_mkeys(struct ib_device *device, u32 *handles, pr_warn_ratelimited( "mlx5_ib: failed to destroy mkey %d: %d", handles[i], err); + else + mlx5_ib_put_frmr_st_handle_ref(dev, + key->kernel_vendor_key); } } @@ -333,6 +369,7 @@ static int mlx5r_build_frmr_key(struct ib_device *device, get_unchangeable_access_flags(dev, in->access_flags); out->vendor_key = in->vendor_key; out->num_dma_blocks = in->num_dma_blocks; + out->kernel_vendor_key = in->kernel_vendor_key; return 0; } @@ -753,6 +790,12 @@ static struct ib_mr *create_real_mr(struct ib_pd *pd, struct ib_umem *umem, xlt_with_umr = mlx5r_umr_can_load_pas(dev, umem->length); if (xlt_with_umr) { + err = mlx5_ib_get_frmr_st_handle_ref(dev, st_index); + if (err) { + ib_umem_release(umem); + return ERR_PTR(err); + } + mr = alloc_cacheable_mr(pd, umem, iova, access_flags, MLX5_MKC_ACCESS_MODE_MTT, st_index, ph); @@ -767,6 +810,8 @@ static struct ib_mr *create_real_mr(struct ib_pd *pd, struct ib_umem *umem, mutex_unlock(&dev->slow_path_mutex); } if (IS_ERR(mr)) { + if (xlt_with_umr) + mlx5_ib_put_st_index_ref(dev, st_index); ib_umem_release(umem); return ERR_CAST(mr); } @@ -899,6 +944,65 @@ static struct dma_buf_attach_ops mlx5_ib_dmabuf_attach_ops = { .invalidate_mappings = mlx5_ib_dmabuf_invalidate_cb, }; +/* + * Query TPH metadata from @dmabuf and translate the raw steering tag into + * an mlx5 ST index. On success *@st_index is updated with a provisional + * reference for a candidate FRMR handle and *@ph is updated to the dma-buf's + * processing hint. Callers that fail to allocate a handle, or that reuse an + * existing pooled handle, must drop the provisional ST reference. On any + * failure *@st_index and *@ph are left untouched, so the caller's no-TPH + * defaults stand. + * + * @dmabuf must already be referenced by the caller (e.g. via the umem's + * attachment) so we don't re-resolve the user's fd here and avoid a + * dup2() TOCTOU between umem creation and TPH lookup. + */ +static void get_tph_mr_dmabuf(struct mlx5_ib_dev *dev, struct dma_buf *dmabuf, + u16 *st_index, u8 *ph) +{ + u16 local_st_index; + u16 steering_tag; + u8 local_ph; + bool extended; + int ret; + + if (!dmabuf->ops->get_tph) + return; + + switch (pcie_tph_enabled_req_type(dev->mdev->pdev)) { + case PCI_TPH_REQ_TPH_ONLY: + extended = false; + break; + case PCI_TPH_REQ_EXT_TPH: + extended = true; + break; + default: + return; + } + + ret = dmabuf->ops->get_tph(dmabuf, extended, &steering_tag, &local_ph); + if (ret) { + mlx5_ib_dbg(dev, "get_tph failed (%d)\n", ret); + return; + } + + ret = mlx5_st_alloc_index_by_tag(dev->mdev, steering_tag, + &local_st_index); + if (ret) { + mlx5_ib_dbg(dev, "st_alloc_index_by_tag failed (%d)\n", ret); + return; + } + + *st_index = local_st_index; + *ph = local_ph; +} + +static void mlx5_ib_mr_put_frmr_st_handle_ref(struct mlx5_ib_mr *mr) +{ + mlx5_ib_put_frmr_st_handle_ref(mr_to_mdev(mr), + mr->ibmr.frmr.key.kernel_vendor_key); +} + static struct ib_mr * reg_user_mr_dmabuf(struct ib_pd *pd, struct device *dma_device, u64 offset, u64 length, u64 virt_addr, @@ -941,12 +1045,22 @@ reg_user_mr_dmabuf(struct ib_pd *pd, struct device *dma_device, ph = dmah->ph; if (dmah->valid_fields & BIT(IB_DMAH_CPU_ID_EXISTS)) st_index = mdmah->st_index; + + err = mlx5_ib_get_frmr_st_handle_ref(dev, st_index); + if (err) { + ib_umem_release(&umem_dmabuf->umem); + return ERR_PTR(err); + } + } else { + get_tph_mr_dmabuf(dev, umem_dmabuf->attach->dmabuf, + &st_index, &ph); } mr = alloc_cacheable_mr(pd, &umem_dmabuf->umem, virt_addr, access_flags, access_mode, st_index, ph); if (IS_ERR(mr)) { + mlx5_ib_put_st_index_ref(dev, st_index); ib_umem_release(&umem_dmabuf->umem); return ERR_CAST(mr); } @@ -1400,6 +1514,8 @@ static int mlx5r_handle_mkey_cleanup(struct mlx5_ib_mr *mr) dma_resv_unlock( to_ib_umem_dmabuf(mr->umem)->attach->dmabuf->resv); } + if (!ret) + mlx5_ib_mr_put_frmr_st_handle_ref(mr); return ret; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c index 7cedc348790d..877b37b4e639 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c @@ -92,23 +92,18 @@ void mlx5_st_destroy(struct mlx5_core_dev *dev) kfree(st); } -int mlx5_st_alloc_index(struct mlx5_core_dev *dev, enum tph_mem_type mem_type, - unsigned int cpu_uid, u16 *st_index) +int mlx5_st_alloc_index_by_tag(struct mlx5_core_dev *dev, u16 tag, + u16 *st_index) { struct mlx5_st_idx_data *idx_data; struct mlx5_st *st = dev->st; unsigned long index; u32 xa_id; - u16 tag; - int ret; + int ret = 0; if (!st) return -EOPNOTSUPP; - ret = pcie_tph_get_cpu_st(dev->pdev, mem_type, cpu_uid, &tag); - if (ret) - return ret; - if (st->direct_mode) { *st_index = tag; return 0; @@ -152,8 +147,46 @@ int mlx5_st_alloc_index(struct mlx5_core_dev *dev, enum tph_mem_type mem_type, mutex_unlock(&st->lock); return ret; } +EXPORT_SYMBOL_GPL(mlx5_st_alloc_index_by_tag); + +int mlx5_st_alloc_index(struct mlx5_core_dev *dev, enum tph_mem_type mem_type, + unsigned int cpu_uid, u16 *st_index) +{ + u16 tag; + int ret; + + ret = pcie_tph_get_cpu_st(dev->pdev, mem_type, cpu_uid, &tag); + if (ret) + return ret; + + return mlx5_st_alloc_index_by_tag(dev, tag, st_index); +} EXPORT_SYMBOL_GPL(mlx5_st_alloc_index); +int mlx5_st_get_index(struct mlx5_core_dev *dev, u16 st_index) +{ + struct mlx5_st_idx_data *idx_data; + struct mlx5_st *st = dev->st; + int ret = 0; + + if (!st) + return -EOPNOTSUPP; + + if (st->direct_mode) + return 0; + + mutex_lock(&st->lock); + idx_data = xa_load(&st->idx_xa, st_index); + if (WARN_ON_ONCE(!idx_data)) + ret = -EINVAL; + else + refcount_inc(&idx_data->usecount); + mutex_unlock(&st->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mlx5_st_get_index); + int mlx5_st_dealloc_index(struct mlx5_core_dev *dev, u16 st_index) { struct mlx5_st_idx_data *idx_data; diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 04b96c5abb57..0480b5c4f189 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -1166,10 +1166,22 @@ int mlx5_dm_sw_icm_dealloc(struct mlx5_core_dev *dev, enum mlx5_sw_icm_type type u64 length, u16 uid, phys_addr_t addr, u32 obj_id); #ifdef CONFIG_PCIE_TPH +int mlx5_st_alloc_index_by_tag(struct mlx5_core_dev *dev, u16 tag, + u16 *st_index); +int mlx5_st_get_index(struct mlx5_core_dev *dev, u16 st_index); int mlx5_st_alloc_index(struct mlx5_core_dev *dev, enum tph_mem_type mem_type, unsigned int cpu_uid, u16 *st_index); int mlx5_st_dealloc_index(struct mlx5_core_dev *dev, u16 st_index); #else +static inline int mlx5_st_alloc_index_by_tag(struct mlx5_core_dev *dev, + u16 tag, u16 *st_index) +{ + return -EOPNOTSUPP; +} +static inline int mlx5_st_get_index(struct mlx5_core_dev *dev, u16 st_index) +{ + return -EOPNOTSUPP; +} static inline int mlx5_st_alloc_index(struct mlx5_core_dev *dev, enum tph_mem_type mem_type, unsigned int cpu_uid, u16 *st_index) diff --git a/include/rdma/frmr_pools.h b/include/rdma/frmr_pools.h index af1b88801fa4..a08d2b2cf9f3 100644 --- a/include/rdma/frmr_pools.h +++ b/include/rdma/frmr_pools.h @@ -24,7 +24,8 @@ struct ib_frmr_key { struct ib_frmr_pool_ops { int (*create_frmrs)(struct ib_device *device, struct ib_frmr_key *key, u32 *handles, u32 count); - void (*destroy_frmrs)(struct ib_device *device, u32 *handles, + void (*destroy_frmrs)(struct ib_device *device, + const struct ib_frmr_key *key, u32 *handles, u32 count); int (*build_key)(struct ib_device *device, const struct ib_frmr_key *in, struct ib_frmr_key *out); @@ -33,7 +34,7 @@ struct ib_frmr_pool_ops { int ib_frmr_pools_init(struct ib_device *device, const struct ib_frmr_pool_ops *pool_ops); void ib_frmr_pools_cleanup(struct ib_device *device); -int ib_frmr_pool_pop(struct ib_device *device, struct ib_mr *mr); +int ib_frmr_pool_pop(struct ib_device *device, struct ib_mr *mr, bool *reused); int ib_frmr_pool_push(struct ib_device *device, struct ib_mr *mr); #endif /* FRMR_POOLS_H */ -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v6 5/5] RDMA/mlx5: get tph for p2p access when registering dma-buf mr 2026-06-08 18:56 ` [PATCH v6 5/5] RDMA/mlx5: get tph for p2p access when registering dma-buf mr Zhiping Zhang @ 2026-06-09 19:38 ` sashiko-bot 0 siblings, 0 replies; 17+ messages in thread From: sashiko-bot @ 2026-06-09 19:38 UTC (permalink / raw) To: Zhiping Zhang; +Cc: dri-devel, linux-pci, kvm Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] This problem wasn't introduced by this patch, but the `mlx5r_create_mkeys` error rollback loop contains an off-by-one error. It leaks the successfully created hardware mkey at `handles[0]` and improperly attempts to destroy the uninitialized failed handle at `handles[i]`, which translates to destroying the reserved hardware mkey index 0. -- commit 37ae7395b661b5e14306aec55bedd4ae96943e49 Author: Zhiping Zhang <zhipingz@meta.com> RDMA/mlx5: get tph for p2p access when registering dma-buf mr Query dma-buf TPH metadata when registering a dma-buf MR for peer-to- peer access and translate the returned steering tag into an mlx5 ST index. Keep the DMAH path as the first priority and only fall back to DMA-buf metadata when no DMAH is supplied. [ ... ] > diff --git a/drivers/infiniband/hw/mlx5/mr.c b/drivers/infiniband/hw/mlx5/mr.c > index 254e6aa4ccafb..7b0a883e04048 100644 > --- a/drivers/infiniband/hw/mlx5/mr.c > +++ b/drivers/infiniband/hw/mlx5/mr.c [ ... ] > @@ -299,7 +331,8 @@ static int mlx5r_create_mkeys(struct ib_device *device, struct ib_frmr_key *key, > return err; > } [Severity: High] This isn't a bug introduced by this patch, but while reviewing the changes in mlx5r_create_mkeys(), I noticed an off-by-one error in the error rollback loop right above this return statement. The error rollback loop is written as: if (err) for (; i > 0; i--) mlx5_core_destroy_mkey(dev->mdev, handles[i]); Since the loop body operates on handles[i] and decrements afterward, doesn't this terminate before i == 0, thereby skipping handles[0] and leaking the first successfully created hardware mkey? Additionally, is it safe to call mlx5_core_destroy_mkey() on the failed handles[i] where the error occurred? Because handles[i] only received an 8-bit variant via assign_mkey_variant() and the firmware failed to populate its hardware index, its upper 24 bits are zeroed. When mlx5_mkey_to_idx() extracts the upper 24 bits, it yields 0. Could this cause the driver to inadvertently destroy mkey index 0, which is a reserved hardware resource? > > -static void mlx5r_destroy_mkeys(struct ib_device *device, u32 *handles, > +static void mlx5r_destroy_mkeys(struct ib_device *device, > + const struct ib_frmr_key *key, u32 *handles, > unsigned int count) > { [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260608185646.4085127-1-zhipingz@meta.com?part=5 ^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-06-09 23:48 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-08 18:56 [PATCH v6 0/5] vfio/dma-buf: add TPH support for peer-to-peer access Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 1/5] net/mlx5: free mlx5_st_idx_data on final dealloc Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 2/5] PCI/TPH: expose enabled requester type and capability helpers Zhiping Zhang 2026-06-09 19:56 ` Bjorn Helgaas 2026-06-09 23:38 ` Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 3/5] dma-buf: add optional get_tph() callback Zhiping Zhang 2026-06-09 8:10 ` Christian König 2026-06-09 14:38 ` Zhiping Zhang 2026-06-09 19:38 ` sashiko-bot 2026-06-08 18:56 ` [PATCH v6 4/5] vfio/pci: implement get_tph and DMA_BUF_TPH feature Zhiping Zhang 2026-06-09 8:12 ` Christian König 2026-06-09 14:39 ` Zhiping Zhang 2026-06-09 19:38 ` sashiko-bot 2026-06-09 21:46 ` Alex Williamson 2026-06-09 23:48 ` Zhiping Zhang 2026-06-08 18:56 ` [PATCH v6 5/5] RDMA/mlx5: get tph for p2p access when registering dma-buf mr Zhiping Zhang 2026-06-09 19:38 ` sashiko-bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.