All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCHv2 0/8] Add UALink Loopback support
@ 2026-10-27 19:54 Mukul Joshi
  2026-10-27 19:54 ` [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure Mukul Joshi
                   ` (7 more replies)
  0 siblings, 8 replies; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

This series of patches adds UALink loopback support. This will be
used for validating UALink HW path in manufacturing.

Mukul Joshi (8):
  drm/amdgpu: Add UALink loopback infrastructure
  drm/amdgpu: Support VMID 0 GPUVA completion WB address for UALink
  drm/amdgpu: Emulate exporter->importer UALink messages in loopback
  drm/amdgpu: Attribute UALink loopback messages to the correct sender
  drm/amdgpu: Set up UALink rings and metadata for loopback
  drm/amdgpu: Enable UALink loopback mode
  drm/amdgpu: Reject config updates in UALink loopback mode
  drm/amdgpu: Temporarily enable UALink loopback via module parameter

 drivers/gpu/drm/amd/amdgpu/amdgpu.h        |   1 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c    |  13 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 491 ++++++++++++++++++---
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h |  29 ++
 4 files changed, 466 insertions(+), 68 deletions(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 20:22   ` Felix Kuehling
  2026-10-27 19:54 ` [PATCHv2 2/8] drm/amdgpu: Support VMID 0 GPUVA completion WB address for UALink Mukul Joshi
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

Add the infrastructure for UALink loopback mode, where a single GPU
plays both the exporter and the importer role under two fixed
accelerator IDs:

- Add struct amdgpu_ualink_loopback to track the loopback state, along
  with the fixed exporter and importer accel IDs.

- Replace ualink_accel_id() with ualink_exp_accel_id() and
  ualink_imp_accel_id(), which return the exporter and importer accel
  ID in loopback mode.

- Add amdgpu_ualink_loopback_init(), which allocates the ordered
  workqueue used to emulate exporter->importer messages, and destroy
  it in amdgpu_ualink_object_fini().

Loopback is not enabled yet, and outside loopback both helpers return
ppod.accel_id, so there is no functional change.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
v1-v2:
- Drop the change in find_peer_adev() as its not needed (Lijo).

 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 99 +++++++++++++++++-----
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h | 29 +++++++
 2 files changed, 109 insertions(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index ca6432610f71..2cd6dc71ab9f 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -89,8 +89,34 @@ static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev)
 	return 0;
 }
 
+/*
+ * amdgpu_ualink_loopback_init - set up UALink loopback mode
+ *
+ * Called once loopback mode has been detected. Allocates the ordered
+ * workqueue used to emulate exporter->importer interrupts in SW.
+ */
+static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
+{
+	adev->ualink.loopback.wq =
+		alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
+	if (!adev->ualink.loopback.wq)
+		return -ENOMEM;
+
+	return 0;
+}
+
 static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
 {
+	/* Tear down the loopback self-interrupt workqueue allocated in
+	 * amdgpu_ualink_loopback_init(). destroy_workqueue() drains any
+	 * pending self-interrupt work first.
+	 */
+	if (adev->ualink.loopback.wq) {
+		destroy_workqueue(adev->ualink.loopback.wq);
+		adev->ualink.loopback.wq = NULL;
+		adev->ualink.loopback.enabled = false;
+	}
+
 	if (!adev->ualink.info)
 		return;
 
@@ -472,6 +498,14 @@ int ualink_ip_late_init(struct amdgpu_ip_block *ip_block)
 		__amdgpu_ualink_activate_vpod_locked(adev);
 	}
 
+	if (adev->ualink.loopback.enabled) {
+		r = amdgpu_ualink_loopback_init(adev);
+		if (r) {
+			dev_err(adev->dev, "Failed to set up UALink loopback: %d\n", r);
+			goto error;
+		}
+	}
+
 	r = amdgpu_ualink_drm_client_create(adev);
 	if (r) {
 		dev_err(adev->dev, "Failed to create UALink DRM client: %d\n",
@@ -1028,6 +1062,31 @@ static struct amdgpu_device *find_peer_adev(unsigned int accel_id)
 	return NULL;
 }
 
+/*
+ * Physical accelerator roles.
+ *
+ * Outside loopback a GPU owns a single accelerator, so both helpers return its
+ * ppod.accel_id and the exporter/importer distinction collapses. In loopback
+ * the one physical GPU plays both roles: the exporter owns the MP nHT and
+ * receives all HW traffic, while the importer is the (virtual) HW sender for
+ * the importer -> exporter path.
+ */
+static inline u32 ualink_exp_accel_id(struct amdgpu_device *adev)
+{
+	if (adev->ualink.loopback.enabled)
+		return adev->ualink.loopback.exporter_accel_id;
+
+	return adev->ualink.info->ppod.accel_id;
+}
+
+static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
+{
+	if (adev->ualink.loopback.enabled)
+		return adev->ualink.loopback.importer_accel_id;
+
+	return adev->ualink.info->ppod.accel_id;
+}
+
 static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
 					 u32 accel_id)
 {
@@ -4487,11 +4546,6 @@ static bool amdgpu_ualink_vpod_membership_changed(struct amdgpu_device *adev)
 			     AMDGPU_UALINK_ACCEL_MAX);
 }
 
-static inline u32 ualink_accel_id(struct amdgpu_device *adev)
-{
-	return adev->ualink.info->ppod.accel_id;
-}
-
 static inline enum amdgpu_ualink_addr_mode ualink_addr_mode(struct amdgpu_device *adev)
 {
 	return adev->ualink.info->vpod.addr_mode;
@@ -4837,7 +4891,7 @@ static void amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
 {
 	struct amdgpu_ualink_remote *remote = to_remote(adev);
 	u64 timeout = msecs_to_jiffies(2000);
-	u32 dst_accel_id = ualink_accel_id(adev);
+	u32 dst_accel_id = ualink_exp_accel_id(adev);
 	struct amdgpu_bo *bos[2];
 	u32 n_bos;
 	struct dma_fence *fence;
@@ -4956,7 +5010,7 @@ static void amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
 static int amdgpu_ualink_metadata_npa_mapping(struct amdgpu_device *adev)
 {
 	struct amdgpu_ualink_remote *remote = to_remote(adev);
-	u32 dst_accel_id = ualink_accel_id(adev);
+	u32 dst_accel_id = ualink_exp_accel_id(adev);
 	u64 timeout = msecs_to_jiffies(2000);
 	struct amdgpu_bo *bos[2];
 	u32 n_bos;
@@ -5241,7 +5295,7 @@ static void amdgpu_ualink_metadata_fini(struct amdgpu_device *adev)
 {
 	struct amdgpu_ualink_remote *remote = to_remote(adev);
 
-	dev_dbg(adev->dev, "accel_id %u\n", ualink_accel_id(adev));
+	dev_dbg(adev->dev, "accel_id %u\n", ualink_exp_accel_id(adev));
 	amdgpu_bo_free_kernel(&remote->rptr_bo, &remote->rptr_gpu_addr,
 				      &remote->rptr_cpu_addr);
 	amdgpu_bo_free_kernel(&remote->ring_bo, &remote->rb_gpu_addr,
@@ -5288,7 +5342,7 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
 	}
 
 	dev_dbg(adev->dev, "accel_id %u addr_mode %d fw status 0x%x\n",
-		ualink_accel_id(adev), ualink_addr_mode(adev), status);
+		ualink_exp_accel_id(adev), ualink_addr_mode(adev), status);
 
 	/* Alloc metadata structure for all GPUs */
 	metadata_size = sizeof(struct amdgpu_ualink_metadata) * AMDGPU_UALINK_ACCEL_MAX;
@@ -5361,12 +5415,12 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
 	}
 
 	if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS)
-		wptr_gpu_addr = remote->rb_gpu_addr + rb_size * ualink_accel_id(adev);
+		wptr_gpu_addr = remote->rb_gpu_addr + rb_size * ualink_exp_accel_id(adev);
 
 	for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) {
 		dev_dbg(adev->dev, "init for accel_id %u\n", accel_id);
 
-		if (accel_id == ualink_accel_id(adev))
+		if (accel_id == ualink_exp_accel_id(adev))
 			continue;
 
 		rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id;
@@ -5392,7 +5446,7 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
 						     ualink_tlb_wptr_offset(adev, accel_id);
 
 		dev_dbg(adev->dev, "init from accel_id %u to accel_id %u, rb_size 0x%x\n",
-			accel_id, ualink_accel_id(adev), metadata[accel_id].rb_size);
+			accel_id, ualink_exp_accel_id(adev), metadata[accel_id].rb_size);
 		dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n",
 			metadata[accel_id].ri_rb, metadata[accel_id].tlb_inv_rb);
 		dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n",
@@ -5806,7 +5860,14 @@ static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
 				      u64 *wb_npa, u32 type)
 {
 	struct amdgpu_ualink_remote *remote = to_remote(adev);
-	u32 accel_id = ualink_accel_id(adev);
+	/* The GPUID encoded in the write-back NPA address is the routing target
+	 * the remote writes the rptr back to, i.e. THIS GPU. It must be the
+	 * physically-owned accel_id: outside loopback that is our own accel_id,
+	 * and in loopback it is the exporter (the only id the single GPU owns
+	 * and can receive on). The importer is only a logical source id for the
+	 * forward path and must never appear as an NPA GPUID.
+	 */
+	u32 accel_id = ualink_exp_accel_id(adev);
 	u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE);
 	uintptr_t wb;
 	u64 npa;
@@ -5981,19 +6042,19 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
 						   &peer->shootdown, &tlb_wptr);
 	if (r) {
 		dev_dbg(adev->dev, "accel_id %u read remote %u wptr failed %d\n",
-			ualink_accel_id(adev), remote_accel_id, r);
+			ualink_exp_accel_id(adev), remote_accel_id, r);
 		return r;
 	}
 
 	if (peer->interrupt.wptr == int_wptr && peer->shootdown.wptr == tlb_wptr) {
 		dev_dbg(adev->dev, "accel_id %u wptr 0x%llx 0x%llx is same as remote %u\n",
-			ualink_accel_id(adev), int_wptr, tlb_wptr, remote_accel_id);
+			ualink_exp_accel_id(adev), int_wptr, tlb_wptr, remote_accel_id);
 		return 0;
 	}
 
 	dev_dbg(adev->dev,
 		"accel_id %u wptr 0x%llx 0x%llx sync to remote %u wptr 0x%llx 0x%llx\n",
-		ualink_accel_id(adev), peer->interrupt.wptr, peer->shootdown.wptr,
+		ualink_exp_accel_id(adev), peer->interrupt.wptr, peer->shootdown.wptr,
 		remote_accel_id, int_wptr, tlb_wptr);
 
 	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL,
@@ -6164,7 +6225,7 @@ static int amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev)
 	struct amdgpu_ualink_peer *peer;
 	struct amdgpu_ualink_ring *interrupt;
 	struct amdgpu_ualink_ring *shootdown;
-	u32 src_accel_id = ualink_accel_id(adev);
+	u32 src_accel_id = ualink_exp_accel_id(adev);
 	u32 dst_accel_id;
 	u64 npa, flags;
 	int r = 0;
