root/drivers/accel/amdxdna/amdxdna_pci_drv.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2022-2024, Advanced Micro Devices, Inc.
 */

#include <drm/amdxdna_accel.h>
#include <drm/drm_accel.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_managed.h>
#include <drm/gpu_scheduler.h>
#include <linux/iommu.h>
#include <linux/pci.h>

#include "amdxdna_ctx.h"
#include "amdxdna_gem.h"
#include "amdxdna_pci_drv.h"
#include "amdxdna_pm.h"

MODULE_FIRMWARE("amdnpu/1502_00/npu.sbin");
MODULE_FIRMWARE("amdnpu/17f0_10/npu.sbin");
MODULE_FIRMWARE("amdnpu/17f0_11/npu.sbin");
MODULE_FIRMWARE("amdnpu/17f0_20/npu.sbin");
MODULE_FIRMWARE("amdnpu/1502_00/npu_7.sbin");
MODULE_FIRMWARE("amdnpu/17f0_10/npu_7.sbin");
MODULE_FIRMWARE("amdnpu/17f0_11/npu_7.sbin");

/*
 * 0.0: Initial version
 * 0.1: Support getting all hardware contexts by DRM_IOCTL_AMDXDNA_GET_ARRAY
 * 0.2: Support getting last error hardware error
 * 0.3: Support firmware debug buffer
 * 0.4: Support getting resource information
 * 0.5: Support getting telemetry data
 * 0.6: Support preemption
 */
#define AMDXDNA_DRIVER_MAJOR            0
#define AMDXDNA_DRIVER_MINOR            6

/*
 * Bind the driver base on (vendor_id, device_id) pair and later use the
 * (device_id, rev_id) pair as a key to select the devices. The devices with
 * same device_id have very similar interface to host driver.
 */
static const struct pci_device_id pci_ids[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x1502) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x17f0) },
        {0}
};

MODULE_DEVICE_TABLE(pci, pci_ids);

static const struct amdxdna_device_id amdxdna_ids[] = {
        { 0x1502, 0x0,  &dev_npu1_info },
        { 0x17f0, 0x10, &dev_npu4_info },
        { 0x17f0, 0x11, &dev_npu5_info },
        { 0x17f0, 0x20, &dev_npu6_info },
        {0}
};

static int amdxdna_drm_open(struct drm_device *ddev, struct drm_file *filp)
{
        struct amdxdna_dev *xdna = to_xdna_dev(ddev);
        struct amdxdna_client *client;
        int ret;

        client = kzalloc_obj(*client);
        if (!client)
                return -ENOMEM;

        client->pid = pid_nr(rcu_access_pointer(filp->pid));
        client->xdna = xdna;

        client->sva = iommu_sva_bind_device(xdna->ddev.dev, current->mm);
        if (IS_ERR(client->sva)) {
                ret = PTR_ERR(client->sva);
                XDNA_ERR(xdna, "SVA bind device failed, ret %d", ret);
                goto failed;
        }
        client->pasid = iommu_sva_get_pasid(client->sva);
        if (client->pasid == IOMMU_PASID_INVALID) {
                XDNA_ERR(xdna, "SVA get pasid failed");
                ret = -ENODEV;
                goto unbind_sva;
        }
        client->mm = current->mm;
        mmgrab(client->mm);
        init_srcu_struct(&client->hwctx_srcu);
        xa_init_flags(&client->hwctx_xa, XA_FLAGS_ALLOC);
        mutex_init(&client->mm_lock);

        mutex_lock(&xdna->dev_lock);
        list_add_tail(&client->node, &xdna->client_list);
        mutex_unlock(&xdna->dev_lock);

        filp->driver_priv = client;
        client->filp = filp;

        XDNA_DBG(xdna, "pid %d opened", client->pid);
        return 0;

unbind_sva:
        iommu_sva_unbind_device(client->sva);
failed:
        kfree(client);

        return ret;
}

static void amdxdna_client_cleanup(struct amdxdna_client *client)
{
        list_del(&client->node);
        amdxdna_hwctx_remove_all(client);
        xa_destroy(&client->hwctx_xa);
        cleanup_srcu_struct(&client->hwctx_srcu);
        mutex_destroy(&client->mm_lock);

        if (client->dev_heap)
                drm_gem_object_put(to_gobj(client->dev_heap));

        iommu_sva_unbind_device(client->sva);
        mmdrop(client->mm);

        kfree(client);
}

