Files
linux-hwpm/os/linux/tegra_hwpm_ioctl.c
Vedashree Vidwans 778356e12d tegra: hwpm: add device_opened flag
Add device_opened flag to indicate device node open success.
Use device_opened flag to allow execution of IOCTL and device teardown
functionality.

Jira THWPM-41

Change-Id: I218a45abed1de9f314cba696fd86c9bd8596cacc
Signed-off-by: Vedashree Vidwans <vvidwans@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2675514
Reviewed-by: svcacv <svcacv@nvidia.com>
Reviewed-by: svc_kernel_abi <svc_kernel_abi@nvidia.com>
Reviewed-by: Seema Khowala <seemaj@nvidia.com>
Reviewed-by: Vasuki Shankar <vasukis@nvidia.com>
GVS: Gerrit_Virtual_Submit
2022-03-17 19:45:12 -07:00

603 lines
15 KiB
C

/*
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/fs.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/of_address.h>
#include <linux/dma-buf.h>
#include <soc/tegra/fuse.h>
#include <uapi/linux/tegra-soc-hwpm-uapi.h>
#include <tegra_hwpm_log.h>
#include <tegra_hwpm_io.h>
#include <tegra_hwpm.h>
#include <tegra_hwpm_common.h>
#define LA_CLK_RATE 625000000UL
struct tegra_soc_hwpm_ioctl {
const char *const name;
const size_t struct_size;
int (*handler)(struct tegra_soc_hwpm *, void *);
};
static int device_info_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int floorsweep_info_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int timer_relation_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int reserve_resource_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int alloc_pma_stream_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int bind_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int query_allowlist_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int exec_reg_ops_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static int update_get_put_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct);
static const struct tegra_soc_hwpm_ioctl ioctls[] = {
[TEGRA_SOC_HWPM_IOCTL_DEVICE_INFO] = {
.name = "device_info",
.struct_size = sizeof(struct tegra_soc_hwpm_device_info),
.handler = device_info_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_FLOORSWEEP_INFO] = {
.name = "floorsweep_info",
.struct_size = sizeof(struct tegra_soc_hwpm_ip_floorsweep_info),
.handler = floorsweep_info_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_GET_GPU_CPU_TIME_CORRELATION_INFO] = {
.name = "timer_relation",
.struct_size = sizeof(struct tegra_soc_hwpm_timer_relation),
.handler = timer_relation_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_RESERVE_RESOURCE] = {
.name = "reserve_resource",
.struct_size = sizeof(struct tegra_soc_hwpm_reserve_resource),
.handler = reserve_resource_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_ALLOC_PMA_STREAM] = {
.name = "alloc_pma_stream",
.struct_size = sizeof(struct tegra_soc_hwpm_alloc_pma_stream),
.handler = alloc_pma_stream_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_BIND] = {
.name = "bind",
.struct_size = 0,
.handler = bind_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_QUERY_ALLOWLIST] = {
.name = "query_allowlist",
.struct_size = sizeof(struct tegra_soc_hwpm_query_allowlist),
.handler = query_allowlist_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_EXEC_REG_OPS] = {
.name = "exec_reg_ops",
.struct_size = sizeof(struct tegra_soc_hwpm_exec_reg_ops),
.handler = exec_reg_ops_ioctl,
},
[TEGRA_SOC_HWPM_IOCTL_UPDATE_GET_PUT] = {
.name = "update_get_put",
.struct_size = sizeof(struct tegra_soc_hwpm_update_get_put),
.handler = update_get_put_ioctl,
},
};
static int device_info_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
struct tegra_soc_hwpm_device_info *device_info =
(struct tegra_soc_hwpm_device_info *)ioctl_struct;
device_info->chip = hwpm->device_info.chip;
device_info->chip_revision = hwpm->device_info.chip_revision;
device_info->revision = hwpm->device_info.revision;
device_info->platform = hwpm->device_info.platform;
tegra_hwpm_dbg(hwpm, hwpm_verbose, "chip id 0x%x", device_info->chip);
tegra_hwpm_dbg(hwpm, hwpm_verbose,
"chip_revision 0x%x", device_info->chip_revision);
tegra_hwpm_dbg(hwpm, hwpm_verbose,
"revision 0x%x", device_info->revision);
tegra_hwpm_dbg(hwpm, hwpm_verbose,
"platform 0x%x", device_info->platform);
return 0;
}
static int floorsweep_info_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
struct tegra_soc_hwpm_ip_floorsweep_info *fs_info =
(struct tegra_soc_hwpm_ip_floorsweep_info *)ioctl_struct;
if (fs_info->num_queries > TEGRA_SOC_HWPM_IP_QUERIES_MAX) {
tegra_hwpm_err(hwpm, "Number of queries exceed max limit of %u",
TEGRA_SOC_HWPM_IP_QUERIES_MAX);
return -EINVAL;
}
return tegra_hwpm_get_floorsweep_info(hwpm, fs_info);
}
static int timer_relation_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
/* FIXME: Implement IOCTL */
#if 0
struct tegra_soc_hwpm_timer_relation *timer_relation =
(struct tegra_soc_hwpm_timer_relation *)ioctl_struct;
#endif
tegra_hwpm_err(hwpm, "The GET_GPU_CPU_TIME_CORRELATION_INFO IOCTL is"
" currently not implemented");
return -ENXIO;
}
static int reserve_resource_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
struct tegra_soc_hwpm_reserve_resource *reserve_resource =
(struct tegra_soc_hwpm_reserve_resource *)ioctl_struct;
u32 resource = reserve_resource->resource;
int ret = 0;
if (hwpm->bind_completed) {
tegra_hwpm_err(hwpm, "The RESERVE_RESOURCE IOCTL can only be"
" called before the BIND IOCTL.");
return -EPERM;
}
if (resource >= TERGA_SOC_HWPM_NUM_RESOURCES) {
tegra_hwpm_err(hwpm, "Requested resource %d is out of bounds.",
resource);
return -EINVAL;
}
ret = tegra_hwpm_reserve_resource(hwpm, resource);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to reserve resource %d", resource);
}
return ret;
}
static int alloc_pma_stream_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
struct tegra_soc_hwpm_alloc_pma_stream *alloc_pma_stream =
(struct tegra_soc_hwpm_alloc_pma_stream *)ioctl_struct;
int ret = 0;
if (hwpm->bind_completed) {
tegra_hwpm_err(hwpm, "The ALLOC_PMA_STREAM IOCTL can only be"
" called before the BIND IOCTL.");
return -EPERM;
}
if (alloc_pma_stream->stream_buf_size == 0) {
tegra_hwpm_err(hwpm, "stream_buf_size is 0");
return -EINVAL;
}
if (alloc_pma_stream->stream_buf_fd == 0) {
tegra_hwpm_err(hwpm, "Invalid stream_buf_fd");
return -EINVAL;
}
if (alloc_pma_stream->mem_bytes_buf_fd == 0) {
tegra_hwpm_err(hwpm, "Invalid mem_bytes_buf_fd");
return -EINVAL;
}
ret = tegra_hwpm_map_stream_buffer(hwpm, alloc_pma_stream);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to map stream buffer");
}
return ret;
}
static int bind_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
int ret = 0;
ret = tegra_hwpm_bind_resources(hwpm);
if (ret != 0) {
tegra_hwpm_err(hwpm, "Failed to bind resources");
} else {
hwpm->bind_completed = true;
}
return ret;
}
static int query_allowlist_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
int ret = 0;
struct tegra_soc_hwpm_query_allowlist *query_allowlist =
(struct tegra_soc_hwpm_query_allowlist *)ioctl_struct;
if (!hwpm->bind_completed) {
tegra_hwpm_err(hwpm,
"The QUERY_ALLOWLIST IOCTL can only be called"
" after the BIND IOCTL.");
return -EPERM;
}
if (query_allowlist->allowlist == NULL) {
/* Userspace is querying allowlist size only */
if (hwpm->full_alist_size == 0) {
/*Full alist size is not computed yet */
ret = tegra_hwpm_get_allowlist_size(hwpm);
if (ret != 0) {
tegra_hwpm_err(hwpm,
"failed to get alist_size");
return ret;
}
}
query_allowlist->allowlist_size = hwpm->full_alist_size;
} else {
/* Concatenate allowlists and return */
ret = tegra_hwpm_update_allowlist(hwpm, query_allowlist);
if (ret != 0) {
tegra_hwpm_err(hwpm, "Failed to update full alist");
return ret;
}
}
return 0;
}
static int exec_reg_ops_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
if (!hwpm->bind_completed) {
tegra_hwpm_err(hwpm, "The EXEC_REG_OPS IOCTL can only be called"
" after the BIND IOCTL.");
return -EPERM;
}
return tegra_hwpm_exec_regops(hwpm,
(struct tegra_soc_hwpm_exec_reg_ops *)ioctl_struct);
}
static int update_get_put_ioctl(struct tegra_soc_hwpm *hwpm,
void *ioctl_struct)
{
struct tegra_soc_hwpm_update_get_put *update_get_put =
(struct tegra_soc_hwpm_update_get_put *)ioctl_struct;
if (!hwpm->bind_completed) {
tegra_hwpm_err(hwpm,
"The UPDATE_GET_PUT IOCTL can only be called"
" after the BIND IOCTL.");
return -EPERM;
}
if (!hwpm->mem_bytes_kernel) {
tegra_hwpm_err(hwpm,
"mem_bytes buffer is not mapped in the driver");
return -ENXIO;
}
return tegra_hwpm_update_mem_bytes(hwpm, update_get_put);
}
static long tegra_hwpm_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
int ret = 0;
enum tegra_soc_hwpm_ioctl_num ioctl_num = _IOC_NR(cmd);
u32 ioc_dir = _IOC_DIR(cmd);
u32 arg_size = _IOC_SIZE(cmd);
struct tegra_soc_hwpm *hwpm = NULL;
void *arg_copy = NULL;
if ((_IOC_TYPE(cmd) != TEGRA_SOC_HWPM_IOC_MAGIC) ||
(ioctl_num < 0) ||
(ioctl_num >= TERGA_SOC_HWPM_NUM_IOCTLS)) {
tegra_hwpm_err(hwpm, "Unsupported IOCTL call");
ret = -EINVAL;
goto end;
}
if (!file) {
tegra_hwpm_err(hwpm, "Invalid file");
ret = -ENODEV;
goto fail;
}
if (arg_size != ioctls[ioctl_num].struct_size) {
tegra_hwpm_err(hwpm, "Invalid userspace struct");
ret = -EINVAL;
goto fail;
}
hwpm = file->private_data;
if (!hwpm) {
tegra_hwpm_err(hwpm, "Invalid hwpm struct");
ret = -ENODEV;
goto fail;
}
if (!hwpm->device_opened) {
tegra_hwpm_err(hwpm, "Device open failed, can't process IOCTL");
ret = -ENODEV;
goto fail;
}
/* Only allocate a buffer if the IOCTL needs a buffer */
if (!(ioc_dir & _IOC_NONE)) {
arg_copy = kzalloc(arg_size, GFP_KERNEL);
if (!arg_copy) {
tegra_hwpm_err(hwpm,
"Can't allocate memory for kernel struct");
ret = -ENOMEM;
goto fail;
}
}
if (ioc_dir & _IOC_WRITE) {
if (copy_from_user(arg_copy, (void __user *)arg, arg_size)) {
tegra_hwpm_err(hwpm,
"Failed to copy data from userspace"
" struct into kernel struct");
ret = -EFAULT;
goto fail;
}
}
/*
* We don't goto fail here because even if the IOCTL fails, we have to
* call copy_to_user() to pass back any valid output params to
* userspace.
*/
ret = ioctls[ioctl_num].handler(hwpm, arg_copy);
if (ioc_dir & _IOC_READ) {
if (copy_to_user((void __user *)arg, arg_copy, arg_size)) {
tegra_hwpm_err(hwpm, "Failed to copy data from kernel"
" struct into userspace struct");
ret = -EFAULT;
goto fail;
}
}
if (ret < 0)
goto fail;
tegra_hwpm_dbg(hwpm, hwpm_info, "The %s IOCTL completed successfully!",
ioctls[ioctl_num].name);
goto cleanup;
fail:
tegra_hwpm_err(hwpm, "The %s IOCTL failed(%d)!",
ioctls[ioctl_num].name, ret);
cleanup:
if (arg_copy)
kfree(arg_copy);
end:
return ret;
}
static int tegra_hwpm_open(struct inode *inode, struct file *filp)
{
int ret = 0;
unsigned int minor;
struct tegra_soc_hwpm *hwpm = NULL;
if (!inode) {
tegra_hwpm_err(hwpm, "Invalid inode");
return -EINVAL;
}
if (!filp) {
tegra_hwpm_err(hwpm, "Invalid file");
return -EINVAL;
}
minor = iminor(inode);
if (minor > 0) {
tegra_hwpm_err(hwpm, "Incorrect minor number");
return -EBADFD;
}
hwpm = container_of(inode->i_cdev, struct tegra_soc_hwpm, cdev);
if (!hwpm) {
tegra_hwpm_err(hwpm, "Invalid hwpm struct");
return -EINVAL;
}
filp->private_data = hwpm;
/* Initialize driver on first open call only */
if (atomic_add_return(1, &hwpm->hwpm_in_use) != 1) {
return 0;
}
if (tegra_platform_is_silicon()) {
ret = reset_control_assert(hwpm->hwpm_rst);
if (ret < 0) {
tegra_hwpm_err(hwpm, "hwpm reset assert failed");
goto fail;
}
ret = reset_control_assert(hwpm->la_rst);
if (ret < 0) {
tegra_hwpm_err(hwpm, "la reset assert failed");
goto fail;
}
/* Set required parent for la_clk */
if (hwpm->la_clk && hwpm->la_parent_clk) {
ret = clk_set_parent(hwpm->la_clk, hwpm->la_parent_clk);
if (ret < 0) {
tegra_hwpm_err(hwpm,
"la clk set parent failed");
goto fail;
}
}
/* set la_clk rate to 625 MHZ */
ret = clk_set_rate(hwpm->la_clk, LA_CLK_RATE);
if (ret < 0) {
tegra_hwpm_err(hwpm, "la clock set rate failed");
goto fail;
}
ret = clk_prepare_enable(hwpm->la_clk);
if (ret < 0) {
tegra_hwpm_err(hwpm, "la clock enable failed");
goto fail;
}
ret = reset_control_deassert(hwpm->la_rst);
if (ret < 0) {
tegra_hwpm_err(hwpm, "la reset deassert failed");
goto fail;
}
ret = reset_control_deassert(hwpm->hwpm_rst);
if (ret < 0) {
tegra_hwpm_err(hwpm, "hwpm reset deassert failed");
goto fail;
}
}
ret = tegra_hwpm_setup_hw(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to setup hw");
goto fail;
}
ret = tegra_hwpm_setup_sw(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to setup sw");
goto fail;
}
hwpm->device_opened = true;
return 0;
fail:
ret = tegra_hwpm_release_hw(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to release hw");
}
tegra_hwpm_err(hwpm, "%s failed", __func__);
return ret;
}
static ssize_t tegra_hwpm_read(struct file *file,
char __user *ubuf,
size_t count,
loff_t *offp)
{
return 0;
}
/* FIXME: Fix double release bug */
static int tegra_hwpm_release(struct inode *inode, struct file *filp)
{
int ret = 0;
struct tegra_soc_hwpm *hwpm = NULL;
if (!inode) {
tegra_hwpm_err(hwpm, "Invalid inode");
return -EINVAL;
}
if (!filp) {
tegra_hwpm_err(hwpm, "Invalid file");
return -EINVAL;
}
hwpm = container_of(inode->i_cdev, struct tegra_soc_hwpm, cdev);
if (!hwpm) {
tegra_hwpm_err(hwpm, "Invalid hwpm struct");
return -EINVAL;
}
/* De-init driver on last close call only */
if (!atomic_dec_and_test(&hwpm->hwpm_in_use)) {
return 0;
}
if (hwpm->device_opened == false) {
/* Device was not opened, do nothing */
return 0;
}
ret = tegra_hwpm_disable_triggers(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to disable PMA triggers");
goto fail;
}
/* Disable and release reserved IPs */
ret = tegra_hwpm_release_resources(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to release IP apertures");
goto fail;
}
/* Clear MEM_BYTES pipeline */
ret = tegra_hwpm_clear_mem_pipeline(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to clear MEM_BYTES pipeline");
goto fail;
}
ret = tegra_hwpm_release_hw(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to release hw");
goto fail;
}
if (tegra_platform_is_silicon()) {
ret = reset_control_assert(hwpm->hwpm_rst);
if (ret < 0) {
tegra_hwpm_err(hwpm, "hwpm reset assert failed");
goto fail;
}
ret = reset_control_assert(hwpm->la_rst);
if (ret < 0) {
tegra_hwpm_err(hwpm, "la reset assert failed");
goto fail;
}
clk_disable_unprepare(hwpm->la_clk);
}
hwpm->device_opened = false;
fail:
return ret;
}
/* File ops for device node */
const struct file_operations tegra_soc_hwpm_ops = {
.owner = THIS_MODULE,
.open = tegra_hwpm_open,
.read = tegra_hwpm_read,
.release = tegra_hwpm_release,
.unlocked_ioctl = tegra_hwpm_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = tegra_hwpm_ioctl,
#endif
};