@@ -6287,7 +6348,7 @@ static void amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev)
 {
 	u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT;
 	struct amdgpu_ualink_remote *remote = to_remote(adev);
-	u32 src_accel_id = ualink_accel_id(adev);
+	u32 src_accel_id = ualink_exp_accel_id(adev);
 	u32 dst_accel_id;
 	struct amdgpu_ualink_peer *peer;
 	struct amdgpu_ualink_ring *ring;
@@ -6374,7 +6435,7 @@ int amdgpu_ualink_sw_init(struct amdgpu_device *adev)
  */
 void amdgpu_ualink_sw_fini(struct amdgpu_device *adev)
 {
-	dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n", ualink_accel_id(adev),
+	dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n", ualink_exp_accel_id(adev),
 		ualink_addr_mode(adev));
 
 	adev->ualink.msg_ctl->send_halt(adev);
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
index 85c9a9634a4b..0b1e8dd38666 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
@@ -97,6 +97,17 @@ enum AMDGPU_UALINK_PROTOCOL_MESSAGES {
 #define AMDGPU_UALINK_LIGHTWEIGHT_TLB_SHOOTDOWN		0x1
 #define AMDGPU_UALINK_HEAVYWEIGHT_TLB_SHOOTDOWN		0x2
 
+/* UALink loopback support.
+ *
+ * In loopback mode a single GPU loops UALink requests back to itself for
+ * manufacturing testing. The GPU exposes two fixed accelerator IDs: it acts
+ * as the exporter on one and the importer on the other. Loopback is enabled
+ * with the amdgpu.ualink_loopback module parameter.
+ */
+/* Fixed accelerator IDs used by a loopback GPU. */
+#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID		1
+#define AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID		0
+
 enum amdgpu_ualink_conn_state {
 	AMDGPU_UALINK_CONN_NOT_READY			= 0,
 	AMDGPU_UALINK_CONN_IN_PROGRESS			= 1,
@@ -319,6 +330,21 @@ struct amdgpu_ualink_msg_ctl {
 	int (*send_halt)(struct amdgpu_device *adev);
 };
 
+/* UALink loopback mode state.
+ *
+ * When @enabled is true this single GPU acts as both exporter
+ * (@exporter_accel_id) and importer (@importer_accel_id). There is no second
+ * physical GPU, so exporter->importer messages cannot be delivered by the
+ * (single) MP nHT and are instead emulated in SW by feeding them back into
+ * our own IRQ handler via @wq.
+ */
+struct amdgpu_ualink_loopback {
+	bool enabled;
+	u32 exporter_accel_id;
+	u32 importer_accel_id;
+	struct workqueue_struct *wq;
+};
+
 struct amdgpu_ualink_mgr {
 	u64 npa_size;
 	u32 psp_if_ver;
@@ -333,6 +359,9 @@ struct amdgpu_ualink_mgr {
 	/* For remote interrupt and shootdown */
 	struct amdgpu_ualink_remote *remote;
 
+	/* UALink loopback mode state (see struct amdgpu_ualink_loopback) */
+	struct amdgpu_ualink_loopback loopback;
+
 	/* handle irq from ualink client of remote GPUs */
 	struct amdgpu_irq_src irq;
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCHv2 2/8] drm/amdgpu: Support VMID 0 GPUVA completion WB address for UALink
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
  2026-10-27 19:54 ` [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 21:23   ` Felix Kuehling
  2026-10-27 19:54 ` [PATCHv2 3/8] drm/amdgpu: Emulate exporter->importer UALink messages in loopback Mukul Joshi
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

The UPDATE_WB_ADDR remote command hands the MP nHT FW the address it
writes command completions back to. So far that has always been an NPA.

The FW can also take a VMID 0 GPUVA, selected by setting DW4 of the
command to 1. Return the VMID 0 GPUVA of the writeback slot from
amdgpu_ualink_get_wb_addr() alongside the NPA, and use it in
UALink loopback mode. There the HW only carries importer -> exporter
traffic, so the FW cannot write the completion back over NPA.

Outside loopback the NPA is still used and DW4 stays 0.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 48 +++++++++++++++-------
 1 file changed, 34 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index 2cd6dc71ab9f..a186859dee3c 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -5641,9 +5641,9 @@ static void amdgpu_ualink_emit_update_wb_addr(u32 **cpu_addr_p, u32 wb_data,
 
 	*cpu_addr++ = cmd;
 	*cpu_addr++ = wb_data;
-	*cpu_addr++ = dw1 & 0xFFFFFFFC;	/* lower32 wb_npa */
-	*cpu_addr++ = dw0 & 0xFFFFF;	/* upper32 wb_npa */
-	*cpu_addr++ = 0;
+	*cpu_addr++ = dw1 & 0xFFFFFFFC;	/* lower32 wb address */
+	*cpu_addr++ = dw0 & 0xFFFFF;	/* upper32 wb address */
+	*cpu_addr++ = dw2;		/* 1: wb address is VMID 0 GPUVA, 0: NPA */
 	*cpu_addr++ = 0;
 	*cpu_addr++ = 0; /* metadata */
 	*cpu_addr++ = 0; /* metadata */
@@ -5857,7 +5857,7 @@ static int amdgpu_ualink_send_command(struct amdgpu_device *adev,
 static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
 				      u32 remote_accel_id,
 				      struct amdgpu_ualink_wb **wb_cpu,
-				      u64 *wb_npa, u32 type)
+				      u64 *wb_npa, u64 *wb_gpu, u32 type)
 {
 	struct amdgpu_ualink_remote *remote = to_remote(adev);
 	/* The GPUID encoded in the write-back NPA address is the routing target
@@ -5870,15 +5870,17 @@ static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
 	u32 accel_id = ualink_exp_accel_id(adev);
 	u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE);
 	uintptr_t wb;
-	u64 npa;
+	u64 gpu, npa;
 	u32 offset = 0;
 
 	if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) {
 		wb = (uintptr_t)remote->rb_cpu_addr + rb_size * remote->num_accel;
+		gpu = remote->rb_gpu_addr + rb_size * remote->num_accel;
 		npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TAILPTR,
 					     remote_accel_id, accel_id);
 	} else {
 		wb = (uintptr_t)remote->rptr_cpu_addr;
+		gpu = remote->rptr_gpu_addr;
 		npa = remote->rptr_npa;
 		npa |= (u64)accel_id << AMDGPU_UALINK_GART_NPA_ADDR_GPUID_SHIFT;
 	}
@@ -5891,9 +5893,11 @@ static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
 	*wb_cpu = (struct amdgpu_ualink_wb *)(wb + offset);
 	if (wb_npa)
 		*wb_npa = npa + offset;
+	if (wb_gpu)
+		*wb_gpu = gpu + offset;
 
-	dev_dbg(adev->dev, "source %d remote %d wb npa 0x%llx\n", accel_id,
-		remote_accel_id, npa + offset);
+	dev_dbg(adev->dev, "source %d remote %d wb npa 0x%llx gpu 0x%llx\n", accel_id,
+		remote_accel_id, npa + offset, gpu + offset);
 }
 
 struct amdgpu_ualink_wptr_fence_cb {
@@ -6057,9 +6061,9 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
 		ualink_exp_accel_id(adev), peer->interrupt.wptr, peer->shootdown.wptr,
 		remote_accel_id, int_wptr, tlb_wptr);
 
-	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL,
+	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL, NULL,
 				  RB_TYPE_REMOTE_INTERRUPT);
-	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &tlb_wb, NULL,
+	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &tlb_wb, NULL, NULL,
 				  RB_TYPE_TLB_INV);
 
 	peer->interrupt.wptr = int_wptr;
@@ -6093,7 +6097,8 @@ static int amdgpu_ualink_update_wb_address(struct amdgpu_device *adev,
 	struct amdgpu_ualink_peer *peer;
 	struct amdgpu_ualink_ring *ring;
 	struct amdgpu_ualink_wb *wb_cpu;
-	u64 wb_npa;
+	u64 wb_npa, wb_gpu, wb_addr;
+	u32 wb_vmid0;
 	int r;
 
 	peer = &remote->peer[remote_accel_id];
@@ -6105,13 +6110,28 @@ static int amdgpu_ualink_update_wb_address(struct amdgpu_device *adev,
 		return -EINVAL;
 
 	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &wb_cpu, &wb_npa,
-				  ring_type);
+				  &wb_gpu, ring_type);
+
+	/* UALink loopback: HW only carries importer -> exporter traffic, so the
+	 * MPNHT FW cannot write the completion back over NPA. Hand it the VMID 0
+	 * GPUVA of the writeback instead and flag it in DW4 of the command.
+	 */
+	if (adev->ualink.loopback.enabled) {
+		wb_addr = wb_gpu;
+		wb_vmid0 = 1;
+	} else {
+		wb_addr = wb_npa;
+		wb_vmid0 = 0;
+	}
+
+	dev_dbg(adev->dev, "remote %u ring type %u wb %s 0x%llx\n",
+		remote_accel_id, ring_type, wb_vmid0 ? "gpuva" : "npa", wb_addr);
 
 	r = amdgpu_ualink_send_command(adev, remote_accel_id, ring, wb_cpu,
 				       amdgpu_ualink_emit_update_wb_addr,
-				       upper_32_bits(wb_npa),
-				       lower_32_bits(wb_npa),
-				       0, 0);
+				       upper_32_bits(wb_addr),
+				       lower_32_bits(wb_addr),
+				       wb_vmid0, 0);
 	if (!r) {
 		ring->wb_cpu = wb_cpu;
 		ring->ready = true;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCHv2 3/8] drm/amdgpu: Emulate exporter->importer UALink messages in loopback
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
  2026-10-27 19:54 ` [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure Mukul Joshi
  2026-10-27 19:54 ` [PATCHv2 2/8] drm/amdgpu: Support VMID 0 GPUVA completion WB address for UALink Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 21:34   ` Felix Kuehling
  2026-10-27 19:54 ` [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender Mukul Joshi
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

In UALink loopback mode a single GPU acts as both the exporter and the
importer. The exporter is the one physical station, so the only path
the HW serves is importer -> exporter. A message the exporter sends to
the importer cannot be delivered by the MP nHT.

Emulate that direction in SW: when loopback is enabled and a remote
interrupt is addressed to the importer, queue it on the loopback
ordered workqueue, which feeds it back into amdgpu_ualink_process_irq()
as if the exporter had sent it. The ordered workqueue keeps successive
messages in order and runs the (possibly sleeping) handlers outside
the sender's context.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
v1->v2:
- Use wrapper functions in amdgpu_ualink_remote_interrupt() (Lijo).
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 82 ++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index a186859dee3c..6c70be7badec 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -60,6 +60,9 @@ static void amdgpu_ualink_reset_peer_rings(struct amdgpu_device *adev,
 					   u32 remote_accel_id);
 static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
 					  u32 remote_accel_id);
+static int amdgpu_ualink_loopback_self_interrupt(struct amdgpu_device *adev,
+						 u32 sender_accel_id, u32 dw0,
+						 u32 dw1, u32 dw2, u32 dw3);
 
 #define STRIP_NPA(addr)						\
 	(((u64)(addr) & ~AMDGPU_UALINK_NPA_ADDR_GPUID_MASK))
@@ -6209,6 +6212,17 @@ static int amdgpu_ualink_remote_interrupt(struct amdgpu_device *adev,
 	struct amdgpu_ualink_ring *ring;
 	int r;
 
+	/* UALink loopback: the single MP nHT can only deliver messages addressed
+	 * to the exporter accelerator. Messages addressed to the importer
+	 * (exporter->importer direction) cannot be delivered by HW, so emulate
+	 * them by feeding the message back into our own IRQ handler.
+	 */
+	if (adev->ualink.loopback.enabled &&
+	    remote_accel_id == ualink_imp_accel_id(adev))
+		return amdgpu_ualink_loopback_self_interrupt(adev,
+				ualink_exp_accel_id(adev),
+				dw0, dw1, dw2, dw3);
+
 	peer = &remote->peer[remote_accel_id];
 	ring = &peer->interrupt;
 
@@ -6630,6 +6644,74 @@ static int amdgpu_ualink_process_irq(struct amdgpu_device *adev,
 	return handled;
 }
 
+/* Work item used to emulate an exporter->importer UALink interrupt in
+ * loopback mode (see amdgpu_ualink_loopback_self_interrupt()).
+ */
+struct amdgpu_ualink_self_irq_work {
+	struct work_struct work;
+	struct amdgpu_device *adev;
+	u32 sender_accel_id;
+	u32 dw[4];
+};
+
+static void amdgpu_ualink_self_irq_worker(struct work_struct *w)
+{
+	struct amdgpu_ualink_self_irq_work *job =
+		container_of(w, struct amdgpu_ualink_self_irq_work, work);
+	struct amdgpu_device *adev = job->adev;
+	struct amdgpu_iv_entry entry = { };
+
+	/* Build an IH entry as if the message had been delivered by the MP nHT
+	 * from the exporter accelerator. amdgpu_ualink_process_irq() consumes
+	 * the sender accel_id from entry->pasid and the four context dwords
+	 * from entry->src_data[]. entry->ih must differ from adev->irq.ih so
+	 * the message is processed here instead of being delegated to the
+	 * UALink soft ring.
+	 */
+	entry.ih = NULL;
+	entry.pasid = job->sender_accel_id;
+	entry.src_data[0] = job->dw[0];
+	entry.src_data[1] = job->dw[1];
+	entry.src_data[2] = job->dw[2];
+	entry.src_data[3] = job->dw[3];
+
+	amdgpu_ualink_process_irq(adev, &adev->ualink.irq, &entry);
+	kfree(job);
+}
+
+/*
+ * amdgpu_ualink_loopback_self_interrupt - deliver a UALink message to ourselves
+ *
+ * Used in loopback mode for the exporter->importer direction, where there is
+ * no HW path. The message is processed asynchronously on an ordered workqueue
+ * so ordering between successive messages is preserved and the (potentially
+ * sleeping) message handlers do not run from the caller's context.
+ */
+static int amdgpu_ualink_loopback_self_interrupt(struct amdgpu_device *adev,
+						 u32 sender_accel_id, u32 dw0,
+						 u32 dw1, u32 dw2, u32 dw3)
+{
+	struct amdgpu_ualink_self_irq_work *job;
+
+	if (!adev->ualink.loopback.wq)
+		return -ENODEV;
+
+	job = kzalloc(sizeof(*job), GFP_KERNEL);
+	if (!job)
+		return -ENOMEM;
+
+	job->adev = adev;
+	job->sender_accel_id = sender_accel_id;
+	job->dw[0] = dw0;
+	job->dw[1] = dw1;
+	job->dw[2] = dw2;
+	job->dw[3] = dw3;
+	INIT_WORK(&job->work, amdgpu_ualink_self_irq_worker);
+	queue_work(adev->ualink.loopback.wq, &job->work);
+
+	return 0;
+}
+
 static int amdgpu_ualink_set_irq_state(struct amdgpu_device *adev,
 					struct amdgpu_irq_src *source,
 					u32 type,
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
                   ` (2 preceding siblings ...)
  2026-10-27 19:54 ` [PATCHv2 3/8] drm/amdgpu: Emulate exporter->importer UALink messages in loopback Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 21:38   ` Felix Kuehling
  2026-10-28  6:23   ` Lazar, Lijo
  2026-10-27 19:54 ` [PATCHv2 5/8] drm/amdgpu: Set up UALink rings and metadata for loopback Mukul Joshi
                   ` (3 subsequent siblings)
  7 siblings, 2 replies; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

In UALink loopback mode a single GPU acts as both the exporter and the
importer, so the sender accel ID of a message depends on its direction.

- Send HELLO as the opposite role of the destination: a message to the
  exporter is sent as the importer and vice versa. Outside loopback
  this is still the GPU's own accel ID.

- The MP nHT stamps the IH cookie of every HW-delivered message with
  the GPU ID of the single physical station, the exporter. The only HW
  path is importer -> exporter, so attribute these messages to the
  importer. SW-emulated exporter -> importer messages carry no IH ring
  and already name the exporter as the sender.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
v1->v2:
- Use ualink_exp_accel_id/ualink_imp_accel_id() wrappers in
  amdgpu_ualink_process_irq(). (Lijo)

 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 32 +++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index 6c70be7badec..96a6023f51c5 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -1090,6 +1090,23 @@ static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
 	return adev->ualink.info->ppod.accel_id;
 }
 
+/*
+ * amdgpu_ualink_local_sender_accel_id - sender accel_id for an outgoing message
+ *
+ * Normally a GPU sends messages as its own (single) accelerator. In loopback
+ * mode the same GPU plays both roles, so the sender identity depends on the
+ * destination: a message to the exporter is sent as the importer and vice
+ * versa. Outside loopback both roles collapse to the GPU's own accel_id.
+ */
+static u32 amdgpu_ualink_local_sender_accel_id(struct amdgpu_device *adev,
+					       u32 remote_acc_id)
+{
+	if (remote_acc_id == ualink_exp_accel_id(adev))
+		return ualink_imp_accel_id(adev);
+
+	return ualink_exp_accel_id(adev);
+}
+
 static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
 					 u32 accel_id)
 {
@@ -2238,7 +2255,10 @@ static int amdgpu_ualink_send_hello_msg(struct amdgpu_device *adev,
 
 	dw0 = AMDGPU_UALINK_HELLO_MSG;
 	dw0 |= (remote_acc_id << AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT);
-	dw0 |= (adev->ualink.info->ppod.accel_id <<
+	/* In loopback the sender is the opposite role of the destination, so
+	 * the exporter side tracks the connection under the importer accel_id.
+	 */
+	dw0 |= (amdgpu_ualink_local_sender_accel_id(adev, remote_acc_id) <<
 		AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT);
 
 	dev_dbg(adev->dev, "SEND HELLO: HELLO message to remote AccId:%u\n",
@@ -6548,6 +6568,16 @@ static int amdgpu_ualink_process_irq(struct amdgpu_device *adev,
 		return handled;
 	}
 
+	/* UALink loopback: the MP nHT stamps every HW-delivered message with
+	 * the GPU ID of the single physical station, the exporter. The only
+	 * HW path is importer -> exporter, so the real sender is the importer.
+	 * SW-emulated exporter -> importer messages carry no IH ring and
+	 * already name the exporter as sender.
+	 */
+	if (adev->ualink.loopback.enabled && entry->ih &&
+	    src_acc_id == ualink_exp_accel_id(adev))
+		src_acc_id = ualink_imp_accel_id(adev);
+
 	msg_type = dw0 & AMDGPU_UALINK_MESSAGE_HEADER_MASK;
 	local_acc_id = adev->ualink.info->ppod.accel_id;
 	dev_dbg(adev->dev, "Got MSG: remote acc_id %u msg_type %u\n",
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCHv2 5/8] drm/amdgpu: Set up UALink rings and metadata for loopback
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
                   ` (3 preceding siblings ...)
  2026-10-27 19:54 ` [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 20:20   ` Felix Kuehling
  2026-10-27 19:54 ` [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode Mukul Joshi
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

In UALink loopback mode a single GPU acts as both the exporter and the
importer, and the only path the HW serves is importer -> exporter.
Adjust the per-peer setup accordingly:

- The importer's doorbell reaches the MP nHT stamped with the GPU ID of
  the exporter, and the FW looks up the metadata entry by that ID. So
  publish the importer's rings and tail pointers in metadata[exporter].

- The local sender of the HW-served direction is the importer, so set
  up (and tear down) the send rings with the importer as the source.

- Skip the ring pointer resync. Both ends of every ring are on this
  GPU, so there is no remote peer that can reboot and leave the
  pointers stale.

- Do not take the local dma-buf shortcut on import. It would bypass
  the NPA path that loopback is meant to exercise.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 66 +++++++++++++++++-----
 1 file changed, 51 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index 96a6023f51c5..e6272446ff4f 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -3965,7 +3965,13 @@ int amdgpu_ualink_import_handle(struct drm_device *dev,
 		return -EINVAL;
 	}
 
-	if (amdgpu_ualink_is_local_accel(adev, remote_acc_id))
+	/* In loopback mode the exporter and importer are the same GPU, but the
+	 * import must still go through the full cross-domain NPA path (the
+	 * dma_buf shortcut in amdgpu_ualink_local_import() bypasses NPA). Only
+	 * take the local shortcut for genuine same-pod local accelerators.
+	 */
+	if (!adev->ualink.loopback.enabled &&
+	    amdgpu_ualink_is_local_accel(adev, remote_acc_id))
 		return amdgpu_ualink_local_import(adev, remote_acc_id,
 						  &handle, fd_out);
 
@@ -5441,11 +5447,28 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
 		wptr_gpu_addr = remote->rb_gpu_addr + rb_size * ualink_exp_accel_id(adev);
 
 	for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) {
+		struct amdgpu_ualink_metadata *md;
+
 		dev_dbg(adev->dev, "init for accel_id %u\n", accel_id);
 
 		if (accel_id == ualink_exp_accel_id(adev))
 			continue;
 
+		/* In loopback the single MP nHT services the exporter half:
+		 * the only messages delivered over HW are importer -> exporter
+		 * (source = importer accel_id), while the exporter -> importer
+		 * direction is emulated in SW and never reaches the MP nHT.
+		 *
+		 * The importer's doorbell reaches the MP nHT stamped with the
+		 * GPU ID of the single physical station, the exporter, so the
+		 * FW looks up metadata[exporter]. Publish the importer's rings
+		 * and tail pointers in that entry.
+		 */
+		if (adev->ualink.loopback.enabled)
+			md = &metadata[ualink_exp_accel_id(adev)];
+		else
+			md = &metadata[accel_id];
+
 		rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id;
 
 		if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) {
@@ -5453,27 +5476,26 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
 			wptr_gpu_addr += wptr_size * accel_id;
 		}
 
-		metadata[accel_id].header = AMDGPU_UALINK_METADATA_HEADER;
-		metadata[accel_id].rb_size = fls(AMDGPU_UALINK_RB_SIZE / 64) - 1;
+		md->header = AMDGPU_UALINK_METADATA_HEADER;
+		md->rb_size = fls(AMDGPU_UALINK_RB_SIZE / 64) - 1;
 
 		/*
 		 * remote shootdown type is 0, remote command type is 1
 		 * with 2MB ring buffer mapping, remote shootdown ring NPA is before interrupt
 		 */
-		metadata[accel_id].ri_rb = rb_gpu_addr + AMDGPU_UALINK_RB_SIZE;
-		metadata[accel_id].tlb_inv_rb = rb_gpu_addr;
+		md->ri_rb = rb_gpu_addr + AMDGPU_UALINK_RB_SIZE;
+		md->tlb_inv_rb = rb_gpu_addr;
 
-		metadata[accel_id].tailptr_ri = wptr_gpu_addr +
-						ualink_wptr_offset(adev, accel_id);
-		metadata[accel_id].tailptr_tlb_inv = wptr_gpu_addr +
-						     ualink_tlb_wptr_offset(adev, accel_id);
+		md->tailptr_ri = wptr_gpu_addr + ualink_wptr_offset(adev, accel_id);
+		md->tailptr_tlb_inv = wptr_gpu_addr +
+				      ualink_tlb_wptr_offset(adev, accel_id);
 
-		dev_dbg(adev->dev, "init from accel_id %u to accel_id %u, rb_size 0x%x\n",
-			accel_id, ualink_exp_accel_id(adev), metadata[accel_id].rb_size);
+		dev_dbg(adev->dev, "init from accel_id %u to accel_id %u, metadata entry %td, rb_size 0x%x\n",
+			accel_id, ualink_exp_accel_id(adev), md - metadata, md->rb_size);
 		dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n",
-			metadata[accel_id].ri_rb, metadata[accel_id].tlb_inv_rb);
+			md->ri_rb, md->tlb_inv_rb);
 		dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n",
-			metadata[accel_id].tailptr_ri, metadata[accel_id].tailptr_tlb_inv);
+			md->tailptr_ri, md->tailptr_tlb_inv);
 	}
 
 	r = adev->ualink.msg_ctl->send_metadata(adev,
@@ -6058,6 +6080,12 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
 	u64 int_wptr, tlb_wptr;
 	int r;
 
+	/* UALink loopback: both ends of every ring live on this GPU, so there
+	 * is no remote peer that can reboot and leave the pointers stale.
+	 */
+	if (adev->ualink.loopback.enabled)
+		return 0;
+
 	/* ring->wptr and ring->rptr are owned by the send path. */
 	guard(mutex)(&peer->lock);
 
@@ -6279,7 +6307,12 @@ static int amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev)
 	struct amdgpu_ualink_peer *peer;
 	struct amdgpu_ualink_ring *interrupt;
 	struct amdgpu_ualink_ring *shootdown;
-	u32 src_accel_id = ualink_exp_accel_id(adev);
+	/* This sets up the send rings (local sender -> each remote dst). In
+	 * loopback the HW-served direction is importer -> exporter, so the
+	 * local sender is the importer; the skip below then skips the importer
+	 * and sets up the peer[exporter] ring that the HW path needs.
+	 */
+	u32 src_accel_id = ualink_imp_accel_id(adev);
 	u32 dst_accel_id;
 	u64 npa, flags;
 	int r = 0;
@@ -6402,7 +6435,10 @@ static void amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev)
 {
 	u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT;
 	struct amdgpu_ualink_remote *remote = to_remote(adev);
-	u32 src_accel_id = ualink_exp_accel_id(adev);
+	/* Mirror peer_remote_init: in loopback the local sender is the
+	 * importer, so unmap the peer[exporter] send ring that was set up.
+	 */
+	u32 src_accel_id = ualink_imp_accel_id(adev);
 	u32 dst_accel_id;
 	struct amdgpu_ualink_peer *peer;
 	struct amdgpu_ualink_ring *ring;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
                   ` (4 preceding siblings ...)
  2026-10-27 19:54 ` [PATCHv2 5/8] drm/amdgpu: Set up UALink rings and metadata for loopback Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 21:45   ` Felix Kuehling
  2026-10-28  6:33   ` Lazar, Lijo
  2026-10-27 19:54 ` [PATCHv2 7/8] drm/amdgpu: Reject config updates in " Mukul Joshi
  2026-10-27 19:54 ` [PATCHv2 8/8] drm/amdgpu: Temporarily enable UALink loopback via module parameter Mukul Joshi
  7 siblings, 2 replies; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

In UALink loopback mode a single GPU acts as both the exporter (accel
ID 1) and the importer (accel ID 0). This lets the existing tests export
a BO and import it on the same device, traversing the UALink HW
pipeline.

Add amdgpu_ualink_loopback_detect(), the single place that decides
whether loopback is enabled. It runs right after the UALink info is
queried from PSP, before any per-peer setup. When loopback is enabled:

- There is no user-space agent to configure the pod, so synthesize the
  pPod/vPod configuration with both accel IDs active.

- Activate the device directly instead of through the cross-GPU vpod
  activation path, which needs peer GPUs and a user-space vpod commit.

The mechanism that requests loopback mode is added separately, so
amdgpu_ualink_loopback_requested() returns false for now. The normal
activation path is unchanged when loopback is not enabled.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 128 ++++++++++++++++++++-
 1 file changed, 123 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index e6272446ff4f..14b9c05a0f0f 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -63,6 +63,7 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
 static int amdgpu_ualink_loopback_self_interrupt(struct amdgpu_device *adev,
 						 u32 sender_accel_id, u32 dw0,
 						 u32 dw1, u32 dw2, u32 dw3);
+static void activate_accelerator(struct amdgpu_device *adev);
 
 #define STRIP_NPA(addr)						\
 	(((u64)(addr) & ~AMDGPU_UALINK_NPA_ADDR_GPUID_MASK))
@@ -93,19 +94,130 @@ static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev)
 }
 
 /*
- * amdgpu_ualink_loopback_init - set up UALink loopback mode
+ * amdgpu_ualink_loopback_requested - check whether UALink loopback is requested
  *
- * Called once loopback mode has been detected. Allocates the ordered
- * workqueue used to emulate exporter->importer interrupts in SW.
+ * There is no mechanism to request loopback mode yet, so it stays disabled.
+ */
+static bool amdgpu_ualink_loopback_requested(struct amdgpu_device *adev)
+{
+	return false;
+}
+
+/*
+ * amdgpu_ualink_loopback_detect - detect UALink loopback mode
+ *
+ * This is the only place that decides whether loopback mode is enabled. It
+ * must run before any per-peer setup, since enabling loopback changes the
+ * accelerator IDs returned by ualink_exp_accel_id() and ualink_imp_accel_id().
+ */
+static void amdgpu_ualink_loopback_detect(struct amdgpu_device *adev)
+{
+	if (!amdgpu_ualink_loopback_requested(adev))
+		return;
+
+	adev->ualink.loopback.enabled = true;
+	adev->ualink.loopback.exporter_accel_id =
+		AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID;
+	adev->ualink.loopback.importer_accel_id =
+		AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID;
+}
+
+/*
+ * amdgpu_ualink_loopback_populate_info - synthesize pPod/vPod config for loopback
+ *
+ * In loopback there is no user-space agent (AIFM / xncmdclient) writing the
+ * UALink sysfs nodes to configure the pod, so populate adev->ualink.info
+ * directly. The layout is fixed and known: a single GPU alone in its own
+ * physical pod, presented as accelerator ID 1 (the exporter) and also owning
+ * accelerator ID 0 (the importer). The values are made self-consistent so
+ * check_vpod_info() would pass and amdgpu_ualink_metadata_init() programs the
+ * importer's ring buffers / GART NPA mappings / MP nHT receive metadata.
+ */
+static void amdgpu_ualink_loopback_populate_info(struct amdgpu_device *adev)
+{
+	struct amdgpu_ualink_info *info = adev->ualink.info;
+	u32 exporter = adev->ualink.loopback.exporter_accel_id; /* 1 */
+	u32 importer = adev->ualink.loopback.importer_accel_id; /* 0 */
+
+	/* Physical pod: this GPU is presented as the exporter accel ID in its
+	 * own pod. ppod.size must be large enough to contain both the exporter
+	 * and importer accel IDs. ppod.id must be unique within the system
+	 * (matters for 1P4G, where every loopback GPU is its own physical pod).
+	 */
+	info->ppod.accel_id = exporter;
+	info->ppod.size = max(exporter, importer) + 1;	/* == 2 */
+	uuid_gen(&info->ppod.id);
+
+	/* Virtual pod: mark BOTH the exporter and the importer active so the
+	 * per-peer setup (amdgpu_ualink_metadata_init()) programs the importer's
+	 * resources for the importer accel ID. Preserve the address mode queried
+	 * from firmware if it is valid (keeps source-identification HW working),
+	 * otherwise default to source-aliasing (MI450).
+	 */
+	info->vpod.id = 0;
+	info->vpod.size = 2;
+	if (info->vpod.addr_mode >= AMDGPU_UALINK_ADDR_MODE_MAX)
+		info->vpod.addr_mode = AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS;
+	bitmap_zero(info->vpod.active_accel_bits, AMDGPU_UALINK_ACCEL_MAX);
+	set_bit(exporter, info->vpod.active_accel_bits);
+	set_bit(importer, info->vpod.active_accel_bits);
+
+	/* Only the exporter (this GPU's own accel ID) is a local accelerator;
+	 * the importer is reached through the (loopback) remote/NPA path.
+	 */
+	info->n_local_accels = 1;
+	info->local_accels[0] = exporter;
+
+	/* Mark the pod vpod-configured so the activation path proceeds. */
+	info->accel_state = AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED;
+}
+
+/*
+ * amdgpu_ualink_loopback_activate - bring up the UALink manager for loopback
+ *
+ * The normal activation is driven by the sysfs vpod_config/commit path via
+ * activate_local_vpod(), which derives the local accelerators from peer GPUs
+ * and runs a cross-GPU integrity check. Neither applies in loopback (the
+ * importer is not a separate GPU), so activate this device directly.
+ */
+static int amdgpu_ualink_loopback_activate(struct amdgpu_device *adev)
+{
+	mutex_lock(&mgpu_info.mutex);
+	activate_accelerator(adev);
+	mutex_unlock(&mgpu_info.mutex);
+
+	if (adev->ualink.info->accel_state < AMDGPU_UALINK_ACCEL_STATE_ACTIVE) {
+		dev_err(adev->dev, "UALink loopback activation did not reach ACTIVE\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/*
+ * amdgpu_ualink_loopback_init - set up and activate UALink loopback mode
+ *
+ * Called once loopback mode has been detected, after the initial pod/vpod
+ * configuration has been queried into adev->ualink.info. Synthesizes the pod
+ * configuration (there is no user-space agent to write the UALink sysfs nodes
+ * in loopback), allocates the ordered workqueue used to emulate
+ * exporter->importer interrupts in SW and activates the device.
  */
 static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
 {
+	amdgpu_ualink_loopback_populate_info(adev);
+
 	adev->ualink.loopback.wq =
 		alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
 	if (!adev->ualink.loopback.wq)
 		return -ENOMEM;
 
-	return 0;
+	dev_info(adev->dev,
+		 "UALink loopback enabled (exporter accel %u, importer accel %u)\n",
+		 adev->ualink.loopback.exporter_accel_id,
+		 adev->ualink.loopback.importer_accel_id);
+
+	return amdgpu_ualink_loopback_activate(adev);
 }
 
 static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
@@ -495,10 +607,16 @@ int ualink_ip_late_init(struct amdgpu_ip_block *ip_block)
 	if (r)
 		return r;
 
+	amdgpu_ualink_loopback_detect(adev);
+
 	scoped_guard(mutex, &mgpu_info.mutex) {
 		amdgpu_ualink_info_set_accel_state(adev, adev->ualink.info,
 						   cfg_state);
-		__amdgpu_ualink_activate_vpod_locked(adev);
+		/* Loopback has no peer GPUs and no user-space vpod commit, so it
+		 * cannot use the normal cross-GPU activation path.
+		 */
+		if (!adev->ualink.loopback.enabled)
+			__amdgpu_ualink_activate_vpod_locked(adev);
 	}
 
 	if (adev->ualink.loopback.enabled) {
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCHv2 7/8] drm/amdgpu: Reject config updates in UALink loopback mode
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
                   ` (5 preceding siblings ...)
  2026-10-27 19:54 ` [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 21:06   ` Felix Kuehling
  2026-10-27 19:54 ` [PATCHv2 8/8] drm/amdgpu: Temporarily enable UALink loopback via module parameter Mukul Joshi
  7 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

In loopback mode the driver synthesizes the pPod/vPod configuration,
since there is no user-space agent to configure the pod. Keep anything
else from overwriting it:

- Ignore PSP config update interrupts. Re-querying the configuration
  from PSP would replace the synthesized one. Still send the completion
  so PSP is not left waiting.

- Don't create the pPod setup, vPod config and station config sysfs
  nodes, so the configuration can't be committed from user space. The
  setup, config and stations directories are still created, empty, as
  they are when the config sysfs nodes are compiled out.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
v1->v2:
- Do not create the config directory in sysfs under loopback mode.(Lijo)
 
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 49 ++++++++++++----------
 1 file changed, 28 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index 14b9c05a0f0f..1c2203ff175d 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -486,6 +486,18 @@ int amdgpu_ualink_config_update_handler(struct amdgpu_device *adev)
 			PSP_GFX_INT_CTXT_UAL_CMD_CFG_UPDATE_ID, status);
 	}
 
+	/* The loopback pod configuration is synthesized by the driver, so
+	 * re-querying it from PSP would overwrite it.
+	 */
+	if (adev->ualink.loopback.enabled) {
+		dev_warn(adev->dev,
+			 "UALINK: config update ignored in loopback mode\n");
+		return psp_ual_send_completion(&adev->psp,
+					       adev->ualink.psp_if_ver,
+					       PSP_GFX_INT_CTXT_UAL_CMD_CFG_UPDATE_ID,
+					       0);
+	}
+
 	prev_state = adev->ualink.info->accel_state;
 	prev_vpod_id = adev->ualink.info->vpod.id;
 
@@ -1779,7 +1791,7 @@ static int ualink_kobj_add(struct kobject *kobj, struct kobject *parent,
 	int r;
 
 	r = kobject_add(kobj, parent, "%s", name);
-	if (r)
+	if (r || !attrs)
 		return r;
 	r = sysfs_create_files(kobj, attrs);
 	if (r)
@@ -1794,48 +1806,43 @@ static void amdgpu_ualink_xcp_sysfs_fini(struct amdgpu_device *adev);
 int amdgpu_ualink_sysfs_init(struct amdgpu_device *adev)
 {
 	struct amdgpu_ualink_info *info = adev->ualink.info;
+	const struct attribute **setup_attrs = NULL;
+	const struct attribute **config_attrs = NULL;
+	const struct attribute **station_attrs = NULL;
 	int r;
 
 	if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE)
 		return 0;
 
+#ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS
+	/* The loopback pod configuration is synthesized by the driver. */
+	if (!adev->ualink.loopback.enabled) {
+		setup_attrs = ualink_ppod_setup_attrs;
+		config_attrs = ualink_vpod_config_attrs;
+		station_attrs = ualink_station_config_attrs;
+	}
+#endif
+
 	/* ualink parent node */
 	r = ualink_kobj_add(&info->kobj, &adev->dev->kobj, "ualink",
 			    ualink_info_attrs);
 	if (r)
 		goto err;
 
-#ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS
 	r = ualink_kobj_add(&adev->ualink.setup->kobj, &info->kobj, "setup",
-			    ualink_ppod_setup_attrs);
+			    setup_attrs);
 	if (r)
 		goto err_info;
 
 	r = ualink_kobj_add(&adev->ualink.config->kobj, &info->kobj, "config",
-			    ualink_vpod_config_attrs);
+			    config_attrs);
 	if (r)
 		goto err_setup;
 
 	r = ualink_kobj_add(&adev->ualink.stations->kobj, &info->kobj,
-			    "stations", ualink_station_config_attrs);
-	if (r)
-		goto err_config;
-#else
-	r = kobject_add(&adev->ualink.setup->kobj, &info->kobj, "%s",
-			"setup");
-	if (r)
-		goto err_info;
-
-	r = kobject_add(&adev->ualink.config->kobj, &info->kobj, "%s",
-			"config");
-	if (r)
-		goto err_setup;
-
-	r = kobject_add(&adev->ualink.stations->kobj, &info->kobj, "%s",
-			"stations");
+			    "stations", station_attrs);
 	if (r)
 		goto err_config;
-#endif
 
 	amdgpu_ualink_xcp_sysfs_init(adev);
 	adev->ualink.sysfs_init = true;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCHv2 8/8] drm/amdgpu: Temporarily enable UALink loopback via module parameter
  2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
                   ` (6 preceding siblings ...)
  2026-10-27 19:54 ` [PATCHv2 7/8] drm/amdgpu: Reject config updates in " Mukul Joshi
@ 2026-10-27 19:54 ` Mukul Joshi
  2026-10-27 21:47   ` Felix Kuehling
  7 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 19:54 UTC (permalink / raw)
  To: amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang, Mukul Joshi

Temporarily add the amdgpu.ualink_loopback module parameter to request
UALink loopback mode for manufacturing testing, until the final
mechanism for enabling loopback is in place. Loopback is disabled by
default.

Only amdgpu_ualink_loopback_requested() reads the parameter, so moving
to the final mechanism only needs to replace it there.

Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
---
 drivers/gpu/drm/amd/amdgpu/amdgpu.h        |  1 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c    | 13 +++++++++++++
 drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c |  5 +++--
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
index 5a32b54b1e9b..b8be093b8a33 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
@@ -270,6 +270,7 @@ extern int amdgpu_rebar;
 extern int amdgpu_wbrf;
 extern int amdgpu_user_queue;
 extern int amdgpu_ptl;
+extern int amdgpu_ualink_loopback;
 
 extern uint amdgpu_hdmi_hpd_debounce_delay_ms;
 
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
index 3e5634de077f..e0cf40f86194 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
@@ -253,6 +253,7 @@ int amdgpu_rebar = -1; /* auto */
 int amdgpu_user_queue = -1;
 uint amdgpu_hdmi_hpd_debounce_delay_ms;
 int amdgpu_ptl = -1; /* auto */
+int amdgpu_ualink_loopback;
 
 DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, DD_CLASS_TYPE_DISJOINT_BITS, 0,
 			"DRM_UT_CORE",
@@ -1165,6 +1166,18 @@ module_param_named(hdmi_hpd_debounce_delay_ms, amdgpu_hdmi_hpd_debounce_delay_ms
 MODULE_PARM_DESC(ptl, "Enable PTL (-1 = auto, 0 = disable (default), 1 = enable, 2 = permanently disable)");
 module_param_named(ptl, amdgpu_ptl, int, 0444);
 
+/**
+ * DOC: ualink_loopback (int)
+ * Enable UALink loopback mode for manufacturing testing. In loopback mode a
+ * single GPU acts as both the exporter (accelerator ID 1) and the importer
+ * (accelerator ID 0) and loops UALink requests back to itself. Possible values:
+ *
+ * - 0 = disable (default)
+ * - 1 = enable
+ */
+MODULE_PARM_DESC(ualink_loopback, "Enable UALink loopback mode (0 = disable (default), 1 = enable)");
+module_param_named(ualink_loopback, amdgpu_ualink_loopback, int, 0444);
+
 /* These devices are not supported by amdgpu.
  * They are supported by the mach64, r128, radeon drivers
  */
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
index 1c2203ff175d..a98fa5f970b1 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
@@ -96,11 +96,12 @@ static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev)
 /*
  * amdgpu_ualink_loopback_requested - check whether UALink loopback is requested
  *
- * There is no mechanism to request loopback mode yet, so it stays disabled.
+ * Loopback mode is temporarily requested with the amdgpu.ualink_loopback
+ * module parameter.
  */
 static bool amdgpu_ualink_loopback_requested(struct amdgpu_device *adev)
 {
-	return false;
+	return amdgpu_ualink_loopback == 1;
 }
 
 /*
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 5/8] drm/amdgpu: Set up UALink rings and metadata for loopback
  2026-10-27 19:54 ` [PATCHv2 5/8] drm/amdgpu: Set up UALink rings and metadata for loopback Mukul Joshi
@ 2026-10-27 20:20   ` Felix Kuehling
  0 siblings, 0 replies; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 20:20 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

On 2026-10-27 15:54, Mukul Joshi wrote:
> In UALink loopback mode a single GPU acts as both the exporter and the
> importer, and the only path the HW serves is importer -> exporter.
> Adjust the per-peer setup accordingly:
>
> - The importer's doorbell reaches the MP nHT stamped with the GPU ID of
>    the exporter, and the FW looks up the metadata entry by that ID. So
>    publish the importer's rings and tail pointers in metadata[exporter].
>
> - The local sender of the HW-served direction is the importer, so set
>    up (and tear down) the send rings with the importer as the source.
>
> - Skip the ring pointer resync. Both ends of every ring are on this
>    GPU, so there is no remote peer that can reboot and leave the
>    pointers stale.
>
> - Do not take the local dma-buf shortcut on import. It would bypass
>    the NPA path that loopback is meant to exercise.
>
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
> ---
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 66 +++++++++++++++++-----
>   1 file changed, 51 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index 96a6023f51c5..e6272446ff4f 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -3965,7 +3965,13 @@ int amdgpu_ualink_import_handle(struct drm_device *dev,
>   		return -EINVAL;
>   	}
>   
> -	if (amdgpu_ualink_is_local_accel(adev, remote_acc_id))
> +	/* In loopback mode the exporter and importer are the same GPU, but the
> +	 * import must still go through the full cross-domain NPA path (the
> +	 * dma_buf shortcut in amdgpu_ualink_local_import() bypasses NPA). Only
> +	 * take the local shortcut for genuine same-pod local accelerators.
> +	 */
> +	if (!adev->ualink.loopback.enabled &&
> +	    amdgpu_ualink_is_local_accel(adev, remote_acc_id))
>   		return amdgpu_ualink_local_import(adev, remote_acc_id,
>   						  &handle, fd_out);
>   
> @@ -5441,11 +5447,28 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
>   		wptr_gpu_addr = remote->rb_gpu_addr + rb_size * ualink_exp_accel_id(adev);
>   
>   	for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) {
> +		struct amdgpu_ualink_metadata *md;
> +
>   		dev_dbg(adev->dev, "init for accel_id %u\n", accel_id);
>   
>   		if (accel_id == ualink_exp_accel_id(adev))
>   			continue;
>   
> +		/* In loopback the single MP nHT services the exporter half:
> +		 * the only messages delivered over HW are importer -> exporter
> +		 * (source = importer accel_id), while the exporter -> importer
> +		 * direction is emulated in SW and never reaches the MP nHT.
> +		 *
> +		 * The importer's doorbell reaches the MP nHT stamped with the
> +		 * GPU ID of the single physical station, the exporter, so the
> +		 * FW looks up metadata[exporter]. Publish the importer's rings
> +		 * and tail pointers in that entry.
> +		 */
> +		if (adev->ualink.loopback.enabled)
> +			md = &metadata[ualink_exp_accel_id(adev)];
> +		else
> +			md = &metadata[accel_id];
> +
>   		rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id;
>   
>   		if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) {
> @@ -5453,27 +5476,26 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
>   			wptr_gpu_addr += wptr_size * accel_id;
>   		}
>   
> -		metadata[accel_id].header = AMDGPU_UALINK_METADATA_HEADER;
> -		metadata[accel_id].rb_size = fls(AMDGPU_UALINK_RB_SIZE / 64) - 1;
> +		md->header = AMDGPU_UALINK_METADATA_HEADER;
> +		md->rb_size = fls(AMDGPU_UALINK_RB_SIZE / 64) - 1;
>   
>   		/*
>   		 * remote shootdown type is 0, remote command type is 1
>   		 * with 2MB ring buffer mapping, remote shootdown ring NPA is before interrupt
>   		 */
> -		metadata[accel_id].ri_rb = rb_gpu_addr + AMDGPU_UALINK_RB_SIZE;
> -		metadata[accel_id].tlb_inv_rb = rb_gpu_addr;
> +		md->ri_rb = rb_gpu_addr + AMDGPU_UALINK_RB_SIZE;
> +		md->tlb_inv_rb = rb_gpu_addr;
>   
> -		metadata[accel_id].tailptr_ri = wptr_gpu_addr +
> -						ualink_wptr_offset(adev, accel_id);
> -		metadata[accel_id].tailptr_tlb_inv = wptr_gpu_addr +
> -						     ualink_tlb_wptr_offset(adev, accel_id);
> +		md->tailptr_ri = wptr_gpu_addr + ualink_wptr_offset(adev, accel_id);
> +		md->tailptr_tlb_inv = wptr_gpu_addr +
> +				      ualink_tlb_wptr_offset(adev, accel_id);
>   
> -		dev_dbg(adev->dev, "init from accel_id %u to accel_id %u, rb_size 0x%x\n",
> -			accel_id, ualink_exp_accel_id(adev), metadata[accel_id].rb_size);
> +		dev_dbg(adev->dev, "init from accel_id %u to accel_id %u, metadata entry %td, rb_size 0x%x\n",
> +			accel_id, ualink_exp_accel_id(adev), md - metadata, md->rb_size);
>   		dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n",
> -			metadata[accel_id].ri_rb, metadata[accel_id].tlb_inv_rb);
> +			md->ri_rb, md->tlb_inv_rb);
>   		dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n",
> -			metadata[accel_id].tailptr_ri, metadata[accel_id].tailptr_tlb_inv);
> +			md->tailptr_ri, md->tailptr_tlb_inv);
>   	}
>   
>   	r = adev->ualink.msg_ctl->send_metadata(adev,
> @@ -6058,6 +6080,12 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>   	u64 int_wptr, tlb_wptr;
>   	int r;
>   
> +	/* UALink loopback: both ends of every ring live on this GPU, so there
> +	 * is no remote peer that can reboot and leave the pointers stale.
> +	 */
> +	if (adev->ualink.loopback.enabled)
> +		return 0;
> +
>   	/* ring->wptr and ring->rptr are owned by the send path. */
>   	guard(mutex)(&peer->lock);
>   
> @@ -6279,7 +6307,12 @@ static int amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev)
>   	struct amdgpu_ualink_peer *peer;
>   	struct amdgpu_ualink_ring *interrupt;
>   	struct amdgpu_ualink_ring *shootdown;
> -	u32 src_accel_id = ualink_exp_accel_id(adev);
> +	/* This sets up the send rings (local sender -> each remote dst). In
> +	 * loopback the HW-served direction is importer -> exporter, so the
> +	 * local sender is the importer; the skip below then skips the importer
> +	 * and sets up the peer[exporter] ring that the HW path needs.
> +	 */
> +	u32 src_accel_id = ualink_imp_accel_id(adev);

Why not do this in patch 1 where the function is introduced?


>   	u32 dst_accel_id;
>   	u64 npa, flags;
>   	int r = 0;
> @@ -6402,7 +6435,10 @@ static void amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev)
>   {
>   	u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT;
>   	struct amdgpu_ualink_remote *remote = to_remote(adev);
> -	u32 src_accel_id = ualink_exp_accel_id(adev);
> +	/* Mirror peer_remote_init: in loopback the local sender is the
> +	 * importer, so unmap the peer[exporter] send ring that was set up.
> +	 */
> +	u32 src_accel_id = ualink_imp_accel_id(adev);

Why not do this in patch 1 where the function is introduced?

Regards,
   Felix


>   	u32 dst_accel_id;
>   	struct amdgpu_ualink_peer *peer;
>   	struct amdgpu_ualink_ring *ring;

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure
  2026-10-27 19:54 ` [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure Mukul Joshi
@ 2026-10-27 20:22   ` Felix Kuehling
  2026-10-27 20:45     ` Mukul Joshi
  0 siblings, 1 reply; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 20:22 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

On 2026-10-27 15:54, Mukul Joshi wrote:
> Add the infrastructure for UALink loopback mode, where a single GPU
> plays both the exporter and the importer role under two fixed
> accelerator IDs:
>
> - Add struct amdgpu_ualink_loopback to track the loopback state, along
>    with the fixed exporter and importer accel IDs.
>
> - Replace ualink_accel_id() with ualink_exp_accel_id() and
>    ualink_imp_accel_id(), which return the exporter and importer accel
>    ID in loopback mode.
>
> - Add amdgpu_ualink_loopback_init(), which allocates the ordered
>    workqueue used to emulate exporter->importer messages, and destroy
>    it in amdgpu_ualink_object_fini().
>
> Loopback is not enabled yet, and outside loopback both helpers return
> ppod.accel_id, so there is no functional change.
>
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
> ---
> v1-v2:
> - Drop the change in find_peer_adev() as its not needed (Lijo).
>
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 99 +++++++++++++++++-----
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h | 29 +++++++
>   2 files changed, 109 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index ca6432610f71..2cd6dc71ab9f 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -89,8 +89,34 @@ static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev)
>   	return 0;
>   }
>   
> +/*
> + * amdgpu_ualink_loopback_init - set up UALink loopback mode
> + *
> + * Called once loopback mode has been detected. Allocates the ordered
> + * workqueue used to emulate exporter->importer interrupts in SW.
> + */
> +static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
> +{
> +	adev->ualink.loopback.wq =
> +		alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
> +	if (!adev->ualink.loopback.wq)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
>   static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
>   {
> +	/* Tear down the loopback self-interrupt workqueue allocated in
> +	 * amdgpu_ualink_loopback_init(). destroy_workqueue() drains any
> +	 * pending self-interrupt work first.
> +	 */
> +	if (adev->ualink.loopback.wq) {
> +		destroy_workqueue(adev->ualink.loopback.wq);
> +		adev->ualink.loopback.wq = NULL;
> +		adev->ualink.loopback.enabled = false;
> +	}
> +
>   	if (!adev->ualink.info)
>   		return;
>   
> @@ -472,6 +498,14 @@ int ualink_ip_late_init(struct amdgpu_ip_block *ip_block)
>   		__amdgpu_ualink_activate_vpod_locked(adev);
>   	}
>   
> +	if (adev->ualink.loopback.enabled) {
> +		r = amdgpu_ualink_loopback_init(adev);
> +		if (r) {
> +			dev_err(adev->dev, "Failed to set up UALink loopback: %d\n", r);
> +			goto error;
> +		}
> +	}
> +
>   	r = amdgpu_ualink_drm_client_create(adev);
>   	if (r) {
>   		dev_err(adev->dev, "Failed to create UALink DRM client: %d\n",
> @@ -1028,6 +1062,31 @@ static struct amdgpu_device *find_peer_adev(unsigned int accel_id)
>   	return NULL;
>   }
>   
> +/*
> + * Physical accelerator roles.
> + *
> + * Outside loopback a GPU owns a single accelerator, so both helpers return its
> + * ppod.accel_id and the exporter/importer distinction collapses. In loopback
> + * the one physical GPU plays both roles: the exporter owns the MP nHT and
> + * receives all HW traffic, while the importer is the (virtual) HW sender for
> + * the importer -> exporter path.
> + */
> +static inline u32 ualink_exp_accel_id(struct amdgpu_device *adev)
> +{
> +	if (adev->ualink.loopback.enabled)
> +		return adev->ualink.loopback.exporter_accel_id;
> +
> +	return adev->ualink.info->ppod.accel_id;
> +}
> +
> +static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
> +{
> +	if (adev->ualink.loopback.enabled)
> +		return adev->ualink.loopback.importer_accel_id;
> +
> +	return adev->ualink.info->ppod.accel_id;
> +}
> +
>   static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>   					 u32 accel_id)
>   {
> @@ -4487,11 +4546,6 @@ static bool amdgpu_ualink_vpod_membership_changed(struct amdgpu_device *adev)
>   			     AMDGPU_UALINK_ACCEL_MAX);
>   }
>   
> -static inline u32 ualink_accel_id(struct amdgpu_device *adev)
> -{
> -	return adev->ualink.info->ppod.accel_id;
> -}
> -
>   static inline enum amdgpu_ualink_addr_mode ualink_addr_mode(struct amdgpu_device *adev)
>   {
>   	return adev->ualink.info->vpod.addr_mode;
> @@ -4837,7 +4891,7 @@ static void amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>   {
>   	struct amdgpu_ualink_remote *remote = to_remote(adev);
>   	u64 timeout = msecs_to_jiffies(2000);
> -	u32 dst_accel_id = ualink_accel_id(adev);
> +	u32 dst_accel_id = ualink_exp_accel_id(adev);
>   	struct amdgpu_bo *bos[2];
>   	u32 n_bos;
>   	struct dma_fence *fence;
> @@ -4956,7 +5010,7 @@ static void amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>   static int amdgpu_ualink_metadata_npa_mapping(struct amdgpu_device *adev)
>   {
>   	struct amdgpu_ualink_remote *remote = to_remote(adev);
> -	u32 dst_accel_id = ualink_accel_id(adev);
> +	u32 dst_accel_id = ualink_exp_accel_id(adev);
>   	u64 timeout = msecs_to_jiffies(2000);
>   	struct amdgpu_bo *bos[2];
>   	u32 n_bos;
> @@ -5241,7 +5295,7 @@ static void amdgpu_ualink_metadata_fini(struct amdgpu_device *adev)
>   {
>   	struct amdgpu_ualink_remote *remote = to_remote(adev);
>   
> -	dev_dbg(adev->dev, "accel_id %u\n", ualink_accel_id(adev));
> +	dev_dbg(adev->dev, "accel_id %u\n", ualink_exp_accel_id(adev));
>   	amdgpu_bo_free_kernel(&remote->rptr_bo, &remote->rptr_gpu_addr,
>   				      &remote->rptr_cpu_addr);
>   	amdgpu_bo_free_kernel(&remote->ring_bo, &remote->rb_gpu_addr,
> @@ -5288,7 +5342,7 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
>   	}
>   
>   	dev_dbg(adev->dev, "accel_id %u addr_mode %d fw status 0x%x\n",
> -		ualink_accel_id(adev), ualink_addr_mode(adev), status);
> +		ualink_exp_accel_id(adev), ualink_addr_mode(adev), status);
>   
>   	/* Alloc metadata structure for all GPUs */
>   	metadata_size = sizeof(struct amdgpu_ualink_metadata) * AMDGPU_UALINK_ACCEL_MAX;
> @@ -5361,12 +5415,12 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
>   	}
>   
>   	if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS)
> -		wptr_gpu_addr = remote->rb_gpu_addr + rb_size * ualink_accel_id(adev);
> +		wptr_gpu_addr = remote->rb_gpu_addr + rb_size * ualink_exp_accel_id(adev);
>   
>   	for_each_set_bit(accel_id, remote->active_accel_bits, AMDGPU_UALINK_ACCEL_MAX) {
>   		dev_dbg(adev->dev, "init for accel_id %u\n", accel_id);
>   
> -		if (accel_id == ualink_accel_id(adev))
> +		if (accel_id == ualink_exp_accel_id(adev))
>   			continue;
>   
>   		rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id;
> @@ -5392,7 +5446,7 @@ static int amdgpu_ualink_metadata_init(struct amdgpu_device *adev)
>   						     ualink_tlb_wptr_offset(adev, accel_id);
>   
>   		dev_dbg(adev->dev, "init from accel_id %u to accel_id %u, rb_size 0x%x\n",
> -			accel_id, ualink_accel_id(adev), metadata[accel_id].rb_size);
> +			accel_id, ualink_exp_accel_id(adev), metadata[accel_id].rb_size);
>   		dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n",
>   			metadata[accel_id].ri_rb, metadata[accel_id].tlb_inv_rb);
>   		dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n",
> @@ -5806,7 +5860,14 @@ static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
>   				      u64 *wb_npa, u32 type)
>   {
>   	struct amdgpu_ualink_remote *remote = to_remote(adev);
> -	u32 accel_id = ualink_accel_id(adev);
> +	/* The GPUID encoded in the write-back NPA address is the routing target
> +	 * the remote writes the rptr back to, i.e. THIS GPU. It must be the
> +	 * physically-owned accel_id: outside loopback that is our own accel_id,
> +	 * and in loopback it is the exporter (the only id the single GPU owns
> +	 * and can receive on). The importer is only a logical source id for the
> +	 * forward path and must never appear as an NPA GPUID.
> +	 */
> +	u32 accel_id = ualink_exp_accel_id(adev);
>   	u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE);
>   	uintptr_t wb;
>   	u64 npa;
> @@ -5981,19 +6042,19 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>   						   &peer->shootdown, &tlb_wptr);
>   	if (r) {
>   		dev_dbg(adev->dev, "accel_id %u read remote %u wptr failed %d\n",
> -			ualink_accel_id(adev), remote_accel_id, r);
> +			ualink_exp_accel_id(adev), remote_accel_id, r);
>   		return r;
>   	}
>   
>   	if (peer->interrupt.wptr == int_wptr && peer->shootdown.wptr == tlb_wptr) {
>   		dev_dbg(adev->dev, "accel_id %u wptr 0x%llx 0x%llx is same as remote %u\n",
> -			ualink_accel_id(adev), int_wptr, tlb_wptr, remote_accel_id);
> +			ualink_exp_accel_id(adev), int_wptr, tlb_wptr, remote_accel_id);
>   		return 0;
>   	}
>   
>   	dev_dbg(adev->dev,
>   		"accel_id %u wptr 0x%llx 0x%llx sync to remote %u wptr 0x%llx 0x%llx\n",
> -		ualink_accel_id(adev), peer->interrupt.wptr, peer->shootdown.wptr,
> +		ualink_exp_accel_id(adev), peer->interrupt.wptr, peer->shootdown.wptr,
>   		remote_accel_id, int_wptr, tlb_wptr);
>   
>   	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL,
> @@ -6164,7 +6225,7 @@ static int amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev)
>   	struct amdgpu_ualink_peer *peer;
>   	struct amdgpu_ualink_ring *interrupt;
>   	struct amdgpu_ualink_ring *shootdown;
> -	u32 src_accel_id = ualink_accel_id(adev);
> +	u32 src_accel_id = ualink_exp_accel_id(adev);

