linux/drivers/gpu/drm/lima/lima_drv.c
Dragan Simic 0c94f58cef drm/lima: Mark simple_ondemand governor as softdep
Lima DRM driver uses devfreq to perform DVFS, while using simple_ondemand
devfreq governor by default.  This causes driver initialization to fail on
boot when simple_ondemand governor isn't built into the kernel statically,
as a result of the missing module dependency and, consequently, the
required governor module not being included in the initial ramdisk.  Thus,
let's mark simple_ondemand governor as a softdep for Lima, to have its
kernel module included in the initial ramdisk.

This is a rather longstanding issue that has forced distributions to build
devfreq governors statically into their kernels, [1][2] or may have forced
some users to introduce unnecessary workarounds.

Having simple_ondemand marked as a softdep for Lima may not resolve this
issue for all Linux distributions.  In particular, it will remain
unresolved for the distributions whose utilities for the initial ramdisk
generation do not handle the available softdep information [3] properly
yet.  However, some Linux distributions already handle softdeps properly
while generating their initial ramdisks, [4] and this is a prerequisite
step in the right direction for the distributions that don't handle them
properly yet.

[1] https://gitlab.manjaro.org/manjaro-arm/packages/core/linux-pinephone/-/blob/6.7-megi/config?ref_type=heads#L5749
[2] 7f64e287e7/main/linux-postmarketos-allwinner/config-postmarketos-allwinner.aarch64 (L4654)
[3] https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git/commit/?id=49d8e0b59052999de577ab732b719cfbeb89504d
[4] 97ac4d37aa

Cc: Philip Muller <philm@manjaro.org>
Cc: Oliver Smith <ollieparanoid@postmarketos.org>
Cc: Daniel Smith <danct12@disroot.org>
Cc: stable@vger.kernel.org
Fixes: 1996970773a3 ("drm/lima: Add optional devfreq and cooling device support")
Signed-off-by: Dragan Simic <dsimic@manjaro.org>
Signed-off-by: Qiang Yu <yuq825@gmail.com>
Link: https://patchwork.freedesktop.org/patch/msgid/fdaf2e41bb6a0c5118ff9cc21f4f62583208d885.1718655070.git.dsimic@manjaro.org
2024-06-30 17:59:24 +08:00