static void amdxdna_drm_close(struct drm_device *ddev, struct drm_file *filp)
{
        struct amdxdna_client *client = filp->driver_priv;
        struct amdxdna_dev *xdna = to_xdna_dev(ddev);
        int idx;

        XDNA_DBG(xdna, "closing pid %d", client->pid);

        if (!drm_dev_enter(&xdna->ddev, &idx))
                return;

        mutex_lock(&xdna->dev_lock);
        amdxdna_client_cleanup(client);
        mutex_unlock(&xdna->dev_lock);

        drm_dev_exit(idx);
}

static int amdxdna_drm_get_info_ioctl(struct drm_device *dev, void *data, struct drm_file *filp)
{
        struct amdxdna_client *client = filp->driver_priv;
        struct amdxdna_dev *xdna = to_xdna_dev(dev);
        struct amdxdna_drm_get_info *args = data;
        int ret;

        if (!xdna->dev_info->ops->get_aie_info)
                return -EOPNOTSUPP;

        XDNA_DBG(xdna, "Request parameter %u", args->param);
        mutex_lock(&xdna->dev_lock);
        ret = xdna->dev_info->ops->get_aie_info(client, args);
        mutex_unlock(&xdna->dev_lock);
        return ret;
}

static int amdxdna_drm_get_array_ioctl(struct drm_device *dev, void *data,
                                       struct drm_file *filp)
{
        struct amdxdna_client *client = filp->driver_priv;
        struct amdxdna_dev *xdna = to_xdna_dev(dev);
        struct amdxdna_drm_get_array *args = data;

        if (!xdna->dev_info->ops->get_array)
                return -EOPNOTSUPP;

        if (args->pad || !args->num_element || !args->element_size)
                return -EINVAL;

        guard(mutex)(&xdna->dev_lock);
        return xdna->dev_info->ops->get_array(client, args);
}

static int amdxdna_drm_set_state_ioctl(struct drm_device *dev, void *data, struct drm_file *filp)
{
        struct amdxdna_client *client = filp->driver_priv;
        struct amdxdna_dev *xdna = to_xdna_dev(dev);
        struct amdxdna_drm_set_state *args = data;
        int ret;

        if (!xdna->dev_info->ops->set_aie_state)
                return -EOPNOTSUPP;

        XDNA_DBG(xdna, "Request parameter %u", args->param);
        mutex_lock(&xdna->dev_lock);
        ret = xdna->dev_info->ops->set_aie_state(client, args);
        mutex_unlock(&xdna->dev_lock);

        return ret;
}

static const struct drm_ioctl_desc amdxdna_drm_ioctls[] = {
        /* Context */
        DRM_IOCTL_DEF_DRV(AMDXDNA_CREATE_HWCTX, amdxdna_drm_create_hwctx_ioctl, 0),
        DRM_IOCTL_DEF_DRV(AMDXDNA_DESTROY_HWCTX, amdxdna_drm_destroy_hwctx_ioctl, 0),
        DRM_IOCTL_DEF_DRV(AMDXDNA_CONFIG_HWCTX, amdxdna_drm_config_hwctx_ioctl, 0),
        /* BO */
        DRM_IOCTL_DEF_DRV(AMDXDNA_CREATE_BO, amdxdna_drm_create_bo_ioctl, 0),
        DRM_IOCTL_DEF_DRV(AMDXDNA_GET_BO_INFO, amdxdna_drm_get_bo_info_ioctl, 0),
        DRM_IOCTL_DEF_DRV(AMDXDNA_SYNC_BO, amdxdna_drm_sync_bo_ioctl, 0),
        /* Execution */
        DRM_IOCTL_DEF_DRV(AMDXDNA_EXEC_CMD, amdxdna_drm_submit_cmd_ioctl, 0),
        /* AIE hardware */
        DRM_IOCTL_DEF_DRV(AMDXDNA_GET_INFO, amdxdna_drm_get_info_ioctl, 0),
        DRM_IOCTL_DEF_DRV(AMDXDNA_GET_ARRAY, amdxdna_drm_get_array_ioctl, 0),
        DRM_IOCTL_DEF_DRV(AMDXDNA_SET_STATE, amdxdna_drm_set_state_ioctl, DRM_ROOT_ONLY),
};

static const struct file_operations amdxdna_fops = {
        .owner          = THIS_MODULE,
        .open           = accel_open,
        .release        = drm_release,
        .unlocked_ioctl = drm_ioctl,
        .compat_ioctl   = drm_compat_ioctl,
        .poll           = drm_poll,
        .read           = drm_read,
        .llseek         = noop_llseek,
        .mmap           = drm_gem_mmap,
        .fop_flags      = FOP_UNSIGNED_OFFSET,
};