This is changed to ualink_imp_accel_id in patch 5. I think we should use 
the correct function here, where it's introduced.


>   	u32 dst_accel_id;
>   	u64 npa, flags;
>   	int r = 0;
> @@ -6287,7 +6348,7 @@ static void amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev)
>   {
>   	u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT;
>   	struct amdgpu_ualink_remote *remote = to_remote(adev);
> -	u32 src_accel_id = ualink_accel_id(adev);
> +	u32 src_accel_id = ualink_exp_accel_id(adev);

This is changed to ualink_imp_accel_id in patch 5. I think we should use 
the correct function here, where it's introduced.

Regards,
   Felix


>   	u32 dst_accel_id;
>   	struct amdgpu_ualink_peer *peer;
>   	struct amdgpu_ualink_ring *ring;
> @@ -6374,7 +6435,7 @@ int amdgpu_ualink_sw_init(struct amdgpu_device *adev)
>    */
>   void amdgpu_ualink_sw_fini(struct amdgpu_device *adev)
>   {
> -	dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n", ualink_accel_id(adev),
> +	dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n", ualink_exp_accel_id(adev),
>   		ualink_addr_mode(adev));
>   
>   	adev->ualink.msg_ctl->send_halt(adev);
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
> index 85c9a9634a4b..0b1e8dd38666 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
> @@ -97,6 +97,17 @@ enum AMDGPU_UALINK_PROTOCOL_MESSAGES {
>   #define AMDGPU_UALINK_LIGHTWEIGHT_TLB_SHOOTDOWN		0x1
>   #define AMDGPU_UALINK_HEAVYWEIGHT_TLB_SHOOTDOWN		0x2
>   
> +/* UALink loopback support.
> + *
> + * In loopback mode a single GPU loops UALink requests back to itself for
> + * manufacturing testing. The GPU exposes two fixed accelerator IDs: it acts
> + * as the exporter on one and the importer on the other. Loopback is enabled
> + * with the amdgpu.ualink_loopback module parameter.
> + */
> +/* Fixed accelerator IDs used by a loopback GPU. */
> +#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID		1
> +#define AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID		0
> +
>   enum amdgpu_ualink_conn_state {
>   	AMDGPU_UALINK_CONN_NOT_READY			= 0,
>   	AMDGPU_UALINK_CONN_IN_PROGRESS			= 1,
> @@ -319,6 +330,21 @@ struct amdgpu_ualink_msg_ctl {
>   	int (*send_halt)(struct amdgpu_device *adev);
>   };
>   
> +/* UALink loopback mode state.
> + *
> + * When @enabled is true this single GPU acts as both exporter
> + * (@exporter_accel_id) and importer (@importer_accel_id). There is no second
> + * physical GPU, so exporter->importer messages cannot be delivered by the
> + * (single) MP nHT and are instead emulated in SW by feeding them back into
> + * our own IRQ handler via @wq.
> + */
> +struct amdgpu_ualink_loopback {
> +	bool enabled;
> +	u32 exporter_accel_id;
> +	u32 importer_accel_id;
> +	struct workqueue_struct *wq;
> +};
> +
>   struct amdgpu_ualink_mgr {
>   	u64 npa_size;
>   	u32 psp_if_ver;
> @@ -333,6 +359,9 @@ struct amdgpu_ualink_mgr {
>   	/* For remote interrupt and shootdown */
>   	struct amdgpu_ualink_remote *remote;
>   
> +	/* UALink loopback mode state (see struct amdgpu_ualink_loopback) */
> +	struct amdgpu_ualink_loopback loopback;
> +
>   	/* handle irq from ualink client of remote GPUs */
>   	struct amdgpu_irq_src irq;
>   

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure
  2026-10-27 20:22   ` Felix Kuehling
@ 2026-10-27 20:45     ` Mukul Joshi
  2026-10-27 20:56       ` Felix Kuehling
  0 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-27 20:45 UTC (permalink / raw)
  To: Felix Kuehling, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang


On 10/27/2026 4:22 PM, Felix Kuehling wrote:
> On 2026-10-27 15:54, Mukul Joshi wrote:
>> Add the infrastructure for UALink loopback mode, where a single GPU
>> plays both the exporter and the importer role under two fixed
>> accelerator IDs:
>>
>> - Add struct amdgpu_ualink_loopback to track the loopback state, along
>>    with the fixed exporter and importer accel IDs.
>>
>> - Replace ualink_accel_id() with ualink_exp_accel_id() and
>>    ualink_imp_accel_id(), which return the exporter and importer accel
>>    ID in loopback mode.
>>
>> - Add amdgpu_ualink_loopback_init(), which allocates the ordered
>>    workqueue used to emulate exporter->importer messages, and destroy
>>    it in amdgpu_ualink_object_fini().
>>
>> Loopback is not enabled yet, and outside loopback both helpers return
>> ppod.accel_id, so there is no functional change.
>>
>> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
>> ---
>> v1-v2:
>> - Drop the change in find_peer_adev() as its not needed (Lijo).
>>
>>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 99 +++++++++++++++++-----
>>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h | 29 +++++++
>>   2 files changed, 109 insertions(+), 19 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> index ca6432610f71..2cd6dc71ab9f 100644
>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> @@ -89,8 +89,34 @@ static int amdgpu_ualink_drm_client_create(struct
>> amdgpu_device *adev)
>>       return 0;
>>   }
>>   +/*
>> + * amdgpu_ualink_loopback_init - set up UALink loopback mode
>> + *
>> + * Called once loopback mode has been detected. Allocates the ordered
>> + * workqueue used to emulate exporter->importer interrupts in SW.
>> + */
>> +static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
>> +{
>> +    adev->ualink.loopback.wq =
>> +        alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
>> +    if (!adev->ualink.loopback.wq)
>> +        return -ENOMEM;
>> +
>> +    return 0;
>> +}
>> +
>>   static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
>>   {
>> +    /* Tear down the loopback self-interrupt workqueue allocated in
>> +     * amdgpu_ualink_loopback_init(). destroy_workqueue() drains any
>> +     * pending self-interrupt work first.
>> +     */
>> +    if (adev->ualink.loopback.wq) {
>> +        destroy_workqueue(adev->ualink.loopback.wq);
>> +        adev->ualink.loopback.wq = NULL;
>> +        adev->ualink.loopback.enabled = false;
>> +    }
>> +
>>       if (!adev->ualink.info)
>>           return;
>>   @@ -472,6 +498,14 @@ int ualink_ip_late_init(struct amdgpu_ip_block
>> *ip_block)
>>           __amdgpu_ualink_activate_vpod_locked(adev);
>>       }
>>   +    if (adev->ualink.loopback.enabled) {
>> +        r = amdgpu_ualink_loopback_init(adev);
>> +        if (r) {
>> +            dev_err(adev->dev, "Failed to set up UALink loopback:
>> %d\n", r);
>> +            goto error;
>> +        }
>> +    }
>> +
>>       r = amdgpu_ualink_drm_client_create(adev);
>>       if (r) {
>>           dev_err(adev->dev, "Failed to create UALink DRM client: %d\n",
>> @@ -1028,6 +1062,31 @@ static struct amdgpu_device
>> *find_peer_adev(unsigned int accel_id)
>>       return NULL;
>>   }
>>   +/*
>> + * Physical accelerator roles.
>> + *
>> + * Outside loopback a GPU owns a single accelerator, so both helpers
>> return its
>> + * ppod.accel_id and the exporter/importer distinction collapses. In
>> loopback
>> + * the one physical GPU plays both roles: the exporter owns the MP
>> nHT and
>> + * receives all HW traffic, while the importer is the (virtual) HW
>> sender for
>> + * the importer -> exporter path.
>> + */
>> +static inline u32 ualink_exp_accel_id(struct amdgpu_device *adev)
>> +{
>> +    if (adev->ualink.loopback.enabled)
>> +        return adev->ualink.loopback.exporter_accel_id;
>> +
>> +    return adev->ualink.info->ppod.accel_id;
>> +}
>> +
>> +static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
>> +{
>> +    if (adev->ualink.loopback.enabled)
>> +        return adev->ualink.loopback.importer_accel_id;
>> +
>> +    return adev->ualink.info->ppod.accel_id;
>> +}
>> +
>>   static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>>                        u32 accel_id)
>>   {
>> @@ -4487,11 +4546,6 @@ static bool
>> amdgpu_ualink_vpod_membership_changed(struct amdgpu_device *adev)
>>                    AMDGPU_UALINK_ACCEL_MAX);
>>   }
>>   -static inline u32 ualink_accel_id(struct amdgpu_device *adev)
>> -{
>> -    return adev->ualink.info->ppod.accel_id;
>> -}
>> -
>>   static inline enum amdgpu_ualink_addr_mode ualink_addr_mode(struct
>> amdgpu_device *adev)
>>   {
>>       return adev->ualink.info->vpod.addr_mode;
>> @@ -4837,7 +4891,7 @@ static void
>> amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>>   {
>>       struct amdgpu_ualink_remote *remote = to_remote(adev);
>>       u64 timeout = msecs_to_jiffies(2000);
>> -    u32 dst_accel_id = ualink_accel_id(adev);
>> +    u32 dst_accel_id = ualink_exp_accel_id(adev);
>>       struct amdgpu_bo *bos[2];
>>       u32 n_bos;
>>       struct dma_fence *fence;
>> @@ -4956,7 +5010,7 @@ static void
>> amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>>   static int amdgpu_ualink_metadata_npa_mapping(struct amdgpu_device
>> *adev)
>>   {
>>       struct amdgpu_ualink_remote *remote = to_remote(adev);
>> -    u32 dst_accel_id = ualink_accel_id(adev);
>> +    u32 dst_accel_id = ualink_exp_accel_id(adev);
>>       u64 timeout = msecs_to_jiffies(2000);
>>       struct amdgpu_bo *bos[2];
>>       u32 n_bos;
>> @@ -5241,7 +5295,7 @@ static void amdgpu_ualink_metadata_fini(struct
>> amdgpu_device *adev)
>>   {
>>       struct amdgpu_ualink_remote *remote = to_remote(adev);
>>   -    dev_dbg(adev->dev, "accel_id %u\n", ualink_accel_id(adev));
>> +    dev_dbg(adev->dev, "accel_id %u\n", ualink_exp_accel_id(adev));
>>       amdgpu_bo_free_kernel(&remote->rptr_bo, &remote->rptr_gpu_addr,
>>                         &remote->rptr_cpu_addr);
>>       amdgpu_bo_free_kernel(&remote->ring_bo, &remote->rb_gpu_addr,
>> @@ -5288,7 +5342,7 @@ static int amdgpu_ualink_metadata_init(struct
>> amdgpu_device *adev)
>>       }
>>         dev_dbg(adev->dev, "accel_id %u addr_mode %d fw status 0x%x\n",
>> -        ualink_accel_id(adev), ualink_addr_mode(adev), status);
>> +        ualink_exp_accel_id(adev), ualink_addr_mode(adev), status);
>>         /* Alloc metadata structure for all GPUs */
>>       metadata_size = sizeof(struct amdgpu_ualink_metadata) *
>> AMDGPU_UALINK_ACCEL_MAX;
>> @@ -5361,12 +5415,12 @@ static int amdgpu_ualink_metadata_init(struct
>> amdgpu_device *adev)
>>       }
>>         if (ualink_addr_mode(adev) ==
>> AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS)
>> -        wptr_gpu_addr = remote->rb_gpu_addr + rb_size *
>> ualink_accel_id(adev);
>> +        wptr_gpu_addr = remote->rb_gpu_addr + rb_size *
>> ualink_exp_accel_id(adev);
>>         for_each_set_bit(accel_id, remote->active_accel_bits,
>> AMDGPU_UALINK_ACCEL_MAX) {
>>           dev_dbg(adev->dev, "init for accel_id %u\n", accel_id);
>>   -        if (accel_id == ualink_accel_id(adev))
>> +        if (accel_id == ualink_exp_accel_id(adev))
>>               continue;
>>             rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id;
>> @@ -5392,7 +5446,7 @@ static int amdgpu_ualink_metadata_init(struct
>> amdgpu_device *adev)
>>                                ualink_tlb_wptr_offset(adev, accel_id);
>>             dev_dbg(adev->dev, "init from accel_id %u to accel_id %u,
>> rb_size 0x%x\n",
>> -            accel_id, ualink_accel_id(adev),
>> metadata[accel_id].rb_size);
>> +            accel_id, ualink_exp_accel_id(adev),
>> metadata[accel_id].rb_size);
>>           dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n",
>>               metadata[accel_id].ri_rb, metadata[accel_id].tlb_inv_rb);
>>           dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n",
>> @@ -5806,7 +5860,14 @@ static void amdgpu_ualink_get_wb_addr(struct
>> amdgpu_device *adev,
>>                         u64 *wb_npa, u32 type)
>>   {
>>       struct amdgpu_ualink_remote *remote = to_remote(adev);
>> -    u32 accel_id = ualink_accel_id(adev);
>> +    /* The GPUID encoded in the write-back NPA address is the
>> routing target
>> +     * the remote writes the rptr back to, i.e. THIS GPU. It must be
>> the
>> +     * physically-owned accel_id: outside loopback that is our own
>> accel_id,
>> +     * and in loopback it is the exporter (the only id the single
>> GPU owns
>> +     * and can receive on). The importer is only a logical source id
>> for the
>> +     * forward path and must never appear as an NPA GPUID.
>> +     */
>> +    u32 accel_id = ualink_exp_accel_id(adev);
>>       u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE);
>>       uintptr_t wb;
>>       u64 npa;
>> @@ -5981,19 +6042,19 @@ static int
>> amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>>                              &peer->shootdown, &tlb_wptr);
>>       if (r) {
>>           dev_dbg(adev->dev, "accel_id %u read remote %u wptr failed
>> %d\n",
>> -            ualink_accel_id(adev), remote_accel_id, r);
>> +            ualink_exp_accel_id(adev), remote_accel_id, r);
>>           return r;
>>       }
>>         if (peer->interrupt.wptr == int_wptr && peer->shootdown.wptr
>> == tlb_wptr) {
>>           dev_dbg(adev->dev, "accel_id %u wptr 0x%llx 0x%llx is same
>> as remote %u\n",
>> -            ualink_accel_id(adev), int_wptr, tlb_wptr,
>> remote_accel_id);
>> +            ualink_exp_accel_id(adev), int_wptr, tlb_wptr,
>> remote_accel_id);
>>           return 0;
>>       }
>>         dev_dbg(adev->dev,
>>           "accel_id %u wptr 0x%llx 0x%llx sync to remote %u wptr
>> 0x%llx 0x%llx\n",
>> -        ualink_accel_id(adev), peer->interrupt.wptr,
>> peer->shootdown.wptr,
>> +        ualink_exp_accel_id(adev), peer->interrupt.wptr,
>> peer->shootdown.wptr,
>>           remote_accel_id, int_wptr, tlb_wptr);
>>         amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL,
>> @@ -6164,7 +6225,7 @@ static int
>> amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev)
>>       struct amdgpu_ualink_peer *peer;
>>       struct amdgpu_ualink_ring *interrupt;
>>       struct amdgpu_ualink_ring *shootdown;
>> -    u32 src_accel_id = ualink_accel_id(adev);
>> +    u32 src_accel_id = ualink_exp_accel_id(adev);
>
> This is changed to ualink_imp_accel_id in patch 5. I think we should
> use the correct function here, where it's introduced.
>
Good catch. I made the change here and removed the change from patch 5.
>
>>       u32 dst_accel_id;
>>       u64 npa, flags;
>>       int r = 0;
>> @@ -6287,7 +6348,7 @@ static void
>> amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev)
>>   {
>>       u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT;
>>       struct amdgpu_ualink_remote *remote = to_remote(adev);
>> -    u32 src_accel_id = ualink_accel_id(adev);
>> +    u32 src_accel_id = ualink_exp_accel_id(adev);
>
> This is changed to ualink_imp_accel_id in patch 5. I think we should
> use the correct function here, where it's introduced.

Good catch. I made the change here and removed the change from patch 5. 

Regards,

Mukul

>
> Regards,
>   Felix
>
>
>>       u32 dst_accel_id;
>>       struct amdgpu_ualink_peer *peer;
>>       struct amdgpu_ualink_ring *ring;
>> @@ -6374,7 +6435,7 @@ int amdgpu_ualink_sw_init(struct amdgpu_device
>> *adev)
>>    */
>>   void amdgpu_ualink_sw_fini(struct amdgpu_device *adev)
>>   {
>> -    dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n",
>> ualink_accel_id(adev),
>> +    dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n",
>> ualink_exp_accel_id(adev),
>>           ualink_addr_mode(adev));
>>         adev->ualink.msg_ctl->send_halt(adev);
>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>> index 85c9a9634a4b..0b1e8dd38666 100644
>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>> @@ -97,6 +97,17 @@ enum AMDGPU_UALINK_PROTOCOL_MESSAGES {
>>   #define AMDGPU_UALINK_LIGHTWEIGHT_TLB_SHOOTDOWN        0x1
>>   #define AMDGPU_UALINK_HEAVYWEIGHT_TLB_SHOOTDOWN        0x2
>>   +/* UALink loopback support.
>> + *
>> + * In loopback mode a single GPU loops UALink requests back to
>> itself for
>> + * manufacturing testing. The GPU exposes two fixed accelerator IDs:
>> it acts
>> + * as the exporter on one and the importer on the other. Loopback is
>> enabled
>> + * with the amdgpu.ualink_loopback module parameter.
>> + */
>> +/* Fixed accelerator IDs used by a loopback GPU. */
>> +#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID        1
>> +#define AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID        0
>> +
>>   enum amdgpu_ualink_conn_state {
>>       AMDGPU_UALINK_CONN_NOT_READY            = 0,
>>       AMDGPU_UALINK_CONN_IN_PROGRESS            = 1,
>> @@ -319,6 +330,21 @@ struct amdgpu_ualink_msg_ctl {
>>       int (*send_halt)(struct amdgpu_device *adev);
>>   };
>>   +/* UALink loopback mode state.
>> + *
>> + * When @enabled is true this single GPU acts as both exporter
>> + * (@exporter_accel_id) and importer (@importer_accel_id). There is
>> no second
>> + * physical GPU, so exporter->importer messages cannot be delivered
>> by the
>> + * (single) MP nHT and are instead emulated in SW by feeding them
>> back into
>> + * our own IRQ handler via @wq.
>> + */
>> +struct amdgpu_ualink_loopback {
>> +    bool enabled;
>> +    u32 exporter_accel_id;
>> +    u32 importer_accel_id;
>> +    struct workqueue_struct *wq;
>> +};
>> +
>>   struct amdgpu_ualink_mgr {
>>       u64 npa_size;
>>       u32 psp_if_ver;
>> @@ -333,6 +359,9 @@ struct amdgpu_ualink_mgr {
>>       /* For remote interrupt and shootdown */
>>       struct amdgpu_ualink_remote *remote;
>>   +    /* UALink loopback mode state (see struct
>> amdgpu_ualink_loopback) */
>> +    struct amdgpu_ualink_loopback loopback;
>> +
>>       /* handle irq from ualink client of remote GPUs */
>>       struct amdgpu_irq_src irq;
>>   

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure
  2026-10-27 20:45     ` Mukul Joshi
@ 2026-10-27 20:56       ` Felix Kuehling
  2026-10-28  6:06         ` Lazar, Lijo
  0 siblings, 1 reply; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 20:56 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

One more nit-pick inline ...

On 2026-10-27 16:45, Mukul Joshi wrote:
> On 10/27/2026 4:22 PM, Felix Kuehling wrote:
>> On 2026-10-27 15:54, Mukul Joshi wrote:
>>> Add the infrastructure for UALink loopback mode, where a single GPU
>>> plays both the exporter and the importer role under two fixed
>>> accelerator IDs:
>>>
>>> - Add struct amdgpu_ualink_loopback to track the loopback state, along
>>>     with the fixed exporter and importer accel IDs.
>>>
>>> - Replace ualink_accel_id() with ualink_exp_accel_id() and
>>>     ualink_imp_accel_id(), which return the exporter and importer accel
>>>     ID in loopback mode.
>>>
>>> - Add amdgpu_ualink_loopback_init(), which allocates the ordered
>>>     workqueue used to emulate exporter->importer messages, and destroy
>>>     it in amdgpu_ualink_object_fini().
>>>
>>> Loopback is not enabled yet, and outside loopback both helpers return
>>> ppod.accel_id, so there is no functional change.
>>>
>>> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
>>> ---
>>> v1-v2:
>>> - Drop the change in find_peer_adev() as its not needed (Lijo).
>>>
>>>    drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 99 +++++++++++++++++-----
>>>    drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h | 29 +++++++
>>>    2 files changed, 109 insertions(+), 19 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> index ca6432610f71..2cd6dc71ab9f 100644
>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> @@ -89,8 +89,34 @@ static int amdgpu_ualink_drm_client_create(struct
>>> amdgpu_device *adev)
>>>        return 0;
>>>    }
>>>    +/*
>>> + * amdgpu_ualink_loopback_init - set up UALink loopback mode
>>> + *
>>> + * Called once loopback mode has been detected. Allocates the ordered
>>> + * workqueue used to emulate exporter->importer interrupts in SW.
>>> + */
>>> +static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
>>> +{
>>> +    adev->ualink.loopback.wq =
>>> +        alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
>>> +    if (!adev->ualink.loopback.wq)
>>> +        return -ENOMEM;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>>    static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
>>>    {
>>> +    /* Tear down the loopback self-interrupt workqueue allocated in
>>> +     * amdgpu_ualink_loopback_init(). destroy_workqueue() drains any
>>> +     * pending self-interrupt work first.
>>> +     */
>>> +    if (adev->ualink.loopback.wq) {
>>> +        destroy_workqueue(adev->ualink.loopback.wq);
>>> +        adev->ualink.loopback.wq = NULL;
>>> +        adev->ualink.loopback.enabled = false;
>>> +    }
>>> +

This function deals with destroying the kobjects. Hence the word 
"object" in the function name. I think this loopback tear-down belongs 
in ualink_ip_sw_fini instead.

Regards,
   Felix


>>>        if (!adev->ualink.info)
>>>            return;
>>>    @@ -472,6 +498,14 @@ int ualink_ip_late_init(struct amdgpu_ip_block
>>> *ip_block)
>>>            __amdgpu_ualink_activate_vpod_locked(adev);
>>>        }
>>>    +    if (adev->ualink.loopback.enabled) {
>>> +        r = amdgpu_ualink_loopback_init(adev);
>>> +        if (r) {
>>> +            dev_err(adev->dev, "Failed to set up UALink loopback:
>>> %d\n", r);
>>> +            goto error;
>>> +        }
>>> +    }
>>> +
>>>        r = amdgpu_ualink_drm_client_create(adev);
>>>        if (r) {
>>>            dev_err(adev->dev, "Failed to create UALink DRM client: %d\n",
>>> @@ -1028,6 +1062,31 @@ static struct amdgpu_device
>>> *find_peer_adev(unsigned int accel_id)
>>>        return NULL;
>>>    }
>>>    +/*
>>> + * Physical accelerator roles.
>>> + *
>>> + * Outside loopback a GPU owns a single accelerator, so both helpers
>>> return its
>>> + * ppod.accel_id and the exporter/importer distinction collapses. In
>>> loopback
>>> + * the one physical GPU plays both roles: the exporter owns the MP
>>> nHT and
>>> + * receives all HW traffic, while the importer is the (virtual) HW
>>> sender for
>>> + * the importer -> exporter path.
>>> + */
>>> +static inline u32 ualink_exp_accel_id(struct amdgpu_device *adev)
>>> +{
>>> +    if (adev->ualink.loopback.enabled)
>>> +        return adev->ualink.loopback.exporter_accel_id;
>>> +
>>> +    return adev->ualink.info->ppod.accel_id;
>>> +}
>>> +
>>> +static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
>>> +{
>>> +    if (adev->ualink.loopback.enabled)
>>> +        return adev->ualink.loopback.importer_accel_id;
>>> +
>>> +    return adev->ualink.info->ppod.accel_id;
>>> +}
>>> +
>>>    static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>>>                         u32 accel_id)
>>>    {
>>> @@ -4487,11 +4546,6 @@ static bool
>>> amdgpu_ualink_vpod_membership_changed(struct amdgpu_device *adev)
>>>                     AMDGPU_UALINK_ACCEL_MAX);
>>>    }
>>>    -static inline u32 ualink_accel_id(struct amdgpu_device *adev)
>>> -{
>>> -    return adev->ualink.info->ppod.accel_id;
>>> -}
>>> -
>>>    static inline enum amdgpu_ualink_addr_mode ualink_addr_mode(struct
>>> amdgpu_device *adev)
>>>    {
>>>        return adev->ualink.info->vpod.addr_mode;
>>> @@ -4837,7 +4891,7 @@ static void
>>> amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>>>    {
>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>>        u64 timeout = msecs_to_jiffies(2000);
>>> -    u32 dst_accel_id = ualink_accel_id(adev);
>>> +    u32 dst_accel_id = ualink_exp_accel_id(adev);
>>>        struct amdgpu_bo *bos[2];
>>>        u32 n_bos;
>>>        struct dma_fence *fence;
>>> @@ -4956,7 +5010,7 @@ static void
>>> amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>>>    static int amdgpu_ualink_metadata_npa_mapping(struct amdgpu_device
>>> *adev)
>>>    {
>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>> -    u32 dst_accel_id = ualink_accel_id(adev);
>>> +    u32 dst_accel_id = ualink_exp_accel_id(adev);
>>>        u64 timeout = msecs_to_jiffies(2000);
>>>        struct amdgpu_bo *bos[2];
>>>        u32 n_bos;
>>> @@ -5241,7 +5295,7 @@ static void amdgpu_ualink_metadata_fini(struct
>>> amdgpu_device *adev)
>>>    {
>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>>    -    dev_dbg(adev->dev, "accel_id %u\n", ualink_accel_id(adev));
>>> +    dev_dbg(adev->dev, "accel_id %u\n", ualink_exp_accel_id(adev));
>>>        amdgpu_bo_free_kernel(&remote->rptr_bo, &remote->rptr_gpu_addr,
>>>                          &remote->rptr_cpu_addr);
>>>        amdgpu_bo_free_kernel(&remote->ring_bo, &remote->rb_gpu_addr,
>>> @@ -5288,7 +5342,7 @@ static int amdgpu_ualink_metadata_init(struct
>>> amdgpu_device *adev)
>>>        }
>>>          dev_dbg(adev->dev, "accel_id %u addr_mode %d fw status 0x%x\n",
>>> -        ualink_accel_id(adev), ualink_addr_mode(adev), status);
>>> +        ualink_exp_accel_id(adev), ualink_addr_mode(adev), status);
>>>          /* Alloc metadata structure for all GPUs */
>>>        metadata_size = sizeof(struct amdgpu_ualink_metadata) *
>>> AMDGPU_UALINK_ACCEL_MAX;
>>> @@ -5361,12 +5415,12 @@ static int amdgpu_ualink_metadata_init(struct
>>> amdgpu_device *adev)
>>>        }
>>>          if (ualink_addr_mode(adev) ==
>>> AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS)
>>> -        wptr_gpu_addr = remote->rb_gpu_addr + rb_size *
>>> ualink_accel_id(adev);
>>> +        wptr_gpu_addr = remote->rb_gpu_addr + rb_size *
>>> ualink_exp_accel_id(adev);
>>>          for_each_set_bit(accel_id, remote->active_accel_bits,
>>> AMDGPU_UALINK_ACCEL_MAX) {
>>>            dev_dbg(adev->dev, "init for accel_id %u\n", accel_id);
>>>    -        if (accel_id == ualink_accel_id(adev))
>>> +        if (accel_id == ualink_exp_accel_id(adev))
>>>                continue;
>>>              rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id;
>>> @@ -5392,7 +5446,7 @@ static int amdgpu_ualink_metadata_init(struct
>>> amdgpu_device *adev)
>>>                                 ualink_tlb_wptr_offset(adev, accel_id);
>>>              dev_dbg(adev->dev, "init from accel_id %u to accel_id %u,
>>> rb_size 0x%x\n",
>>> -            accel_id, ualink_accel_id(adev),
>>> metadata[accel_id].rb_size);
>>> +            accel_id, ualink_exp_accel_id(adev),
>>> metadata[accel_id].rb_size);
>>>            dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n",
>>>                metadata[accel_id].ri_rb, metadata[accel_id].tlb_inv_rb);
>>>            dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n",
>>> @@ -5806,7 +5860,14 @@ static void amdgpu_ualink_get_wb_addr(struct
>>> amdgpu_device *adev,
>>>                          u64 *wb_npa, u32 type)
>>>    {
>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>> -    u32 accel_id = ualink_accel_id(adev);
>>> +    /* The GPUID encoded in the write-back NPA address is the
>>> routing target
>>> +     * the remote writes the rptr back to, i.e. THIS GPU. It must be
>>> the
>>> +     * physically-owned accel_id: outside loopback that is our own
>>> accel_id,
>>> +     * and in loopback it is the exporter (the only id the single
>>> GPU owns
>>> +     * and can receive on). The importer is only a logical source id
>>> for the
>>> +     * forward path and must never appear as an NPA GPUID.
>>> +     */
>>> +    u32 accel_id = ualink_exp_accel_id(adev);
>>>        u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE);
>>>        uintptr_t wb;
>>>        u64 npa;
>>> @@ -5981,19 +6042,19 @@ static int
>>> amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>>>                               &peer->shootdown, &tlb_wptr);
>>>        if (r) {
>>>            dev_dbg(adev->dev, "accel_id %u read remote %u wptr failed
>>> %d\n",
>>> -            ualink_accel_id(adev), remote_accel_id, r);
>>> +            ualink_exp_accel_id(adev), remote_accel_id, r);
>>>            return r;
>>>        }
>>>          if (peer->interrupt.wptr == int_wptr && peer->shootdown.wptr
>>> == tlb_wptr) {
>>>            dev_dbg(adev->dev, "accel_id %u wptr 0x%llx 0x%llx is same
>>> as remote %u\n",
>>> -            ualink_accel_id(adev), int_wptr, tlb_wptr,
>>> remote_accel_id);
>>> +            ualink_exp_accel_id(adev), int_wptr, tlb_wptr,
>>> remote_accel_id);
>>>            return 0;
>>>        }
>>>          dev_dbg(adev->dev,
>>>            "accel_id %u wptr 0x%llx 0x%llx sync to remote %u wptr
>>> 0x%llx 0x%llx\n",
>>> -        ualink_accel_id(adev), peer->interrupt.wptr,
>>> peer->shootdown.wptr,
>>> +        ualink_exp_accel_id(adev), peer->interrupt.wptr,
>>> peer->shootdown.wptr,
>>>            remote_accel_id, int_wptr, tlb_wptr);
>>>          amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL,
>>> @@ -6164,7 +6225,7 @@ static int
>>> amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev)
>>>        struct amdgpu_ualink_peer *peer;
>>>        struct amdgpu_ualink_ring *interrupt;
>>>        struct amdgpu_ualink_ring *shootdown;
>>> -    u32 src_accel_id = ualink_accel_id(adev);
>>> +    u32 src_accel_id = ualink_exp_accel_id(adev);
>> This is changed to ualink_imp_accel_id in patch 5. I think we should
>> use the correct function here, where it's introduced.
>>
> Good catch. I made the change here and removed the change from patch 5.
>>>        u32 dst_accel_id;
>>>        u64 npa, flags;
>>>        int r = 0;
>>> @@ -6287,7 +6348,7 @@ static void
>>> amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev)
>>>    {
>>>        u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT;
>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>> -    u32 src_accel_id = ualink_accel_id(adev);
>>> +    u32 src_accel_id = ualink_exp_accel_id(adev);
>> This is changed to ualink_imp_accel_id in patch 5. I think we should
>> use the correct function here, where it's introduced.
> Good catch. I made the change here and removed the change from patch 5.
>
> Regards,
>
> Mukul
>
>> Regards,
>>    Felix
>>
>>
>>>        u32 dst_accel_id;
>>>        struct amdgpu_ualink_peer *peer;
>>>        struct amdgpu_ualink_ring *ring;
>>> @@ -6374,7 +6435,7 @@ int amdgpu_ualink_sw_init(struct amdgpu_device
>>> *adev)
>>>     */
>>>    void amdgpu_ualink_sw_fini(struct amdgpu_device *adev)
>>>    {
>>> -    dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n",
>>> ualink_accel_id(adev),
>>> +    dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n",
>>> ualink_exp_accel_id(adev),
>>>            ualink_addr_mode(adev));
>>>          adev->ualink.msg_ctl->send_halt(adev);
>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>> index 85c9a9634a4b..0b1e8dd38666 100644
>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>> @@ -97,6 +97,17 @@ enum AMDGPU_UALINK_PROTOCOL_MESSAGES {
>>>    #define AMDGPU_UALINK_LIGHTWEIGHT_TLB_SHOOTDOWN        0x1
>>>    #define AMDGPU_UALINK_HEAVYWEIGHT_TLB_SHOOTDOWN        0x2
>>>    +/* UALink loopback support.
>>> + *
>>> + * In loopback mode a single GPU loops UALink requests back to
>>> itself for
>>> + * manufacturing testing. The GPU exposes two fixed accelerator IDs:
>>> it acts
>>> + * as the exporter on one and the importer on the other. Loopback is
>>> enabled
>>> + * with the amdgpu.ualink_loopback module parameter.
>>> + */
>>> +/* Fixed accelerator IDs used by a loopback GPU. */
>>> +#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID        1
>>> +#define AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID        0
>>> +
>>>    enum amdgpu_ualink_conn_state {
>>>        AMDGPU_UALINK_CONN_NOT_READY            = 0,
>>>        AMDGPU_UALINK_CONN_IN_PROGRESS            = 1,
>>> @@ -319,6 +330,21 @@ struct amdgpu_ualink_msg_ctl {
>>>        int (*send_halt)(struct amdgpu_device *adev);
>>>    };
>>>    +/* UALink loopback mode state.
>>> + *
>>> + * When @enabled is true this single GPU acts as both exporter
>>> + * (@exporter_accel_id) and importer (@importer_accel_id). There is
>>> no second
>>> + * physical GPU, so exporter->importer messages cannot be delivered
>>> by the
>>> + * (single) MP nHT and are instead emulated in SW by feeding them
>>> back into
>>> + * our own IRQ handler via @wq.
>>> + */
>>> +struct amdgpu_ualink_loopback {
>>> +    bool enabled;
>>> +    u32 exporter_accel_id;
>>> +    u32 importer_accel_id;
>>> +    struct workqueue_struct *wq;
>>> +};
>>> +
>>>    struct amdgpu_ualink_mgr {
>>>        u64 npa_size;
>>>        u32 psp_if_ver;
>>> @@ -333,6 +359,9 @@ struct amdgpu_ualink_mgr {
>>>        /* For remote interrupt and shootdown */
>>>        struct amdgpu_ualink_remote *remote;
>>>    +    /* UALink loopback mode state (see struct
>>> amdgpu_ualink_loopback) */
>>> +    struct amdgpu_ualink_loopback loopback;
>>> +
>>>        /* handle irq from ualink client of remote GPUs */
>>>        struct amdgpu_irq_src irq;
>>>    

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 7/8] drm/amdgpu: Reject config updates in UALink loopback mode
  2026-10-27 19:54 ` [PATCHv2 7/8] drm/amdgpu: Reject config updates in " Mukul Joshi
@ 2026-10-27 21:06   ` Felix Kuehling
  0 siblings, 0 replies; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 21:06 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

[-- Attachment #1: Type: text/plain, Size: 5648 bytes --]


On 2026-10-27 15:54, Mukul Joshi wrote:
> In loopback mode the driver synthesizes the pPod/vPod configuration,
> since there is no user-space agent to configure the pod. Keep anything
> else from overwriting it:
>
> - Ignore PSP config update interrupts. Re-querying the configuration
>    from PSP would replace the synthesized one. Still send the completion
>    so PSP is not left waiting.
>
> - Don't create the pPod setup, vPod config and station config sysfs
>    nodes, so the configuration can't be committed from user space. The
>    setup, config and stations directories are still created, empty, as
>    they are when the config sysfs nodes are compiled out.
>
> Signed-off-by: Mukul Joshi<mukul.joshi@amd.com>
> ---
> v1->v2:
> - Do not create the config directory in sysfs under loopback mode.(Lijo)
>   
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 49 ++++++++++++----------
>   1 file changed, 28 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index 14b9c05a0f0f..1c2203ff175d 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -486,6 +486,18 @@ int amdgpu_ualink_config_update_handler(struct amdgpu_device *adev)
>   			PSP_GFX_INT_CTXT_UAL_CMD_CFG_UPDATE_ID, status);
>   	}
>   
> +	/* The loopback pod configuration is synthesized by the driver, so
> +	 * re-querying it from PSP would overwrite it.
> +	 */
> +	if (adev->ualink.loopback.enabled) {
> +		dev_warn(adev->dev,
> +			 "UALINK: config update ignored in loopback mode\n");
> +		return psp_ual_send_completion(&adev->psp,
> +					       adev->ualink.psp_if_ver,
> +					       PSP_GFX_INT_CTXT_UAL_CMD_CFG_UPDATE_ID,
> +					       0);
> +	}
> +
>   	prev_state = adev->ualink.info->accel_state;
>   	prev_vpod_id = adev->ualink.info->vpod.id;
>   
> @@ -1779,7 +1791,7 @@ static int ualink_kobj_add(struct kobject *kobj, struct kobject *parent,
>   	int r;
>   
>   	r = kobject_add(kobj, parent, "%s", name);
> -	if (r)
> +	if (r || !attrs)
>   		return r;
>   	r = sysfs_create_files(kobj, attrs);
>   	if (r)
> @@ -1794,48 +1806,43 @@ static void amdgpu_ualink_xcp_sysfs_fini(struct amdgpu_device *adev);
>   int amdgpu_ualink_sysfs_init(struct amdgpu_device *adev)
>   {
>   	struct amdgpu_ualink_info *info = adev->ualink.info;
> +	const struct attribute **setup_attrs = NULL;
> +	const struct attribute **config_attrs = NULL;
> +	const struct attribute **station_attrs = NULL;
>   	int r;
>   
>   	if (adev->ualink.mgr_state != AMDGPU_UALINK_INIT_COMPLETE)
>   		return 0;
>   
> +#ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS
> +	/* The loopback pod configuration is synthesized by the driver. */
> +	if (!adev->ualink.loopback.enabled) {
> +		setup_attrs = ualink_ppod_setup_attrs;
> +		config_attrs = ualink_vpod_config_attrs;
> +		station_attrs = ualink_station_config_attrs;
> +	}
> +#endif
> +
>   	/* ualink parent node */
>   	r = ualink_kobj_add(&info->kobj, &adev->dev->kobj, "ualink",
>   			    ualink_info_attrs);
>   	if (r)
>   		goto err;
>   
> -#ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS

I'd prefer to keep this #ifdef and a clean separation so we only ever 
call ualink_kobj_add for the subdirectories when DEPRECATED_CONFIG_SYSFS 
is enabled. I don't want the deprecated APIs to be exposed accidentally 
by some future change.

Maybe you can leverage the #else branch for loopback. Something like this:

#ifdef UALINK_ENABLE_DEPRECATED_CONFIG_SYSFS
	if (!adev->ualink.loopback.enabled) {
	        r = ualink_kobj_add(&adev->ualink.setup->kobj, &info->kobj, "setup",
         	                    ualink_ppod_setup_attrs);
	        if (r)
         	        goto err_info;

	        r = ualink_kobj_add(&adev->ualink.config->kobj, &info->kobj, "config",
         	                    ualink_vpod_config_attrs);
	        if (r)
         	        goto err_setup;

	        r = ualink_kobj_add(&adev->ualink.stations->kobj, &info->kobj,
         	                    "stations", ualink_station_config_attrs);
	        if (r)
         	        goto err_config;
	} else
#endif
	{
	        r = kobject_add(&adev->ualink.setup->kobj, &info->kobj, "%s",
         	                "setup");
	        if (r)
         	        goto err_info;

	        r = kobject_add(&adev->ualink.config->kobj, &info->kobj, "%s",
         	                "config");
	        if (r)
         	        goto err_setup;

	        r = kobject_add(&adev->ualink.stations->kobj, &info->kobj, "%s",
         	                "stations");
	        if (r)
         	        goto err_config;
	}

Regards,
   Felix

>   	r = ualink_kobj_add(&adev->ualink.setup->kobj, &info->kobj, "setup",
> -			    ualink_ppod_setup_attrs);
> +			    setup_attrs);
>   	if (r)
>   		goto err_info;
>   
>   	r = ualink_kobj_add(&adev->ualink.config->kobj, &info->kobj, "config",
> -			    ualink_vpod_config_attrs);
> +			    config_attrs);
>   	if (r)
>   		goto err_setup;
>   
>   	r = ualink_kobj_add(&adev->ualink.stations->kobj, &info->kobj,
> -			    "stations", ualink_station_config_attrs);
> -	if (r)
> -		goto err_config;
> -#else
> -	r = kobject_add(&adev->ualink.setup->kobj, &info->kobj, "%s",
> -			"setup");
> -	if (r)
> -		goto err_info;
> -
> -	r = kobject_add(&adev->ualink.config->kobj, &info->kobj, "%s",
> -			"config");
> -	if (r)
> -		goto err_setup;
> -
> -	r = kobject_add(&adev->ualink.stations->kobj, &info->kobj, "%s",
> -			"stations");
> +			    "stations", station_attrs);
>   	if (r)
>   		goto err_config;
> -#endif
>   
>   	amdgpu_ualink_xcp_sysfs_init(adev);
>   	adev->ualink.sysfs_init = true;

[-- Attachment #2: Type: text/html, Size: 6521 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 2/8] drm/amdgpu: Support VMID 0 GPUVA completion WB address for UALink
  2026-10-27 19:54 ` [PATCHv2 2/8] drm/amdgpu: Support VMID 0 GPUVA completion WB address for UALink Mukul Joshi
@ 2026-10-27 21:23   ` Felix Kuehling
  0 siblings, 0 replies; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 21:23 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

On 2026-10-27 15:54, Mukul Joshi wrote:
> The UPDATE_WB_ADDR remote command hands the MP nHT FW the address it
> writes command completions back to. So far that has always been an NPA.
>
> The FW can also take a VMID 0 GPUVA, selected by setting DW4 of the
> command to 1. Return the VMID 0 GPUVA of the writeback slot from
> amdgpu_ualink_get_wb_addr() alongside the NPA, and use it in
> UALink loopback mode. There the HW only carries importer -> exporter
> traffic, so the FW cannot write the completion back over NPA.
>
> Outside loopback the NPA is still used and DW4 stays 0.
>
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>

Reviewed-by: Felix Kuehling <felix.kuehling@amd.com>


> ---
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 48 +++++++++++++++-------
>   1 file changed, 34 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index 2cd6dc71ab9f..a186859dee3c 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -5641,9 +5641,9 @@ static void amdgpu_ualink_emit_update_wb_addr(u32 **cpu_addr_p, u32 wb_data,
>   
>   	*cpu_addr++ = cmd;
>   	*cpu_addr++ = wb_data;
> -	*cpu_addr++ = dw1 & 0xFFFFFFFC;	/* lower32 wb_npa */
> -	*cpu_addr++ = dw0 & 0xFFFFF;	/* upper32 wb_npa */
> -	*cpu_addr++ = 0;
> +	*cpu_addr++ = dw1 & 0xFFFFFFFC;	/* lower32 wb address */
> +	*cpu_addr++ = dw0 & 0xFFFFF;	/* upper32 wb address */
> +	*cpu_addr++ = dw2;		/* 1: wb address is VMID 0 GPUVA, 0: NPA */
>   	*cpu_addr++ = 0;
>   	*cpu_addr++ = 0; /* metadata */
>   	*cpu_addr++ = 0; /* metadata */
> @@ -5857,7 +5857,7 @@ static int amdgpu_ualink_send_command(struct amdgpu_device *adev,
>   static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
>   				      u32 remote_accel_id,
>   				      struct amdgpu_ualink_wb **wb_cpu,
> -				      u64 *wb_npa, u32 type)
> +				      u64 *wb_npa, u64 *wb_gpu, u32 type)
>   {
>   	struct amdgpu_ualink_remote *remote = to_remote(adev);
>   	/* The GPUID encoded in the write-back NPA address is the routing target
> @@ -5870,15 +5870,17 @@ static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
>   	u32 accel_id = ualink_exp_accel_id(adev);
>   	u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE);
>   	uintptr_t wb;
> -	u64 npa;
> +	u64 gpu, npa;
>   	u32 offset = 0;
>   
>   	if (ualink_addr_mode(adev) == AMDGPU_UALINK_ADDR_MODE_SOURCE_IDENT) {
>   		wb = (uintptr_t)remote->rb_cpu_addr + rb_size * remote->num_accel;
> +		gpu = remote->rb_gpu_addr + rb_size * remote->num_accel;
>   		npa = amdgpu_ualink_npa_addr(adev, RB_TYPE_TAILPTR,
>   					     remote_accel_id, accel_id);
>   	} else {
>   		wb = (uintptr_t)remote->rptr_cpu_addr;
> +		gpu = remote->rptr_gpu_addr;
>   		npa = remote->rptr_npa;
>   		npa |= (u64)accel_id << AMDGPU_UALINK_GART_NPA_ADDR_GPUID_SHIFT;
>   	}
> @@ -5891,9 +5893,11 @@ static void amdgpu_ualink_get_wb_addr(struct amdgpu_device *adev,
>   	*wb_cpu = (struct amdgpu_ualink_wb *)(wb + offset);
>   	if (wb_npa)
>   		*wb_npa = npa + offset;
> +	if (wb_gpu)
> +		*wb_gpu = gpu + offset;
>   
> -	dev_dbg(adev->dev, "source %d remote %d wb npa 0x%llx\n", accel_id,
> -		remote_accel_id, npa + offset);
> +	dev_dbg(adev->dev, "source %d remote %d wb npa 0x%llx gpu 0x%llx\n", accel_id,
> +		remote_accel_id, npa + offset, gpu + offset);
>   }
>   
>   struct amdgpu_ualink_wptr_fence_cb {
> @@ -6057,9 +6061,9 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>   		ualink_exp_accel_id(adev), peer->interrupt.wptr, peer->shootdown.wptr,
>   		remote_accel_id, int_wptr, tlb_wptr);
>   
> -	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL,
> +	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, NULL, NULL,
>   				  RB_TYPE_REMOTE_INTERRUPT);
> -	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &tlb_wb, NULL,
> +	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &tlb_wb, NULL, NULL,
>   				  RB_TYPE_TLB_INV);
>   
>   	peer->interrupt.wptr = int_wptr;
> @@ -6093,7 +6097,8 @@ static int amdgpu_ualink_update_wb_address(struct amdgpu_device *adev,
>   	struct amdgpu_ualink_peer *peer;
>   	struct amdgpu_ualink_ring *ring;
>   	struct amdgpu_ualink_wb *wb_cpu;
> -	u64 wb_npa;
> +	u64 wb_npa, wb_gpu, wb_addr;
> +	u32 wb_vmid0;
>   	int r;
>   
>   	peer = &remote->peer[remote_accel_id];
> @@ -6105,13 +6110,28 @@ static int amdgpu_ualink_update_wb_address(struct amdgpu_device *adev,
>   		return -EINVAL;
>   
>   	amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &wb_cpu, &wb_npa,
> -				  ring_type);
> +				  &wb_gpu, ring_type);
> +
> +	/* UALink loopback: HW only carries importer -> exporter traffic, so the
> +	 * MPNHT FW cannot write the completion back over NPA. Hand it the VMID 0
> +	 * GPUVA of the writeback instead and flag it in DW4 of the command.
> +	 */
> +	if (adev->ualink.loopback.enabled) {
> +		wb_addr = wb_gpu;
> +		wb_vmid0 = 1;
> +	} else {
> +		wb_addr = wb_npa;
> +		wb_vmid0 = 0;
> +	}
> +
> +	dev_dbg(adev->dev, "remote %u ring type %u wb %s 0x%llx\n",
> +		remote_accel_id, ring_type, wb_vmid0 ? "gpuva" : "npa", wb_addr);
>   
>   	r = amdgpu_ualink_send_command(adev, remote_accel_id, ring, wb_cpu,
>   				       amdgpu_ualink_emit_update_wb_addr,
> -				       upper_32_bits(wb_npa),
> -				       lower_32_bits(wb_npa),
> -				       0, 0);
> +				       upper_32_bits(wb_addr),
> +				       lower_32_bits(wb_addr),
> +				       wb_vmid0, 0);
>   	if (!r) {
>   		ring->wb_cpu = wb_cpu;
>   		ring->ready = true;

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 3/8] drm/amdgpu: Emulate exporter->importer UALink messages in loopback
  2026-10-27 19:54 ` [PATCHv2 3/8] drm/amdgpu: Emulate exporter->importer UALink messages in loopback Mukul Joshi
@ 2026-10-27 21:34   ` Felix Kuehling
  0 siblings, 0 replies; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 21:34 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

On 2026-10-27 15:54, Mukul Joshi wrote:
> In UALink loopback mode a single GPU acts as both the exporter and the
> importer. The exporter is the one physical station, so the only path
> the HW serves is importer -> exporter. A message the exporter sends to
> the importer cannot be delivered by the MP nHT.
>
> Emulate that direction in SW: when loopback is enabled and a remote
> interrupt is addressed to the importer, queue it on the loopback
> ordered workqueue, which feeds it back into amdgpu_ualink_process_irq()
> as if the exporter had sent it. The ordered workqueue keeps successive
> messages in order and runs the (possibly sleeping) handlers outside
> the sender's context.
>
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
> ---
> v1->v2:
> - Use wrapper functions in amdgpu_ualink_remote_interrupt() (Lijo).
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 82 ++++++++++++++++++++++
>   1 file changed, 82 insertions(+)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index a186859dee3c..6c70be7badec 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -60,6 +60,9 @@ static void amdgpu_ualink_reset_peer_rings(struct amdgpu_device *adev,
>   					   u32 remote_accel_id);
>   static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>   					  u32 remote_accel_id);
> +static int amdgpu_ualink_loopback_self_interrupt(struct amdgpu_device *adev,
> +						 u32 sender_accel_id, u32 dw0,
> +						 u32 dw1, u32 dw2, u32 dw3);
>   
>   #define STRIP_NPA(addr)						\
>   	(((u64)(addr) & ~AMDGPU_UALINK_NPA_ADDR_GPUID_MASK))
> @@ -6209,6 +6212,17 @@ static int amdgpu_ualink_remote_interrupt(struct amdgpu_device *adev,
>   	struct amdgpu_ualink_ring *ring;
>   	int r;
>   
> +	/* UALink loopback: the single MP nHT can only deliver messages addressed
> +	 * to the exporter accelerator. Messages addressed to the importer
> +	 * (exporter->importer direction) cannot be delivered by HW, so emulate
> +	 * them by feeding the message back into our own IRQ handler.
> +	 */
> +	if (adev->ualink.loopback.enabled &&
> +	    remote_accel_id == ualink_imp_accel_id(adev))
> +		return amdgpu_ualink_loopback_self_interrupt(adev,
> +				ualink_exp_accel_id(adev),
> +				dw0, dw1, dw2, dw3);
> +
>   	peer = &remote->peer[remote_accel_id];
>   	ring = &peer->interrupt;
>   
> @@ -6630,6 +6644,74 @@ static int amdgpu_ualink_process_irq(struct amdgpu_device *adev,
>   	return handled;
>   }
>   
> +/* Work item used to emulate an exporter->importer UALink interrupt in
> + * loopback mode (see amdgpu_ualink_loopback_self_interrupt()).
> + */
> +struct amdgpu_ualink_self_irq_work {
> +	struct work_struct work;
> +	struct amdgpu_device *adev;
> +	u32 sender_accel_id;
> +	u32 dw[4];
> +};
> +
> +static void amdgpu_ualink_self_irq_worker(struct work_struct *w)
> +{
> +	struct amdgpu_ualink_self_irq_work *job =
> +		container_of(w, struct amdgpu_ualink_self_irq_work, work);
> +	struct amdgpu_device *adev = job->adev;
> +	struct amdgpu_iv_entry entry = { };
> +
> +	/* Build an IH entry as if the message had been delivered by the MP nHT
> +	 * from the exporter accelerator. amdgpu_ualink_process_irq() consumes
> +	 * the sender accel_id from entry->pasid and the four context dwords
> +	 * from entry->src_data[]. entry->ih must differ from adev->irq.ih so
> +	 * the message is processed here instead of being delegated to the
> +	 * UALink soft ring.
> +	 */
> +	entry.ih = NULL;
> +	entry.pasid = job->sender_accel_id;
> +	entry.src_data[0] = job->dw[0];
> +	entry.src_data[1] = job->dw[1];
> +	entry.src_data[2] = job->dw[2];
> +	entry.src_data[3] = job->dw[3];
> +
> +	amdgpu_ualink_process_irq(adev, &adev->ualink.irq, &entry);
> +	kfree(job);
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_self_interrupt - deliver a UALink message to ourselves
> + *
> + * Used in loopback mode for the exporter->importer direction, where there is
> + * no HW path. The message is processed asynchronously on an ordered workqueue
> + * so ordering between successive messages is preserved and the (potentially
> + * sleeping) message handlers do not run from the caller's context.
> + */
> +static int amdgpu_ualink_loopback_self_interrupt(struct amdgpu_device *adev,
> +						 u32 sender_accel_id, u32 dw0,
> +						 u32 dw1, u32 dw2, u32 dw3)
> +{
> +	struct amdgpu_ualink_self_irq_work *job;
> +
> +	if (!adev->ualink.loopback.wq)
> +		return -ENODEV;
> +
> +	job = kzalloc(sizeof(*job), GFP_KERNEL);

Using a fixed size FIFO would be more efficient than dynamically 
allocating and freeing every job. That made me think, could we just 
create an IH cookie and inject it into the normal IH soft ring with 
amdgpu_irq_delegate?

Regards,
   Felix


> +	if (!job)
> +		return -ENOMEM;
> +
> +	job->adev = adev;
> +	job->sender_accel_id = sender_accel_id;
> +	job->dw[0] = dw0;
> +	job->dw[1] = dw1;
> +	job->dw[2] = dw2;
> +	job->dw[3] = dw3;
> +	INIT_WORK(&job->work, amdgpu_ualink_self_irq_worker);
> +	queue_work(adev->ualink.loopback.wq, &job->work);
> +
> +	return 0;
> +}
> +
>   static int amdgpu_ualink_set_irq_state(struct amdgpu_device *adev,
>   					struct amdgpu_irq_src *source,
>   					u32 type,

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender
  2026-10-27 19:54 ` [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender Mukul Joshi
@ 2026-10-27 21:38   ` Felix Kuehling
  2026-10-28  6:23   ` Lazar, Lijo
  1 sibling, 0 replies; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 21:38 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

On 2026-10-27 15:54, Mukul Joshi wrote:
> In UALink loopback mode a single GPU acts as both the exporter and the
> importer, so the sender accel ID of a message depends on its direction.
>
> - Send HELLO as the opposite role of the destination: a message to the
>    exporter is sent as the importer and vice versa. Outside loopback
>    this is still the GPU's own accel ID.
>
> - The MP nHT stamps the IH cookie of every HW-delivered message with
>    the GPU ID of the single physical station, the exporter. The only HW
>    path is importer -> exporter, so attribute these messages to the
>    importer. SW-emulated exporter -> importer messages carry no IH ring
>    and already name the exporter as the sender.
>
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>

Reviewed-by: Felix Kuehling <felix.kuehling@amd.com>


> ---
> v1->v2:
> - Use ualink_exp_accel_id/ualink_imp_accel_id() wrappers in
>    amdgpu_ualink_process_irq(). (Lijo)
>
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 32 +++++++++++++++++++++-
>   1 file changed, 31 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index 6c70be7badec..96a6023f51c5 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -1090,6 +1090,23 @@ static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
>   	return adev->ualink.info->ppod.accel_id;
>   }
>   
> +/*
> + * amdgpu_ualink_local_sender_accel_id - sender accel_id for an outgoing message
> + *
> + * Normally a GPU sends messages as its own (single) accelerator. In loopback
> + * mode the same GPU plays both roles, so the sender identity depends on the
> + * destination: a message to the exporter is sent as the importer and vice
> + * versa. Outside loopback both roles collapse to the GPU's own accel_id.
> + */
> +static u32 amdgpu_ualink_local_sender_accel_id(struct amdgpu_device *adev,
> +					       u32 remote_acc_id)
> +{
> +	if (remote_acc_id == ualink_exp_accel_id(adev))
> +		return ualink_imp_accel_id(adev);
> +
> +	return ualink_exp_accel_id(adev);
> +}
> +
>   static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>   					 u32 accel_id)
>   {
> @@ -2238,7 +2255,10 @@ static int amdgpu_ualink_send_hello_msg(struct amdgpu_device *adev,
>   
>   	dw0 = AMDGPU_UALINK_HELLO_MSG;
>   	dw0 |= (remote_acc_id << AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT);
> -	dw0 |= (adev->ualink.info->ppod.accel_id <<
> +	/* In loopback the sender is the opposite role of the destination, so
> +	 * the exporter side tracks the connection under the importer accel_id.
> +	 */
> +	dw0 |= (amdgpu_ualink_local_sender_accel_id(adev, remote_acc_id) <<
>   		AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT);
>   
>   	dev_dbg(adev->dev, "SEND HELLO: HELLO message to remote AccId:%u\n",
> @@ -6548,6 +6568,16 @@ static int amdgpu_ualink_process_irq(struct amdgpu_device *adev,
>   		return handled;
>   	}
>   
> +	/* UALink loopback: the MP nHT stamps every HW-delivered message with
> +	 * the GPU ID of the single physical station, the exporter. The only
> +	 * HW path is importer -> exporter, so the real sender is the importer.
> +	 * SW-emulated exporter -> importer messages carry no IH ring and
> +	 * already name the exporter as sender.
> +	 */
> +	if (adev->ualink.loopback.enabled && entry->ih &&
> +	    src_acc_id == ualink_exp_accel_id(adev))
> +		src_acc_id = ualink_imp_accel_id(adev);
> +
>   	msg_type = dw0 & AMDGPU_UALINK_MESSAGE_HEADER_MASK;
>   	local_acc_id = adev->ualink.info->ppod.accel_id;
>   	dev_dbg(adev->dev, "Got MSG: remote acc_id %u msg_type %u\n",

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode
  2026-10-27 19:54 ` [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode Mukul Joshi
@ 2026-10-27 21:45   ` Felix Kuehling
  2026-10-28  6:33   ` Lazar, Lijo
  1 sibling, 0 replies; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 21:45 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

On 2026-10-27 15:54, Mukul Joshi wrote:
> In UALink loopback mode a single GPU acts as both the exporter (accel
> ID 1) and the importer (accel ID 0). This lets the existing tests export
> a BO and import it on the same device, traversing the UALink HW
> pipeline.
>
> Add amdgpu_ualink_loopback_detect(), the single place that decides
> whether loopback is enabled. It runs right after the UALink info is
> queried from PSP, before any per-peer setup. When loopback is enabled:
>
> - There is no user-space agent to configure the pod, so synthesize the
>    pPod/vPod configuration with both accel IDs active.
>
> - Activate the device directly instead of through the cross-GPU vpod
>    activation path, which needs peer GPUs and a user-space vpod commit.
>
> The mechanism that requests loopback mode is added separately, so
> amdgpu_ualink_loopback_requested() returns false for now. The normal
> activation path is unchanged when loopback is not enabled.
>
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>

Reviewed-by: Felix Kuehling <felix.kuehling@amd.com>


> ---
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 128 ++++++++++++++++++++-
>   1 file changed, 123 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index e6272446ff4f..14b9c05a0f0f 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -63,6 +63,7 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>   static int amdgpu_ualink_loopback_self_interrupt(struct amdgpu_device *adev,
>   						 u32 sender_accel_id, u32 dw0,
>   						 u32 dw1, u32 dw2, u32 dw3);
> +static void activate_accelerator(struct amdgpu_device *adev);
>   
>   #define STRIP_NPA(addr)						\
>   	(((u64)(addr) & ~AMDGPU_UALINK_NPA_ADDR_GPUID_MASK))
> @@ -93,19 +94,130 @@ static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev)
>   }
>   
>   /*
> - * amdgpu_ualink_loopback_init - set up UALink loopback mode
> + * amdgpu_ualink_loopback_requested - check whether UALink loopback is requested
>    *
> - * Called once loopback mode has been detected. Allocates the ordered
> - * workqueue used to emulate exporter->importer interrupts in SW.
> + * There is no mechanism to request loopback mode yet, so it stays disabled.
> + */
> +static bool amdgpu_ualink_loopback_requested(struct amdgpu_device *adev)
> +{
> +	return false;
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_detect - detect UALink loopback mode
> + *
> + * This is the only place that decides whether loopback mode is enabled. It
> + * must run before any per-peer setup, since enabling loopback changes the
> + * accelerator IDs returned by ualink_exp_accel_id() and ualink_imp_accel_id().
> + */
> +static void amdgpu_ualink_loopback_detect(struct amdgpu_device *adev)
> +{
> +	if (!amdgpu_ualink_loopback_requested(adev))
> +		return;
> +
> +	adev->ualink.loopback.enabled = true;
> +	adev->ualink.loopback.exporter_accel_id =
> +		AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID;
> +	adev->ualink.loopback.importer_accel_id =
> +		AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID;
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_populate_info - synthesize pPod/vPod config for loopback
> + *
> + * In loopback there is no user-space agent (AIFM / xncmdclient) writing the
> + * UALink sysfs nodes to configure the pod, so populate adev->ualink.info
> + * directly. The layout is fixed and known: a single GPU alone in its own
> + * physical pod, presented as accelerator ID 1 (the exporter) and also owning
> + * accelerator ID 0 (the importer). The values are made self-consistent so
> + * check_vpod_info() would pass and amdgpu_ualink_metadata_init() programs the
> + * importer's ring buffers / GART NPA mappings / MP nHT receive metadata.
> + */
> +static void amdgpu_ualink_loopback_populate_info(struct amdgpu_device *adev)
> +{
> +	struct amdgpu_ualink_info *info = adev->ualink.info;
> +	u32 exporter = adev->ualink.loopback.exporter_accel_id; /* 1 */
> +	u32 importer = adev->ualink.loopback.importer_accel_id; /* 0 */
> +
> +	/* Physical pod: this GPU is presented as the exporter accel ID in its
> +	 * own pod. ppod.size must be large enough to contain both the exporter
> +	 * and importer accel IDs. ppod.id must be unique within the system
> +	 * (matters for 1P4G, where every loopback GPU is its own physical pod).
> +	 */
> +	info->ppod.accel_id = exporter;
> +	info->ppod.size = max(exporter, importer) + 1;	/* == 2 */
> +	uuid_gen(&info->ppod.id);
> +
> +	/* Virtual pod: mark BOTH the exporter and the importer active so the
> +	 * per-peer setup (amdgpu_ualink_metadata_init()) programs the importer's
> +	 * resources for the importer accel ID. Preserve the address mode queried
> +	 * from firmware if it is valid (keeps source-identification HW working),
> +	 * otherwise default to source-aliasing (MI450).
> +	 */
> +	info->vpod.id = 0;
> +	info->vpod.size = 2;
> +	if (info->vpod.addr_mode >= AMDGPU_UALINK_ADDR_MODE_MAX)
> +		info->vpod.addr_mode = AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS;
> +	bitmap_zero(info->vpod.active_accel_bits, AMDGPU_UALINK_ACCEL_MAX);
> +	set_bit(exporter, info->vpod.active_accel_bits);
> +	set_bit(importer, info->vpod.active_accel_bits);
> +
> +	/* Only the exporter (this GPU's own accel ID) is a local accelerator;
> +	 * the importer is reached through the (loopback) remote/NPA path.
> +	 */
> +	info->n_local_accels = 1;
> +	info->local_accels[0] = exporter;
> +
> +	/* Mark the pod vpod-configured so the activation path proceeds. */
> +	info->accel_state = AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED;
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_activate - bring up the UALink manager for loopback
> + *
> + * The normal activation is driven by the sysfs vpod_config/commit path via
> + * activate_local_vpod(), which derives the local accelerators from peer GPUs
> + * and runs a cross-GPU integrity check. Neither applies in loopback (the
> + * importer is not a separate GPU), so activate this device directly.
> + */
> +static int amdgpu_ualink_loopback_activate(struct amdgpu_device *adev)
> +{
> +	mutex_lock(&mgpu_info.mutex);
> +	activate_accelerator(adev);
> +	mutex_unlock(&mgpu_info.mutex);
> +
> +	if (adev->ualink.info->accel_state < AMDGPU_UALINK_ACCEL_STATE_ACTIVE) {
> +		dev_err(adev->dev, "UALink loopback activation did not reach ACTIVE\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_init - set up and activate UALink loopback mode
> + *
> + * Called once loopback mode has been detected, after the initial pod/vpod
> + * configuration has been queried into adev->ualink.info. Synthesizes the pod
> + * configuration (there is no user-space agent to write the UALink sysfs nodes
> + * in loopback), allocates the ordered workqueue used to emulate
> + * exporter->importer interrupts in SW and activates the device.
>    */
>   static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
>   {
> +	amdgpu_ualink_loopback_populate_info(adev);
> +
>   	adev->ualink.loopback.wq =
>   		alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
>   	if (!adev->ualink.loopback.wq)
>   		return -ENOMEM;
>   
> -	return 0;
> +	dev_info(adev->dev,
> +		 "UALink loopback enabled (exporter accel %u, importer accel %u)\n",
> +		 adev->ualink.loopback.exporter_accel_id,
> +		 adev->ualink.loopback.importer_accel_id);
> +
> +	return amdgpu_ualink_loopback_activate(adev);
>   }
>   
>   static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
> @@ -495,10 +607,16 @@ int ualink_ip_late_init(struct amdgpu_ip_block *ip_block)
>   	if (r)
>   		return r;
>   
> +	amdgpu_ualink_loopback_detect(adev);
> +
>   	scoped_guard(mutex, &mgpu_info.mutex) {
>   		amdgpu_ualink_info_set_accel_state(adev, adev->ualink.info,
>   						   cfg_state);
> -		__amdgpu_ualink_activate_vpod_locked(adev);
> +		/* Loopback has no peer GPUs and no user-space vpod commit, so it
> +		 * cannot use the normal cross-GPU activation path.
> +		 */
> +		if (!adev->ualink.loopback.enabled)
> +			__amdgpu_ualink_activate_vpod_locked(adev);
>   	}
>   
>   	if (adev->ualink.loopback.enabled) {

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 8/8] drm/amdgpu: Temporarily enable UALink loopback via module parameter
  2026-10-27 19:54 ` [PATCHv2 8/8] drm/amdgpu: Temporarily enable UALink loopback via module parameter Mukul Joshi
@ 2026-10-27 21:47   ` Felix Kuehling
  0 siblings, 0 replies; 25+ messages in thread
From: Felix Kuehling @ 2026-10-27 21:47 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang

On 2026-10-27 15:54, Mukul Joshi wrote:
> Temporarily add the amdgpu.ualink_loopback module parameter to request
> UALink loopback mode for manufacturing testing, until the final
> mechanism for enabling loopback is in place. Loopback is disabled by
> default.
>
> Only amdgpu_ualink_loopback_requested() reads the parameter, so moving
> to the final mechanism only needs to replace it there.
>
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>

Acked-by: Felix Kuehling <felix.kuehling@amd.com>

We probably don't want to push this one upstream. It could be for our 
internal branch only until we have the proper detection method in place.

Regards,
   Felix


> ---
>   drivers/gpu/drm/amd/amdgpu/amdgpu.h        |  1 +
>   drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c    | 13 +++++++++++++
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c |  5 +++--
>   3 files changed, 17 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> index 5a32b54b1e9b..b8be093b8a33 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> @@ -270,6 +270,7 @@ extern int amdgpu_rebar;
>   extern int amdgpu_wbrf;
>   extern int amdgpu_user_queue;
>   extern int amdgpu_ptl;
> +extern int amdgpu_ualink_loopback;
>   
>   extern uint amdgpu_hdmi_hpd_debounce_delay_ms;
>   
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
> index 3e5634de077f..e0cf40f86194 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
> @@ -253,6 +253,7 @@ int amdgpu_rebar = -1; /* auto */
>   int amdgpu_user_queue = -1;
>   uint amdgpu_hdmi_hpd_debounce_delay_ms;
>   int amdgpu_ptl = -1; /* auto */
> +int amdgpu_ualink_loopback;
>   
>   DECLARE_DYNDBG_CLASSMAP(drm_debug_classes, DD_CLASS_TYPE_DISJOINT_BITS, 0,
>   			"DRM_UT_CORE",
> @@ -1165,6 +1166,18 @@ module_param_named(hdmi_hpd_debounce_delay_ms, amdgpu_hdmi_hpd_debounce_delay_ms
>   MODULE_PARM_DESC(ptl, "Enable PTL (-1 = auto, 0 = disable (default), 1 = enable, 2 = permanently disable)");
>   module_param_named(ptl, amdgpu_ptl, int, 0444);
>   
> +/**
> + * DOC: ualink_loopback (int)
> + * Enable UALink loopback mode for manufacturing testing. In loopback mode a
> + * single GPU acts as both the exporter (accelerator ID 1) and the importer
> + * (accelerator ID 0) and loops UALink requests back to itself. Possible values:
> + *
> + * - 0 = disable (default)
> + * - 1 = enable
> + */
> +MODULE_PARM_DESC(ualink_loopback, "Enable UALink loopback mode (0 = disable (default), 1 = enable)");
> +module_param_named(ualink_loopback, amdgpu_ualink_loopback, int, 0444);
> +
>   /* These devices are not supported by amdgpu.
>    * They are supported by the mach64, r128, radeon drivers
>    */
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index 1c2203ff175d..a98fa5f970b1 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -96,11 +96,12 @@ static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev)
>   /*
>    * amdgpu_ualink_loopback_requested - check whether UALink loopback is requested
>    *
> - * There is no mechanism to request loopback mode yet, so it stays disabled.
> + * Loopback mode is temporarily requested with the amdgpu.ualink_loopback
> + * module parameter.
>    */
>   static bool amdgpu_ualink_loopback_requested(struct amdgpu_device *adev)
>   {
> -	return false;
> +	return amdgpu_ualink_loopback == 1;
>   }
>   
>   /*

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure
  2026-10-27 20:56       ` Felix Kuehling
@ 2026-10-28  6:06         ` Lazar, Lijo
  0 siblings, 0 replies; 25+ messages in thread
From: Lazar, Lijo @ 2026-10-28  6:06 UTC (permalink / raw)
  To: Felix Kuehling, Mukul Joshi, amd-gfx; +Cc: Harish.Kasiviswanathan, philip.yang



On 28-Oct-26 2:26 AM, Felix Kuehling wrote:
> One more nit-pick inline ...
> 
> On 2026-10-27 16:45, Mukul Joshi wrote:
>> On 10/27/2026 4:22 PM, Felix Kuehling wrote:
>>> On 2026-10-27 15:54, Mukul Joshi wrote:
>>>> Add the infrastructure for UALink loopback mode, where a single GPU
>>>> plays both the exporter and the importer role under two fixed
>>>> accelerator IDs:
>>>>
>>>> - Add struct amdgpu_ualink_loopback to track the loopback state, along
>>>>     with the fixed exporter and importer accel IDs.
>>>>
>>>> - Replace ualink_accel_id() with ualink_exp_accel_id() and
>>>>     ualink_imp_accel_id(), which return the exporter and importer accel
>>>>     ID in loopback mode.
>>>>
>>>> - Add amdgpu_ualink_loopback_init(), which allocates the ordered
>>>>     workqueue used to emulate exporter->importer messages, and destroy
>>>>     it in amdgpu_ualink_object_fini().
>>>>
>>>> Loopback is not enabled yet, and outside loopback both helpers return
>>>> ppod.accel_id, so there is no functional change.
>>>>
>>>> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
>>>> ---
>>>> v1-v2:
>>>> - Drop the change in find_peer_adev() as its not needed (Lijo).
>>>>
>>>>    drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 99 ++++++++++++++++ 
>>>> +-----
>>>>    drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h | 29 +++++++
>>>>    2 files changed, 109 insertions(+), 19 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> index ca6432610f71..2cd6dc71ab9f 100644
>>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> @@ -89,8 +89,34 @@ static int amdgpu_ualink_drm_client_create(struct
>>>> amdgpu_device *adev)
>>>>        return 0;
>>>>    }
>>>>    +/*
>>>> + * amdgpu_ualink_loopback_init - set up UALink loopback mode
>>>> + *
>>>> + * Called once loopback mode has been detected. Allocates the ordered
>>>> + * workqueue used to emulate exporter->importer interrupts in SW.
>>>> + */
>>>> +static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
>>>> +{
>>>> +    adev->ualink.loopback.wq =
>>>> +        alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
>>>> +    if (!adev->ualink.loopback.wq)
>>>> +        return -ENOMEM;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>>    static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
>>>>    {
>>>> +    /* Tear down the loopback self-interrupt workqueue allocated in
>>>> +     * amdgpu_ualink_loopback_init(). destroy_workqueue() drains any
>>>> +     * pending self-interrupt work first.
>>>> +     */
>>>> +    if (adev->ualink.loopback.wq) {
>>>> +        destroy_workqueue(adev->ualink.loopback.wq);
>>>> +        adev->ualink.loopback.wq = NULL;
>>>> +        adev->ualink.loopback.enabled = false;
>>>> +    }
>>>> +
> 
> This function deals with destroying the kobjects. Hence the word 
> "object" in the function name. I think this loopback tear-down belongs 
> in ualink_ip_sw_fini instead.
> 

Adding to this one -

If loopback mode is through module param or can be detected as early as 
sw_init phase, then loopback wq allocation may also be moved to sw init 
phase. That's the place for one-time allocations, late init gets called 
during runtime also.

Thanks,
Lijo


> Regards,
>    Felix
> 
> 
>>>>        if (!adev->ualink.info)
>>>>            return;
>>>>    @@ -472,6 +498,14 @@ int ualink_ip_late_init(struct amdgpu_ip_block
>>>> *ip_block)
>>>>            __amdgpu_ualink_activate_vpod_locked(adev);
>>>>        }
>>>>    +    if (adev->ualink.loopback.enabled) {
>>>> +        r = amdgpu_ualink_loopback_init(adev);
>>>> +        if (r) {
>>>> +            dev_err(adev->dev, "Failed to set up UALink loopback:
>>>> %d\n", r);
>>>> +            goto error;
>>>> +        }
>>>> +    }
>>>> +
>>>>        r = amdgpu_ualink_drm_client_create(adev);
>>>>        if (r) {
>>>>            dev_err(adev->dev, "Failed to create UALink DRM client: 
>>>> %d\n",
>>>> @@ -1028,6 +1062,31 @@ static struct amdgpu_device
>>>> *find_peer_adev(unsigned int accel_id)
>>>>        return NULL;
>>>>    }
>>>>    +/*
>>>> + * Physical accelerator roles.
>>>> + *
>>>> + * Outside loopback a GPU owns a single accelerator, so both helpers
>>>> return its
>>>> + * ppod.accel_id and the exporter/importer distinction collapses. In
>>>> loopback
>>>> + * the one physical GPU plays both roles: the exporter owns the MP
>>>> nHT and
>>>> + * receives all HW traffic, while the importer is the (virtual) HW
>>>> sender for
>>>> + * the importer -> exporter path.
>>>> + */
>>>> +static inline u32 ualink_exp_accel_id(struct amdgpu_device *adev)
>>>> +{
>>>> +    if (adev->ualink.loopback.enabled)
>>>> +        return adev->ualink.loopback.exporter_accel_id;
>>>> +
>>>> +    return adev->ualink.info->ppod.accel_id;
>>>> +}
>>>> +
>>>> +static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
>>>> +{
>>>> +    if (adev->ualink.loopback.enabled)
>>>> +        return adev->ualink.loopback.importer_accel_id;
>>>> +
>>>> +    return adev->ualink.info->ppod.accel_id;
>>>> +}
>>>> +
>>>>    static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>>>>                         u32 accel_id)
>>>>    {
>>>> @@ -4487,11 +4546,6 @@ static bool
>>>> amdgpu_ualink_vpod_membership_changed(struct amdgpu_device *adev)
>>>>                     AMDGPU_UALINK_ACCEL_MAX);
>>>>    }
>>>>    -static inline u32 ualink_accel_id(struct amdgpu_device *adev)
>>>> -{
>>>> -    return adev->ualink.info->ppod.accel_id;
>>>> -}
>>>> -
>>>>    static inline enum amdgpu_ualink_addr_mode ualink_addr_mode(struct
>>>> amdgpu_device *adev)
>>>>    {
>>>>        return adev->ualink.info->vpod.addr_mode;
>>>> @@ -4837,7 +4891,7 @@ static void
>>>> amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>>>>    {
>>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>>>        u64 timeout = msecs_to_jiffies(2000);
>>>> -    u32 dst_accel_id = ualink_accel_id(adev);
>>>> +    u32 dst_accel_id = ualink_exp_accel_id(adev);
>>>>        struct amdgpu_bo *bos[2];
>>>>        u32 n_bos;
>>>>        struct dma_fence *fence;
>>>> @@ -4956,7 +5010,7 @@ static void
>>>> amdgpu_ualink_metadata_npa_unmapping(struct amdgpu_device *adev)
>>>>    static int amdgpu_ualink_metadata_npa_mapping(struct amdgpu_device
>>>> *adev)
>>>>    {
>>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>>> -    u32 dst_accel_id = ualink_accel_id(adev);
>>>> +    u32 dst_accel_id = ualink_exp_accel_id(adev);
>>>>        u64 timeout = msecs_to_jiffies(2000);
>>>>        struct amdgpu_bo *bos[2];
>>>>        u32 n_bos;
>>>> @@ -5241,7 +5295,7 @@ static void amdgpu_ualink_metadata_fini(struct
>>>> amdgpu_device *adev)
>>>>    {
>>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>>>    -    dev_dbg(adev->dev, "accel_id %u\n", ualink_accel_id(adev));
>>>> +    dev_dbg(adev->dev, "accel_id %u\n", ualink_exp_accel_id(adev));
>>>>        amdgpu_bo_free_kernel(&remote->rptr_bo, &remote->rptr_gpu_addr,
>>>>                          &remote->rptr_cpu_addr);
>>>>        amdgpu_bo_free_kernel(&remote->ring_bo, &remote->rb_gpu_addr,
>>>> @@ -5288,7 +5342,7 @@ static int amdgpu_ualink_metadata_init(struct
>>>> amdgpu_device *adev)
>>>>        }
>>>>          dev_dbg(adev->dev, "accel_id %u addr_mode %d fw status 
>>>> 0x%x\n",
>>>> -        ualink_accel_id(adev), ualink_addr_mode(adev), status);
>>>> +        ualink_exp_accel_id(adev), ualink_addr_mode(adev), status);
>>>>          /* Alloc metadata structure for all GPUs */
>>>>        metadata_size = sizeof(struct amdgpu_ualink_metadata) *
>>>> AMDGPU_UALINK_ACCEL_MAX;
>>>> @@ -5361,12 +5415,12 @@ static int amdgpu_ualink_metadata_init(struct
>>>> amdgpu_device *adev)
>>>>        }
>>>>          if (ualink_addr_mode(adev) ==
>>>> AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS)
>>>> -        wptr_gpu_addr = remote->rb_gpu_addr + rb_size *
>>>> ualink_accel_id(adev);
>>>> +        wptr_gpu_addr = remote->rb_gpu_addr + rb_size *
>>>> ualink_exp_accel_id(adev);
>>>>          for_each_set_bit(accel_id, remote->active_accel_bits,
>>>> AMDGPU_UALINK_ACCEL_MAX) {
>>>>            dev_dbg(adev->dev, "init for accel_id %u\n", accel_id);
>>>>    -        if (accel_id == ualink_accel_id(adev))
>>>> +        if (accel_id == ualink_exp_accel_id(adev))
>>>>                continue;
>>>>              rb_gpu_addr = remote->rb_gpu_addr + rb_size * accel_id;
>>>> @@ -5392,7 +5446,7 @@ static int amdgpu_ualink_metadata_init(struct
>>>> amdgpu_device *adev)
>>>>                                 ualink_tlb_wptr_offset(adev, accel_id);
>>>>              dev_dbg(adev->dev, "init from accel_id %u to accel_id %u,
>>>> rb_size 0x%x\n",
>>>> -            accel_id, ualink_accel_id(adev),
>>>> metadata[accel_id].rb_size);
>>>> +            accel_id, ualink_exp_accel_id(adev),
>>>> metadata[accel_id].rb_size);
>>>>            dev_dbg(adev->dev, "rb 0x%llx tlb rb 0x%llx\n",
>>>>                metadata[accel_id].ri_rb, 
>>>> metadata[accel_id].tlb_inv_rb);
>>>>            dev_dbg(adev->dev, "rb wptr at 0x%llx tlb wptr at 0x%llx\n",
>>>> @@ -5806,7 +5860,14 @@ static void amdgpu_ualink_get_wb_addr(struct
>>>> amdgpu_device *adev,
>>>>                          u64 *wb_npa, u32 type)
>>>>    {
>>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>>> -    u32 accel_id = ualink_accel_id(adev);
>>>> +    /* The GPUID encoded in the write-back NPA address is the
>>>> routing target
>>>> +     * the remote writes the rptr back to, i.e. THIS GPU. It must be
>>>> the
>>>> +     * physically-owned accel_id: outside loopback that is our own
>>>> accel_id,
>>>> +     * and in loopback it is the exporter (the only id the single
>>>> GPU owns
>>>> +     * and can receive on). The importer is only a logical source id
>>>> for the
>>>> +     * forward path and must never appear as an NPA GPUID.
>>>> +     */
>>>> +    u32 accel_id = ualink_exp_accel_id(adev);
>>>>        u32 rb_size = AMDGPU_GPU_PAGE_ALIGN(2 * AMDGPU_UALINK_RB_SIZE);
>>>>        uintptr_t wb;
>>>>        u64 npa;
>>>> @@ -5981,19 +6042,19 @@ static int
>>>> amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>>>>                               &peer->shootdown, &tlb_wptr);
>>>>        if (r) {
>>>>            dev_dbg(adev->dev, "accel_id %u read remote %u wptr failed
>>>> %d\n",
>>>> -            ualink_accel_id(adev), remote_accel_id, r);
>>>> +            ualink_exp_accel_id(adev), remote_accel_id, r);
>>>>            return r;
>>>>        }
>>>>          if (peer->interrupt.wptr == int_wptr && peer->shootdown.wptr
>>>> == tlb_wptr) {
>>>>            dev_dbg(adev->dev, "accel_id %u wptr 0x%llx 0x%llx is same
>>>> as remote %u\n",
>>>> -            ualink_accel_id(adev), int_wptr, tlb_wptr,
>>>> remote_accel_id);
>>>> +            ualink_exp_accel_id(adev), int_wptr, tlb_wptr,
>>>> remote_accel_id);
>>>>            return 0;
>>>>        }
>>>>          dev_dbg(adev->dev,
>>>>            "accel_id %u wptr 0x%llx 0x%llx sync to remote %u wptr
>>>> 0x%llx 0x%llx\n",
>>>> -        ualink_accel_id(adev), peer->interrupt.wptr,
>>>> peer->shootdown.wptr,
>>>> +        ualink_exp_accel_id(adev), peer->interrupt.wptr,
>>>> peer->shootdown.wptr,
>>>>            remote_accel_id, int_wptr, tlb_wptr);
>>>>          amdgpu_ualink_get_wb_addr(adev, remote_accel_id, &int_wb, 
>>>> NULL,
>>>> @@ -6164,7 +6225,7 @@ static int
>>>> amdgpu_ualink_peer_remote_init(struct amdgpu_device *adev)
>>>>        struct amdgpu_ualink_peer *peer;
>>>>        struct amdgpu_ualink_ring *interrupt;
>>>>        struct amdgpu_ualink_ring *shootdown;
>>>> -    u32 src_accel_id = ualink_accel_id(adev);
>>>> +    u32 src_accel_id = ualink_exp_accel_id(adev);
>>> This is changed to ualink_imp_accel_id in patch 5. I think we should
>>> use the correct function here, where it's introduced.
>>>
>> Good catch. I made the change here and removed the change from patch 5.
>>>>        u32 dst_accel_id;
>>>>        u64 npa, flags;
>>>>        int r = 0;
>>>> @@ -6287,7 +6348,7 @@ static void
>>>> amdgpu_ualink_peer_remote_fini(struct amdgpu_device *adev)
>>>>    {
>>>>        u32 rb_pages = AMDGPU_UALINK_RB_SIZE >> AMDGPU_GPU_PAGE_SHIFT;
>>>>        struct amdgpu_ualink_remote *remote = to_remote(adev);
>>>> -    u32 src_accel_id = ualink_accel_id(adev);
>>>> +    u32 src_accel_id = ualink_exp_accel_id(adev);
>>> This is changed to ualink_imp_accel_id in patch 5. I think we should
>>> use the correct function here, where it's introduced.
>> Good catch. I made the change here and removed the change from patch 5.
>>
>> Regards,
>>
>> Mukul
>>
>>> Regards,
>>>    Felix
>>>
>>>
>>>>        u32 dst_accel_id;
>>>>        struct amdgpu_ualink_peer *peer;
>>>>        struct amdgpu_ualink_ring *ring;
>>>> @@ -6374,7 +6435,7 @@ int amdgpu_ualink_sw_init(struct amdgpu_device
>>>> *adev)
>>>>     */
>>>>    void amdgpu_ualink_sw_fini(struct amdgpu_device *adev)
>>>>    {
>>>> -    dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n",
>>>> ualink_accel_id(adev),
>>>> +    dev_dbg(adev->dev, "halt accel_id %u addr_mode %d\n",
>>>> ualink_exp_accel_id(adev),
>>>>            ualink_addr_mode(adev));
>>>>          adev->ualink.msg_ctl->send_halt(adev);
>>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>>> index 85c9a9634a4b..0b1e8dd38666 100644
>>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.h
>>>> @@ -97,6 +97,17 @@ enum AMDGPU_UALINK_PROTOCOL_MESSAGES {
>>>>    #define AMDGPU_UALINK_LIGHTWEIGHT_TLB_SHOOTDOWN        0x1
>>>>    #define AMDGPU_UALINK_HEAVYWEIGHT_TLB_SHOOTDOWN        0x2
>>>>    +/* UALink loopback support.
>>>> + *
>>>> + * In loopback mode a single GPU loops UALink requests back to
>>>> itself for
>>>> + * manufacturing testing. The GPU exposes two fixed accelerator IDs:
>>>> it acts
>>>> + * as the exporter on one and the importer on the other. Loopback is
>>>> enabled
>>>> + * with the amdgpu.ualink_loopback module parameter.
>>>> + */
>>>> +/* Fixed accelerator IDs used by a loopback GPU. */
>>>> +#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID        1
>>>> +#define AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID        0
>>>> +
>>>>    enum amdgpu_ualink_conn_state {
>>>>        AMDGPU_UALINK_CONN_NOT_READY            = 0,
>>>>        AMDGPU_UALINK_CONN_IN_PROGRESS            = 1,
>>>> @@ -319,6 +330,21 @@ struct amdgpu_ualink_msg_ctl {
>>>>        int (*send_halt)(struct amdgpu_device *adev);
>>>>    };
>>>>    +/* UALink loopback mode state.
>>>> + *
>>>> + * When @enabled is true this single GPU acts as both exporter
>>>> + * (@exporter_accel_id) and importer (@importer_accel_id). There is
>>>> no second
>>>> + * physical GPU, so exporter->importer messages cannot be delivered
>>>> by the
>>>> + * (single) MP nHT and are instead emulated in SW by feeding them
>>>> back into
>>>> + * our own IRQ handler via @wq.
>>>> + */
>>>> +struct amdgpu_ualink_loopback {
>>>> +    bool enabled;
>>>> +    u32 exporter_accel_id;
>>>> +    u32 importer_accel_id;
>>>> +    struct workqueue_struct *wq;
>>>> +};
>>>> +
>>>>    struct amdgpu_ualink_mgr {
>>>>        u64 npa_size;
>>>>        u32 psp_if_ver;
>>>> @@ -333,6 +359,9 @@ struct amdgpu_ualink_mgr {
>>>>        /* For remote interrupt and shootdown */
>>>>        struct amdgpu_ualink_remote *remote;
>>>>    +    /* UALink loopback mode state (see struct
>>>> amdgpu_ualink_loopback) */
>>>> +    struct amdgpu_ualink_loopback loopback;
>>>> +
>>>>        /* handle irq from ualink client of remote GPUs */
>>>>        struct amdgpu_irq_src irq;


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender
  2026-10-27 19:54 ` [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender Mukul Joshi
  2026-10-27 21:38   ` Felix Kuehling
@ 2026-10-28  6:23   ` Lazar, Lijo
  2026-10-28 13:53     ` Mukul Joshi
  1 sibling, 1 reply; 25+ messages in thread
From: Lazar, Lijo @ 2026-10-28  6:23 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang



On 28-Oct-26 1:24 AM, Mukul Joshi wrote:
> In UALink loopback mode a single GPU acts as both the exporter and the
> importer, so the sender accel ID of a message depends on its direction.
> 
> - Send HELLO as the opposite role of the destination: a message to the
>    exporter is sent as the importer and vice versa. Outside loopback
>    this is still the GPU's own accel ID.
> 
> - The MP nHT stamps the IH cookie of every HW-delivered message with
>    the GPU ID of the single physical station, the exporter. The only HW
>    path is importer -> exporter, so attribute these messages to the
>    importer. SW-emulated exporter -> importer messages carry no IH ring
>    and already name the exporter as the sender.
> 
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
> ---
> v1->v2:
> - Use ualink_exp_accel_id/ualink_imp_accel_id() wrappers in
>    amdgpu_ualink_process_irq(). (Lijo)
> 
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 32 +++++++++++++++++++++-
>   1 file changed, 31 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index 6c70be7badec..96a6023f51c5 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -1090,6 +1090,23 @@ static inline u32 ualink_imp_accel_id(struct amdgpu_device *adev)
>   	return adev->ualink.info->ppod.accel_id;
>   }
>   
> +/*
> + * amdgpu_ualink_local_sender_accel_id - sender accel_id for an outgoing message
> + *
> + * Normally a GPU sends messages as its own (single) accelerator. In loopback
> + * mode the same GPU plays both roles, so the sender identity depends on the
> + * destination: a message to the exporter is sent as the importer and vice
> + * versa. Outside loopback both roles collapse to the GPU's own accel_id.
> + */
> +static u32 amdgpu_ualink_local_sender_accel_id(struct amdgpu_device *adev,
> +					       u32 remote_acc_id)
> +{
> +	if (remote_acc_id == ualink_exp_accel_id(adev))

+#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID		1

1 is a possible id in a regular vpod config also. I think a check of 
loopback mode also is required here.

Thanks,
Lijo

> +		return ualink_imp_accel_id(adev);
> +
> +	return ualink_exp_accel_id(adev);
> +}
> +
>   static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>   					 u32 accel_id)
>   {
> @@ -2238,7 +2255,10 @@ static int amdgpu_ualink_send_hello_msg(struct amdgpu_device *adev,
>   
>   	dw0 = AMDGPU_UALINK_HELLO_MSG;
>   	dw0 |= (remote_acc_id << AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT);
> -	dw0 |= (adev->ualink.info->ppod.accel_id <<
> +	/* In loopback the sender is the opposite role of the destination, so
> +	 * the exporter side tracks the connection under the importer accel_id.
> +	 */
> +	dw0 |= (amdgpu_ualink_local_sender_accel_id(adev, remote_acc_id) <<
>   		AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT);
>   
>   	dev_dbg(adev->dev, "SEND HELLO: HELLO message to remote AccId:%u\n",
> @@ -6548,6 +6568,16 @@ static int amdgpu_ualink_process_irq(struct amdgpu_device *adev,
>   		return handled;
>   	}
>   
> +	/* UALink loopback: the MP nHT stamps every HW-delivered message with
> +	 * the GPU ID of the single physical station, the exporter. The only
> +	 * HW path is importer -> exporter, so the real sender is the importer.
> +	 * SW-emulated exporter -> importer messages carry no IH ring and
> +	 * already name the exporter as sender.
> +	 */
> +	if (adev->ualink.loopback.enabled && entry->ih &&
> +	    src_acc_id == ualink_exp_accel_id(adev))
> +		src_acc_id = ualink_imp_accel_id(adev);
> +
>   	msg_type = dw0 & AMDGPU_UALINK_MESSAGE_HEADER_MASK;
>   	local_acc_id = adev->ualink.info->ppod.accel_id;
>   	dev_dbg(adev->dev, "Got MSG: remote acc_id %u msg_type %u\n",


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode
  2026-10-27 19:54 ` [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode Mukul Joshi
  2026-10-27 21:45   ` Felix Kuehling
@ 2026-10-28  6:33   ` Lazar, Lijo
  1 sibling, 0 replies; 25+ messages in thread
From: Lazar, Lijo @ 2026-10-28  6:33 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang



On 28-Oct-26 1:24 AM, Mukul Joshi wrote:
> In UALink loopback mode a single GPU acts as both the exporter (accel
> ID 1) and the importer (accel ID 0). This lets the existing tests export
> a BO and import it on the same device, traversing the UALink HW
> pipeline.
> 
> Add amdgpu_ualink_loopback_detect(), the single place that decides
> whether loopback is enabled. It runs right after the UALink info is
> queried from PSP, before any per-peer setup. When loopback is enabled:
> 
> - There is no user-space agent to configure the pod, so synthesize the
>    pPod/vPod configuration with both accel IDs active.
> 
> - Activate the device directly instead of through the cross-GPU vpod
>    activation path, which needs peer GPUs and a user-space vpod commit.
> 
> The mechanism that requests loopback mode is added separately, so
> amdgpu_ualink_loopback_requested() returns false for now. The normal
> activation path is unchanged when loopback is not enabled.
> 
> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
> ---
>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 128 ++++++++++++++++++++-
>   1 file changed, 123 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> index e6272446ff4f..14b9c05a0f0f 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
> @@ -63,6 +63,7 @@ static int amdgpu_ualink_ring_resync_ptrs(struct amdgpu_device *adev,
>   static int amdgpu_ualink_loopback_self_interrupt(struct amdgpu_device *adev,
>   						 u32 sender_accel_id, u32 dw0,
>   						 u32 dw1, u32 dw2, u32 dw3);
> +static void activate_accelerator(struct amdgpu_device *adev);
>   
>   #define STRIP_NPA(addr)						\
>   	(((u64)(addr) & ~AMDGPU_UALINK_NPA_ADDR_GPUID_MASK))
> @@ -93,19 +94,130 @@ static int amdgpu_ualink_drm_client_create(struct amdgpu_device *adev)
>   }
>   
>   /*
> - * amdgpu_ualink_loopback_init - set up UALink loopback mode
> + * amdgpu_ualink_loopback_requested - check whether UALink loopback is requested
>    *
> - * Called once loopback mode has been detected. Allocates the ordered
> - * workqueue used to emulate exporter->importer interrupts in SW.
> + * There is no mechanism to request loopback mode yet, so it stays disabled.
> + */
> +static bool amdgpu_ualink_loopback_requested(struct amdgpu_device *adev)
> +{
> +	return false;
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_detect - detect UALink loopback mode
> + *
> + * This is the only place that decides whether loopback mode is enabled. It
> + * must run before any per-peer setup, since enabling loopback changes the
> + * accelerator IDs returned by ualink_exp_accel_id() and ualink_imp_accel_id().
> + */
> +static void amdgpu_ualink_loopback_detect(struct amdgpu_device *adev)
> +{
> +	if (!amdgpu_ualink_loopback_requested(adev))
> +		return;
> +
> +	adev->ualink.loopback.enabled = true;
> +	adev->ualink.loopback.exporter_accel_id =
> +		AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID;
> +	adev->ualink.loopback.importer_accel_id =
> +		AMDGPU_UALINK_LOOPBACK_IMPORTER_ACCID;

A better place for this is amdgpu_ualink_loopback_populate_info which 
fills all the static info for loopback mode.

Then you may deprecate requested() also as detect() is good enough to 
find whether loopback mode is requested and sets loopback enabled.

Thanks,
Lijo
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_populate_info - synthesize pPod/vPod config for loopback
> + *
> + * In loopback there is no user-space agent (AIFM / xncmdclient) writing the
> + * UALink sysfs nodes to configure the pod, so populate adev->ualink.info
> + * directly. The layout is fixed and known: a single GPU alone in its own
> + * physical pod, presented as accelerator ID 1 (the exporter) and also owning
> + * accelerator ID 0 (the importer). The values are made self-consistent so
> + * check_vpod_info() would pass and amdgpu_ualink_metadata_init() programs the
> + * importer's ring buffers / GART NPA mappings / MP nHT receive metadata.
> + */
> +static void amdgpu_ualink_loopback_populate_info(struct amdgpu_device *adev)
> +{
> +	struct amdgpu_ualink_info *info = adev->ualink.info;
> +	u32 exporter = adev->ualink.loopback.exporter_accel_id; /* 1 */
> +	u32 importer = adev->ualink.loopback.importer_accel_id; /* 0 */
> +
> +	/* Physical pod: this GPU is presented as the exporter accel ID in its
> +	 * own pod. ppod.size must be large enough to contain both the exporter
> +	 * and importer accel IDs. ppod.id must be unique within the system
> +	 * (matters for 1P4G, where every loopback GPU is its own physical pod).
> +	 */
> +	info->ppod.accel_id = exporter;
> +	info->ppod.size = max(exporter, importer) + 1;	/* == 2 */
> +	uuid_gen(&info->ppod.id);
> +
> +	/* Virtual pod: mark BOTH the exporter and the importer active so the
> +	 * per-peer setup (amdgpu_ualink_metadata_init()) programs the importer's
> +	 * resources for the importer accel ID. Preserve the address mode queried
> +	 * from firmware if it is valid (keeps source-identification HW working),
> +	 * otherwise default to source-aliasing (MI450).
> +	 */
> +	info->vpod.id = 0;
> +	info->vpod.size = 2;
> +	if (info->vpod.addr_mode >= AMDGPU_UALINK_ADDR_MODE_MAX)
> +		info->vpod.addr_mode = AMDGPU_UALINK_ADDR_MODE_SOURCE_ALIAS;
> +	bitmap_zero(info->vpod.active_accel_bits, AMDGPU_UALINK_ACCEL_MAX);
> +	set_bit(exporter, info->vpod.active_accel_bits);
> +	set_bit(importer, info->vpod.active_accel_bits);
> +
> +	/* Only the exporter (this GPU's own accel ID) is a local accelerator;
> +	 * the importer is reached through the (loopback) remote/NPA path.
> +	 */
> +	info->n_local_accels = 1;
> +	info->local_accels[0] = exporter;
> +
> +	/* Mark the pod vpod-configured so the activation path proceeds. */
> +	info->accel_state = AMDGPU_UALINK_ACCEL_STATE_VPOD_CONFIGURED;
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_activate - bring up the UALink manager for loopback
> + *
> + * The normal activation is driven by the sysfs vpod_config/commit path via
> + * activate_local_vpod(), which derives the local accelerators from peer GPUs
> + * and runs a cross-GPU integrity check. Neither applies in loopback (the
> + * importer is not a separate GPU), so activate this device directly.
> + */
> +static int amdgpu_ualink_loopback_activate(struct amdgpu_device *adev)
> +{
> +	mutex_lock(&mgpu_info.mutex);
> +	activate_accelerator(adev);
> +	mutex_unlock(&mgpu_info.mutex);
> +
> +	if (adev->ualink.info->accel_state < AMDGPU_UALINK_ACCEL_STATE_ACTIVE) {
> +		dev_err(adev->dev, "UALink loopback activation did not reach ACTIVE\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * amdgpu_ualink_loopback_init - set up and activate UALink loopback mode
> + *
> + * Called once loopback mode has been detected, after the initial pod/vpod
> + * configuration has been queried into adev->ualink.info. Synthesizes the pod
> + * configuration (there is no user-space agent to write the UALink sysfs nodes
> + * in loopback), allocates the ordered workqueue used to emulate
> + * exporter->importer interrupts in SW and activates the device.
>    */
>   static int amdgpu_ualink_loopback_init(struct amdgpu_device *adev)
>   {
> +	amdgpu_ualink_loopback_populate_info(adev);
> +
>   	adev->ualink.loopback.wq =
>   		alloc_ordered_workqueue("amdgpu_ualink_loopback", 0);
>   	if (!adev->ualink.loopback.wq)
>   		return -ENOMEM;
>   
> -	return 0;
> +	dev_info(adev->dev,
> +		 "UALink loopback enabled (exporter accel %u, importer accel %u)\n",
> +		 adev->ualink.loopback.exporter_accel_id,
> +		 adev->ualink.loopback.importer_accel_id);
> +
> +	return amdgpu_ualink_loopback_activate(adev);
>   }
>   
>   static void amdgpu_ualink_object_fini(struct amdgpu_device *adev)
> @@ -495,10 +607,16 @@ int ualink_ip_late_init(struct amdgpu_ip_block *ip_block)
>   	if (r)
>   		return r;
>   
> +	amdgpu_ualink_loopback_detect(adev);
> +
>   	scoped_guard(mutex, &mgpu_info.mutex) {
>   		amdgpu_ualink_info_set_accel_state(adev, adev->ualink.info,
>   						   cfg_state);
> -		__amdgpu_ualink_activate_vpod_locked(adev);
> +		/* Loopback has no peer GPUs and no user-space vpod commit, so it
> +		 * cannot use the normal cross-GPU activation path.
> +		 */
> +		if (!adev->ualink.loopback.enabled)
> +			__amdgpu_ualink_activate_vpod_locked(adev);
>   	}
>   
>   	if (adev->ualink.loopback.enabled) {


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender
  2026-10-28  6:23   ` Lazar, Lijo
@ 2026-10-28 13:53     ` Mukul Joshi
  2026-10-28 14:00       ` Lazar, Lijo
  0 siblings, 1 reply; 25+ messages in thread
From: Mukul Joshi @ 2026-10-28 13:53 UTC (permalink / raw)
  To: Lazar, Lijo, amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang


On 10/28/2026 2:23 AM, Lazar, Lijo wrote:
>
>
> On 28-Oct-26 1:24 AM, Mukul Joshi wrote:
>> In UALink loopback mode a single GPU acts as both the exporter and the
>> importer, so the sender accel ID of a message depends on its direction.
>>
>> - Send HELLO as the opposite role of the destination: a message to the
>>    exporter is sent as the importer and vice versa. Outside loopback
>>    this is still the GPU's own accel ID.
>>
>> - The MP nHT stamps the IH cookie of every HW-delivered message with
>>    the GPU ID of the single physical station, the exporter. The only HW
>>    path is importer -> exporter, so attribute these messages to the
>>    importer. SW-emulated exporter -> importer messages carry no IH ring
>>    and already name the exporter as the sender.
>>
>> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
>> ---
>> v1->v2:
>> - Use ualink_exp_accel_id/ualink_imp_accel_id() wrappers in
>>    amdgpu_ualink_process_irq(). (Lijo)
>>
>>   drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 32 +++++++++++++++++++++-
>>   1 file changed, 31 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> index 6c70be7badec..96a6023f51c5 100644
>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>> @@ -1090,6 +1090,23 @@ static inline u32 ualink_imp_accel_id(struct
>> amdgpu_device *adev)
>>       return adev->ualink.info->ppod.accel_id;
>>   }
>>   +/*
>> + * amdgpu_ualink_local_sender_accel_id - sender accel_id for an
>> outgoing message
>> + *
>> + * Normally a GPU sends messages as its own (single) accelerator. In
>> loopback
>> + * mode the same GPU plays both roles, so the sender identity
>> depends on the
>> + * destination: a message to the exporter is sent as the importer
>> and vice
>> + * versa. Outside loopback both roles collapse to the GPU's own
>> accel_id.
>> + */
>> +static u32 amdgpu_ualink_local_sender_accel_id(struct amdgpu_device
>> *adev,
>> +                           u32 remote_acc_id)
>> +{
>> +    if (remote_acc_id == ualink_exp_accel_id(adev))
>
> +#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID        1
>
> 1 is a possible id in a regular vpod config also. I think a check of
> loopback mode also is required here.

We check loopback mode inside the helper, ualink_exp_accel_id().

Thanks,

Mukul

>
> Thanks,
> Lijo
>
>> +        return ualink_imp_accel_id(adev);
>> +
>> +    return ualink_exp_accel_id(adev);
>> +}
>> +
>>   static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>>                        u32 accel_id)
>>   {
>> @@ -2238,7 +2255,10 @@ static int amdgpu_ualink_send_hello_msg(struct
>> amdgpu_device *adev,
>>         dw0 = AMDGPU_UALINK_HELLO_MSG;
>>       dw0 |= (remote_acc_id <<
>> AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT);
>> -    dw0 |= (adev->ualink.info->ppod.accel_id <<
>> +    /* In loopback the sender is the opposite role of the
>> destination, so
>> +     * the exporter side tracks the connection under the importer
>> accel_id.
>> +     */
>> +    dw0 |= (amdgpu_ualink_local_sender_accel_id(adev, remote_acc_id) <<
>>           AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT);
>>         dev_dbg(adev->dev, "SEND HELLO: HELLO message to remote
>> AccId:%u\n",
>> @@ -6548,6 +6568,16 @@ static int amdgpu_ualink_process_irq(struct
>> amdgpu_device *adev,
>>           return handled;
>>       }
>>   +    /* UALink loopback: the MP nHT stamps every HW-delivered
>> message with
>> +     * the GPU ID of the single physical station, the exporter. The
>> only
>> +     * HW path is importer -> exporter, so the real sender is the
>> importer.
>> +     * SW-emulated exporter -> importer messages carry no IH ring and
>> +     * already name the exporter as sender.
>> +     */
>> +    if (adev->ualink.loopback.enabled && entry->ih &&
>> +        src_acc_id == ualink_exp_accel_id(adev))
>> +        src_acc_id = ualink_imp_accel_id(adev);
>> +
>>       msg_type = dw0 & AMDGPU_UALINK_MESSAGE_HEADER_MASK;
>>       local_acc_id = adev->ualink.info->ppod.accel_id;
>>       dev_dbg(adev->dev, "Got MSG: remote acc_id %u msg_type %u\n",
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender
  2026-10-28 13:53     ` Mukul Joshi
@ 2026-10-28 14:00       ` Lazar, Lijo
  2026-10-28 14:43         ` Mukul Joshi
  0 siblings, 1 reply; 25+ messages in thread
From: Lazar, Lijo @ 2026-10-28 14:00 UTC (permalink / raw)
  To: Mukul Joshi, amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang



On 28-Oct-26 7:23 PM, Mukul Joshi wrote:
> 
> On 10/28/2026 2:23 AM, Lazar, Lijo wrote:
>>
>>
>> On 28-Oct-26 1:24 AM, Mukul Joshi wrote:
>>> In UALink loopback mode a single GPU acts as both the exporter and the
>>> importer, so the sender accel ID of a message depends on its direction.
>>>
>>> - Send HELLO as the opposite role of the destination: a message to the
>>>     exporter is sent as the importer and vice versa. Outside loopback
>>>     this is still the GPU's own accel ID.
>>>
>>> - The MP nHT stamps the IH cookie of every HW-delivered message with
>>>     the GPU ID of the single physical station, the exporter. The only HW
>>>     path is importer -> exporter, so attribute these messages to the
>>>     importer. SW-emulated exporter -> importer messages carry no IH ring
>>>     and already name the exporter as the sender.
>>>
>>> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
>>> ---
>>> v1->v2:
>>> - Use ualink_exp_accel_id/ualink_imp_accel_id() wrappers in
>>>     amdgpu_ualink_process_irq(). (Lijo)
>>>
>>>    drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 32 +++++++++++++++++++++-
>>>    1 file changed, 31 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> index 6c70be7badec..96a6023f51c5 100644
>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>> @@ -1090,6 +1090,23 @@ static inline u32 ualink_imp_accel_id(struct
>>> amdgpu_device *adev)
>>>        return adev->ualink.info->ppod.accel_id;
>>>    }
>>>    +/*
>>> + * amdgpu_ualink_local_sender_accel_id - sender accel_id for an
>>> outgoing message
>>> + *
>>> + * Normally a GPU sends messages as its own (single) accelerator. In
>>> loopback
>>> + * mode the same GPU plays both roles, so the sender identity
>>> depends on the
>>> + * destination: a message to the exporter is sent as the importer
>>> and vice
>>> + * versa. Outside loopback both roles collapse to the GPU's own
>>> accel_id.
>>> + */
>>> +static u32 amdgpu_ualink_local_sender_accel_id(struct amdgpu_device
>>> *adev,
>>> +                           u32 remote_acc_id)
>>> +{
>>> +    if (remote_acc_id == ualink_exp_accel_id(adev))
>>
>> +#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID        1
>>
>> 1 is a possible id in a regular vpod config also. I think a check of
>> loopback mode also is required here.
> 
> We check loopback mode inside the helper, ualink_exp_accel_id().
> 

Thanks for clarifying. Adding a check may be a bit easier to read.

if (!loopback_mode)
	return adev->ualink.info->ppod.accel_id;

Thanks,
Lijo

> Thanks,
> 
> Mukul
> 
>>
>> Thanks,
>> Lijo
>>
>>> +        return ualink_imp_accel_id(adev);
>>> +
>>> +    return ualink_exp_accel_id(adev);
>>> +}
>>> +
>>>    static bool amdgpu_ualink_is_local_accel(struct amdgpu_device *adev,
>>>                         u32 accel_id)
>>>    {
>>> @@ -2238,7 +2255,10 @@ static int amdgpu_ualink_send_hello_msg(struct
>>> amdgpu_device *adev,
>>>          dw0 = AMDGPU_UALINK_HELLO_MSG;
>>>        dw0 |= (remote_acc_id <<
>>> AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT);
>>> -    dw0 |= (adev->ualink.info->ppod.accel_id <<
>>> +    /* In loopback the sender is the opposite role of the
>>> destination, so
>>> +     * the exporter side tracks the connection under the importer
>>> accel_id.
>>> +     */
>>> +    dw0 |= (amdgpu_ualink_local_sender_accel_id(adev, remote_acc_id) <<
>>>            AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT);
>>>          dev_dbg(adev->dev, "SEND HELLO: HELLO message to remote
>>> AccId:%u\n",
>>> @@ -6548,6 +6568,16 @@ static int amdgpu_ualink_process_irq(struct
>>> amdgpu_device *adev,
>>>            return handled;
>>>        }
>>>    +    /* UALink loopback: the MP nHT stamps every HW-delivered
>>> message with
>>> +     * the GPU ID of the single physical station, the exporter. The
>>> only
>>> +     * HW path is importer -> exporter, so the real sender is the
>>> importer.
>>> +     * SW-emulated exporter -> importer messages carry no IH ring and
>>> +     * already name the exporter as sender.
>>> +     */
>>> +    if (adev->ualink.loopback.enabled && entry->ih &&
>>> +        src_acc_id == ualink_exp_accel_id(adev))
>>> +        src_acc_id = ualink_imp_accel_id(adev);
>>> +
>>>        msg_type = dw0 & AMDGPU_UALINK_MESSAGE_HEADER_MASK;
>>>        local_acc_id = adev->ualink.info->ppod.accel_id;
>>>        dev_dbg(adev->dev, "Got MSG: remote acc_id %u msg_type %u\n",
>>


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender
  2026-10-28 14:00       ` Lazar, Lijo
@ 2026-10-28 14:43         ` Mukul Joshi
  0 siblings, 0 replies; 25+ messages in thread
From: Mukul Joshi @ 2026-10-28 14:43 UTC (permalink / raw)
  To: Lazar, Lijo, amd-gfx; +Cc: Felix.Kuehling, Harish.Kasiviswanathan, philip.yang


On 10/28/2026 10:00 AM, Lazar, Lijo wrote:
>
>
> On 28-Oct-26 7:23 PM, Mukul Joshi wrote:
>>
>> On 10/28/2026 2:23 AM, Lazar, Lijo wrote:
>>>
>>>
>>> On 28-Oct-26 1:24 AM, Mukul Joshi wrote:
>>>> In UALink loopback mode a single GPU acts as both the exporter and the
>>>> importer, so the sender accel ID of a message depends on its
>>>> direction.
>>>>
>>>> - Send HELLO as the opposite role of the destination: a message to the
>>>>     exporter is sent as the importer and vice versa. Outside loopback
>>>>     this is still the GPU's own accel ID.
>>>>
>>>> - The MP nHT stamps the IH cookie of every HW-delivered message with
>>>>     the GPU ID of the single physical station, the exporter. The
>>>> only HW
>>>>     path is importer -> exporter, so attribute these messages to the
>>>>     importer. SW-emulated exporter -> importer messages carry no IH
>>>> ring
>>>>     and already name the exporter as the sender.
>>>>
>>>> Signed-off-by: Mukul Joshi <mukul.joshi@amd.com>
>>>> ---
>>>> v1->v2:
>>>> - Use ualink_exp_accel_id/ualink_imp_accel_id() wrappers in
>>>>     amdgpu_ualink_process_irq(). (Lijo)
>>>>
>>>>    drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c | 32
>>>> +++++++++++++++++++++-
>>>>    1 file changed, 31 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> index 6c70be7badec..96a6023f51c5 100644
>>>> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ualink.c
>>>> @@ -1090,6 +1090,23 @@ static inline u32 ualink_imp_accel_id(struct
>>>> amdgpu_device *adev)
>>>>        return adev->ualink.info->ppod.accel_id;
>>>>    }
>>>>    +/*
>>>> + * amdgpu_ualink_local_sender_accel_id - sender accel_id for an
>>>> outgoing message
>>>> + *
>>>> + * Normally a GPU sends messages as its own (single) accelerator. In
>>>> loopback
>>>> + * mode the same GPU plays both roles, so the sender identity
>>>> depends on the
>>>> + * destination: a message to the exporter is sent as the importer
>>>> and vice
>>>> + * versa. Outside loopback both roles collapse to the GPU's own
>>>> accel_id.
>>>> + */
>>>> +static u32 amdgpu_ualink_local_sender_accel_id(struct amdgpu_device
>>>> *adev,
>>>> +                           u32 remote_acc_id)
>>>> +{
>>>> +    if (remote_acc_id == ualink_exp_accel_id(adev))
>>>
>>> +#define AMDGPU_UALINK_LOOPBACK_EXPORTER_ACCID        1
>>>
>>> 1 is a possible id in a regular vpod config also. I think a check of
>>> loopback mode also is required here.
>>
>> We check loopback mode inside the helper, ualink_exp_accel_id().
>>
>
> Thanks for clarifying. Adding a check may be a bit easier to read.
>
> if (!loopback_mode)
>     return adev->ualink.info->ppod.accel_id;
>
The wrappers are handling all this in one place, because we need same
check for imp_accel_id as well.

So, better to keep them in one place I think.


Thanks,

Mukul

> Thanks,
> Lijo
>
>> Thanks,
>>
>> Mukul
>>
>>>
>>> Thanks,
>>> Lijo
>>>
>>>> +        return ualink_imp_accel_id(adev);
>>>> +
>>>> +    return ualink_exp_accel_id(adev);
>>>> +}
>>>> +
>>>>    static bool amdgpu_ualink_is_local_accel(struct amdgpu_device
>>>> *adev,
>>>>                         u32 accel_id)
>>>>    {
>>>> @@ -2238,7 +2255,10 @@ static int amdgpu_ualink_send_hello_msg(struct
>>>> amdgpu_device *adev,
>>>>          dw0 = AMDGPU_UALINK_HELLO_MSG;
>>>>        dw0 |= (remote_acc_id <<
>>>> AMDGPU_UALINK_HELLO_MSG_RECV_ACCID_SHIFT);
>>>> -    dw0 |= (adev->ualink.info->ppod.accel_id <<
>>>> +    /* In loopback the sender is the opposite role of the
>>>> destination, so
>>>> +     * the exporter side tracks the connection under the importer
>>>> accel_id.
>>>> +     */
>>>> +    dw0 |= (amdgpu_ualink_local_sender_accel_id(adev,
>>>> remote_acc_id) <<
>>>>            AMDGPU_UALINK_HELLO_MSG_SENDER_ACCID_SHIFT);
>>>>          dev_dbg(adev->dev, "SEND HELLO: HELLO message to remote
>>>> AccId:%u\n",
>>>> @@ -6548,6 +6568,16 @@ static int amdgpu_ualink_process_irq(struct
>>>> amdgpu_device *adev,
>>>>            return handled;
>>>>        }
>>>>    +    /* UALink loopback: the MP nHT stamps every HW-delivered
>>>> message with
>>>> +     * the GPU ID of the single physical station, the exporter. The
>>>> only
>>>> +     * HW path is importer -> exporter, so the real sender is the
>>>> importer.
>>>> +     * SW-emulated exporter -> importer messages carry no IH ring and
>>>> +     * already name the exporter as sender.
>>>> +     */
>>>> +    if (adev->ualink.loopback.enabled && entry->ih &&
>>>> +        src_acc_id == ualink_exp_accel_id(adev))
>>>> +        src_acc_id = ualink_imp_accel_id(adev);
>>>> +
>>>>        msg_type = dw0 & AMDGPU_UALINK_MESSAGE_HEADER_MASK;
>>>>        local_acc_id = adev->ualink.info->ppod.accel_id;
>>>>        dev_dbg(adev->dev, "Got MSG: remote acc_id %u msg_type %u\n",
>>>
>

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2026-10-28 14:44 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 19:54 [PATCHv2 0/8] Add UALink Loopback support Mukul Joshi
2026-10-27 19:54 ` [PATCHv2 1/8] drm/amdgpu: Add UALink loopback infrastructure Mukul Joshi
2026-10-27 20:22   ` Felix Kuehling
2026-10-27 20:45     ` Mukul Joshi
2026-10-27 20:56       ` Felix Kuehling
2026-10-28  6:06         ` Lazar, Lijo
2026-10-27 19:54 ` [PATCHv2 2/8] drm/amdgpu: Support VMID 0 GPUVA completion WB address for UALink Mukul Joshi
2026-10-27 21:23   ` Felix Kuehling
2026-10-27 19:54 ` [PATCHv2 3/8] drm/amdgpu: Emulate exporter->importer UALink messages in loopback Mukul Joshi
2026-10-27 21:34   ` Felix Kuehling
2026-10-27 19:54 ` [PATCHv2 4/8] drm/amdgpu: Attribute UALink loopback messages to the correct sender Mukul Joshi
2026-10-27 21:38   ` Felix Kuehling
2026-10-28  6:23   ` Lazar, Lijo
2026-10-28 13:53     ` Mukul Joshi
2026-10-28 14:00       ` Lazar, Lijo
2026-10-28 14:43         ` Mukul Joshi
2026-10-27 19:54 ` [PATCHv2 5/8] drm/amdgpu: Set up UALink rings and metadata for loopback Mukul Joshi
2026-10-27 20:20   ` Felix Kuehling
2026-10-27 19:54 ` [PATCHv2 6/8] drm/amdgpu: Enable UALink loopback mode Mukul Joshi
2026-10-27 21:45   ` Felix Kuehling
2026-10-28  6:33   ` Lazar, Lijo
2026-10-27 19:54 ` [PATCHv2 7/8] drm/amdgpu: Reject config updates in " Mukul Joshi
2026-10-27 21:06   ` Felix Kuehling
2026-10-27 19:54 ` [PATCHv2 8/8] drm/amdgpu: Temporarily enable UALink loopback via module parameter Mukul Joshi
2026-10-27 21:47   ` Felix Kuehling

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.