diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index 68cf34e21..e1bbbdcaa 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -947,7 +947,7 @@ static struct aie2_exec_msg_ops npu_exec_message_ops = { static int aie2_init_exec_req(void *req, struct amdxdna_gem_obj *cmd_abo, size_t *size, u32 *msg_op) { - struct amdxdna_dev *xdna = cmd_abo->client->xdna; + struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(cmd_abo)->dev); int ret; u32 op; @@ -981,7 +981,7 @@ static int aie2_cmdlist_fill_slot(void *slot, struct amdxdna_gem_obj *cmd_abo, size_t *size, u32 *cmd_op) { - struct amdxdna_dev *xdna = cmd_abo->client->xdna; + struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(cmd_abo)->dev); int ret; u32 op; diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index 8580219b6..ebb50b6c7 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -214,6 +214,45 @@ int amdxdna_cmd_set_error(struct amdxdna_gem_obj *abo, return 0; } +#define AMDXDNA_MIN_HWCTX_ID (AMDXDNA_INVALID_CTX_HANDLE + 1) + +/* + * Allocate a device-wide-unique hwctx ID cyclically. + * + * The ID must be unique across the whole device (not just per client) so a + * context is identified unambiguously even when one process opens the device + * more than once. Allocation is therefore done from a device-wide IDA, while + * the id->hwctx map stays per-client (amdxdna_client.hwctx_xa) so lookups only + * ever see the caller's own contexts. + * + * The allocation must be cyclic. IDA has no cyclic variant (only xa_alloc_cyclic + * does), so keep an explicit @next_hwctxid cursor and search [cursor, MAX] then + * wrap to [MIN, MAX]. Cyclic (rather than IDA's default lowest-free) is required + * because the virtio-gpu guest shim derives its ring index from this ID by + * modulo and virtio-gpu exposes only 64 rings: not reusing a just-freed ID keeps + * a new context off a ring slot whose previous owner may not be fully torn down + * yet. The IDA has its own internal lock; @next_hwctxid is serialized by + * dev_lock, which the only caller (create) holds. + */ +static int amdxdna_hwctx_id_alloc(struct amdxdna_dev *xdna) +{ + int id; + + if (xdna->next_hwctxid < AMDXDNA_MIN_HWCTX_ID) + xdna->next_hwctxid = AMDXDNA_MIN_HWCTX_ID; + + id = ida_alloc_range(&xdna->hwctx_ida, xdna->next_hwctxid, + MAX_HWCTX_ID, GFP_KERNEL); + if (id == -ENOSPC) + id = ida_alloc_range(&xdna->hwctx_ida, AMDXDNA_MIN_HWCTX_ID, + MAX_HWCTX_ID, GFP_KERNEL); + if (id >= 0) + xdna->next_hwctxid = (id >= MAX_HWCTX_ID) ? + AMDXDNA_MIN_HWCTX_ID : id + 1; + + return id; +} + /* * This should be called in close() and remove(). DO NOT call in other syscalls. * This guarantee that when hwctx and resources will be released, if user @@ -221,14 +260,17 @@ int amdxdna_cmd_set_error(struct amdxdna_gem_obj *abo, */ void amdxdna_hwctx_remove_all(struct amdxdna_client *client) { + struct amdxdna_dev *xdna = client->xdna; struct amdxdna_hwctx *hwctx; unsigned long hwctx_id; amdxdna_for_each_hwctx(client, hwctx_id, hwctx) { - XDNA_DBG(client->xdna, "PID %d close HW context %d", - client->pid, hwctx->id); - xa_erase(&client->hwctx_xa, hwctx->id); + u32 id = hwctx->id; + + XDNA_DBG(xdna, "PID %d close HW context %d", client->pid, id); + xa_erase(&client->hwctx_xa, id); amdxdna_hwctx_destroy_rcu(hwctx, &client->hwctx_srcu); + ida_free(&xdna->hwctx_ida, id); } } @@ -283,13 +325,19 @@ int amdxdna_drm_create_hwctx_ioctl(struct drm_device *dev, void *data, struct dr goto fini_hwctx; } - ret = xa_alloc_cyclic(&client->hwctx_xa, &hwctx->id, hwctx, - XA_LIMIT(AMDXDNA_INVALID_CTX_HANDLE + 1, MAX_HWCTX_ID), - &client->next_hwctxid, GFP_KERNEL); + ret = amdxdna_hwctx_id_alloc(xdna); if (ret < 0) { XDNA_ERR(xdna, "Allocate hwctx ID failed, ret %d", ret); goto free_name; } + hwctx->id = ret; + + /* Publish into the per-client map; submit/wait/etc. look up here. */ + ret = xa_err(xa_store(&client->hwctx_xa, hwctx->id, hwctx, GFP_KERNEL)); + if (ret) { + XDNA_ERR(xdna, "Store hwctx %d failed, ret %d", hwctx->id, ret); + goto free_id; + } args->handle = hwctx->id; args->syncobj_handle = hwctx->syncobj_hdl; @@ -301,6 +349,8 @@ int amdxdna_drm_create_hwctx_ioctl(struct drm_device *dev, void *data, struct dr drm_dev_exit(idx); return 0; +free_id: + ida_free(&xdna->hwctx_ida, hwctx->id); free_name: kfree(hwctx->name); fini_hwctx: @@ -342,6 +392,7 @@ int amdxdna_drm_destroy_hwctx_ioctl(struct drm_device *dev, void *data, struct d * SRCU to synchronize with exec command ioctls. */ amdxdna_hwctx_destroy_rcu(hwctx, &client->hwctx_srcu); + ida_free(&xdna->hwctx_ida, args->handle); XDNA_DBG(xdna, "PID %d destroyed HW context %d", client->pid, args->handle); out: @@ -641,6 +692,11 @@ int amdxdna_cmd_submit(struct amdxdna_client *client, goto put_bos; } + /* + * The lookup is in the caller's own hwctx_xa, so it can only ever return + * this client's context; the per-client hwctx_srcu keeps it alive across + * the blocking submit (drained by amdxdna_hwctx_destroy_rcu()). + */ idx = srcu_read_lock(&client->hwctx_srcu); hwctx = xa_load(&client->hwctx_xa, hwctx_hdl); if (!hwctx) { @@ -749,12 +805,15 @@ int amdxdna_cmd_wait(struct amdxdna_client *client, u32 ctx_hdl, if (!xdna->dev_info->ops->cmd_wait) return -EOPNOTSUPP; - /* For locking concerns, see amdxdna_drm_exec_cmd_ioctl. */ + /* + * Per-client lookup + per-client srcu: the wait (possibly unbounded) holds + * only this client's srcu, so a parked waiter stalls only its own client's + * teardown. See amdxdna_drm_exec_cmd_ioctl for the broader locking rationale. + */ idx = srcu_read_lock(&client->hwctx_srcu); hwctx = xa_load(&client->hwctx_xa, ctx_hdl); if (!hwctx) { - XDNA_DBG(xdna, "PID %d failed to get ctx %d", - client->pid, ctx_hdl); + XDNA_DBG(xdna, "PID %d failed to get ctx %d", client->pid, ctx_hdl); ret = -EINVAL; goto unlock_ctx_srcu; } diff --git a/drivers/accel/amdxdna/amdxdna_gem.c b/drivers/accel/amdxdna/amdxdna_gem.c index 96fc74f6b..3f57c99c7 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.c +++ b/drivers/accel/amdxdna/amdxdna_gem.c @@ -218,6 +218,7 @@ amdxdna_gem_destroy_obj(struct amdxdna_gem_obj *abo) */ void *amdxdna_gem_vmap(struct amdxdna_gem_obj *abo) { + struct amdxdna_dev *xdna = to_xdna_dev(to_gobj(abo)->dev); struct iosys_map map = IOSYS_MAP_INIT_VADDR(NULL); int ret; @@ -230,7 +231,7 @@ void *amdxdna_gem_vmap(struct amdxdna_gem_obj *abo) if (!abo->mem.kva) { ret = drm_gem_vmap(to_gobj(abo), &map); if (ret) - XDNA_ERR(abo->client->xdna, "Vmap bo failed, ret %d", ret); + XDNA_ERR(xdna, "Vmap bo failed, ret %d", ret); else abo->mem.kva = map.vaddr; } @@ -374,7 +375,13 @@ static int amdxdna_hmm_register(struct amdxdna_gem_obj *abo, u32 nr_pages; int ret; - if (!amdxdna_pasid_on(abo->client)) + /* + * When PASID is off, amdxdna_gem_obj_open() called amdxdna_dma_map_bo() + * and mem.dma_addr is valid; use the DMA address directly and skip HMM. + * Avoid dereferencing abo->client which may be NULL (cleared in close()) + * while internal kernel references are still held. + */ + if (abo->mem.dma_addr != AMDXDNA_INVALID_ADDR) return 0; down_read(&xdna->notifier_lock); diff --git a/drivers/accel/amdxdna/amdxdna_gem.h b/drivers/accel/amdxdna/amdxdna_gem.h index 31b5e32a5..7f2af1f6d 100644 --- a/drivers/accel/amdxdna/amdxdna_gem.h +++ b/drivers/accel/amdxdna/amdxdna_gem.h @@ -88,12 +88,19 @@ u64 amdxdna_gem_dev_addr(struct amdxdna_gem_obj *abo); static inline u64 amdxdna_dev_bo_offset(struct amdxdna_gem_obj *abo) { - return amdxdna_gem_dev_addr(abo) - abo->client->xdna->dev_info->dev_mem_base; + return amdxdna_gem_dev_addr(abo) - to_xdna_dev(to_gobj(abo)->dev)->dev_info->dev_mem_base; } static inline u64 amdxdna_obj_dma_addr(struct amdxdna_gem_obj *abo) { - return amdxdna_pasid_on(abo->client) ? amdxdna_gem_uva(abo) : abo->mem.dma_addr; + /* + * amdxdna_gem_obj_open() calls amdxdna_dma_map_bo() only when PASID is + * off, leaving mem.dma_addr at AMDXDNA_INVALID_ADDR when PASID is on. + * Avoid dereferencing abo->client, which is cleared to NULL by + * amdxdna_gem_obj_close() while internal kernel references remain. + */ + return (abo->mem.dma_addr != AMDXDNA_INVALID_ADDR) ? + abo->mem.dma_addr : amdxdna_gem_uva(abo); } void amdxdna_umap_put(struct amdxdna_umap *mapp); diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c index 3fe878b49..fcc0bb327 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c @@ -149,7 +149,7 @@ static void amdxdna_sva_fini(struct amdxdna_client *client) static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) { struct amdxdna_dev *xdna = to_xdna_dev(ddev); - struct amdxdna_client *tmp, *client; + struct amdxdna_client *client; int ret; client = kzalloc_obj(*client); @@ -157,8 +157,10 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) return -ENOMEM; ret = init_srcu_struct(&client->hwctx_srcu); - if (ret) - goto free_client; + if (ret) { + kfree(client); + return ret; + } client->pid = pid_nr(rcu_access_pointer(filp->pid)); client->xdna = xdna; @@ -172,14 +174,15 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) XDNA_WARN(xdna, "PASID not available for pid %d", client->pid); if (!amdxdna_use_carveout(xdna)) { XDNA_ERR(xdna, "PASID unavailable and carveout not configured"); - ret = -EINVAL; - goto cleanup_srcu; + cleanup_srcu_struct(&client->hwctx_srcu); + kfree(client); + return -EINVAL; } } } #endif mmgrab(client->mm); - xa_init_flags(&client->hwctx_xa, XA_FLAGS_ALLOC); + xa_init(&client->hwctx_xa); xa_init_flags(&client->dev_heap_xa, XA_FLAGS_ALLOC); /* Devices without a managed dev-heap aperture (e.g. PA-mode aie4) leave * dev_heap_max_size at 0; drm_mm_init() BUGs on a zero-sized range. @@ -191,15 +194,6 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) mutex_lock(&xdna->client_lock); mutex_lock(&xdna->dev_lock); - amdxdna_for_each_client(xdna, tmp) { - if (tmp->pid == client->pid) { - mutex_unlock(&xdna->dev_lock); - mutex_unlock(&xdna->client_lock); - XDNA_WARN(xdna, "pid %d already opened the device", client->pid); - ret = -EBUSY; - goto fail; - } - } list_add_tail(&client->node, &xdna->client_list); mutex_unlock(&xdna->dev_lock); mutex_unlock(&xdna->client_lock); @@ -211,22 +205,6 @@ static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp) XDNA_DBG(xdna, "pid %d opened", client->pid); return 0; - -fail: - if (xdna->dev_info->dev_heap_max_size) - drm_mm_takedown(&client->dev_heap_mm); - xa_destroy(&client->dev_heap_xa); - xa_destroy(&client->hwctx_xa); - mutex_destroy(&client->mm_lock); - mmdrop(client->mm); - amdxdna_sva_fini(client); -#ifndef AMDXDNA_NPU3A -cleanup_srcu: -#endif - cleanup_srcu_struct(&client->hwctx_srcu); -free_client: - kfree(client); - return ret; } static void amdxdna_client_cleanup(struct amdxdna_client *client) @@ -448,6 +426,7 @@ static void amdxdna_xdna_drm_release(struct drm_device *drm, void *res) amdxdna_carveout_fini(xdna); cleanup_srcu_struct(&xdna->dpt_srcu); + ida_destroy(&xdna->hwctx_ida); } static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id) @@ -473,6 +452,7 @@ static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id) drmm_mutex_init(ddev, &xdna->dev_lock); init_rwsem(&xdna->notifier_lock); INIT_LIST_HEAD(&xdna->client_list); + ida_init(&xdna->hwctx_ida); pci_set_drvdata(pdev, xdna); ret = init_srcu_struct(&xdna->dpt_srcu); diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.h b/drivers/accel/amdxdna/amdxdna_pci_drv.h index 863a9dd0a..d6f7d7b05 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.h +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -141,6 +142,14 @@ struct amdxdna_dev { struct mutex dev_lock; /* per device lock */ struct list_head client_list; struct mutex client_lock; /* client_list */ + /* + * Device-wide cyclic allocator for hwctx IDs. Only the ID space is shared + * so a context ID is unique across the whole device; the id->hwctx map and + * its lifetime srcu live per-client (see struct amdxdna_client). next_hwctxid + * is the cyclic cursor. Both are serialized by dev_lock. + */ + struct ida hwctx_ida; + u32 next_hwctxid; struct amdxdna_drm_query_firmware_version fw_ver; struct rw_semaphore notifier_lock; /* for mmu notifier*/ struct workqueue_struct *notifier_wq; @@ -186,9 +195,15 @@ struct amdxdna_io_stats { struct amdxdna_client { struct list_head node; pid_t pid; + /* + * Guards hwctx lifetime against this client's own blocking submit/wait. + * A lookup only ever returns this client's own hwctx (per-client xa), so a + * single per-client srcu is sufficient and a parked waiter can only stall + * its own client's teardown. + */ struct srcu_struct hwctx_srcu; + /* id->hwctx map for this client; IDs come from xdna->hwctx_ida. */ struct xarray hwctx_xa; - u32 next_hwctxid; struct amdxdna_dev *xdna; struct drm_file *filp; diff --git a/drivers/accel/amdxdna/amdxdna_ubuf.c b/drivers/accel/amdxdna/amdxdna_ubuf.c index 6f741c443..a5db3afd6 100644 --- a/drivers/accel/amdxdna/amdxdna_ubuf.c +++ b/drivers/accel/amdxdna/amdxdna_ubuf.c @@ -149,6 +149,7 @@ struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev, u32 npages, start = 0; struct dma_buf *dbuf; bool readonly = true; + bool need_contig; int i, ret = 0; DEFINE_DMA_BUF_EXPORT_INFO(exp_info); @@ -174,6 +175,15 @@ struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev, goto free_ent; } + /* + * With an IOMMU domain the scattered pages are mapped to a single + * contiguous device IOVA (iommu_map_sgtable), so the entries need not be + * contiguous in user VA. Without one (PASID/SVA or PA mode) the device + * addresses the BO at its user VA and the BO records only the first + * entry's VA, so require the entries to describe one contiguous VA range. + */ + need_contig = !amdxdna_iova_on(xdna); + for (i = 0, exp_info.size = 0; i < num_entries; i++) { if (!IS_ALIGNED(va_ent[i].vaddr, PAGE_SIZE) || !IS_ALIGNED(va_ent[i].len, PAGE_SIZE)) { @@ -183,6 +193,15 @@ struct dma_buf *amdxdna_get_ubuf(struct drm_device *dev, goto free_ent; } + if (need_contig && i && + va_ent[i].vaddr != va_ent[i - 1].vaddr + va_ent[i - 1].len) { + XDNA_ERR(xdna, "Non-contiguous va entry %d, %llx after %llx+%llx", + i, va_ent[i].vaddr, va_ent[i - 1].vaddr, + va_ent[i - 1].len); + ret = -EINVAL; + goto free_ent; + } + if (check_add_overflow(exp_info.size, va_ent[i].len, &exp_info.size)) { ret = -EINVAL; goto free_ent; diff --git a/src/shim/virtio/platform_virtio.cpp b/src/shim/virtio/platform_virtio.cpp index 201cc5282..fd574d283 100644 --- a/src/shim/virtio/platform_virtio.cpp +++ b/src/shim/virtio/platform_virtio.cpp @@ -92,6 +92,8 @@ hcall_cmd2name(unsigned long cmd) return "AMDXDNA_CCMD_GET_INFO"; case AMDXDNA_CCMD_READ_SYSFS: return "AMDXDNA_CCMD_READ_SYSFS"; + case AMDXDNA_CCMD_SYNC_BO: + return "AMDXDNA_CCMD_SYNC_BO"; } return "UNKNOWN(" + std::to_string(cmd) + ")"; @@ -173,10 +175,16 @@ hcall_wait(int dev_fd, void *buf, size_t size) XRT_TRACE_POINT_SCOPE1(hcall, req->cmd); - // For now, only AMDXDNA_CCMD_WAIT_CMD requires non-zero ring index + // For now, only AMDXDNA_CCMD_WAIT_CMD requires non-zero ring index. + // The ctx handle is the driver ctx id (up to MAX_CTX_ID), but virtio-gpu only + // accepts ring indices in [0, num_rings). Fold it into [1, AMDXDNA_MAX_HWCTX_PER_CTX] + // (ring 0 stays reserved); the host keys its hwctx table by the same function. + // A handle of AMDXDNA_INVALID_CTX_HANDLE (0) is not a hwctx: leave ring_idx 0 + // (the reserved platform ring) instead of letting (ctx_handle - 1) underflow. if (req->cmd == AMDXDNA_CCMD_WAIT_CMD) { auto wcmd = reinterpret_cast(req); - ring_idx = wcmd->ctx_handle; + if (wcmd->ctx_handle != AMDXDNA_INVALID_CTX_HANDLE) + ring_idx = ((wcmd->ctx_handle - 1) % AMDXDNA_MAX_HWCTX_PER_CTX) + 1; } drm_virtgpu_execbuffer exec = { @@ -745,4 +753,20 @@ import_bo(import_bo_arg& bo_arg) const save_bo_info(gboh, bo_arg.boinfo); } +void +platform_drv_virtio:: +sync_bo(sync_bo_arg& arg) const +{ + amdxdna_ccmd_sync_bo_req req = { + .hdr = { AMDXDNA_CCMD_SYNC_BO, sizeof(req) }, + .handle = arg.bo.handle, + .direction = arg.direction == xrt_core::buffer_handle::direction::host2device ? + SYNC_DIRECT_TO_DEVICE : SYNC_DIRECT_FROM_DEVICE, + .offset = arg.offset, + .size = arg.size, + }; + amdxdna_ccmd_sync_bo_rsp rsp = {}; + hcall(&req, &rsp, sizeof(rsp)); +} + } diff --git a/src/shim/virtio/platform_virtio.h b/src/shim/virtio/platform_virtio.h index 2418acc0e..b86bfaef9 100644 --- a/src/shim/virtio/platform_virtio.h +++ b/src/shim/virtio/platform_virtio.h @@ -101,6 +101,9 @@ class platform_drv_virtio : public platform_drv void import_bo(import_bo_arg& arg) const override; + + void + sync_bo(sync_bo_arg& arg) const override; }; } diff --git a/src/vxdna/src/amdxdna_proto.h b/src/vxdna/src/amdxdna_proto.h index 5fda35a39..696f07256 100644 --- a/src/vxdna/src/amdxdna_proto.h +++ b/src/vxdna/src/amdxdna_proto.h @@ -8,6 +8,21 @@ #define AMDXDNA_MAX_RING_NUM 64 +/* + * Max hw contexts per guest context, and the modulus that maps a driver ctx + * handle (1..MAX_CTX_ID) to a virtio ring index. The guest and host must agree + * on this value: the guest routes a WAIT on ring ((ctx_handle - 1) % N) + 1, + * and the host keys its hwctx table by the same function. Range is [1, N], so + * ring 0 (AMDXDNA_INVALID_CTX_HANDLE / platform ring) is never used. + * + * Note the ring index is only a routing key: create_ctx_rsp.handle still + * carries the raw driver ctx handle (not the ring index), so guest-side + * correlation (telemetry ctx_map, coredump, HW_CONTEXT_BY_ID) is unchanged. + * handle != ring_idx; the ring index is derived from the handle by the modulo + * above. + */ +#define AMDXDNA_MAX_HWCTX_PER_CTX 32 + enum amdxdna_ccmd { AMDXDNA_CCMD_NOP = 1, AMDXDNA_CCMD_INIT, @@ -20,6 +35,7 @@ enum amdxdna_ccmd { AMDXDNA_CCMD_WAIT_CMD, AMDXDNA_CCMD_GET_INFO, AMDXDNA_CCMD_READ_SYSFS, + AMDXDNA_CCMD_SYNC_BO, }; #ifdef __cplusplus @@ -187,6 +203,25 @@ struct amdxdna_ccmd_read_sysfs_req { struct vdrm_ccmd_req hdr; char node_name[]; }; + +/* + * AMDXDNA_CCMD_SYNC_BO + * + * direction carries the driver SYNC_DIRECT_TO_DEVICE / SYNC_DIRECT_FROM_DEVICE + * value; handle is the guest BO handle (== host GEM handle). + */ +struct amdxdna_ccmd_sync_bo_req { + struct vdrm_ccmd_req hdr; + uint32_t handle; + uint32_t direction; + uint64_t offset; + uint64_t size; +}; +DEFINE_CAST(vdrm_ccmd_req, amdxdna_ccmd_sync_bo_req) + +struct amdxdna_ccmd_sync_bo_rsp { + struct amdxdna_ccmd_rsp hdr; +}; DEFINE_CAST(vdrm_ccmd_req, amdxdna_ccmd_read_sysfs_req) struct amdxdna_ccmd_read_sysfs_rsp { diff --git a/src/vxdna/src/vaccel_amdxdna.cpp b/src/vxdna/src/vaccel_amdxdna.cpp index 8ae0e7fc5..bcb6d64a1 100644 --- a/src/vxdna/src/vaccel_amdxdna.cpp +++ b/src/vxdna/src/vaccel_amdxdna.cpp @@ -131,6 +131,34 @@ validate_exec_cmd_inline_payload(const struct amdxdna_ccmd_exec_cmd_req *req) */ static constexpr uint32_t k_platform_ctx_id = 0; +/* + * Map a driver ctx handle (ctx->id, 1..MAX_CTX_ID) to the virtio ring index + * used for its completion fences. virtio-gpu only accepts ring indices in + * [0, num_rings), and ring 0 is reserved (platform ring / + * AMDXDNA_INVALID_CTX_HANDLE), so fold the handle into [1, AMDXDNA_MAX_HWCTX_PER_CTX]. + * The guest computes the same function when it sets ring_idx on a WAIT, so the + * hwctx table is keyed by this value and fences are signalled on it. + * + * NOTE: this is not injective over the full handle range; two live handles that + * are congruent under the modulus map to the same ring. That is enforced at + * runtime -- create_hwctx rejects a create whose ring slot is already taken + * with -EBUSY -- so at most one context per ring (AMDXDNA_MAX_HWCTX_PER_CTX + * live contexts) exists and a lookup never resolves to the wrong context. This + * holds regardless of how the driver allocates ids; with cyclic allocation a + * colliding create is transiently rejected until the occupying context is freed. + * + * A handle of AMDXDNA_INVALID_CTX_HANDLE (0) is not a valid hwctx. Return the + * reserved ring 0 for it -- which find_hwctx() rejects -- rather than let the + * unsigned (ctx_handle - 1) underflow fold a guest-supplied 0 onto a live ring. + */ +static uint32_t +hwctx_ring_idx(uint32_t ctx_handle) +{ + if (ctx_handle == AMDXDNA_INVALID_CTX_HANDLE) + return 0; + return ((ctx_handle - 1) % AMDXDNA_MAX_HWCTX_PER_CTX) + 1; +} + /* * Resources live in a single per-device table addressable by any context that * shares the cookie (e.g. multiple guest user processes inside the same VM). @@ -195,7 +223,7 @@ validate_read_sysfs_inline_payload(const struct amdxdna_ccmd_read_sysfs_req *req bool uintptr_range_end(uintptr_t base, size_t len, uintptr_t *end_out) { - if (len > std::numeric_limits::max() || base > std::numeric_limits::max() - len) + if (base > std::numeric_limits::max() - len) return false; *end_out = base + len; return true; @@ -495,6 +523,7 @@ vxdna_bo(const std::shared_ptr &res, vxdna_context &ctx, auto num_iovs = res->get_iovecs(&iovecs); bool created_here = false; + bool use_raw_iovs = false; if (m_opaque_handle == AMDXDNA_INVALID_BO_HANDLE) { const bool heap_chunk = (m_bo_type == AMDXDNA_BO_DEV_HEAP); void *coalesce = nullptr; @@ -558,21 +587,63 @@ vxdna_bo(const std::shared_ptr &res, vxdna_context &ctx, m_coalesce_va = nullptr; m_coalesce_len = 0; } - throw; + /* + * Coalescing needs shareable guest memory: mremap(old_size=0) + * only duplicates MAP_SHARED slices. When QEMU is started + * without share=on the slices are private-anon and mremap fails. + * Do not fail here -- for heap chunks too: fall back to handing + * the driver the raw scattered iovecs as a multi-entry va_tbl + * and let CREATE_BO be the arbiter. With an IOMMU (force_iova) + * the driver coalesces the pages into one device address -- for + * a heap into its reserved heap IOVA region; without one + * (SVA/PA) CREATE_BO fails and so does BO creation. + */ + use_raw_iovs = true; + vxdna_dbg("coalesce failed (guest mem not shareable); falling " + "back to %u raw iovecs for BO create", num_iovs); } } m_iov_bytes = total; - m_vaddr = static_cast(reinterpret_cast(coalesce)); - size_t buf_size = sizeof(amdxdna_drm_va_tbl) + sizeof(amdxdna_drm_va_entry); - std::vector buf_vec(buf_size); + uint32_t n_entries = 1; + if (use_raw_iovs) { + /* Count entries needed to cover pin_len (truncate the last). */ + m_vaddr = AMDXDNA_INVALID_ADDR; + size_t remaining = pin_len; + n_entries = 0; + for (uint32_t i = 0; i < num_iovs && remaining; i++) { + size_t l = iovecs[i].iov_len < remaining ? iovecs[i].iov_len : remaining; + remaining -= l; + n_entries++; + } + } else { + m_vaddr = static_cast(reinterpret_cast(coalesce)); + } + + size_t buf_size = sizeof(amdxdna_drm_va_tbl) + + static_cast(n_entries) * sizeof(amdxdna_drm_va_entry); + // Back the va_tbl with uint64_t so the storage is 8-byte aligned for its + // u64 fields; a uint8_t vector only guarantees byte alignment, and + // reinterpreting it as amdxdna_drm_va_tbl would be UB on strict-align archs. + std::vector buf_vec((buf_size + sizeof(uint64_t) - 1) / sizeof(uint64_t)); auto tbl = reinterpret_cast(buf_vec.data()); tbl->udma_fd = -1; - tbl->num_entries = 1; - tbl->va_entries[0].vaddr = - static_cast(reinterpret_cast(coalesce)); - tbl->va_entries[0].len = static_cast(pin_len); + tbl->num_entries = n_entries; + if (use_raw_iovs) { + size_t remaining = pin_len; + for (uint32_t i = 0; i < n_entries; i++) { + size_t l = iovecs[i].iov_len < remaining ? iovecs[i].iov_len : remaining; + tbl->va_entries[i].vaddr = + static_cast(reinterpret_cast(iovecs[i].iov_base)); + tbl->va_entries[i].len = static_cast(l); + remaining -= l; + } + } else { + tbl->va_entries[0].vaddr = + static_cast(reinterpret_cast(coalesce)); + tbl->va_entries[0].len = static_cast(pin_len); + } struct amdxdna_drm_create_bo args = {}; args.vaddr = static_cast(reinterpret_cast(buf_vec.data())); @@ -666,8 +737,9 @@ poll_and_retire_pending(std::vector> &©_pendin if (ret) vxdna_err("vxdna_hwctx::poll_and_retire_pending: Wait for fence failed ret %d, errno %d, %s, expect timeout: %ld", ret, errno, strerror(errno), fence->get_timeout_nsec()); - // Fence is retired, write fence callback - m_write_fence_callback(m_cookie, m_ctx_id, m_hwctx_handle, fence->get_id()); + // Fence is retired, write fence callback (signal on the guest ring). + m_write_fence_callback(m_cookie, m_ctx_id, hwctx_ring_idx(m_hwctx_handle), + fence->get_id()); } } @@ -874,7 +946,7 @@ submit_fence(uint64_t fence_id) MAX_PENDING_FENCES, static_cast(fence_id)); - auto fence = std::make_shared(fence_id, m_sync_point, m_syncobj_handle, m_hwctx_handle, m_timeout_nsec); + auto fence = std::make_shared(fence_id, m_sync_point, m_syncobj_handle, hwctx_ring_idx(m_hwctx_handle), m_timeout_nsec); m_pending_fences.push_back(std::move(fence)); m_has_sync_point = false; m_cv.notify_one(); @@ -882,7 +954,8 @@ submit_fence(uint64_t fence_id) } // Invoke callback outside lock to avoid deadlock if (immediate_callback) { - m_write_fence_callback(m_cookie, m_ctx_id, m_hwctx_handle, fence_id); + m_write_fence_callback(m_cookie, m_ctx_id, hwctx_ring_idx(m_hwctx_handle), + fence_id); } } @@ -967,30 +1040,89 @@ export_resource_fd(const std::shared_ptr &res) return args.fd; } +std::shared_ptr +vxdna_context:: +find_hwctx(uint32_t ring_idx) const +{ + if (ring_idx == 0 || ring_idx > MAX_HWCTX_PER_CTX) + return nullptr; + std::lock_guard lock(m_hwctx_lock); + return m_hwctx_slots[ring_idx]; +} + +std::shared_ptr +vxdna_context:: +find_hwctx_by_handle(uint32_t ctx_handle) const +{ + // hwctx_ring_idx() is not injective, so a stale or forged handle can be + // congruent to a live one under the modulus (or be AMDXDNA_INVALID_CTX_HANDLE + // -> reserved ring 0). Verify the stored handle matches so a guest can only + // reach the context it actually named. get_handle() is immutable, so it is + // safe to compare on the returned keep-alive copy. + auto hwctx = find_hwctx(hwctx_ring_idx(ctx_handle)); + if (hwctx && hwctx->get_handle() == ctx_handle) + return hwctx; + return nullptr; +} + void vxdna_context:: create_hwctx(const struct amdxdna_ccmd_create_ctx_req *req) { - struct amdxdna_ccmd_create_ctx_rsp rsp = {}; + // Construct outside the lock (CREATE_HWCTX ioctl and polling-thread start + // can block). The slot index is the virtio ring index derived from the + // driver ctx id and doubles as the allocator: a taken slot means either the + // cap is reached or two ctx ids collide under hwctx_ring_idx(). auto hwctx = std::make_shared(*this, req); + uint32_t ring_idx = hwctx_ring_idx(hwctx->get_handle()); + { + std::lock_guard lock(m_hwctx_lock); + if (m_hwctx_slots[ring_idx]) + VACCEL_THROW_MSG(-EBUSY, + "virtio ring index %u unavailable (ctx id %u, max %u hw contexts)", + ring_idx, hwctx->get_handle(), MAX_HWCTX_PER_CTX); + m_hwctx_slots[ring_idx] = hwctx; + } + + // rsp.handle stays the driver ctx id so guest-side correlation (telemetry + // ctx_map, coredump, HW_CONTEXT_BY_ID) is unchanged. + struct amdxdna_ccmd_create_ctx_rsp rsp = {}; rsp.hdr.base.len = sizeof(rsp); rsp.handle = hwctx->get_handle(); - m_hwctx_table.insert(hwctx->get_handle(), std::move(hwctx)); - write_rsp(&rsp, sizeof(rsp), req->hdr.rsp_off); + try { + write_rsp(&rsp, sizeof(rsp), req->hdr.rsp_off); + } catch (...) { + std::lock_guard lock(m_hwctx_lock); + m_hwctx_slots[ring_idx].reset(); + throw; + } } void vxdna_context:: remove_hwctx(uint32_t handle) { - m_hwctx_table.erase(handle); + uint32_t ring_idx = hwctx_ring_idx(handle); + // Move the entry out under the lock, then let it destruct outside the lock + // (~vxdna_hwctx joins the polling thread). + std::shared_ptr old; + { + std::lock_guard lock(m_hwctx_lock); + auto &slot = m_hwctx_slots[ring_idx]; + // Only evict when the slot actually holds this handle. hwctx_ring_idx() + // is not injective, so a stale or invalid handle can be congruent to a + // live one (or be AMDXDNA_INVALID_CTX_HANDLE -> reserved ring 0); such a + // handle must not tear down the context occupying that ring. + if (slot && slot->get_handle() == handle) + old = std::move(slot); + } } void vxdna_context:: config_hwctx(const struct amdxdna_ccmd_config_ctx_req *req) { - auto hwctx = m_hwctx_table.lookup(req->handle); + auto hwctx = find_hwctx_by_handle(req->handle); if (!hwctx) VACCEL_THROW_MSG(-EINVAL, "HW context not found handle %u", req->handle); hwctx->config(req); @@ -1008,7 +1140,7 @@ submit_fence(uint32_t ring_idx, uint64_t fence_id) get_callbacks()->write_context_fence(get_cookie(), get_id(), ring_idx, fence_id); return; } - auto hwctx = m_hwctx_table.lookup(ring_idx); + auto hwctx = find_hwctx(ring_idx); if (!hwctx) VACCEL_THROW_MSG(-EINVAL, "HW context not found ring_idx %u", ring_idx); hwctx->submit_fence(fence_id); @@ -1018,7 +1150,7 @@ void vxdna_context:: exec_cmd(const struct amdxdna_ccmd_exec_cmd_req *req) { - auto hwctx = m_hwctx_table.lookup(req->ctx_handle); + auto hwctx = find_hwctx_by_handle(req->ctx_handle); if (!hwctx) VACCEL_THROW_MSG(-EINVAL, "HW context not found handle %u", req->ctx_handle); struct amdxdna_ccmd_exec_cmd_rsp rsp = {}; @@ -1031,13 +1163,37 @@ void vxdna_context:: wait_cmd(const struct amdxdna_ccmd_wait_cmd_req *req) { - auto hwctx = m_hwctx_table.lookup(req->ctx_handle); + auto hwctx = find_hwctx_by_handle(req->ctx_handle); if (!hwctx) VACCEL_THROW_MSG(-EINVAL, "HW context not found handle %u", req->ctx_handle); hwctx->set_sync_point(req->seq, req->timeout_nsec); write_err_rsp(0); // Success } +void +vxdna_context:: +sync_bo(const struct amdxdna_ccmd_sync_bo_req *req) +{ + // The guest BO handle is the host GEM handle, but all guest contexts share + // the native client's GEM namespace (single client per QEMU). Validate the + // BO is owned by this context before issuing the driver ioctl. + auto bo = m_bo_table.lookup(req->handle); + if (!bo) + VACCEL_THROW_MSG(-EINVAL, "sync_bo: BO %u not found in ctx %u", + req->handle, get_id()); + + struct amdxdna_drm_sync_bo arg = {}; + arg.handle = req->handle; + arg.direction = req->direction; + arg.offset = req->offset; + arg.size = req->size; + if (ioctl(get_fd(), DRM_IOCTL_AMDXDNA_SYNC_BO, &arg)) + VACCEL_THROW_MSG(-errno, "sync_bo ioctl failed for BO %u, errno %d", + req->handle, errno); + + write_err_rsp(0); // Success +} + void vxdna_context:: get_info(const struct amdxdna_ccmd_get_info_req *req) @@ -1442,6 +1598,16 @@ vxdna_ccmd_read_sysfs([[maybe_unused]] vxdna &device, const std::shared_ptr& ctx, + const void *hdr) +{ + auto *req = static_cast(hdr); + vxdna_ccmd_error_wrap(ctx, [&]() { + ctx->sync_bo(req); + }); +} + // Definition of the CCMD handler type for AMDXDNA using amdxdna_ccmd_handler_t = void(*)(vxdna &device, const std::shared_ptr& ctx, @@ -1460,7 +1626,7 @@ struct amdxdna_ccmd_dispatch_entry { #define AMD_CCMD_DISPATCH_ENTRY(name) \ { #name, vxdna_ccmd_##name, sizeof(struct amdxdna_ccmd_##name##_req) } -constexpr size_t AMDXDNA_CCMD_COUNT = 11; +constexpr size_t AMDXDNA_CCMD_COUNT = 12; constexpr std::array amdxdna_ccmd_dispatch_table = {{ AMD_CCMD_DISPATCH_ENTRY(nop), AMD_CCMD_DISPATCH_ENTRY(init), @@ -1473,6 +1639,7 @@ constexpr std::array amdxdna_cc AMD_CCMD_DISPATCH_ENTRY(wait_cmd), AMD_CCMD_DISPATCH_ENTRY(get_info), AMD_CCMD_DISPATCH_ENTRY(read_sysfs), + AMD_CCMD_DISPATCH_ENTRY(sync_bo), }}; void diff --git a/src/vxdna/src/vaccel_amdxdna.h b/src/vxdna/src/vaccel_amdxdna.h index f70ef6032..46eb8485d 100644 --- a/src/vxdna/src/vaccel_amdxdna.h +++ b/src/vxdna/src/vaccel_amdxdna.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include "drm_hw.h" // from xdna shim virtio @@ -160,6 +162,9 @@ class vxdna_context : public vaccel_context { public: static constexpr uint64_t HEAP_MAX_SIZE = 512ULL << 20; + /** Max concurrent hw contexts allowed per context (see amdxdna_proto.h). */ + static constexpr uint32_t MAX_HWCTX_PER_CTX = AMDXDNA_MAX_HWCTX_PER_CTX; + using base_type = vaccel_context; /** @@ -349,6 +354,18 @@ class vxdna_context : public vaccel_context { */ void read_sysfs(const struct amdxdna_ccmd_read_sysfs_req *req); + /** + * @brief Sync a BO's cache/DMA state via the driver (DRM_IOCTL_AMDXDNA_SYNC_BO) + * + * Validates the BO belongs to this context, then forwards to the host + * driver. Needed for BOs whose sync can't be done by a guest CPU cache + * flush (e.g. firmware-written debug/uc_debug BOs read back device->host). + * + * @param req Sync request (BO handle, direction, offset, size) + * @throws vaccel_error if the BO isn't owned by this ctx or the ioctl fails + */ + void sync_bo(const struct amdxdna_ccmd_sync_bo_req *req); + /** @} */ /** @@ -532,10 +549,37 @@ class vxdna_context : public vaccel_context { /** @} */ }; + /** + * @brief Look up a hw context by its virtio ring index. + * @param ring_idx Index in [1, MAX_HWCTX_PER_CTX]; 0/out-of-range -> nullptr. + * @return shared_ptr copy (kept alive for the caller), or nullptr if empty. + */ + std::shared_ptr find_hwctx(uint32_t ring_idx) const; + + /** + * @brief Look up a hw context named by a guest ctx handle. + * @param ctx_handle Driver ctx handle from a guest request. + * @return shared_ptr copy, or nullptr if no live context has this handle. + * + * hwctx_ring_idx() is a modulo mapping, so this also verifies the stored + * context's handle matches @ctx_handle to reject a stale or forged handle + * that is congruent to a live one under the modulus. + */ + std::shared_ptr find_hwctx_by_handle(uint32_t ctx_handle) const; + // Context-owned resources (cookie/callbacks accessed via base_type::get_device()) std::shared_ptr m_resp_res; vaccel_map> m_bo_table; - vaccel_map> m_hwctx_table; + + /* + * Hw contexts indexed directly by their virtio ring index (hwctx_ring_idx() + * of the driver ctx id), which lies in [1, MAX_HWCTX_PER_CTX]. A null slot + * is free, so this array is both allocator and storage: create claims the + * slot (rejecting a taken one), and lookup is a direct O(1) index. Slot 0 is + * unused (reserved platform ring / AMDXDNA_INVALID_CTX_HANDLE). + */ + mutable std::mutex m_hwctx_lock; + std::array, MAX_HWCTX_PER_CTX + 1> m_hwctx_slots; /** Cumulative DEV_HEAP size committed on this context (bytes). */ uint64_t m_heap_committed = 0; diff --git a/test/shim_test/shim_test.cpp b/test/shim_test/shim_test.cpp index c8e539535..6ec5de8b1 100644 --- a/test/shim_test/shim_test.cpp +++ b/test/shim_test/shim_test.cpp @@ -274,6 +274,33 @@ dev_filter_is_npu4_and_amdxdna_drv(device::id_type id, device* dev) return true; } +// Tests that bypass the shim and issue DRM_IOCTL_AMDXDNA_* directly on the accel +// fd only work against the native amdxdna driver. On the virtio-gpu guest that fd +// is virtio-gpu and does not implement those ioctls, so require the native driver. +bool +dev_filter_is_aie_and_amdxdna_drv(device::id_type id, device* dev) +{ + if (!dev_filter_is_aie(id, dev)) + return false; + if (!is_amdxdna_drv(dev)) + return false; + return true; +} + +// set_state()-based tests (force preemption, DPM/power mode) mutate device-wide +// firmware state via DRM_AMDXDNA_SET_STATE, which the virtio-gpu guest shim does +// not forward. Require the native amdxdna driver so these tests are skipped on +// the QEMU guest. +bool +dev_filter_is_aie4_or_npu4_and_amdxdna_drv(device::id_type id, device* dev) +{ + if (!dev_filter_is_aie4_or_npu4(id, dev)) + return false; + if (!is_amdxdna_drv(dev)) + return false; + return true; +} + static void TEST_async_error_io_any(device::id_type id, std::shared_ptr& sdev, arg_type& arg) { if (dev_filter_is_npu4(id, sdev.get())) @@ -973,33 +1000,6 @@ TEST_dev_bo_cross_heap_stress(device::id_type id, std::shared_ptr& sdev, } } -// Verify the driver allows only one open per process: with the shim device -// already open (sdev), a second open() of the same node from this process must -// be rejected with EBUSY, and the existing device must keep working afterwards. -void -TEST_reject_second_open(device::id_type id, std::shared_ptr& sdev, arg_type& arg) -{ - auto dev = sdev.get(); - const std::string accel_node = resolve_accel_node_path(dev); - - errno = 0; - int fd = ::open(accel_node.c_str(), accel_node_open_flags(accel_node)); - if (fd >= 0) { - ::close(fd); - throw std::runtime_error("second open() of " + accel_node + " unexpectedly succeeded"); - } - if (errno != EBUSY) { - throw std::runtime_error("second open() of " + accel_node + " failed with errno " + - std::to_string(errno) + " (" + std::strerror(errno) + "), expected EBUSY"); - } - std::cout << "second open() correctly rejected with EBUSY" << std::endl; - - // The already-open shim device must still be usable after the rejected open. - auto raw = device_query(dev); - if (raw.empty()) - throw std::runtime_error("existing device query failed after rejected second open"); -} - class mmapped_file { public: mmapped_file(size_t size, bool readonly) @@ -1640,7 +1640,7 @@ std::vector test_list { TEST_POSITIVE, dev_filter_is_aie2_and_amdxdna_drv, TEST_io_with_ubuf_bo, {} }, test_case{ "multi-command preempt full ELF io test real kernel good run", {}, - TEST_POSITIVE, dev_filter_is_aie4_or_npu4, TEST_preempt_full_elf_io, { IO_TEST_FORCE_PREEMPTION, 8 } + TEST_POSITIVE, dev_filter_is_aie4_or_npu4_and_amdxdna_drv, TEST_preempt_full_elf_io, { IO_TEST_FORCE_PREEMPTION, 8 } }, test_case{ "Real kernel delay run for auto-suspend/resume", {}, TEST_POSITIVE, dev_filter_is_aie2, TEST_io_suspend_resume, {} @@ -1652,16 +1652,16 @@ std::vector test_list { TEST_POSITIVE, dev_filter_is_npu4_and_amdxdna_drv, TEST_app_health_query_multi_ctx, {} }, test_case{ "query hw_contexts (get_info)", {}, - TEST_POSITIVE, dev_filter_is_aie, TEST_query_hw_contexts, {} + TEST_POSITIVE, dev_filter_is_aie_and_amdxdna_drv, TEST_query_hw_contexts, {} }, test_case{ "hw_context_all (get_array)", {}, - TEST_POSITIVE, dev_filter_is_aie, TEST_hw_context_all, {} + TEST_POSITIVE, dev_filter_is_aie_and_amdxdna_drv, TEST_hw_context_all, {} }, test_case{ "query telemetry", {}, - TEST_POSITIVE, dev_filter_is_aie, TEST_query_telemetry, {} + TEST_POSITIVE, dev_filter_is_aie_and_amdxdna_drv, TEST_query_telemetry, {} }, test_case{ "query telemetry header-only buffer fails", {}, - TEST_POSITIVE, dev_filter_is_aie, TEST_query_telemetry_short_buf, {} + TEST_POSITIVE, dev_filter_is_aie_and_amdxdna_drv, TEST_query_telemetry_short_buf, {} }, //test_case{ "io test no-op kernel good run", {}, // TEST_POSITIVE, dev_filter_is_aie2, TEST_io, { IO_TEST_NOOP_RUN, 1 } @@ -1699,20 +1699,17 @@ std::vector test_list { test_case{ "dev BO larger than 512MB accepted (aie4)", {}, TEST_POSITIVE, dev_filter_is_aie4, TEST_dev_bo_cross_heap, { 576ul * 1024 * 1024 } }, - test_case{ "reject second device open from same process", {}, - TEST_POSITIVE, dev_filter_is_aie2_and_amdxdna_drv, TEST_reject_second_open, {} - }, test_case{ "export BO then close device", {}, TEST_POSITIVE, dev_filter_xdna, TEST_export_bo_then_close_device, {} }, test_case{ "get AIE coredump and check registers", {}, - TEST_POSITIVE, dev_filter_is_npu4, TEST_io_coredump, {} + TEST_POSITIVE, dev_filter_is_npu4_and_amdxdna_drv, TEST_io_coredump, {} }, test_case{ "AIE MEM read/write", {}, - TEST_POSITIVE, dev_filter_is_npu4, TEST_io_aie_mem, {} + TEST_POSITIVE, dev_filter_is_npu4_and_amdxdna_drv, TEST_io_aie_mem, {} }, test_case{ "AIE REG read/write", {}, - TEST_POSITIVE, dev_filter_is_npu4, TEST_io_aie_reg, {} + TEST_POSITIVE, dev_filter_is_npu4_and_amdxdna_drv, TEST_io_aie_reg, {} }, test_case{ "failed chained command", {}, TEST_POSITIVE, dev_filter_is_npu4, TEST_io_runlist_bad_cmd, {false} @@ -1724,13 +1721,13 @@ std::vector test_list { TEST_POSITIVE, dev_filter_is_aie2_and_amdxdna_drv, TEST_create_free_mmaped_uptr_bo, {} }, test_case{ "DPM noop (no QoS)", {}, - TEST_POSITIVE, dev_filter_is_npu4, TEST_dpm_noop_no_qos, {} + TEST_POSITIVE, dev_filter_is_npu4_and_amdxdna_drv, TEST_dpm_noop_no_qos, {} }, test_case{ "DPM refcount scaling", {}, - TEST_POSITIVE, dev_filter_is_npu4, TEST_dpm_refcount_scaling, {} + TEST_POSITIVE, dev_filter_is_npu4_and_amdxdna_drv, TEST_dpm_refcount_scaling, {} }, test_case{ "DPM power modes", {}, - TEST_POSITIVE, dev_filter_is_npu4, TEST_dpm_power_modes, {} + TEST_POSITIVE, dev_filter_is_npu4_and_amdxdna_drv, TEST_dpm_power_modes, {} }, test_case{ "CERT log: attach/detach", {}, TEST_POSITIVE, dev_filter_is_aie4, TEST_certlog_attach_detach, {}