mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
Expose the command doorbell register to userspace on a per-hardware context basis, enabling applications to notify the firmware of pending commands via doorbell writes. Introduce DRM_IOCTL_AMDXDNA_WAIT_CMD to allow userspace to wait for completion of individual commands. Co-developed-by: Hayden Laccabue <Hayden.Laccabue@amd.com> Signed-off-by: Hayden Laccabue <Hayden.Laccabue@amd.com> Signed-off-by: David Zhang <yidong.zhang@amd.com> Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org> Signed-off-by: Lizhi Hou <lizhi.hou@amd.com> Link: https://patch.msgid.link/20260505160936.3917732-5-lizhi.hou@amd.com
334 lines
8.4 KiB
C
334 lines
8.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2026, Advanced Micro Devices, Inc.
|
|
*/
|
|
|
|
#include <drm/amdxdna_accel.h>
|
|
#include <drm/drm_device.h>
|
|
#include <drm/drm_gem.h>
|
|
#include <drm/drm_gem_shmem_helper.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/gpu_scheduler.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "aie.h"
|
|
#include "aie4_host_queue.h"
|
|
#include "aie4_msg_priv.h"
|
|
#include "aie4_pci.h"
|
|
#include "amdxdna_ctx.h"
|
|
#include "amdxdna_gem.h"
|
|
#include "amdxdna_mailbox.h"
|
|
#include "amdxdna_mailbox_helper.h"
|
|
#include "amdxdna_pci_drv.h"
|
|
|
|
static irqreturn_t cert_comp_isr(int irq, void *p)
|
|
{
|
|
struct cert_comp *cert_comp = p;
|
|
|
|
wake_up_all(&cert_comp->waitq);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct cert_comp *aie4_lookup_cert_comp(struct amdxdna_dev_hdl *ndev, u32 msix_idx)
|
|
{
|
|
struct amdxdna_dev *xdna = ndev->aie.xdna;
|
|
struct pci_dev *pdev = to_pci_dev(xdna->ddev.dev);
|
|
struct cert_comp *cert_comp;
|
|
int ret;
|
|
|
|
guard(mutex)(&ndev->cert_comp_lock);
|
|
|
|
cert_comp = xa_load(&ndev->cert_comp_xa, msix_idx);
|
|
if (cert_comp) {
|
|
kref_get(&cert_comp->kref);
|
|
return cert_comp;
|
|
}
|
|
|
|
cert_comp = kzalloc_obj(*cert_comp);
|
|
if (!cert_comp)
|
|
return NULL;
|
|
|
|
cert_comp->ndev = ndev;
|
|
cert_comp->msix_idx = msix_idx;
|
|
init_waitqueue_head(&cert_comp->waitq);
|
|
kref_init(&cert_comp->kref);
|
|
|
|
ret = pci_irq_vector(pdev, cert_comp->msix_idx);
|
|
if (ret < 0) {
|
|
XDNA_ERR(xdna, "MSI-X idx %u is invalid, ret:%d", msix_idx, ret);
|
|
goto free_cert_comp;
|
|
}
|
|
cert_comp->irq = ret;
|
|
|
|
ret = request_irq(cert_comp->irq, cert_comp_isr, 0, "xdna_hsa", cert_comp);
|
|
if (ret) {
|
|
XDNA_ERR(xdna, "request irq %d failed %d", cert_comp->irq, ret);
|
|
goto free_cert_comp;
|
|
}
|
|
|
|
ret = xa_err(xa_store(&ndev->cert_comp_xa, msix_idx, cert_comp, GFP_KERNEL));
|
|
if (ret) {
|
|
XDNA_ERR(xdna, "store cert_comp for msix idx %d failed %d", msix_idx, ret);
|
|
goto free_irq;
|
|
}
|
|
|
|
return cert_comp;
|
|
|
|
free_irq:
|
|
free_irq(cert_comp->irq, cert_comp);
|
|
free_cert_comp:
|
|
kfree(cert_comp);
|
|
return NULL;
|
|
}
|
|
|
|
static void cert_comp_release(struct kref *kref)
|
|
{
|
|
struct cert_comp *cert_comp = container_of(kref, struct cert_comp, kref);
|
|
struct amdxdna_dev_hdl *ndev = cert_comp->ndev;
|
|
|
|
drm_WARN_ON(&ndev->aie.xdna->ddev, !mutex_is_locked(&ndev->cert_comp_lock));
|
|
|
|
xa_erase(&ndev->cert_comp_xa, cert_comp->msix_idx);
|
|
free_irq(cert_comp->irq, cert_comp);
|
|
kfree(cert_comp);
|
|
}
|
|
|
|
static void aie4_put_cert_comp(struct cert_comp *cert_comp)
|
|
{
|
|
struct amdxdna_dev_hdl *ndev;
|
|
|
|
ndev = cert_comp->ndev;
|
|
guard(mutex)(&ndev->cert_comp_lock);
|
|
kref_put(&cert_comp->kref, cert_comp_release);
|
|
}
|
|
|
|
static int aie4_msg_destroy_context(struct amdxdna_dev_hdl *ndev, u32 hw_context_id)
|
|
{
|
|
DECLARE_AIE_MSG(aie4_msg_destroy_hw_context, AIE4_MSG_OP_DESTROY_HW_CONTEXT);
|
|
|
|
req.hw_context_id = hw_context_id;
|
|
return aie_send_mgmt_msg_wait(&ndev->aie, &msg);
|
|
}
|
|
|
|
static int aie4_hwctx_create(struct amdxdna_hwctx *hwctx)
|
|
{
|
|
DECLARE_AIE_MSG(aie4_msg_create_hw_context, AIE4_MSG_OP_CREATE_HW_CONTEXT);
|
|
struct amdxdna_client *client = hwctx->client;
|
|
struct amdxdna_hwctx_priv *priv = hwctx->priv;
|
|
struct amdxdna_dev *xdna = hwctx->client->xdna;
|
|
struct amdxdna_dev_hdl *ndev = xdna->dev_handle;
|
|
int ret;
|
|
|
|
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
|
|
|
if (!ndev->partition_id || !hwctx->num_tiles) {
|
|
XDNA_ERR(xdna, "invalid request partition_id %d, num_tiles %d",
|
|
ndev->partition_id, hwctx->num_tiles);
|
|
return -EINVAL;
|
|
}
|
|
|
|
req.partition_id = ndev->partition_id;
|
|
req.request_num_tiles = hwctx->num_tiles;
|
|
req.pasid = FIELD_PREP(AIE4_MSG_PASID, client->pasid) |
|
|
FIELD_PREP(AIE4_MSG_PASID_VLD, 1);
|
|
req.priority_band = hwctx->qos.priority;
|
|
|
|
req.hsa_addr_high = upper_32_bits(amdxdna_gem_dev_addr(priv->umq_bo));
|
|
req.hsa_addr_low = lower_32_bits(amdxdna_gem_dev_addr(priv->umq_bo));
|
|
|
|
XDNA_DBG(xdna, "pasid 0x%x, num_tiles %d, hsa[0x%x 0x%x]",
|
|
req.pasid, req.request_num_tiles, req.hsa_addr_high, req.hsa_addr_low);
|
|
|
|
ret = aie_send_mgmt_msg_wait(&ndev->aie, &msg);
|
|
if (ret) {
|
|
XDNA_ERR(xdna, "create ctx failed: %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
XDNA_DBG(xdna, "resp msix: %d, ctx id: %d, doorbell: %d",
|
|
resp.job_complete_msix_idx,
|
|
resp.hw_context_id,
|
|
resp.doorbell_offset);
|
|
|
|
/* setup interrupt completion per msix index */
|
|
priv->cert_comp = aie4_lookup_cert_comp(ndev, resp.job_complete_msix_idx);
|
|
if (!priv->cert_comp) {
|
|
aie4_msg_destroy_context(ndev, resp.hw_context_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv->hw_ctx_id = resp.hw_context_id;
|
|
hwctx->doorbell_offset = resp.doorbell_offset;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aie4_hwctx_destroy(struct amdxdna_hwctx *hwctx)
|
|
{
|
|
struct amdxdna_client *client = hwctx->client;
|
|
struct amdxdna_hwctx_priv *priv = hwctx->priv;
|
|
struct amdxdna_dev *xdna = client->xdna;
|
|
struct amdxdna_dev_hdl *ndev = xdna->dev_handle;
|
|
|
|
drm_WARN_ON(&xdna->ddev, !mutex_is_locked(&xdna->dev_lock));
|
|
|
|
aie4_msg_destroy_context(ndev, priv->hw_ctx_id);
|
|
aie4_put_cert_comp(priv->cert_comp);
|
|
}
|
|
|
|
static void aie4_hwctx_umq_fini(struct amdxdna_hwctx *hwctx)
|
|
{
|
|
if (hwctx->priv && hwctx->priv->umq_bo)
|
|
amdxdna_gem_put_obj(hwctx->priv->umq_bo);
|
|
}
|
|
|
|
static int aie4_hwctx_umq_init(struct amdxdna_hwctx *hwctx)
|
|
{
|
|
struct amdxdna_hwctx_priv *priv = hwctx->priv;
|
|
struct amdxdna_dev *xdna = hwctx->client->xdna;
|
|
struct amdxdna_gem_obj *umq_bo;
|
|
struct host_queue_header *qhdr;
|
|
int ret;
|
|
|
|
umq_bo = amdxdna_gem_get_obj(hwctx->client, hwctx->umq_bo_hdl, AMDXDNA_BO_SHARE);
|
|
if (!umq_bo) {
|
|
XDNA_ERR(xdna, "cannot find umq_bo handle %d", hwctx->umq_bo_hdl);
|
|
return -ENOENT;
|
|
}
|
|
if (umq_bo->mem.size < sizeof(*qhdr)) {
|
|
XDNA_ERR(xdna, "umq_bo size is too small");
|
|
ret = -EINVAL;
|
|
goto put_umq_bo;
|
|
}
|
|
|
|
/* get kva address for host queue read index and write index */
|
|
qhdr = amdxdna_gem_vmap(umq_bo);
|
|
if (!qhdr) {
|
|
ret = -ENOMEM;
|
|
goto put_umq_bo;
|
|
}
|
|
|
|
priv->umq_bo = umq_bo;
|
|
priv->umq_read_index = &qhdr->read_index;
|
|
priv->umq_write_index = &qhdr->write_index;
|
|
|
|
return 0;
|
|
|
|
put_umq_bo:
|
|
amdxdna_gem_put_obj(umq_bo);
|
|
return ret;
|
|
}
|
|
|
|
int aie4_hwctx_init(struct amdxdna_hwctx *hwctx)
|
|
{
|
|
struct amdxdna_client *client = hwctx->client;
|
|
struct amdxdna_dev *xdna = client->xdna;
|
|
struct amdxdna_hwctx_priv *priv;
|
|
int ret;
|
|
|
|
priv = kzalloc_obj(*priv);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
hwctx->priv = priv;
|
|
|
|
ret = aie4_hwctx_umq_init(hwctx);
|
|
if (ret)
|
|
goto free_priv;
|
|
|
|
ret = aie4_hwctx_create(hwctx);
|
|
if (ret)
|
|
goto umq_fini;
|
|
|
|
XDNA_DBG(xdna, "hwctx %s init completed", hwctx->name);
|
|
return 0;
|
|
|
|
umq_fini:
|
|
aie4_hwctx_umq_fini(hwctx);
|
|
free_priv:
|
|
kfree(priv);
|
|
hwctx->priv = NULL;
|
|
return ret;
|
|
}
|
|
|
|
void aie4_hwctx_fini(struct amdxdna_hwctx *hwctx)
|
|
{
|
|
aie4_hwctx_destroy(hwctx);
|
|
aie4_hwctx_umq_fini(hwctx);
|
|
kfree(hwctx->priv);
|
|
}
|
|
|
|
static inline bool valid_queue_index(u64 read, u64 write, u32 capacity)
|
|
{
|
|
return (write >= read) && ((write - read) <= capacity);
|
|
}
|
|
|
|
static u64 get_read_index(struct amdxdna_hwctx *hwctx)
|
|
{
|
|
u64 wi = READ_ONCE(*hwctx->priv->umq_write_index);
|
|
u64 ri = READ_ONCE(*hwctx->priv->umq_read_index);
|
|
struct amdxdna_dev *xdna = hwctx->client->xdna;
|
|
|
|
/*
|
|
* CERT cannot update read index as uint64 atomically. Driver may read
|
|
* half-updated read index when it has bits in high 32bit. In case read
|
|
* index is not valid, wait for some time and retry once. It should
|
|
* allow CERT to complete the read index update.
|
|
*/
|
|
if (!valid_queue_index(ri, wi, CTX_MAX_CMDS)) {
|
|
XDNA_WARN(xdna, "Invalid index, ri %llu, wi %llu", ri, wi);
|
|
usleep_range(100, 200);
|
|
ri = READ_ONCE(*hwctx->priv->umq_read_index);
|
|
if (!valid_queue_index(ri, wi, CTX_MAX_CMDS)) {
|
|
XDNA_ERR(xdna, "Invalid index after retry, ri %llu, wi %llu", ri, wi);
|
|
ri = 0;
|
|
}
|
|
}
|
|
|
|
return ri;
|
|
}
|
|
|
|
static inline bool check_cmd_done(struct amdxdna_hwctx *hwctx, u64 seq)
|
|
{
|
|
u64 read_idx = get_read_index(hwctx);
|
|
|
|
return read_idx > seq;
|
|
}
|
|
|
|
int aie4_cmd_wait(struct amdxdna_hwctx *hwctx, u64 seq, u32 timeout)
|
|
{
|
|
unsigned long wait_jifs = MAX_SCHEDULE_TIMEOUT;
|
|
struct amdxdna_hwctx_priv *priv = hwctx->priv;
|
|
struct cert_comp *cert_comp = priv->cert_comp;
|
|
long ret;
|
|
|
|
if (timeout)
|
|
wait_jifs = msecs_to_jiffies(timeout);
|
|
|
|
ret = wait_event_interruptible_timeout(cert_comp->waitq,
|
|
(check_cmd_done(hwctx, seq)),
|
|
wait_jifs);
|
|
|
|
if (!ret)
|
|
ret = -ETIME;
|
|
|
|
return ret <= 0 ? ret : 0;
|
|
}
|
|
|
|
int aie4_hwctx_valid_doorbell(struct amdxdna_client *client, u32 vm_pgoff)
|
|
{
|
|
struct amdxdna_hwctx *hwctx;
|
|
unsigned long hwctx_id;
|
|
int idx;
|
|
|
|
idx = srcu_read_lock(&client->hwctx_srcu);
|
|
amdxdna_for_each_hwctx(client, hwctx_id, hwctx) {
|
|
if (vm_pgoff == (hwctx->doorbell_offset >> PAGE_SHIFT)) {
|
|
srcu_read_unlock(&client->hwctx_srcu, idx);
|
|
return 1;
|
|
}
|
|
}
|
|
srcu_read_unlock(&client->hwctx_srcu, idx);
|
|
|
|
return 0;
|
|
}
|