const struct drm_driver amdxdna_drm_drv = {
        .driver_features = DRIVER_GEM | DRIVER_COMPUTE_ACCEL |
                DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE,
        .fops = &amdxdna_fops,
        .name = "amdxdna_accel_driver",
        .desc = "AMD XDNA DRM implementation",
        .major = AMDXDNA_DRIVER_MAJOR,
        .minor = AMDXDNA_DRIVER_MINOR,
        .open = amdxdna_drm_open,
        .postclose = amdxdna_drm_close,
        .ioctls = amdxdna_drm_ioctls,
        .num_ioctls = ARRAY_SIZE(amdxdna_drm_ioctls),

        .gem_create_object = amdxdna_gem_create_object_cb,
        .gem_prime_import = amdxdna_gem_prime_import,
};

static const struct amdxdna_dev_info *
amdxdna_get_dev_info(struct pci_dev *pdev)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(amdxdna_ids); i++) {
                if (pdev->device == amdxdna_ids[i].device &&
                    pdev->revision == amdxdna_ids[i].revision)
                        return amdxdna_ids[i].dev_info;
        }
        return NULL;
}

static int amdxdna_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
        struct device *dev = &pdev->dev;
        struct amdxdna_dev *xdna;
        int ret;

        xdna = devm_drm_dev_alloc(dev, &amdxdna_drm_drv, typeof(*xdna), ddev);
        if (IS_ERR(xdna))
                return PTR_ERR(xdna);

        xdna->dev_info = amdxdna_get_dev_info(pdev);
        if (!xdna->dev_info)
                return -ENODEV;

        drmm_mutex_init(&xdna->ddev, &xdna->dev_lock);
        init_rwsem(&xdna->notifier_lock);
        INIT_LIST_HEAD(&xdna->client_list);
        pci_set_drvdata(pdev, xdna);

        if (IS_ENABLED(CONFIG_LOCKDEP)) {
                fs_reclaim_acquire(GFP_KERNEL);
                might_lock(&xdna->notifier_lock);
                fs_reclaim_release(GFP_KERNEL);
        }

        xdna->notifier_wq = alloc_ordered_workqueue("notifier_wq", WQ_MEM_RECLAIM);
        if (!xdna->notifier_wq)
                return -ENOMEM;

        mutex_lock(&xdna->dev_lock);
        ret = xdna->dev_info->ops->init(xdna);
        mutex_unlock(&xdna->dev_lock);
        if (ret) {
                XDNA_ERR(xdna, "Hardware init failed, ret %d", ret);
                goto destroy_notifier_wq;
        }

        ret = amdxdna_sysfs_init(xdna);
        if (ret) {
                XDNA_ERR(xdna, "Create amdxdna attrs failed: %d", ret);
                goto failed_dev_fini;
        }

        ret = drm_dev_register(&xdna->ddev, 0);
        if (ret) {
                XDNA_ERR(xdna, "DRM register failed, ret %d", ret);
                goto failed_sysfs_fini;
        }

        return 0;

failed_sysfs_fini:
        amdxdna_sysfs_fini(xdna);
failed_dev_fini:
        mutex_lock(&xdna->dev_lock);
        xdna->dev_info->ops->fini(xdna);
        mutex_unlock(&xdna->dev_lock);
destroy_notifier_wq:
        destroy_workqueue(xdna->notifier_wq);
        return ret;
}

static void amdxdna_remove(struct pci_dev *pdev)
{
        struct amdxdna_dev *xdna = pci_get_drvdata(pdev);
        struct amdxdna_client *client;

        destroy_workqueue(xdna->notifier_wq);

        drm_dev_unplug(&xdna->ddev);
        amdxdna_sysfs_fini(xdna);

        mutex_lock(&xdna->dev_lock);
        client = list_first_entry_or_null(&xdna->client_list,
                                          struct amdxdna_client, node);
        while (client) {
                amdxdna_client_cleanup(client);

                client = list_first_entry_or_null(&xdna->client_list,
                                                  struct amdxdna_client, node);
        }

        xdna->dev_info->ops->fini(xdna);
        mutex_unlock(&xdna->dev_lock);
}

static const struct dev_pm_ops amdxdna_pm_ops = {
        SYSTEM_SLEEP_PM_OPS(amdxdna_pm_suspend, amdxdna_pm_resume)
        RUNTIME_PM_OPS(amdxdna_pm_suspend, amdxdna_pm_resume, NULL)
};

static struct pci_driver amdxdna_pci_driver = {
        .name = KBUILD_MODNAME,
        .id_table = pci_ids,
        .probe = amdxdna_probe,
        .remove = amdxdna_remove,
        .driver.pm = &amdxdna_pm_ops,
};

module_pci_driver(amdxdna_pci_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("XRT Team <runtimeca39d@amd.com>");
MODULE_DESCRIPTION("amdxdna driver");