Two patches to implement a generic framework for dma-buf to support local private interconnects, in particular interconnects that are not driver-private.
The interconnect support is negotiated as part of an attachment and is not a property of the dma-buf itself. Just like pcie p2p support.
The first patch adds members to the dma_buf_attach_ops and to the dma_buf_attachment structure. These are needed for generic check of interconnect support, typically when an interconnect is shared between drivers. For truly driver-private interconnects they are not strictly needed, but still could be convenient.
The second patch implements an interconnect negotiation for xe, without actually changing the protocol itself from pcie_p2p. Just as an example. This patch is not intended to be merged.
Note: This RFC only deals with interconnect negotiation. The attachment state and what data-structure to use to convey the mapping information is not dealt with in this RFC.
v2: - Get rid of void pointers and instead provide generic structures that are intended to be embedded / subclassed for each interconnect implementation.
Thomas Hellström (2): dma-buf: Add support for private interconnects drm/xe/dma-buf: Add generic interconnect support framework
drivers/gpu/drm/xe/tests/xe_dma_buf.c | 12 ++--- drivers/gpu/drm/xe/xe_dma_buf.c | 73 ++++++++++++++++++++++++--- drivers/gpu/drm/xe/xe_dma_buf.h | 1 - drivers/gpu/drm/xe/xe_interconnect.h | 31 ++++++++++++ include/linux/dma-buf.h | 51 +++++++++++++++++++ 5 files changed, 155 insertions(+), 13 deletions(-) create mode 100644 drivers/gpu/drm/xe/xe_interconnect.h
Add a function to the dma_buf_attach_ops to indicate whether the connection is a private interconnect. If so the function returns the address to an interconnect-defined structure that can be used for further negotiating.
Also add a field to the dma_buf_attachment that indicates whether a private interconnect is used by the attachment.
Signed-off-by: Thomas Hellström thomas.hellstrom@linux.intel.com --- include/linux/dma-buf.h | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index d58e329ac0e7..25dbf1fea09a 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -442,6 +442,39 @@ struct dma_buf { #endif };
+/* RFC: Separate header for the interconnect defines? */ + +/** + * struct dma_buf_interconnect - Private interconnect + * @name: The name of the interconnect + */ +struct dma_buf_interconnect { + const char *name; +}; + +/** + * struct dma_buf_interconnect_attach_ops - Interconnect attach ops base-class + * + * Declared for type-safety. Interconnect implementations should subclass to + * implement negotiation-specific ops. + */ +struct dma_buf_interconnect_attach_ops { +}; + +/** + * struct dma_buf_interconnect_attach - Interconnect state + * @interconnect: The struct dma_buf_interconnect identifying the interconnect + * + * Interconnect implementations subclass as needed for attachment state + * that can't be stored elsewhere. It could, for example, hold a pointer + * to a replacement of the sg-list after the attachment has been mapped. + * If no additional state is needed, an exporter could define a single + * static instance of this struct. + */ +struct dma_buf_interconnect_attach { + const struct dma_buf_interconnect *interconnect; +}; + /** * struct dma_buf_attach_ops - importer operations for an attachment * @@ -475,6 +508,21 @@ struct dma_buf_attach_ops { * point to the new location of the DMA-buf. */ void (*move_notify)(struct dma_buf_attachment *attach); + + /** + * @supports_interconnect: [optional] - Does the driver support a local interconnect? + * + * Does the importer support a private interconnect? The interconnect is + * identified using a unique address defined instantiated either by the driver + * if the interconnect is driver-private or globally + * (RFC added to the dma-buf-interconnect.c file) if cross-driver. + * + * Return: A pointer to the interconnect-private attach_ops structure if supported, + * %NULL otherwise. + */ + const struct dma_buf_interconnect_attach_ops * + (*supports_interconnect)(struct dma_buf_attachment *attach, + const struct dma_buf_interconnect *interconnect); };
/** @@ -484,6 +532,8 @@ struct dma_buf_attach_ops { * @node: list of dma_buf_attachment, protected by dma_resv lock of the dmabuf. * @peer2peer: true if the importer can handle peer resources without pages. * @priv: exporter specific attachment data. + * @interconnect_attach: Private interconnect state for the connection if used, + * NULL otherwise. * @importer_ops: importer operations for this attachment, if provided * dma_buf_map/unmap_attachment() must be called with the dma_resv lock held. * @importer_priv: importer specific attachment data. @@ -503,6 +553,7 @@ struct dma_buf_attachment { struct list_head node; bool peer2peer; const struct dma_buf_attach_ops *importer_ops; + struct dma_buf_interconnect_attach *interconnect_attach; void *importer_priv; void *priv; };
On 26.09.25 10:46, Thomas Hellström wrote:
Add a function to the dma_buf_attach_ops to indicate whether the connection is a private interconnect. If so the function returns the address to an interconnect-defined structure that can be used for further negotiating.
Also add a field to the dma_buf_attachment that indicates whether a private interconnect is used by the attachment.
Signed-off-by: Thomas Hellström thomas.hellstrom@linux.intel.com
include/linux/dma-buf.h | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index d58e329ac0e7..25dbf1fea09a 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -442,6 +442,39 @@ struct dma_buf { #endif }; +/* RFC: Separate header for the interconnect defines? */
+/**
- struct dma_buf_interconnect - Private interconnect
- @name: The name of the interconnect
- */
+struct dma_buf_interconnect {
- const char *name;
+};
+/**
- struct dma_buf_interconnect_attach_ops - Interconnect attach ops base-class
- Declared for type-safety. Interconnect implementations should subclass to
- implement negotiation-specific ops.
- */
+struct dma_buf_interconnect_attach_ops { +};
+/**
- struct dma_buf_interconnect_attach - Interconnect state
- @interconnect: The struct dma_buf_interconnect identifying the interconnect
- Interconnect implementations subclass as needed for attachment state
- that can't be stored elsewhere. It could, for example, hold a pointer
- to a replacement of the sg-list after the attachment has been mapped.
- If no additional state is needed, an exporter could define a single
- static instance of this struct.
- */
+struct dma_buf_interconnect_attach {
- const struct dma_buf_interconnect *interconnect;
+};
/**
- struct dma_buf_attach_ops - importer operations for an attachment
@@ -475,6 +508,21 @@ struct dma_buf_attach_ops { * point to the new location of the DMA-buf. */ void (*move_notify)(struct dma_buf_attachment *attach);
- /**
* @supports_interconnect: [optional] - Does the driver support a local interconnect?
*
* Does the importer support a private interconnect? The interconnect is
* identified using a unique address defined instantiated either by the driver
* if the interconnect is driver-private or globally
* (RFC added to the dma-buf-interconnect.c file) if cross-driver.
*
* Return: A pointer to the interconnect-private attach_ops structure if supported,
* %NULL otherwise.
*/
- const struct dma_buf_interconnect_attach_ops *
- (*supports_interconnect)(struct dma_buf_attachment *attach,
const struct dma_buf_interconnect *interconnect);
This looks like it sits in the wrong structure. The dma_buf_attach_ops are the operations provided by the importer, e.g. move notification.
When we want to check if using an interconnect is possible we need to do that on the exporter, e.g. dma_buf_ops().
I think we should have an map_interconnect(connector type descriptor) that the importer can use to establish a mapping for itself.
Additional to that we need an unmap_interconnect() to let the exporter know that an importer doesn't need a specific mapping any more.
}; /** @@ -484,6 +532,8 @@ struct dma_buf_attach_ops {
- @node: list of dma_buf_attachment, protected by dma_resv lock of the dmabuf.
- @peer2peer: true if the importer can handle peer resources without pages.
- @priv: exporter specific attachment data.
- @interconnect_attach: Private interconnect state for the connection if used,
- NULL otherwise.
- @importer_ops: importer operations for this attachment, if provided
- dma_buf_map/unmap_attachment() must be called with the dma_resv lock held.
- @importer_priv: importer specific attachment data.
@@ -503,6 +553,7 @@ struct dma_buf_attachment { struct list_head node; bool peer2peer; const struct dma_buf_attach_ops *importer_ops;
- struct dma_buf_interconnect_attach *interconnect_attach;
We already have an importer and an exporter private void *. Do we really need that?
Regards, Christian.
void *importer_priv; void *priv; };
On Fri, 2025-09-26 at 14:56 +0200, Christian König wrote:
On 26.09.25 10:46, Thomas Hellström wrote:
Add a function to the dma_buf_attach_ops to indicate whether the connection is a private interconnect. If so the function returns the address to an interconnect-defined structure that can be used for further negotiating.
Also add a field to the dma_buf_attachment that indicates whether a private interconnect is used by the attachment.
Signed-off-by: Thomas Hellström thomas.hellstrom@linux.intel.com
include/linux/dma-buf.h | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
diff --git a/include/linux/dma-buf.h b/include/linux/dma-buf.h index d58e329ac0e7..25dbf1fea09a 100644 --- a/include/linux/dma-buf.h +++ b/include/linux/dma-buf.h @@ -442,6 +442,39 @@ struct dma_buf { #endif }; +/* RFC: Separate header for the interconnect defines? */
+/**
- struct dma_buf_interconnect - Private interconnect
- @name: The name of the interconnect
- */
+struct dma_buf_interconnect {
- const char *name;
+};
+/**
- struct dma_buf_interconnect_attach_ops - Interconnect attach
ops base-class
- Declared for type-safety. Interconnect implementations should
subclass to
- implement negotiation-specific ops.
- */
+struct dma_buf_interconnect_attach_ops { +};
+/**
- struct dma_buf_interconnect_attach - Interconnect state
- @interconnect: The struct dma_buf_interconnect identifying the
interconnect
- Interconnect implementations subclass as needed for attachment
state
- that can't be stored elsewhere. It could, for example, hold a
pointer
- to a replacement of the sg-list after the attachment has been
mapped.
- If no additional state is needed, an exporter could define a
single
- static instance of this struct.
- */
+struct dma_buf_interconnect_attach {
- const struct dma_buf_interconnect *interconnect;
+};
/** * struct dma_buf_attach_ops - importer operations for an attachment * @@ -475,6 +508,21 @@ struct dma_buf_attach_ops { * point to the new location of the DMA-buf. */ void (*move_notify)(struct dma_buf_attachment *attach);
- /**
* @supports_interconnect: [optional] - Does the driver
support a local interconnect?
*
* Does the importer support a private interconnect? The
interconnect is
* identified using a unique address defined instantiated
either by the driver
* if the interconnect is driver-private or globally
* (RFC added to the dma-buf-interconnect.c file) if
cross-driver.
*
* Return: A pointer to the interconnect-private
attach_ops structure if supported,
* %NULL otherwise.
*/
- const struct dma_buf_interconnect_attach_ops *
- (*supports_interconnect)(struct dma_buf_attachment
*attach,
const struct dma_buf_interconnect
*interconnect);
This looks like it sits in the wrong structure. The dma_buf_attach_ops are the operations provided by the importer, e.g. move notification.
When we want to check if using an interconnect is possible we need to do that on the exporter, e.g. dma_buf_ops().
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
So the above function mimics the dma_buf_attach_ops::allow_peer2peer bool, except it's not a single interconnect so we'd either use a set of bools, one for each potential interconnect, or a function like this. A function has the benefit that it can also provide any additional attach ops the interconnect might need.
So the flow becomes: 1) Importer calls exporter attach() with a non-NULL supports_interconnect() to signal that it supports some additional interconnects. 2) exporter calls supports_interconnect(my_interconnect) to figure out whether the importer supports a specific interconnect it wants to try. This is similar to the exporter checking "allow_peer2peer" (or rather the core checking "allow_peer2peer" on the behalf of the exporter). 3) Importer finds it supports the interconnect and provides additional dma_buf_interconnect_attach_ops. 4) Now the exporter checks that the interconnect is indeed possible. This is similar to calling pci_p2p_distance(), but interconnect- specific. This might involve querying the importer, for example if the importer feels like the exporting device:bar pair does indeed have an implicit VF_PF connection. This can be done if needed using the dma_buf_interconnect_attach_ops. 5) Exporter is happy, and sets the dma_buf_attachment::interconnect_attach field. This is similar to setting the dma_buf_attachment::peer2peer field.
So basically this is the pcie peer2peer negotiation flow generalized. It would be trivial to implement the pcie peer2peer negotiation as a private protocol using the above.
I think we should have an map_interconnect(connector type descriptor) that the importer can use to establish a mapping for itself.
Additional to that we need an unmap_interconnect() to let the exporter know that an importer doesn't need a specific mapping any more.
Is this to not overload the map_attachment() and unmap_attachment() functions that otherwise could be used? Is it because they return an sg_table? Yeah, that could make sense but not for the interconnect negotiation itself, right? That happens during attach time like pcie_p2p?
}; /** @@ -484,6 +532,8 @@ struct dma_buf_attach_ops { * @node: list of dma_buf_attachment, protected by dma_resv lock of the dmabuf. * @peer2peer: true if the importer can handle peer resources without pages. * @priv: exporter specific attachment data.
- @interconnect_attach: Private interconnect state for the
connection if used,
- NULL otherwise.
* @importer_ops: importer operations for this attachment, if provided * dma_buf_map/unmap_attachment() must be called with the dma_resv lock held. * @importer_priv: importer specific attachment data. @@ -503,6 +553,7 @@ struct dma_buf_attachment { struct list_head node; bool peer2peer; const struct dma_buf_attach_ops *importer_ops;
- struct dma_buf_interconnect_attach *interconnect_attach;
We already have an importer and an exporter private void *. Do we really need that?
See above. It looks like the exporter private is largely unused in xekmd at least but the importer would want to inspect that as well, to find out whether the attachment indeed is an interconnect attachment. And I'm not sure whether a driver that already uses the exporter priv would ever want to use a private interconnect like this.
Thanks, Thomas
Regards, Christian.
void *importer_priv; void *priv; };
On Fri, Sep 26, 2025 at 03:51:21PM +0200, Thomas Hellström wrote:
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
Having a single option seems too limited to me..
I think it would be nice if the importer could supply a list of 'interconnects' it can accept, eg:
- VRAM offset within this specific VRAM memory - dma_addr_t for this struct device - "IOVA" for this initiator on a private interconnect - PCI bar slice - phys_addr_t (used between vfio, kvm, iommufd)
The exporter has a function to run down the list and return the first compatible. Maybe something like
struct dma_buf_interconnect_negotiation { struct dma_buf_interconnect *interconnect, void *interconnect_args, };
struct dma_buf_interconnect_negotiation importer_offer[2] = { // On stack [0] = {.interconnect = myself->xe_vram}, [1] = {.interconnect = &dmabuf_generic_dma_addr_t, .interconnects_args = dev}, }; idx = dma_buf_negotiate(dmabuf, importer_offer, ARRAY_SIZE(importer_offer)); if (idx < 0) return -EOPNOTSUPP;
Then you'd 'interconnect attach' with that compatible item and get back an attach. Using container_of to get the specific ops which then has a function to get the address list.
attach = dma_buf_attach_interconnect(dmabuf, importer_offer[idx], &dma_buf_attach_ops);
if (idx == 0) { xe_vram_ops = container_of(attach->ops, ..); struct device_private_address *addrs = xe_vram_ops->map(attach); [..] xe_vram_ops->unmap(attach); } dma_buf_detach_interconnect(attach);
I can imagine some scheme where if the exporter does not support interconnect then the core code will automatically look for dmabuf_generic_dma_addr_t, select it, and supply some ops that call existing dma_buf_dynamic_attach()/dma_buf_map_attachment() transparently.
So the above function mimics the dma_buf_attach_ops::allow_peer2peer bool, except it's not a single interconnect so we'd either use a set of bools, one for each potential interconnect, or a function like this. A function has the benefit that it can also provide any additional attach ops the interconnect might need.
allow_peer2peer seems to indicate if sg_page() can be used on the sgt? It doesn't have any meaning for an importer only using dma_addr_t?
In the above language it would be an interconnect exchanging 'struct page *'.. I'm a little confused by this I thought touching the struct page was forbidden?
Is this to not overload the map_attachment() and unmap_attachment() functions that otherwise could be used? Is it because they return an sg_table?
It would be good to avoid going through APIs that use sg_table in the design..
Jason
On 26.09.25 16:41, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 03:51:21PM +0200, Thomas Hellström wrote:
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
Having a single option seems too limited to me..
Yeah, agree.
I think it would be nice if the importer could supply a list of 'interconnects' it can accept, eg:
- VRAM offset within this specific VRAM memory
- dma_addr_t for this struct device
- "IOVA" for this initiator on a private interconnect
- PCI bar slice
- phys_addr_t (used between vfio, kvm, iommufd)
I would rather say that the exporter should provide the list of what interconnects the buffer might be accessible through.
Having the p2p flag set by the importer was basically just the easiest approach to implement the flow, that is not necessarily a design pattern how to approach a general solution.
Regards, Christian.
The exporter has a function to run down the list and return the first compatible. Maybe something like
struct dma_buf_interconnect_negotiation { struct dma_buf_interconnect *interconnect, void *interconnect_args, }; struct dma_buf_interconnect_negotiation importer_offer[2] = { // On stack [0] = {.interconnect = myself->xe_vram}, [1] = {.interconnect = &dmabuf_generic_dma_addr_t, .interconnects_args = dev}, }; idx = dma_buf_negotiate(dmabuf, importer_offer, ARRAY_SIZE(importer_offer)); if (idx < 0) return -EOPNOTSUPP;
Then you'd 'interconnect attach' with that compatible item and get back an attach. Using container_of to get the specific ops which then has a function to get the address list.
attach = dma_buf_attach_interconnect(dmabuf, importer_offer[idx], &dma_buf_attach_ops);
if (idx == 0) { xe_vram_ops = container_of(attach->ops, ..); struct device_private_address *addrs = xe_vram_ops->map(attach); [..] xe_vram_ops->unmap(attach); } dma_buf_detach_interconnect(attach);
I can imagine some scheme where if the exporter does not support interconnect then the core code will automatically look for dmabuf_generic_dma_addr_t, select it, and supply some ops that call existing dma_buf_dynamic_attach()/dma_buf_map_attachment() transparently.
So the above function mimics the dma_buf_attach_ops::allow_peer2peer bool, except it's not a single interconnect so we'd either use a set of bools, one for each potential interconnect, or a function like this. A function has the benefit that it can also provide any additional attach ops the interconnect might need.
allow_peer2peer seems to indicate if sg_page() can be used on the sgt? It doesn't have any meaning for an importer only using dma_addr_t?
In the above language it would be an interconnect exchanging 'struct page *'.. I'm a little confused by this I thought touching the struct page was forbidden?
Is this to not overload the map_attachment() and unmap_attachment() functions that otherwise could be used? Is it because they return an sg_table?
It would be good to avoid going through APIs that use sg_table in the design..
Jason
On Fri, Sep 26, 2025 at 04:51:29PM +0200, Christian König wrote:
On 26.09.25 16:41, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 03:51:21PM +0200, Thomas Hellström wrote:
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
Having a single option seems too limited to me..
Yeah, agree.
I think it would be nice if the importer could supply a list of 'interconnects' it can accept, eg:
- VRAM offset within this specific VRAM memory
- dma_addr_t for this struct device
- "IOVA" for this initiator on a private interconnect
- PCI bar slice
- phys_addr_t (used between vfio, kvm, iommufd)
I would rather say that the exporter should provide the list of what interconnects the buffer might be accessible through.
Either direction works, I sketched it like this because I thought there were more importers than exporters, and in the flow it is easy for the importer to provide a list on the stack
I didn't sketch further, but I think the exporter and importer should both be providing a compatible list and then in almost all cases the core code should do the matching.
If the importer works as I showed, then the exporter version would be in an op:
int exporter_negotiate_op(struct dma_buf *dmabuf, struct dma_buf_interconnect_negotiation *importer_support, size_t importer_len) { struct dma_buf_interconnect_negotiation exporter_support[2] = { [0] = {.interconnect = myself->xe_vram}, [1] = {.interconnect = &dmabuf_generic_dma_addr_t, .interconnect_args = exporter_dev}, }; return dma_buf_helper_negotiate(dmabuf, exporter_support, ARRAY_SIZE(exporter_support), importer_support, importer_len); }
Which the dma_buf_negotiate() calls.
The core code does the matching generically, probably there is a struct dma_buf_interconnect match() op it uses to help this process.
I don't think importer or exporter should be open coding any matching.
For example, we have some systems with multipath PCI. This could actually support those properly. The RDMA NIC has two struct devices it operates with different paths, so it would write out two &dmabuf_generic_dma_addr_t's - one for each.
The GPU would do the same. The core code can have generic code to evaluate if P2P is possible and estimate some QOR between the options.
Jason
On Fri, 2025-09-26 at 13:00 -0300, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 04:51:29PM +0200, Christian König wrote:
On 26.09.25 16:41, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 03:51:21PM +0200, Thomas Hellström wrote:
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
Having a single option seems too limited to me..
Yeah, agree.
I think it would be nice if the importer could supply a list of 'interconnects' it can accept, eg:
- VRAM offset within this specific VRAM memory - dma_addr_t for this struct device - "IOVA" for this initiator on a private interconnect - PCI bar slice - phys_addr_t (used between vfio, kvm, iommufd)
I would rather say that the exporter should provide the list of what interconnects the buffer might be accessible through.
Either direction works, I sketched it like this because I thought there were more importers than exporters, and in the flow it is easy for the importer to provide a list on the stack
I didn't sketch further, but I think the exporter and importer should both be providing a compatible list and then in almost all cases the core code should do the matching.
If the importer works as I showed, then the exporter version would be in an op:
int exporter_negotiate_op(struct dma_buf *dmabuf, struct dma_buf_interconnect_negotiation *importer_support, size_t importer_len) { struct dma_buf_interconnect_negotiation exporter_support[2] = { [0] = {.interconnect = myself->xe_vram}, [1] = {.interconnect = &dmabuf_generic_dma_addr_t, .interconnect_args = exporter_dev}, }; return dma_buf_helper_negotiate(dmabuf, exporter_support, ARRAY_SIZE(exporter_support), importer_support, importer_len); }
Which the dma_buf_negotiate() calls.
The core code does the matching generically, probably there is a struct dma_buf_interconnect match() op it uses to help this process.
I don't think importer or exporter should be open coding any matching.
For example, we have some systems with multipath PCI. This could actually support those properly. The RDMA NIC has two struct devices it operates with different paths, so it would write out two &dmabuf_generic_dma_addr_t's - one for each.
The GPU would do the same. The core code can have generic code to evaluate if P2P is possible and estimate some QOR between the options.
This sounds OK with me. I have some additional questions, though,
1) Everybody agrees that the interconnect used is a property of the attachment? It should be negotiated during attach()?
2) dma-buf pcie-p2p allows transparent fallback to system memory dma- buf. I think that is a good thing to keep even for other interconnects (if possible). Like if someone wants to pull the network cable, we could trigger a move_notify() and on next map() we'd fall back. Any ideas around this?
Thanks, Thomas
Jason
On 29.09.25 10:16, Thomas Hellström wrote:
On Fri, 2025-09-26 at 13:00 -0300, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 04:51:29PM +0200, Christian König wrote:
On 26.09.25 16:41, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 03:51:21PM +0200, Thomas Hellström wrote:
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
Having a single option seems too limited to me..
Yeah, agree.
I think it would be nice if the importer could supply a list of 'interconnects' it can accept, eg:
- VRAM offset within this specific VRAM memory - dma_addr_t for this struct device - "IOVA" for this initiator on a private interconnect - PCI bar slice - phys_addr_t (used between vfio, kvm, iommufd)
I would rather say that the exporter should provide the list of what interconnects the buffer might be accessible through.
Either direction works, I sketched it like this because I thought there were more importers than exporters, and in the flow it is easy for the importer to provide a list on the stack
I didn't sketch further, but I think the exporter and importer should both be providing a compatible list and then in almost all cases the core code should do the matching.
If the importer works as I showed, then the exporter version would be in an op:
int exporter_negotiate_op(struct dma_buf *dmabuf, struct dma_buf_interconnect_negotiation *importer_support, size_t importer_len) { struct dma_buf_interconnect_negotiation exporter_support[2] = { [0] = {.interconnect = myself->xe_vram}, [1] = {.interconnect = &dmabuf_generic_dma_addr_t, .interconnect_args = exporter_dev}, }; return dma_buf_helper_negotiate(dmabuf, exporter_support, ARRAY_SIZE(exporter_support), importer_support, importer_len); }
Which the dma_buf_negotiate() calls.
The core code does the matching generically, probably there is a struct dma_buf_interconnect match() op it uses to help this process.
I don't think importer or exporter should be open coding any matching.
For example, we have some systems with multipath PCI. This could actually support those properly. The RDMA NIC has two struct devices it operates with different paths, so it would write out two &dmabuf_generic_dma_addr_t's - one for each.
The GPU would do the same. The core code can have generic code to evaluate if P2P is possible and estimate some QOR between the options.
This sounds OK with me. I have some additional questions, though,
- Everybody agrees that the interconnect used is a property of the
attachment? It should be negotiated during attach()?
Yes, attach allows the exporter to know who wants to access it's buffer.
Map/unmap then requests the actual location where the exporter has moved the buffer so that it is accessible by everybody.
- dma-buf pcie-p2p allows transparent fallback to system memory dma-
buf. I think that is a good thing to keep even for other interconnects (if possible). Like if someone wants to pull the network cable, we could trigger a move_notify() and on next map() we'd fall back. Any ideas around this?
We already do that if new importers come along.
E.g. you have a connection which can do PCIe P2P and then suddenly somebody attaches which can only do DMA to system memory. In that situation we use move_notify to move the buffer into system memory and imports re-map it to grasp the new location.
Regards, Christian.
Thanks, Thomas
Jason
On Mon, 2025-09-29 at 10:20 +0200, Christian König wrote:
On 29.09.25 10:16, Thomas Hellström wrote:
On Fri, 2025-09-26 at 13:00 -0300, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 04:51:29PM +0200, Christian König wrote:
On 26.09.25 16:41, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 03:51:21PM +0200, Thomas Hellström wrote:
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
Having a single option seems too limited to me..
Yeah, agree.
I think it would be nice if the importer could supply a list of 'interconnects' it can accept, eg:
- VRAM offset within this specific VRAM memory - dma_addr_t for this struct device - "IOVA" for this initiator on a private interconnect - PCI bar slice - phys_addr_t (used between vfio, kvm, iommufd)
I would rather say that the exporter should provide the list of what interconnects the buffer might be accessible through.
Either direction works, I sketched it like this because I thought there were more importers than exporters, and in the flow it is easy for the importer to provide a list on the stack
I didn't sketch further, but I think the exporter and importer should both be providing a compatible list and then in almost all cases the core code should do the matching.
If the importer works as I showed, then the exporter version would be in an op:
int exporter_negotiate_op(struct dma_buf *dmabuf, struct dma_buf_interconnect_negotiation *importer_support, size_t importer_len) { struct dma_buf_interconnect_negotiation exporter_support[2] = { [0] = {.interconnect = myself->xe_vram}, [1] = {.interconnect = &dmabuf_generic_dma_addr_t, .interconnect_args = exporter_dev}, }; return dma_buf_helper_negotiate(dmabuf, exporter_support, ARRAY_SIZE(exporter_support), importer_support, importer_len); }
Which the dma_buf_negotiate() calls.
The core code does the matching generically, probably there is a struct dma_buf_interconnect match() op it uses to help this process.
I don't think importer or exporter should be open coding any matching.
For example, we have some systems with multipath PCI. This could actually support those properly. The RDMA NIC has two struct devices it operates with different paths, so it would write out two &dmabuf_generic_dma_addr_t's - one for each.
The GPU would do the same. The core code can have generic code to evaluate if P2P is possible and estimate some QOR between the options.
This sounds OK with me. I have some additional questions, though,
- Everybody agrees that the interconnect used is a property of the
attachment? It should be negotiated during attach()?
Yes, attach allows the exporter to know who wants to access it's buffer.
Map/unmap then requests the actual location where the exporter has moved the buffer so that it is accessible by everybody.
- dma-buf pcie-p2p allows transparent fallback to system memory
dma- buf. I think that is a good thing to keep even for other interconnects (if possible). Like if someone wants to pull the network cable, we could trigger a move_notify() and on next map() we'd fall back. Any ideas around this?
We already do that if new importers come along.
E.g. you have a connection which can do PCIe P2P and then suddenly somebody attaches which can only do DMA to system memory. In that situation we use move_notify to move the buffer into system memory and imports re-map it to grasp the new location.
Sure, Just wandering whether we should document and require that also for fast interconnects. So that if we use a new map_attachment() function, like was suggested ealier, if that fails, the importer should ideally retry with the old one to get an sg-list to system memory?
Thanks, Thomas
Regards, Christian.
Thanks, Thomas
Jason
On Mon, Sep 29, 2025 at 10:25:06AM +0200, Thomas Hellström wrote:
- dma-buf pcie-p2p allows transparent fallback to system memory
dma- buf. I think that is a good thing to keep even for other interconnects (if possible). Like if someone wants to pull the network cable, we could trigger a move_notify() and on next map() we'd fall back. Any ideas around this?
We already do that if new importers come along.
E.g. you have a connection which can do PCIe P2P and then suddenly somebody attaches which can only do DMA to system memory. In that situation we use move_notify to move the buffer into system memory and imports re-map it to grasp the new location.
Sure, Just wandering whether we should document and require that also for fast interconnects.
I thin Thomas is pushing toward a question of what happens to the interconnect during a move?
If the interconnect is established during attach, can/should move re-negotiate it?
It seems like yes - if the attachment negotiated using some private interconnect then move happens and the memory is on CPU and no longer available to the private interconnect the attachment needs to renegotiate and change to a new interconnect during the move sequence.
If the importer supports only 1 interconnect, then it shouldn't have to implement renegotiate.
I think this needs to be called out explicitly in the flow and documentation.
So that if we use a new map_attachment() function, like was suggested ealier, if that fails, the importer should ideally retry with the old one to get an sg-list to system memory?
It would be amazing to avoid this hassle, if the core code could see the exporter is old and automatically match the sg-list behavior to generic interconnect options. And vice versa for old importers.
Jason
On 26.09.25 18:00, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 04:51:29PM +0200, Christian König wrote:
On 26.09.25 16:41, Jason Gunthorpe wrote:
On Fri, Sep 26, 2025 at 03:51:21PM +0200, Thomas Hellström wrote:
Well both exporter and exporter has specific information WRT this. The ultimate decision is done in the exporter attach() callback, just like pcie_p2p. And the exporter acknowledges that by setting the dma_buf_attachment::interconnect_attach field. In analogy with the dma_buf_attachment::peer2peer member.
Having a single option seems too limited to me..
Yeah, agree.
I think it would be nice if the importer could supply a list of 'interconnects' it can accept, eg:
- VRAM offset within this specific VRAM memory
- dma_addr_t for this struct device
- "IOVA" for this initiator on a private interconnect
- PCI bar slice
- phys_addr_t (used between vfio, kvm, iommufd)
I would rather say that the exporter should provide the list of what interconnects the buffer might be accessible through.
Either direction works, I sketched it like this because I thought there were more importers than exporters, and in the flow it is easy for the importer to provide a list on the stack
The point is that the exporter manages all accesses to it's buffer and there can be more than one importer accessing it at the same time.
So when an exporter sees that it already has an importer which can only do DMA to system memory it will expose only DMA address to all other importers as well.
But in general if we start with the exporter or the importer list doesn't really matter I think.
I didn't sketch further, but I think the exporter and importer should both be providing a compatible list and then in almost all cases the core code should do the matching.
More or less matches my idea. I would just start with the exporter providing a list of how it's buffer is accessible because it knows about other importers and can pre-reduce the list if necessary.
It can also be that this list changes when new importers come along (that was one of the big motivations for the move_notify callback).
In other words we have use cases where we need to do scanout, render and V4L to the same buffer at the same time and all three of that are different devices with different requirements.
If the importer works as I showed, then the exporter version would be in an op:
int exporter_negotiate_op(struct dma_buf *dmabuf, struct dma_buf_interconnect_negotiation *importer_support, size_t importer_len) { struct dma_buf_interconnect_negotiation exporter_support[2] = { [0] = {.interconnect = myself->xe_vram}, [1] = {.interconnect = &dmabuf_generic_dma_addr_t, .interconnect_args = exporter_dev}, }; return dma_buf_helper_negotiate(dmabuf, exporter_support, ARRAY_SIZE(exporter_support), importer_support, importer_len); }
Which the dma_buf_negotiate() calls.
The core code does the matching generically, probably there is a struct dma_buf_interconnect match() op it uses to help this process.
I don't think importer or exporter should be open coding any matching.
Agree, that should be somehow handled by the framework.
For example, we have some systems with multipath PCI. This could actually support those properly. The RDMA NIC has two struct devices it operates with different paths, so it would write out two &dmabuf_generic_dma_addr_t's - one for each.
That is actually something we try rather hard to avoid. E.g. the exporter should offer only one path to each importer.
We can of course do load balancing on a round robin bases.
Regards, Christian.
The GPU would do the same. The core code can have generic code to evaluate if P2P is possible and estimate some QOR between the options.
Jason
On Mon, Sep 29, 2025 at 10:16:30AM +0200, Christian König wrote:
The point is that the exporter manages all accesses to it's buffer and there can be more than one importer accessing it at the same time.
So when an exporter sees that it already has an importer which can only do DMA to system memory it will expose only DMA address to all other importers as well.
I would rephrase that, if the exporter supports multiple placement options for the memory (VRAM/CPU for example) then it needs to track which placement options all its importer support and never place the memory someplace an active importer cannot reach.
I don't want to say that just because one importer wants to use dma_addr_t then all private interconnect options are disabled. If the memory is in VRAM then multiple importers using private interconnect concurrently with dma_addr_t should be possible.
This seems like it is making the argument that the exporter does need to know the importer capability so it can figure out what placement options are valid.
I didn't sketch further, but I think the exporter and importer should both be providing a compatible list and then in almost all cases the core code should do the matching.
More or less matches my idea. I would just start with the exporter providing a list of how it's buffer is accessible because it knows about other importers and can pre-reduce the list if necessary.
I think the importer also has to advertise what it is able to support. A big point of the private interconnect is that it won't use scatterlist so it needs to be a negotiated feature.
For example, we have some systems with multipath PCI. This could actually support those properly. The RDMA NIC has two struct devices it operates with different paths, so it would write out two &dmabuf_generic_dma_addr_t's - one for each.
That is actually something we try rather hard to avoid. E.g. the exporter should offer only one path to each importer.
Real systems have multipath. We need to do a NxM negotiation where both sides offer all their paths and the best quality path is selected.
Once the attachment is made it should be one interconnect and one stable address within that interconnect.
In this example I'd expect the Xe GPU driver to always offer its private interconnect and a dma_addr_t based interconnct as both exporter and importer. The core code should select one for the attachment.
We can of course do load balancing on a round robin bases.
I'm not thinking about load balancing, more a 'quality of path' metric.
Jason
Hi,
On Mon, 2025-09-29 at 09:45 -0300, Jason Gunthorpe wrote:
On Mon, Sep 29, 2025 at 10:16:30AM +0200, Christian König wrote:
The point is that the exporter manages all accesses to it's buffer and there can be more than one importer accessing it at the same time.
So when an exporter sees that it already has an importer which can only do DMA to system memory it will expose only DMA address to all other importers as well.
I would rephrase that, if the exporter supports multiple placement options for the memory (VRAM/CPU for example) then it needs to track which placement options all its importer support and never place the memory someplace an active importer cannot reach.
I don't want to say that just because one importer wants to use dma_addr_t then all private interconnect options are disabled. If the memory is in VRAM then multiple importers using private interconnect concurrently with dma_addr_t should be possible.
This seems like it is making the argument that the exporter does need to know the importer capability so it can figure out what placement options are valid.
I didn't sketch further, but I think the exporter and importer should both be providing a compatible list and then in almost all cases the core code should do the matching.
More or less matches my idea. I would just start with the exporter providing a list of how it's buffer is accessible because it knows about other importers and can pre-reduce the list if necessary.
I think the importer also has to advertise what it is able to support. A big point of the private interconnect is that it won't use scatterlist so it needs to be a negotiated feature.
For example, we have some systems with multipath PCI. This could actually support those properly. The RDMA NIC has two struct devices it operates with different paths, so it would write out two &dmabuf_generic_dma_addr_t's - one for each.
That is actually something we try rather hard to avoid. E.g. the exporter should offer only one path to each importer.
Real systems have multipath. We need to do a NxM negotiation where both sides offer all their paths and the best quality path is selected.
Once the attachment is made it should be one interconnect and one stable address within that interconnect.
In this example I'd expect the Xe GPU driver to always offer its private interconnect and a dma_addr_t based interconnct as both exporter and importer. The core code should select one for the attachment.
We can of course do load balancing on a round robin bases.
I'm not thinking about load balancing, more a 'quality of path' metric.
This sounds like it's getting increasingly complex. TBH I think that at least all fast interconnects we have in the planning for xe either are fine with falling back to the current pcie-p2p / dma-buf or in worst case system memory. The virtual interconnect we've been discussing would probably not be able to fall back at all unless negotiation gets somehow forwarded to the vm guest.
So I wonder whether for now it's simply sufficient to
sg_table_replacement = dma_buf_map_interconnect();
if (IS_ERROR(sg_list_replacement)) { sg_table = dma_buf_map_attachment(); if (IS_ERROR(sg_table)) bail(); }
/Thomas
Jason
On Mon, Sep 29, 2025 at 06:02:50PM +0200, Thomas Hellström wrote:
I'm not thinking about load balancing, more a 'quality of path' metric.
This sounds like it's getting increasingly complex. TBH I think that at least all fast interconnects we have in the planning for xe either are fine with falling back to the current pcie-p2p / dma-buf or in worst case system memory.
Yah, fallback is fine, but they will still want to prefer the better paths if they are available. That's my point..
The virtual interconnect we've been discussing would probably not be able to fall back at all unless negotiation gets somehow forwarded to the vm guest.
This is basically proposing to open code all the priority and QOR matching in the drivers. I strongly think that will become unmaintainable if things evolve in that direction..
What I'm suggesting is to have just enough infrastructure that we can have the priority selection be in common code and implement the simplest possible selection for right now. Run down the exports list in order and the firt interconnect to match the importer's list is selected.
All the more fancy stuff I've mentioned is someone else's problem down the road to enhance the core matcher.
Jason
Negotiate to use an xe-specific interconnect.
Signed-off-by: Thomas Hellström thomas.hellstrom@linux.intel.com --- drivers/gpu/drm/xe/tests/xe_dma_buf.c | 12 ++--- drivers/gpu/drm/xe/xe_dma_buf.c | 73 ++++++++++++++++++++++++--- drivers/gpu/drm/xe/xe_dma_buf.h | 1 - drivers/gpu/drm/xe/xe_interconnect.h | 31 ++++++++++++ 4 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 drivers/gpu/drm/xe/xe_interconnect.h
diff --git a/drivers/gpu/drm/xe/tests/xe_dma_buf.c b/drivers/gpu/drm/xe/tests/xe_dma_buf.c index 5df98de5ba3c..8eaea6c2a3b7 100644 --- a/drivers/gpu/drm/xe/tests/xe_dma_buf.c +++ b/drivers/gpu/drm/xe/tests/xe_dma_buf.c @@ -210,9 +210,9 @@ static const struct dma_buf_attach_ops nop2p_attach_ops = { */ static const struct dma_buf_test_params test_params[] = { {.mem_mask = XE_BO_FLAG_VRAM0, - .attach_ops = &xe_dma_buf_attach_ops}, + .attach_ops = &xe_dma_buf_attach_ops.dma_ops}, {.mem_mask = XE_BO_FLAG_VRAM0 | XE_BO_FLAG_NEEDS_CPU_ACCESS, - .attach_ops = &xe_dma_buf_attach_ops, + .attach_ops = &xe_dma_buf_attach_ops.dma_ops, .force_different_devices = true},
{.mem_mask = XE_BO_FLAG_VRAM0, @@ -226,9 +226,9 @@ static const struct dma_buf_test_params test_params[] = { .force_different_devices = true},
{.mem_mask = XE_BO_FLAG_SYSTEM, - .attach_ops = &xe_dma_buf_attach_ops}, + .attach_ops = &xe_dma_buf_attach_ops.dma_ops}, {.mem_mask = XE_BO_FLAG_SYSTEM, - .attach_ops = &xe_dma_buf_attach_ops, + .attach_ops = &xe_dma_buf_attach_ops.dma_ops, .force_different_devices = true},
{.mem_mask = XE_BO_FLAG_SYSTEM, @@ -242,10 +242,10 @@ static const struct dma_buf_test_params test_params[] = { .force_different_devices = true},
{.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0, - .attach_ops = &xe_dma_buf_attach_ops}, + .attach_ops = &xe_dma_buf_attach_ops.dma_ops}, {.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0 | XE_BO_FLAG_NEEDS_CPU_ACCESS, - .attach_ops = &xe_dma_buf_attach_ops, + .attach_ops = &xe_dma_buf_attach_ops.dma_ops, .force_different_devices = true},
{.mem_mask = XE_BO_FLAG_SYSTEM | XE_BO_FLAG_VRAM0, diff --git a/drivers/gpu/drm/xe/xe_dma_buf.c b/drivers/gpu/drm/xe/xe_dma_buf.c index 54e42960daad..ffb00d54bb9e 100644 --- a/drivers/gpu/drm/xe/xe_dma_buf.c +++ b/drivers/gpu/drm/xe/xe_dma_buf.c @@ -16,18 +16,49 @@ #include "tests/xe_test.h" #include "xe_bo.h" #include "xe_device.h" +#include "xe_interconnect.h" #include "xe_pm.h" #include "xe_ttm_vram_mgr.h" #include "xe_vm.h"
MODULE_IMPORT_NS("DMA_BUF");
+struct xe_dma_buf_attach_ops { + struct dma_buf_attach_ops dma_ops; + struct xe_interconnect_attach_ops ic_ops; +}; + +static const struct xe_dma_buf_attach_ops * +to_xe_dma_buf_attach_ops(struct dma_buf_attachment *attach) +{ + const struct dma_buf_attach_ops *aops = attach->importer_ops; + const struct dma_buf_interconnect_attach_ops *iaops; + + if (!aops || !aops->supports_interconnect) + return NULL; + + iaops = aops->supports_interconnect(attach, xe_interconnect); + return iaops ? container_of(iaops, struct xe_dma_buf_attach_ops, ic_ops.base) : NULL; +} + static int xe_dma_buf_attach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach) { struct drm_gem_object *obj = attach->dmabuf->priv; + const struct xe_dma_buf_attach_ops *xe_attach_ops = + to_xe_dma_buf_attach_ops(attach); + + if (xe_attach_ops && xe_attach_ops->ic_ops.allow_ic) { + struct xe_interconnect_attach *xe_attach = kzalloc(sizeof(*attach), GFP_KERNEL); + + if (xe_attach) { + xe_attach->base.interconnect = xe_interconnect; + xe_attach->sg_list_replacement = NULL; + attach->interconnect_attach = &xe_attach->base; + } + }
- if (attach->peer2peer && + if (!attach->interconnect_attach && attach->peer2peer && pci_p2pdma_distance(to_pci_dev(obj->dev->dev), attach->dev, false) < 0) attach->peer2peer = false;
@@ -43,6 +74,7 @@ static void xe_dma_buf_detach(struct dma_buf *dmabuf, { struct drm_gem_object *obj = attach->dmabuf->priv;
+ kfree(attach->interconnect_attach); xe_pm_runtime_put(to_xe_device(obj->dev)); }
@@ -135,6 +167,11 @@ static struct sg_table *xe_dma_buf_map(struct dma_buf_attachment *attach,
case XE_PL_VRAM0: case XE_PL_VRAM1: + if (attach->interconnect_attach && + attach->interconnect_attach->interconnect == xe_interconnect) { + /* Map using something else than sglist */ + ; + } r = xe_ttm_vram_mgr_alloc_sgt(xe_bo_device(bo), bo->ttm.resource, 0, bo->ttm.base.size, attach->dev, @@ -285,9 +322,28 @@ static void xe_dma_buf_move_notify(struct dma_buf_attachment *attach) XE_WARN_ON(xe_bo_evict(bo, exec)); }
-static const struct dma_buf_attach_ops xe_dma_buf_attach_ops = { - .allow_peer2peer = true, - .move_notify = xe_dma_buf_move_notify +static const struct dma_buf_interconnect_attach_ops * +xe_dma_buf_supports_interconnect(struct dma_buf_attachment *attach, + const struct dma_buf_interconnect *interconnect) +{ + if (interconnect == xe_interconnect) { + return &container_of(attach->importer_ops, + const struct xe_dma_buf_attach_ops, + dma_ops)->ic_ops.base; + } + + return NULL; +} + +static const struct xe_dma_buf_attach_ops xe_dma_buf_attach_ops = { + .dma_ops = { + .allow_peer2peer = true, + .move_notify = xe_dma_buf_move_notify, + .supports_interconnect = xe_dma_buf_supports_interconnect, + }, + .ic_ops = { + .allow_ic = true, + } };
#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) @@ -336,12 +392,11 @@ struct drm_gem_object *xe_gem_prime_import(struct drm_device *dev, if (IS_ERR(bo)) return ERR_CAST(bo);
- attach_ops = &xe_dma_buf_attach_ops; + attach_ops = &xe_dma_buf_attach_ops.dma_ops; #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) if (test) attach_ops = test->attach_ops; #endif - attach = dma_buf_dynamic_attach(dma_buf, dev->dev, attach_ops, &bo->ttm.base); if (IS_ERR(attach)) { obj = ERR_CAST(attach); @@ -364,6 +419,12 @@ struct drm_gem_object *xe_gem_prime_import(struct drm_device *dev, return obj; }
+static const struct dma_buf_interconnect _xe_interconnect = { + .name = "xe_interconnect", +}; + +const struct dma_buf_interconnect *xe_interconnect = &_xe_interconnect; + #if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST) #include "tests/xe_dma_buf.c" #endif diff --git a/drivers/gpu/drm/xe/xe_dma_buf.h b/drivers/gpu/drm/xe/xe_dma_buf.h index 861dd28a862c..6b381ce4b7c1 100644 --- a/drivers/gpu/drm/xe/xe_dma_buf.h +++ b/drivers/gpu/drm/xe/xe_dma_buf.h @@ -11,5 +11,4 @@ struct dma_buf *xe_gem_prime_export(struct drm_gem_object *obj, int flags); struct drm_gem_object *xe_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf); - #endif diff --git a/drivers/gpu/drm/xe/xe_interconnect.h b/drivers/gpu/drm/xe/xe_interconnect.h new file mode 100644 index 000000000000..2b8bc9bf1c8d --- /dev/null +++ b/drivers/gpu/drm/xe/xe_interconnect.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2025 Intel Corporation + */ +#ifndef _XE_INTERCONNECT_H_ +#define _XE_INTERCONNECT_H_ + +#include <linux/types.h> +#include <linux/dma-buf.h> + +struct device_private_address; + +/* This file needs to be shared between the importer and exporter of the interconnect */ + +extern const struct dma_buf_interconnect *xe_interconnect; + +struct xe_interconnect_attach_ops { + struct dma_buf_interconnect_attach_ops base; + /* + * Here interconnect-private stuff can be added. + * Like a function to check interconnect possibility. + */ + bool allow_ic; +}; + +struct xe_interconnect_attach { + struct dma_buf_interconnect_attach base; + struct device_private_address *sg_list_replacement; +}; + +#endif
linaro-mm-sig@lists.linaro.org