505 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0 OR MIT
/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_drv.h>
#include <drm/drm_prime.h>
#include <drm/lima_drm.h>
#include "lima_device.h"
#include "lima_drv.h"
#include "lima_gem.h"
#include "lima_vm.h"
int lima_sched_timeout_ms;
uint lima_heap_init_nr_pages = 8;
uint lima_max_error_tasks;
uint lima_job_hang_limit;
MODULE_PARM_DESC(sched_timeout_ms, "task run timeout in ms");
module_param_named(sched_timeout_ms, lima_sched_timeout_ms, int, 0444);
MODULE_PARM_DESC(heap_init_nr_pages, "heap buffer init number of pages");
module_param_named(heap_init_nr_pages, lima_heap_init_nr_pages, uint, 0444);
MODULE_PARM_DESC(max_error_tasks, "max number of error tasks to save");
module_param_named(max_error_tasks, lima_max_error_tasks, uint, 0644);
MODULE_PARM_DESC(job_hang_limit, "number of times to allow a job to hang before dropping it (default 0)");
module_param_named(job_hang_limit, lima_job_hang_limit, uint, 0444);
static int lima_ioctl_get_param(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_lima_get_param *args = data;
struct lima_device *ldev = to_lima_dev(dev);
if (args->pad)
return -EINVAL;
switch (args->param) {
case DRM_LIMA_PARAM_GPU_ID:
switch (ldev->id) {
case lima_gpu_mali400:
args->value = DRM_LIMA_PARAM_GPU_ID_MALI400;
break;
case lima_gpu_mali450:
args->value = DRM_LIMA_PARAM_GPU_ID_MALI450;
break;
default:
args->value = DRM_LIMA_PARAM_GPU_ID_UNKNOWN;
break;
}
break;
case DRM_LIMA_PARAM_NUM_PP:
args->value = ldev->pipe[lima_pipe_pp].num_processor;
break;
case DRM_LIMA_PARAM_GP_VERSION:
args->value = ldev->gp_version;
break;
case DRM_LIMA_PARAM_PP_VERSION:
args->value = ldev->pp_version;
break;
default:
return -EINVAL;
}
return 0;
}
static int lima_ioctl_gem_create(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_lima_gem_create *args = data;
if (args->pad)
return -EINVAL;
if (args->flags & ~(LIMA_BO_FLAG_HEAP))
return -EINVAL;
if (args->size == 0)
return -EINVAL;
return lima_gem_create_handle(dev, file, args->size, args->flags, &args->handle);
}
static int lima_ioctl_gem_info(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_lima_gem_info *args = data;
return lima_gem_get_info(file, args->handle, &args->va, &args->offset);
}
static int lima_ioctl_gem_submit(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_lima_gem_submit *args = data;
struct lima_device *ldev = to_lima_dev(dev);
struct lima_drm_priv *priv = file->driver_priv;
struct drm_lima_gem_submit_bo *bos;
struct lima_sched_pipe *pipe;
struct lima_sched_task *task;
struct lima_ctx *ctx;
struct lima_submit submit = {0};
size_t size;
int err = 0;
if (args->pipe >= lima_pipe_num || args->nr_bos == 0)
return -EINVAL;
if (args->flags & ~(LIMA_SUBMIT_FLAG_EXPLICIT_FENCE))
return -EINVAL;
pipe = ldev->pipe + args->pipe;
if (args->frame_size != pipe->frame_size)
return -EINVAL;
bos = kvcalloc(args->nr_bos, sizeof(*submit.bos) + sizeof(*submit.lbos), GFP_KERNEL);
if (!bos)
return -ENOMEM;
size = args->nr_bos * sizeof(*submit.bos);
if (copy_from_user(bos, u64_to_user_ptr(args->bos), size)) {
err = -EFAULT;
goto out0;
}
task = kmem_cache_zalloc(pipe->task_slab, GFP_KERNEL);
if (!task) {
err = -ENOMEM;
goto out0;
}
task->frame = task + 1;
if (copy_from_user(task->frame, u64_to_user_ptr(args->frame), args->frame_size)) {
err = -EFAULT;
goto out1;
}
err = pipe->task_validate(pipe, task);
if (err)
goto out1;
ctx = lima_ctx_get(&priv->ctx_mgr, args->ctx);
if (!ctx) {
err = -ENOENT;
goto out1;
}
submit.pipe = args->pipe;
submit.bos = bos;
submit.lbos = (void *)bos + size;
submit.nr_bos = args->nr_bos;
submit.task = task;
submit.ctx = ctx;
submit.flags = args->flags;
submit.in_sync[0] = args->in_sync[0];
submit.in_sync[1] = args->in_sync[1];
submit.out_sync = args->out_sync;
err = lima_gem_submit(file, &submit);
lima_ctx_put(ctx);
out1:
if (err)
kmem_cache_free(pipe->task_slab, task);
out0:
kvfree(bos);
return err;
}
static int lima_ioctl_gem_wait(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_lima_gem_wait *args = data;
if (args->op & ~(LIMA_GEM_WAIT_READ|LIMA_GEM_WAIT_WRITE))
return -EINVAL;
return lima_gem_wait(file, args->handle, args->op, args->timeout_ns);
}
static int lima_ioctl_ctx_create(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_lima_ctx_create *args = data;
struct lima_drm_priv *priv = file->driver_priv;
struct lima_device *ldev = to_lima_dev(dev);
if (args->_pad)
return -EINVAL;
return lima_ctx_create(ldev, &priv->ctx_mgr, &args->id);
}
static int lima_ioctl_ctx_free(struct drm_device *dev, void *data, struct drm_file *file)
{
struct drm_lima_ctx_create *args = data;
struct lima_drm_priv *priv = file->driver_priv;
if (args->_pad)
return -EINVAL;
return lima_ctx_free(&priv->ctx_mgr, args->id);
}
static int lima_drm_driver_open(struct drm_device *dev, struct drm_file *file)
{
int err;
struct lima_drm_priv *priv;
struct lima_device *ldev = to_lima_dev(dev);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->vm = lima_vm_create(ldev);
if (!priv->vm) {
err = -ENOMEM;
goto err_out0;
}
lima_ctx_mgr_init(&priv->ctx_mgr);
file->driver_priv = priv;
return 0;
err_out0:
kfree(priv);
return err;
}
static void lima_drm_driver_postclose(struct drm_device *dev, struct drm_file *file)
{
struct lima_drm_priv *priv = file->driver_priv;
lima_ctx_mgr_fini(&priv->ctx_mgr);
lima_vm_put(priv->vm);
kfree(priv);
}
static const struct drm_ioctl_desc lima_drm_driver_ioctls[] = {
DRM_IOCTL_DEF_DRV(LIMA_GET_PARAM, lima_ioctl_get_param, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(LIMA_GEM_CREATE, lima_ioctl_gem_create, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(LIMA_GEM_INFO, lima_ioctl_gem_info, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(LIMA_GEM_SUBMIT, lima_ioctl_gem_submit, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(LIMA_GEM_WAIT, lima_ioctl_gem_wait, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(LIMA_CTX_CREATE, lima_ioctl_ctx_create, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(LIMA_CTX_FREE, lima_ioctl_ctx_free, DRM_RENDER_ALLOW),
};
DEFINE_DRM_GEM_FOPS(lima_drm_driver_fops);
/*
* Changelog:
*
* - 1.1.0 - add heap buffer support
*/
static const struct drm_driver lima_drm_driver = {
.driver_features = DRIVER_RENDER | DRIVER_GEM | DRIVER_SYNCOBJ,
.open = lima_drm_driver_open,
.postclose = lima_drm_driver_postclose,
.ioctls = lima_drm_driver_ioctls,
.num_ioctls = ARRAY_SIZE(lima_drm_driver_ioctls),
.fops = &lima_drm_driver_fops,
.name = "lima",
.desc = "lima DRM",
.date = "20191231",
.major = 1,
.minor = 1,
.patchlevel = 0,
.gem_create_object = lima_gem_create_object,
.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
};
struct lima_block_reader {
void *dst;
size_t base;
size_t count;
size_t off;
ssize_t read;
};
static bool lima_read_block(struct lima_block_reader *reader,
void *src, size_t src_size)
{
size_t max_off = reader->base + src_size;
if (reader->off < max_off) {
size_t size = min_t(size_t, max_off - reader->off,
reader->count);
memcpy(reader->dst, src + (reader->off - reader->base), size);
reader->dst += size;
reader->off += size;
reader->read += size;
reader->count -= size;
}
reader->base = max_off;
return !!reader->count;
}
static ssize_t lima_error_state_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct lima_device *ldev = dev_get_drvdata(dev);
struct lima_sched_error_task *et;
struct lima_block_reader reader = {
.dst = buf,
.count = count,
.off = off,
};
mutex_lock(&ldev->error_task_list_lock);
if (lima_read_block(&reader, &ldev->dump, sizeof(ldev->dump))) {
list_for_each_entry(et, &ldev->error_task_list, list) {
if (!lima_read_block(&reader, et->data, et->size))
break;
}
}
mutex_unlock(&ldev->error_task_list_lock);
return reader.read;
}
static ssize_t lima_error_state_write(struct file *file, struct kobject *kobj,
struct bin_attribute *attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct lima_device *ldev = dev_get_drvdata(dev);
struct lima_sched_error_task *et, *tmp;
mutex_lock(&ldev->error_task_list_lock);
list_for_each_entry_safe(et, tmp, &ldev->error_task_list, list) {
list_del(&et->list);
kvfree(et);
}
ldev->dump.size = 0;
ldev->dump.num_tasks = 0;
mutex_unlock(&ldev->error_task_list_lock);
return count;
}
static const struct bin_attribute lima_error_state_attr = {
.attr.name = "error",
.attr.mode = 0600,
.size = 0,
.read = lima_error_state_read,
.write = lima_error_state_write,
};
static int lima_pdev_probe(struct platform_device *pdev)
{
struct lima_device *ldev;
struct drm_device *ddev;
const struct lima_compatible *comp;
int err;
err = lima_sched_slab_init();
if (err)
return err;
ldev = devm_kzalloc(&pdev->dev, sizeof(*ldev), GFP_KERNEL);
if (!ldev) {
err = -ENOMEM;
goto err_out0;
}
ldev->dev = &pdev->dev;
comp = of_device_get_match_data(&pdev->dev);
if (!comp) {
err = -ENODEV;
goto err_out0;
}
ldev->id = comp->id;
platform_set_drvdata(pdev, ldev);
/* Allocate and initialize the DRM device. */
ddev = drm_dev_alloc(&lima_drm_driver, &pdev->dev);
if (IS_ERR(ddev)) {
err = PTR_ERR(ddev);
goto err_out0;
}
ddev->dev_private = ldev;
ldev->ddev = ddev;
err = lima_device_init(ldev);
if (err)
goto err_out1;
err = lima_devfreq_init(ldev);
if (err) {
dev_err(&pdev->dev, "Fatal error during devfreq init\n");
goto err_out2;
}
pm_runtime_set_active(ldev->dev);
pm_runtime_mark_last_busy(ldev->dev);
pm_runtime_set_autosuspend_delay(ldev->dev, 200);
pm_runtime_use_autosuspend(ldev->dev);
pm_runtime_enable(ldev->dev);
/*
* Register the DRM device with the core and the connectors with
* sysfs.
*/
err = drm_dev_register(ddev, 0);
if (err < 0)
goto err_out3;
if (sysfs_create_bin_file(&ldev->dev->kobj, &lima_error_state_attr))
dev_warn(ldev->dev, "fail to create error state sysfs\n");
return 0;
err_out3:
pm_runtime_disable(ldev->dev);
lima_devfreq_fini(ldev);
err_out2:
lima_device_fini(ldev);
err_out1:
drm_dev_put(ddev);
err_out0:
lima_sched_slab_fini();
return err;
}
static void lima_pdev_remove(struct platform_device *pdev)
{
struct lima_device *ldev = platform_get_drvdata(pdev);
struct drm_device *ddev = ldev->ddev;
sysfs_remove_bin_file(&ldev->dev->kobj, &lima_error_state_attr);
drm_dev_unregister(ddev);
/* stop autosuspend to make sure device is in active state */
pm_runtime_set_autosuspend_delay(ldev->dev, -1);
pm_runtime_disable(ldev->dev);
lima_devfreq_fini(ldev);
lima_device_fini(ldev);
drm_dev_put(ddev);
lima_sched_slab_fini();
}
static const struct lima_compatible lima_mali400_data = {
.id = lima_gpu_mali400,
};
static const struct lima_compatible lima_mali450_data = {
.id = lima_gpu_mali450,
};
static const struct of_device_id dt_match[] = {
{ .compatible = "arm,mali-400", .data = &lima_mali400_data },
{ .compatible = "arm,mali-450", .data = &lima_mali450_data },
{}
};
MODULE_DEVICE_TABLE(of, dt_match);
static const struct dev_pm_ops lima_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(lima_device_suspend, lima_device_resume, NULL)
};
static struct platform_driver lima_platform_driver = {
.probe = lima_pdev_probe,
.remove_new = lima_pdev_remove,
.driver = {
.name = "lima",
.pm = &lima_pm_ops,
.of_match_table = dt_match,
},
};
module_platform_driver(lima_platform_driver);
MODULE_AUTHOR("Lima Project Developers");
MODULE_DESCRIPTION("Lima DRM Driver");
MODULE_LICENSE("GPL v2");
MODULE_SOFTDEP("pre: governor_simpleondemand");