Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_ACCEL_ROCKET
tristate "Rocket (support for Rockchip NPUs)"
depends on DRM_ACCEL
depends on (ARCH_ROCKCHIP && ARM64) || COMPILE_TEST
depends on ROCKCHIP_IOMMU || COMPILE_TEST
depends on MMU
select DRM_SCHED
select DRM_GEM_SHMEM_HELPER
help
Choose this option if you have a Rockchip SoC that contains a
compatible Neural Processing Unit (NPU), such as the RK3588. Called by
Rockchip either RKNN or RKNPU, it accelerates inference of neural
networks.
The interface exposed to userspace is described in
include/uapi/drm/rocket_accel.h and is used by the Rocket userspace
driver in Mesa3D.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called rocket.
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_ACCEL_ROCKET) := rocket.o
rocket-y := \
rocket_core.o \
rocket_device.o \
rocket_drv.o \
rocket_gem.o \
rocket_job.o
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include "rocket_core.h"
#include "rocket_job.h"
int rocket_core_init(struct rocket_core *core)
{
struct device *dev = core->dev;
struct platform_device *pdev = to_platform_device(dev);
u32 version;
int err = 0;
core->resets[0].id = "srst_a";
core->resets[1].id = "srst_h";
err = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(core->resets),
core->resets);
if (err)
return dev_err_probe(dev, err, "failed to get resets for core %d\n", core->index);
err = devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks);
if (err)
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
core->pc_iomem = devm_platform_ioremap_resource_byname(pdev, "pc");
if (IS_ERR(core->pc_iomem)) {
dev_err(dev, "couldn't find PC registers %ld\n", PTR_ERR(core->pc_iomem));
return PTR_ERR(core->pc_iomem);
}
core->cna_iomem = devm_platform_ioremap_resource_byname(pdev, "cna");
if (IS_ERR(core->cna_iomem)) {
dev_err(dev, "couldn't find CNA registers %ld\n", PTR_ERR(core->cna_iomem));
return PTR_ERR(core->cna_iomem);
}
core->core_iomem = devm_platform_ioremap_resource_byname(pdev, "core");
if (IS_ERR(core->core_iomem)) {
dev_err(dev, "couldn't find CORE registers %ld\n", PTR_ERR(core->core_iomem));
return PTR_ERR(core->core_iomem);
}
dma_set_max_seg_size(dev, UINT_MAX);
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));
if (err)
return err;
core->iommu_group = iommu_group_get(dev);
err = rocket_job_init(core);
if (err) {
iommu_group_put(core->iommu_group);
core->iommu_group = NULL;
return err;
}
pm_runtime_use_autosuspend(dev);
/*
* As this NPU will be most often used as part of a media pipeline that
* ends presenting in a display, choose 50 ms (~3 frames at 60Hz) as an
* autosuspend delay as that will keep the device powered up while the
* pipeline is running.
*/
pm_runtime_set_autosuspend_delay(dev, 50);
pm_runtime_enable(dev);
err = pm_runtime_resume_and_get(dev);
if (err) {
rocket_core_fini(core);
return err;
}
version = rocket_pc_readl(core, VERSION);
version += rocket_pc_readl(core, VERSION_NUM) & 0xffff;
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
dev_info(dev, "Rockchip NPU core %d version: %d\n", core->index, version);
return 0;
}
void rocket_core_fini(struct rocket_core *core)
{
pm_runtime_dont_use_autosuspend(core->dev);
pm_runtime_disable(core->dev);
iommu_group_put(core->iommu_group);
core->iommu_group = NULL;
rocket_job_fini(core);
}
void rocket_core_reset(struct rocket_core *core)
{
reset_control_bulk_assert(ARRAY_SIZE(core->resets), core->resets);
udelay(10);
reset_control_bulk_deassert(ARRAY_SIZE(core->resets), core->resets);
}
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#ifndef __ROCKET_CORE_H__
#define __ROCKET_CORE_H__
#include <drm/gpu_scheduler.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/mutex_types.h>
#include <linux/reset.h>
#include "rocket_registers.h"
#define rocket_pc_readl(core, reg) \
readl((core)->pc_iomem + (REG_PC_##reg))
#define rocket_pc_writel(core, reg, value) \
writel(value, (core)->pc_iomem + (REG_PC_##reg))
#define rocket_cna_readl(core, reg) \
readl((core)->cna_iomem + (REG_CNA_##reg) - REG_CNA_S_STATUS)
#define rocket_cna_writel(core, reg, value) \
writel(value, (core)->cna_iomem + (REG_CNA_##reg) - REG_CNA_S_STATUS)
#define rocket_core_readl(core, reg) \
readl((core)->core_iomem + (REG_CORE_##reg) - REG_CORE_S_STATUS)
#define rocket_core_writel(core, reg, value) \
writel(value, (core)->core_iomem + (REG_CORE_##reg) - REG_CORE_S_STATUS)
struct rocket_core {
struct device *dev;
struct rocket_device *rdev;
unsigned int index;
int irq;
void __iomem *pc_iomem;
void __iomem *cna_iomem;
void __iomem *core_iomem;
struct clk_bulk_data clks[4];
struct reset_control_bulk_data resets[2];
struct iommu_group *iommu_group;
struct mutex job_lock;
struct rocket_job *in_flight_job;
spinlock_t fence_lock;
struct {
struct workqueue_struct *wq;
struct work_struct work;
atomic_t pending;
} reset;
struct drm_gpu_scheduler sched;
u64 fence_context;
u64 emit_seqno;
};
int rocket_core_init(struct rocket_core *core);
void rocket_core_fini(struct rocket_core *core);
void rocket_core_reset(struct rocket_core *core);
#endif
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#include <drm/drm_drv.h>
#include <linux/array_size.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include "rocket_device.h"
struct rocket_device *rocket_device_init(struct platform_device *pdev,
const struct drm_driver *rocket_drm_driver)
{
struct device *dev = &pdev->dev;
struct device_node *core_node;
struct rocket_device *rdev;
struct drm_device *ddev;
unsigned int num_cores = 0;
int err;
rdev = devm_drm_dev_alloc(dev, rocket_drm_driver, struct rocket_device, ddev);
if (IS_ERR(rdev))
return rdev;
ddev = &rdev->ddev;
dev_set_drvdata(dev, rdev);
for_each_compatible_node(core_node, NULL, "rockchip,rk3588-rknn-core")
if (of_device_is_available(core_node))
num_cores++;
rdev->cores = devm_kcalloc(dev, num_cores, sizeof(*rdev->cores), GFP_KERNEL);
if (!rdev->cores)
return ERR_PTR(-ENOMEM);
dma_set_max_seg_size(dev, UINT_MAX);
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));
if (err)
return ERR_PTR(err);
err = devm_mutex_init(dev, &rdev->sched_lock);
if (err)
return ERR_PTR(-ENOMEM);
err = drm_dev_register(ddev, 0);
if (err)
return ERR_PTR(err);
return rdev;
}
void rocket_device_fini(struct rocket_device *rdev)
{
WARN_ON(rdev->num_cores > 0);
drm_dev_unregister(&rdev->ddev);
}
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#ifndef __ROCKET_DEVICE_H__
#define __ROCKET_DEVICE_H__
#include <drm/drm_device.h>
#include <linux/clk.h>
#include <linux/container_of.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
#include "rocket_core.h"
struct rocket_device {
struct drm_device ddev;
struct mutex sched_lock;
struct rocket_core *cores;
unsigned int num_cores;
};
struct rocket_device *rocket_device_init(struct platform_device *pdev,
const struct drm_driver *rocket_drm_driver);
void rocket_device_fini(struct rocket_device *rdev);
#define to_rocket_device(drm_dev) \
((struct rocket_device *)(container_of((drm_dev), struct rocket_device, ddev)))
#endif /* __ROCKET_DEVICE_H__ */
@@ -0,0 +1,304 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#include <drm/drm_accel.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
#include <drm/rocket_accel.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/iommu.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "rocket_device.h"
#include "rocket_drv.h"
#include "rocket_gem.h"
#include "rocket_job.h"
/*
* Facade device, used to expose a single DRM device to userspace, that
* schedules jobs to any RKNN cores in the system.
*/
static struct platform_device *drm_dev;
static struct rocket_device *rdev;
static void
rocket_iommu_domain_destroy(struct kref *kref)
{
struct rocket_iommu_domain *domain = container_of(kref, struct rocket_iommu_domain, kref);
iommu_domain_free(domain->domain);
domain->domain = NULL;
kfree(domain);
}
static struct rocket_iommu_domain*
rocket_iommu_domain_create(struct device *dev)
{
struct rocket_iommu_domain *domain = kmalloc_obj(*domain);
void *err;
if (!domain)
return ERR_PTR(-ENOMEM);
domain->domain = iommu_paging_domain_alloc(dev);
if (IS_ERR(domain->domain)) {
err = ERR_CAST(domain->domain);
kfree(domain);
return err;
}
kref_init(&domain->kref);
return domain;
}
struct rocket_iommu_domain *
rocket_iommu_domain_get(struct rocket_file_priv *rocket_priv)
{
kref_get(&rocket_priv->domain->kref);
return rocket_priv->domain;
}
void
rocket_iommu_domain_put(struct rocket_iommu_domain *domain)
{
kref_put(&domain->kref, rocket_iommu_domain_destroy);
}
static int
rocket_open(struct drm_device *dev, struct drm_file *file)
{
struct rocket_device *rdev = to_rocket_device(dev);
struct rocket_file_priv *rocket_priv;
u64 start, end;
int ret;
if (!try_module_get(THIS_MODULE))
return -EINVAL;
rocket_priv = kzalloc_obj(*rocket_priv);
if (!rocket_priv) {
ret = -ENOMEM;
goto err_put_mod;
}
rocket_priv->rdev = rdev;
rocket_priv->domain = rocket_iommu_domain_create(rdev->cores[0].dev);
if (IS_ERR(rocket_priv->domain)) {
ret = PTR_ERR(rocket_priv->domain);
goto err_free;
}
file->driver_priv = rocket_priv;
start = rocket_priv->domain->domain->geometry.aperture_start;
end = rocket_priv->domain->domain->geometry.aperture_end;
drm_mm_init(&rocket_priv->mm, start, end - start + 1);
mutex_init(&rocket_priv->mm_lock);
ret = rocket_job_open(rocket_priv);
if (ret)
goto err_mm_takedown;
return 0;
err_mm_takedown:
mutex_destroy(&rocket_priv->mm_lock);
drm_mm_takedown(&rocket_priv->mm);
rocket_iommu_domain_put(rocket_priv->domain);
err_free:
kfree(rocket_priv);
err_put_mod:
module_put(THIS_MODULE);
return ret;
}
static void
rocket_postclose(struct drm_device *dev, struct drm_file *file)
{
struct rocket_file_priv *rocket_priv = file->driver_priv;
rocket_job_close(rocket_priv);
mutex_destroy(&rocket_priv->mm_lock);
drm_mm_takedown(&rocket_priv->mm);
rocket_iommu_domain_put(rocket_priv->domain);
kfree(rocket_priv);
module_put(THIS_MODULE);
}
static const struct drm_ioctl_desc rocket_drm_driver_ioctls[] = {
#define ROCKET_IOCTL(n, func) \
DRM_IOCTL_DEF_DRV(ROCKET_##n, rocket_ioctl_##func, 0)
ROCKET_IOCTL(CREATE_BO, create_bo),
ROCKET_IOCTL(SUBMIT, submit),
ROCKET_IOCTL(PREP_BO, prep_bo),
ROCKET_IOCTL(FINI_BO, fini_bo),
};
DEFINE_DRM_ACCEL_FOPS(rocket_accel_driver_fops);
/*
* Rocket driver version:
* - 1.0 - initial interface
*/
static const struct drm_driver rocket_drm_driver = {
.driver_features = DRIVER_COMPUTE_ACCEL | DRIVER_GEM,
.open = rocket_open,
.postclose = rocket_postclose,
.gem_create_object = rocket_gem_create_object,
.ioctls = rocket_drm_driver_ioctls,
.num_ioctls = ARRAY_SIZE(rocket_drm_driver_ioctls),
.fops = &rocket_accel_driver_fops,
.name = "rocket",
.desc = "rocket DRM",
};
static int rocket_probe(struct platform_device *pdev)
{
int ret;
if (rdev == NULL) {
/* First core probing, initialize DRM device. */
rdev = rocket_device_init(drm_dev, &rocket_drm_driver);
if (IS_ERR(rdev)) {
dev_err(&pdev->dev, "failed to initialize rocket device\n");
return PTR_ERR(rdev);
}
}
unsigned int core = rdev->num_cores;
dev_set_drvdata(&pdev->dev, rdev);
rdev->cores[core].rdev = rdev;
rdev->cores[core].dev = &pdev->dev;
rdev->cores[core].index = core;
rdev->num_cores++;
ret = rocket_core_init(&rdev->cores[core]);
if (ret) {
rdev->num_cores--;
if (rdev->num_cores == 0) {
rocket_device_fini(rdev);
rdev = NULL;
}
}
return ret;
}
static int find_core_for_dev(struct device *dev);
static void rocket_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int core = find_core_for_dev(dev);
if (core < 0)
return;
rocket_core_fini(&rdev->cores[core]);
rdev->num_cores--;
if (rdev->num_cores == 0) {
/* Last core removed, deinitialize DRM device. */
rocket_device_fini(rdev);
rdev = NULL;
}
}
static const struct of_device_id dt_match[] = {
{ .compatible = "rockchip,rk3588-rknn-core" },
{}
};
MODULE_DEVICE_TABLE(of, dt_match);
static int find_core_for_dev(struct device *dev)
{
struct rocket_device *rdev = dev_get_drvdata(dev);
for (unsigned int core = 0; core < rdev->num_cores; core++) {
if (dev == rdev->cores[core].dev)
return core;
}
return -1;
}
static int rocket_device_runtime_resume(struct device *dev)
{
struct rocket_device *rdev = dev_get_drvdata(dev);
int core = find_core_for_dev(dev);
int err = 0;
if (core < 0)
return -ENODEV;
err = clk_bulk_prepare_enable(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
if (err) {
dev_err(dev, "failed to enable (%d) clocks for core %d\n", err, core);
return err;
}
return 0;
}
static int rocket_device_runtime_suspend(struct device *dev)
{
struct rocket_device *rdev = dev_get_drvdata(dev);
int core = find_core_for_dev(dev);
if (core < 0)
return -ENODEV;
if (!rocket_job_is_idle(&rdev->cores[core]))
return -EBUSY;
clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
return 0;
}
EXPORT_GPL_DEV_PM_OPS(rocket_pm_ops) = {
RUNTIME_PM_OPS(rocket_device_runtime_suspend, rocket_device_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver rocket_driver = {
.probe = rocket_probe,
.remove = rocket_remove,
.driver = {
.name = "rocket",
.pm = pm_ptr(&rocket_pm_ops),
.of_match_table = dt_match,
},
};
static int __init rocket_register(void)
{
drm_dev = platform_device_register_simple("rknn", -1, NULL, 0);
if (IS_ERR(drm_dev))
return PTR_ERR(drm_dev);
return platform_driver_register(&rocket_driver);
}
static void __exit rocket_unregister(void)
{
platform_driver_unregister(&rocket_driver);
platform_device_unregister(drm_dev);
}
module_init(rocket_register);
module_exit(rocket_unregister);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DRM driver for the Rockchip NPU IP");
MODULE_AUTHOR("Tomeu Vizoso");
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#ifndef __ROCKET_DRV_H__
#define __ROCKET_DRV_H__
#include <drm/drm_mm.h>
#include <drm/gpu_scheduler.h>
#include "rocket_device.h"
extern const struct dev_pm_ops rocket_pm_ops;
struct rocket_iommu_domain {
struct iommu_domain *domain;
struct kref kref;
};
struct rocket_file_priv {
struct rocket_device *rdev;
struct rocket_iommu_domain *domain;
struct drm_mm mm;
struct mutex mm_lock;
struct drm_sched_entity sched_entity;
};
struct rocket_iommu_domain *rocket_iommu_domain_get(struct rocket_file_priv *rocket_priv);
void rocket_iommu_domain_put(struct rocket_iommu_domain *domain);
#endif
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include <drm/drm_utils.h>
#include <drm/rocket_accel.h>
#include <linux/dma-mapping.h>
#include <linux/iommu.h>
#include "rocket_drv.h"
#include "rocket_gem.h"
static void rocket_gem_bo_free(struct drm_gem_object *obj)
{
struct rocket_gem_object *bo = to_rocket_bo(obj);
struct rocket_file_priv *rocket_priv = bo->driver_priv;
size_t unmapped;
drm_WARN_ON(obj->dev, refcount_read(&bo->base.pages_use_count) > 1);
unmapped = iommu_unmap(bo->domain->domain, bo->mm.start, bo->size);
drm_WARN_ON(obj->dev, unmapped != bo->size);
mutex_lock(&rocket_priv->mm_lock);
drm_mm_remove_node(&bo->mm);
mutex_unlock(&rocket_priv->mm_lock);
rocket_iommu_domain_put(bo->domain);
bo->domain = NULL;
drm_gem_shmem_free(&bo->base);
}
static const struct drm_gem_object_funcs rocket_gem_funcs = {
.free = rocket_gem_bo_free,
.print_info = drm_gem_shmem_object_print_info,
.pin = drm_gem_shmem_object_pin,
.unpin = drm_gem_shmem_object_unpin,
.get_sg_table = drm_gem_shmem_object_get_sg_table,
.vmap = drm_gem_shmem_object_vmap,
.vunmap = drm_gem_shmem_object_vunmap,
.mmap = drm_gem_shmem_object_mmap,
.vm_ops = &drm_gem_shmem_vm_ops,
};
struct drm_gem_object *rocket_gem_create_object(struct drm_device *dev, size_t size)
{
struct rocket_gem_object *obj;
obj = kzalloc_obj(*obj);
if (!obj)
return ERR_PTR(-ENOMEM);
obj->base.base.funcs = &rocket_gem_funcs;
return &obj->base.base;
}
int rocket_ioctl_create_bo(struct drm_device *dev, void *data, struct drm_file *file)
{
struct rocket_file_priv *rocket_priv = file->driver_priv;
struct drm_rocket_create_bo *args = data;
struct drm_gem_shmem_object *shmem_obj;
struct rocket_gem_object *rkt_obj;
struct drm_gem_object *gem_obj;
struct sg_table *sgt;
int ret;
shmem_obj = drm_gem_shmem_create(dev, args->size);
if (IS_ERR(shmem_obj))
return PTR_ERR(shmem_obj);
gem_obj = &shmem_obj->base;
rkt_obj = to_rocket_bo(gem_obj);
rkt_obj->driver_priv = rocket_priv;
rkt_obj->domain = rocket_iommu_domain_get(rocket_priv);
rkt_obj->size = args->size;
rkt_obj->offset = 0;
sgt = drm_gem_shmem_get_pages_sgt(shmem_obj);
if (IS_ERR(sgt)) {
ret = PTR_ERR(sgt);
goto err;
}
mutex_lock(&rocket_priv->mm_lock);
ret = drm_mm_insert_node_generic(&rocket_priv->mm, &rkt_obj->mm,
rkt_obj->size, PAGE_SIZE,
0, 0);
mutex_unlock(&rocket_priv->mm_lock);
if (ret)
goto err;
ret = iommu_map_sgtable(rocket_priv->domain->domain,
rkt_obj->mm.start,
shmem_obj->sgt,
IOMMU_READ | IOMMU_WRITE);
if (ret < 0 || ret < args->size) {
drm_err(dev, "failed to map buffer: size=%d request_size=%u\n",
ret, args->size);
ret = -ENOMEM;
goto err_remove_node;
}
/* iommu_map_sgtable might have aligned the size */
rkt_obj->size = ret;
args->offset = drm_vma_node_offset_addr(&gem_obj->vma_node);
args->dma_address = rkt_obj->mm.start;
ret = drm_gem_handle_create(file, gem_obj, &args->handle);
if (ret)
goto err_unmap;
drm_gem_object_put(gem_obj);
return 0;
err_unmap:
iommu_unmap(rocket_priv->domain->domain,
rkt_obj->mm.start, rkt_obj->size);
err_remove_node:
mutex_lock(&rocket_priv->mm_lock);
drm_mm_remove_node(&rkt_obj->mm);
mutex_unlock(&rocket_priv->mm_lock);
err:
drm_gem_shmem_object_free(gem_obj);
return ret;
}
int rocket_ioctl_prep_bo(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_rocket_prep_bo *args = data;
unsigned long timeout = drm_timeout_abs_to_jiffies(args->timeout_ns);
struct drm_gem_object *gem_obj;
struct drm_gem_shmem_object *shmem_obj;
long ret = 0;
if (args->reserved != 0) {
drm_dbg(dev, "Reserved field in drm_rocket_prep_bo struct should be 0.\n");
return -EINVAL;
}
gem_obj = drm_gem_object_lookup(file, args->handle);
if (!gem_obj)
return -ENOENT;
ret = dma_resv_wait_timeout(gem_obj->resv, DMA_RESV_USAGE_WRITE, true, timeout);
if (!ret)
ret = timeout ? -ETIMEDOUT : -EBUSY;
else if (ret > 0)
ret = 0;
shmem_obj = &to_rocket_bo(gem_obj)->base;
dma_sync_sgtable_for_cpu(dev->dev, shmem_obj->sgt, DMA_BIDIRECTIONAL);
drm_gem_object_put(gem_obj);
return ret;
}
int rocket_ioctl_fini_bo(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_rocket_fini_bo *args = data;
struct drm_gem_shmem_object *shmem_obj;
struct rocket_gem_object *rkt_obj;
struct drm_gem_object *gem_obj;
if (args->reserved != 0) {
drm_dbg(dev, "Reserved field in drm_rocket_fini_bo struct should be 0.\n");
return -EINVAL;
}
gem_obj = drm_gem_object_lookup(file, args->handle);
if (!gem_obj)
return -ENOENT;
rkt_obj = to_rocket_bo(gem_obj);
shmem_obj = &rkt_obj->base;
dma_sync_sgtable_for_device(dev->dev, shmem_obj->sgt, DMA_BIDIRECTIONAL);
drm_gem_object_put(gem_obj);
return 0;
}
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#ifndef __ROCKET_GEM_H__
#define __ROCKET_GEM_H__
#include <drm/drm_gem_shmem_helper.h>
struct rocket_gem_object {
struct drm_gem_shmem_object base;
struct rocket_file_priv *driver_priv;
struct rocket_iommu_domain *domain;
struct drm_mm_node mm;
size_t size;
u32 offset;
};
struct drm_gem_object *rocket_gem_create_object(struct drm_device *dev, size_t size);
int rocket_ioctl_create_bo(struct drm_device *dev, void *data, struct drm_file *file);
int rocket_ioctl_prep_bo(struct drm_device *dev, void *data, struct drm_file *file);
int rocket_ioctl_fini_bo(struct drm_device *dev, void *data, struct drm_file *file);
static inline
struct rocket_gem_object *to_rocket_bo(struct drm_gem_object *obj)
{
return container_of(to_drm_gem_shmem_obj(obj), struct rocket_gem_object, base);
}
#endif
@@ -0,0 +1,636 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/* Copyright 2019 Collabora ltd. */
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#include <drm/drm_print.h>
#include <drm/drm_file.h>
#include <drm/drm_gem.h>
#include <drm/rocket_accel.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "rocket_core.h"
#include "rocket_device.h"
#include "rocket_drv.h"
#include "rocket_job.h"
#include "rocket_registers.h"
#define JOB_TIMEOUT_MS 500
static struct rocket_job *
to_rocket_job(struct drm_sched_job *sched_job)
{
return container_of(sched_job, struct rocket_job, base);
}
static const char *rocket_fence_get_driver_name(struct dma_fence *fence)
{
return "rocket";
}
static const char *rocket_fence_get_timeline_name(struct dma_fence *fence)
{
return "rockchip-npu";
}
static const struct dma_fence_ops rocket_fence_ops = {
.get_driver_name = rocket_fence_get_driver_name,
.get_timeline_name = rocket_fence_get_timeline_name,
};
static struct dma_fence *rocket_fence_create(struct rocket_core *core)
{
struct dma_fence *fence;
fence = kzalloc_obj(*fence);
if (!fence)
return ERR_PTR(-ENOMEM);
dma_fence_init(fence, &rocket_fence_ops, &core->fence_lock,
core->fence_context, ++core->emit_seqno);
return fence;
}
static int
rocket_copy_tasks(struct drm_device *dev,
struct drm_file *file_priv,
struct drm_rocket_job *job,
struct rocket_job *rjob)
{
int ret = 0;
if (job->task_struct_size < sizeof(struct drm_rocket_task))
return -EINVAL;
rjob->task_count = job->task_count;
if (!rjob->task_count)
return 0;
rjob->tasks = kvmalloc_objs(*rjob->tasks, job->task_count);
if (!rjob->tasks) {
drm_dbg(dev, "Failed to allocate task array\n");
return -ENOMEM;
}
for (int i = 0; i < rjob->task_count; i++) {
struct drm_rocket_task task = {0};
if (copy_from_user(&task,
u64_to_user_ptr(job->tasks) + i * job->task_struct_size,
sizeof(task))) {
drm_dbg(dev, "Failed to copy incoming tasks\n");
ret = -EFAULT;
goto fail;
}
if (task.regcmd_count == 0) {
drm_dbg(dev, "regcmd_count field in drm_rocket_task should be > 0.\n");
ret = -EINVAL;
goto fail;
}
rjob->tasks[i].regcmd = task.regcmd;
rjob->tasks[i].regcmd_count = task.regcmd_count;
}
return 0;
fail:
kvfree(rjob->tasks);
return ret;
}
static void rocket_job_hw_submit(struct rocket_core *core, struct rocket_job *job)
{
struct rocket_task *task;
unsigned int extra_bit;
/* Don't queue the job if a reset is in progress */
if (atomic_read(&core->reset.pending))
return;
/* GO ! */
task = &job->tasks[job->next_task_idx];
job->next_task_idx++;
rocket_pc_writel(core, BASE_ADDRESS, 0x1);
/* From rknpu, in the TRM this bit is marked as reserved */
extra_bit = 0x10000000 * core->index;
rocket_cna_writel(core, S_POINTER, CNA_S_POINTER_POINTER_PP_EN(1) |
CNA_S_POINTER_EXECUTER_PP_EN(1) |
CNA_S_POINTER_POINTER_PP_MODE(1) |
extra_bit);
rocket_core_writel(core, S_POINTER, CORE_S_POINTER_POINTER_PP_EN(1) |
CORE_S_POINTER_EXECUTER_PP_EN(1) |
CORE_S_POINTER_POINTER_PP_MODE(1) |
extra_bit);
rocket_pc_writel(core, BASE_ADDRESS, task->regcmd);
rocket_pc_writel(core, REGISTER_AMOUNTS,
PC_REGISTER_AMOUNTS_PC_DATA_AMOUNT((task->regcmd_count + 1) / 2 - 1));
rocket_pc_writel(core, INTERRUPT_MASK, PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
rocket_pc_writel(core, INTERRUPT_CLEAR, PC_INTERRUPT_CLEAR_DPU_0 | PC_INTERRUPT_CLEAR_DPU_1);
rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
PC_TASK_CON_TASK_COUNT_CLEAR(1) |
PC_TASK_CON_TASK_NUMBER(1) |
PC_TASK_CON_TASK_PP_EN(1));
rocket_pc_writel(core, TASK_DMA_BASE_ADDR, PC_TASK_DMA_BASE_ADDR_DMA_BASE_ADDR(0x0));
rocket_pc_writel(core, OPERATION_ENABLE, PC_OPERATION_ENABLE_OP_EN(1));
dev_dbg(core->dev, "Submitted regcmd at 0x%llx to core %d", task->regcmd, core->index);
}
static int rocket_acquire_object_fences(struct drm_gem_object **bos,
int bo_count,
struct drm_sched_job *job,
bool is_write)
{
int i, ret;
for (i = 0; i < bo_count; i++) {
ret = dma_resv_reserve_fences(bos[i]->resv, 1);
if (ret)
return ret;
ret = drm_sched_job_add_implicit_dependencies(job, bos[i],
is_write);
if (ret)
return ret;
}
return 0;
}
static void rocket_attach_object_fences(struct drm_gem_object **bos,
int bo_count,
struct dma_fence *fence)
{
int i;
for (i = 0; i < bo_count; i++)
dma_resv_add_fence(bos[i]->resv, fence, DMA_RESV_USAGE_WRITE);
}
static int rocket_job_push(struct rocket_job *job)
{
struct rocket_device *rdev = job->rdev;
struct drm_gem_object **bos;
struct ww_acquire_ctx acquire_ctx;
int ret = 0;
bos = kvmalloc_array(job->in_bo_count + job->out_bo_count, sizeof(void *),
GFP_KERNEL);
memcpy(bos, job->in_bos, job->in_bo_count * sizeof(void *));
memcpy(&bos[job->in_bo_count], job->out_bos, job->out_bo_count * sizeof(void *));
ret = drm_gem_lock_reservations(bos, job->in_bo_count + job->out_bo_count, &acquire_ctx);
if (ret)
goto err;
scoped_guard(mutex, &rdev->sched_lock) {
drm_sched_job_arm(&job->base);
job->inference_done_fence = dma_fence_get(&job->base.s_fence->finished);
ret = rocket_acquire_object_fences(job->in_bos, job->in_bo_count, &job->base, false);
if (ret)
goto err_unlock;
ret = rocket_acquire_object_fences(job->out_bos, job->out_bo_count, &job->base, true);
if (ret)
goto err_unlock;
kref_get(&job->refcount); /* put by scheduler job completion */
drm_sched_entity_push_job(&job->base);
}
rocket_attach_object_fences(job->out_bos, job->out_bo_count, job->inference_done_fence);
err_unlock:
drm_gem_unlock_reservations(bos, job->in_bo_count + job->out_bo_count, &acquire_ctx);
err:
kvfree(bos);
return ret;
}
static void rocket_job_cleanup(struct kref *ref)
{
struct rocket_job *job = container_of(ref, struct rocket_job,
refcount);
unsigned int i;
rocket_iommu_domain_put(job->domain);
dma_fence_put(job->done_fence);
dma_fence_put(job->inference_done_fence);
if (job->in_bos) {
for (i = 0; i < job->in_bo_count; i++)
drm_gem_object_put(job->in_bos[i]);
kvfree(job->in_bos);
}
if (job->out_bos) {
for (i = 0; i < job->out_bo_count; i++)
drm_gem_object_put(job->out_bos[i]);
kvfree(job->out_bos);
}
kvfree(job->tasks);
kfree(job);
}
static void rocket_job_put(struct rocket_job *job)
{
kref_put(&job->refcount, rocket_job_cleanup);
}
static void rocket_job_free(struct drm_sched_job *sched_job)
{
struct rocket_job *job = to_rocket_job(sched_job);
drm_sched_job_cleanup(sched_job);
rocket_job_put(job);
}
static struct rocket_core *sched_to_core(struct rocket_device *rdev,
struct drm_gpu_scheduler *sched)
{
unsigned int core;
for (core = 0; core < rdev->num_cores; core++) {
if (&rdev->cores[core].sched == sched)
return &rdev->cores[core];
}
return NULL;
}
static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job)
{
struct rocket_job *job = to_rocket_job(sched_job);
struct rocket_device *rdev = job->rdev;
struct rocket_core *core = sched_to_core(rdev, sched_job->sched);
struct dma_fence *fence = NULL;
int ret;
if (unlikely(job->base.s_fence->finished.error))
return NULL;
/*
* Nothing to execute: can happen if the job has finished while
* we were resetting the NPU.
*/
if (job->next_task_idx == job->task_count)
return NULL;
fence = rocket_fence_create(core);
if (IS_ERR(fence))
return fence;
if (job->done_fence)
dma_fence_put(job->done_fence);
job->done_fence = dma_fence_get(fence);
ret = pm_runtime_get_sync(core->dev);
if (ret < 0)
return fence;
ret = iommu_attach_group(job->domain->domain, core->iommu_group);
if (ret < 0)
return fence;
scoped_guard(mutex, &core->job_lock) {
core->in_flight_job = job;
rocket_job_hw_submit(core, job);
}
return fence;
}
static void rocket_job_handle_irq(struct rocket_core *core)
{
pm_runtime_mark_last_busy(core->dev);
rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
scoped_guard(mutex, &core->job_lock)
if (core->in_flight_job) {
if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
rocket_job_hw_submit(core, core->in_flight_job);
return;
}
iommu_detach_group(NULL, iommu_group_get(core->dev));
dma_fence_signal(core->in_flight_job->done_fence);
pm_runtime_put_autosuspend(core->dev);
core->in_flight_job = NULL;
}
}
static void
rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
{
if (!atomic_read(&core->reset.pending))
return;
drm_sched_stop(&core->sched, bad);
/*
* Remaining interrupts have been handled, but we might still have
* stuck jobs. Let's make sure the PM counters stay balanced by
* manually calling pm_runtime_put_noidle().
*/
scoped_guard(mutex, &core->job_lock) {
if (core->in_flight_job)
pm_runtime_put_noidle(core->dev);
iommu_detach_group(NULL, core->iommu_group);
core->in_flight_job = NULL;
}
/* Proceed with reset now. */
rocket_core_reset(core);
/* NPU has been reset, we can clear the reset pending bit. */
atomic_set(&core->reset.pending, 0);
/* Restart the scheduler */
drm_sched_start(&core->sched, 0);
}
static enum drm_gpu_sched_stat rocket_job_timedout(struct drm_sched_job *sched_job)
{
struct rocket_job *job = to_rocket_job(sched_job);
struct rocket_device *rdev = job->rdev;
struct rocket_core *core = sched_to_core(rdev, sched_job->sched);
dev_err(core->dev, "NPU job timed out");
atomic_set(&core->reset.pending, 1);
rocket_reset(core, sched_job);
return DRM_GPU_SCHED_STAT_RESET;
}
static void rocket_reset_work(struct work_struct *work)
{
struct rocket_core *core;
core = container_of(work, struct rocket_core, reset.work);
rocket_reset(core, NULL);
}
static const struct drm_sched_backend_ops rocket_sched_ops = {
.run_job = rocket_job_run,
.timedout_job = rocket_job_timedout,
.free_job = rocket_job_free
};
static irqreturn_t rocket_job_irq_handler_thread(int irq, void *data)
{
struct rocket_core *core = data;
rocket_job_handle_irq(core);
return IRQ_HANDLED;
}
static irqreturn_t rocket_job_irq_handler(int irq, void *data)
{
struct rocket_core *core = data;
u32 raw_status = rocket_pc_readl(core, INTERRUPT_RAW_STATUS);
WARN_ON(raw_status & PC_INTERRUPT_RAW_STATUS_DMA_READ_ERROR);
WARN_ON(raw_status & PC_INTERRUPT_RAW_STATUS_DMA_WRITE_ERROR);
if (!(raw_status & PC_INTERRUPT_RAW_STATUS_DPU_0 ||
raw_status & PC_INTERRUPT_RAW_STATUS_DPU_1))
return IRQ_NONE;
rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
return IRQ_WAKE_THREAD;
}
int rocket_job_init(struct rocket_core *core)
{
struct drm_sched_init_args args = {
.ops = &rocket_sched_ops,
.num_rqs = DRM_SCHED_PRIORITY_COUNT,
.credit_limit = 1,
.timeout = msecs_to_jiffies(JOB_TIMEOUT_MS),
.name = dev_name(core->dev),
.dev = core->dev,
};
int ret;
INIT_WORK(&core->reset.work, rocket_reset_work);
spin_lock_init(&core->fence_lock);
mutex_init(&core->job_lock);
core->irq = platform_get_irq(to_platform_device(core->dev), 0);
if (core->irq < 0)
return core->irq;
ret = devm_request_threaded_irq(core->dev, core->irq,
rocket_job_irq_handler,
rocket_job_irq_handler_thread,
IRQF_SHARED, dev_name(core->dev),
core);
if (ret) {
dev_err(core->dev, "failed to request job irq");
return ret;
}
core->reset.wq = alloc_ordered_workqueue("rocket-reset-%d", 0, core->index);
if (!core->reset.wq)
return -ENOMEM;
core->fence_context = dma_fence_context_alloc(1);
args.timeout_wq = core->reset.wq;
ret = drm_sched_init(&core->sched, &args);
if (ret) {
dev_err(core->dev, "Failed to create scheduler: %d.", ret);
goto err_sched;
}
return 0;
err_sched:
drm_sched_fini(&core->sched);
destroy_workqueue(core->reset.wq);
return ret;
}
void rocket_job_fini(struct rocket_core *core)
{
drm_sched_fini(&core->sched);
cancel_work_sync(&core->reset.work);
destroy_workqueue(core->reset.wq);
}
int rocket_job_open(struct rocket_file_priv *rocket_priv)
{
struct rocket_device *rdev = rocket_priv->rdev;
struct drm_gpu_scheduler **scheds = kmalloc_objs(*scheds,
rdev->num_cores);
unsigned int core;
int ret;
for (core = 0; core < rdev->num_cores; core++)
scheds[core] = &rdev->cores[core].sched;
ret = drm_sched_entity_init(&rocket_priv->sched_entity,
DRM_SCHED_PRIORITY_NORMAL,
scheds,
rdev->num_cores, NULL);
if (WARN_ON(ret))
return ret;
return 0;
}
void rocket_job_close(struct rocket_file_priv *rocket_priv)
{
struct drm_sched_entity *entity = &rocket_priv->sched_entity;
kfree(entity->sched_list);
drm_sched_entity_destroy(entity);
}
int rocket_job_is_idle(struct rocket_core *core)
{
/* If there are any jobs in this HW queue, we're not idle */
if (atomic_read(&core->sched.credit_count))
return false;
return true;
}
static int rocket_ioctl_submit_job(struct drm_device *dev, struct drm_file *file,
struct drm_rocket_job *job)
{
struct rocket_device *rdev = to_rocket_device(dev);
struct rocket_file_priv *file_priv = file->driver_priv;
struct rocket_job *rjob = NULL;
int ret = 0;
if (job->task_count == 0)
return -EINVAL;
rjob = kzalloc_obj(*rjob);
if (!rjob)
return -ENOMEM;
kref_init(&rjob->refcount);
rjob->rdev = rdev;
ret = drm_sched_job_init(&rjob->base,
&file_priv->sched_entity,
1, NULL, file->client_id);
if (ret)
goto out_put_job;
ret = rocket_copy_tasks(dev, file, job, rjob);
if (ret)
goto out_cleanup_job;
ret = drm_gem_objects_lookup(file, u64_to_user_ptr(job->in_bo_handles),
job->in_bo_handle_count, &rjob->in_bos);
if (ret)
goto out_cleanup_job;
rjob->in_bo_count = job->in_bo_handle_count;
ret = drm_gem_objects_lookup(file, u64_to_user_ptr(job->out_bo_handles),
job->out_bo_handle_count, &rjob->out_bos);
if (ret)
goto out_cleanup_job;
rjob->out_bo_count = job->out_bo_handle_count;
rjob->domain = rocket_iommu_domain_get(file_priv);
ret = rocket_job_push(rjob);
if (ret)
goto out_cleanup_job;
out_cleanup_job:
if (ret)
drm_sched_job_cleanup(&rjob->base);
out_put_job:
rocket_job_put(rjob);
return ret;
}
int rocket_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_rocket_submit *args = data;
struct drm_rocket_job *jobs;
int ret = 0;
unsigned int i = 0;
if (args->job_count == 0)
return 0;
if (args->job_struct_size < sizeof(struct drm_rocket_job)) {
drm_dbg(dev, "job_struct_size field in drm_rocket_submit struct is too small.\n");
return -EINVAL;
}
if (args->reserved != 0) {
drm_dbg(dev, "Reserved field in drm_rocket_submit struct should be 0.\n");
return -EINVAL;
}
jobs = kvmalloc_objs(*jobs, args->job_count);
if (!jobs) {
drm_dbg(dev, "Failed to allocate incoming job array\n");
return -ENOMEM;
}
for (i = 0; i < args->job_count; i++) {
if (copy_from_user(&jobs[i],
u64_to_user_ptr(args->jobs) + i * args->job_struct_size,
sizeof(*jobs))) {
ret = -EFAULT;
drm_dbg(dev, "Failed to copy incoming job array\n");
goto exit;
}
}
for (i = 0; i < args->job_count; i++)
rocket_ioctl_submit_job(dev, file, &jobs[i]);
exit:
kvfree(jobs);
return ret;
}
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
#ifndef __ROCKET_JOB_H__
#define __ROCKET_JOB_H__
#include <drm/drm_drv.h>
#include <drm/gpu_scheduler.h>
#include "rocket_core.h"
#include "rocket_drv.h"
struct rocket_task {
u64 regcmd;
u32 regcmd_count;
};
struct rocket_job {
struct drm_sched_job base;
struct rocket_device *rdev;
struct drm_gem_object **in_bos;
struct drm_gem_object **out_bos;
u32 in_bo_count;
u32 out_bo_count;
struct rocket_task *tasks;
u32 task_count;
u32 next_task_idx;
/* Fence to be signaled by drm-sched once its done with the job */
struct dma_fence *inference_done_fence;
/* Fence to be signaled by IRQ handler when the job is complete. */
struct dma_fence *done_fence;
struct rocket_iommu_domain *domain;
struct kref refcount;
};
int rocket_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *file);
int rocket_job_init(struct rocket_core *core);
void rocket_job_fini(struct rocket_core *core);
int rocket_job_open(struct rocket_file_priv *rocket_priv);
void rocket_job_close(struct rocket_file_priv *rocket_priv);
int rocket_job_is_idle(struct rocket_core *core);
#endif
File diff suppressed because it is too large Load Diff