Files
linux-hwpm/os/linux/ioctl.c
Vedashree Vidwans c893ae2cd9 tegra: hwpm: move regops functions to os folder
Regops functions refer to linux uapi structures. As an effort to make
HWPM driver OS agnostic, move regops functions to os linux folder.

Jira THWPM-59

Change-Id: Ia06c4da5c91a59b088678daaaf6063d70af99177
Signed-off-by: Vedashree Vidwans <vvidwans@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2738155
Reviewed-by: svc_kernel_abi <svc_kernel_abi@nvidia.com>
Reviewed-by: Vasuki Shankar <vasukis@nvidia.com>
Reviewed-by: Seema Khowala <seemaj@nvidia.com>
GVS: Gerrit_Virtual_Submit
2022-08-12 15:50:46 -07:00

562 lines
14 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>
#include <os/linux/mem_mgmt_utils.h>
#include <os/linux/regops_utils.h>
#define LA_CLK_RATE 625000000UL
static int tegra_hwpm_get_device_info_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_device_info *device_info)
{
tegra_hwpm_fn(hwpm, " ");
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_info | hwpm_dbg_device_info,
"chip id 0x%x", device_info->chip);
tegra_hwpm_dbg(hwpm, hwpm_info | hwpm_dbg_device_info,
"chip_revision 0x%x", device_info->chip_revision);
tegra_hwpm_dbg(hwpm, hwpm_info | hwpm_dbg_device_info,
"revision 0x%x", device_info->revision);
tegra_hwpm_dbg(hwpm, hwpm_info | hwpm_dbg_device_info,
"platform 0x%x", device_info->platform);
return 0;
}
static int tegra_hwpm_get_floorsweep_info_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_ip_floorsweep_info *fs_info)
{
tegra_hwpm_fn(hwpm, " ");
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 tegra_hwpm_get_resource_info_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_resource_info *rsrc_info)
{
tegra_hwpm_fn(hwpm, " ");
if (rsrc_info->num_queries > TEGRA_SOC_HWPM_RESOURCE_QUERIES_MAX) {
tegra_hwpm_err(hwpm, "Number of queries exceed max limit of %u",
TEGRA_SOC_HWPM_RESOURCE_QUERIES_MAX);
return -EINVAL;
}
return tegra_hwpm_get_resource_info(hwpm, rsrc_info);
}
static int tegra_hwpm_reserve_resource_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_reserve_resource *reserve_resource)
{
u32 resource = reserve_resource->resource;
int ret = 0;
tegra_hwpm_fn(hwpm, " ");
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 tegra_hwpm_alloc_pma_stream_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_alloc_pma_stream *alloc_pma_stream)
{
int ret = 0;
tegra_hwpm_fn(hwpm, " ");
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 tegra_hwpm_bind_ioctl(struct tegra_soc_hwpm *hwpm)
{
int ret = 0;
tegra_hwpm_fn(hwpm, " ");
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 tegra_hwpm_query_allowlist_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_query_allowlist *query_allowlist)
{
int ret = 0;
tegra_hwpm_fn(hwpm, " ");
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_map_update_allowlist(hwpm, query_allowlist);
if (ret != 0) {
tegra_hwpm_err(hwpm, "Failed to update full alist");
return ret;
}
}
return 0;
}
static int tegra_hwpm_exec_reg_ops_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_exec_reg_ops *exec_reg_ops)
{
tegra_hwpm_fn(hwpm, " ");
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, exec_reg_ops);
}
static int tegra_hwpm_update_get_put_ioctl(struct tegra_soc_hwpm *hwpm,
struct tegra_soc_hwpm_update_get_put *update_get_put)
{
tegra_hwpm_fn(hwpm, " ");
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;
struct tegra_soc_hwpm *hwpm = NULL;
u8 *buf;
if ((_IOC_TYPE(cmd) != TEGRA_SOC_HWPM_IOC_MAGIC) ||
(_IOC_NR(cmd) < 0) ||
(_IOC_NR(cmd) >= TERGA_SOC_HWPM_NUM_IOCTLS) ||
(_IOC_SIZE(cmd) > TEGRA_SOC_HWPM_MAX_ARG_SIZE)) {
tegra_hwpm_err(hwpm, "Invalid IOCTL call");
ret = -EINVAL;
goto fail;
}
if (!file) {
tegra_hwpm_err(hwpm, "Invalid file");
ret = -ENODEV;
goto fail;
}
hwpm = file->private_data;
if (!hwpm) {
tegra_hwpm_err(hwpm, "Invalid hwpm struct");
ret = -ENODEV;
goto fail;
}
tegra_hwpm_fn(hwpm, " ");
if (!hwpm->device_opened) {
tegra_hwpm_err(hwpm, "Device open failed, can't process IOCTL");
ret = -ENODEV;
goto fail;
}
if (!(_IOC_DIR(cmd) & _IOC_NONE)) {
buf = kzalloc(TEGRA_SOC_HWPM_MAX_ARG_SIZE, GFP_KERNEL);
if (!buf) {
tegra_hwpm_err(hwpm, "Kernel buf allocation failed");
ret = -ENOMEM;
goto fail;
}
}
if (_IOC_DIR(cmd) & _IOC_WRITE) {
if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd))) {
tegra_hwpm_err(hwpm, "Copy data from userspace failed");
ret = -EFAULT;
goto fail;
}
}
switch (cmd) {
case TEGRA_CTRL_CMD_SOC_HWPM_DEVICE_INFO:
ret = tegra_hwpm_get_device_info_ioctl(hwpm,
(struct tegra_soc_hwpm_device_info *)buf);
break;
case TEGRA_CTRL_CMD_SOC_HWPM_IP_FLOORSWEEP_INFO:
ret = tegra_hwpm_get_floorsweep_info_ioctl(hwpm,
(struct tegra_soc_hwpm_ip_floorsweep_info *)buf);
break;
case TEGRA_CTRL_CMD_SOC_HWPM_RESOURCE_INFO:
ret = tegra_hwpm_get_resource_info_ioctl(hwpm,
(struct tegra_soc_hwpm_resource_info *)buf);
break;
case TEGRA_CTRL_CMD_SOC_HWPM_RESERVE_RESOURCE:
ret = tegra_hwpm_reserve_resource_ioctl(hwpm,
(struct tegra_soc_hwpm_reserve_resource *)buf);
break;
case TEGRA_CTRL_CMD_SOC_HWPM_ALLOC_PMA_STREAM:
ret = tegra_hwpm_alloc_pma_stream_ioctl(hwpm,
(struct tegra_soc_hwpm_alloc_pma_stream *)buf);
break;
case TEGRA_CTRL_CMD_BIND:
ret = tegra_hwpm_bind_ioctl(hwpm);
break;
case TEGRA_CTRL_CMD_SOC_HWPM_QUERY_ALLOWLIST:
ret = tegra_hwpm_query_allowlist_ioctl(hwpm,
(struct tegra_soc_hwpm_query_allowlist *)buf);
break;
case TEGRA_CTRL_CMD_SOC_HWPM_EXEC_REG_OPS:
ret = tegra_hwpm_exec_reg_ops_ioctl(hwpm,
(struct tegra_soc_hwpm_exec_reg_ops *)buf);
break;
case TEGRA_CTRL_CMD_SOC_HWPM_UPDATE_GET_PUT:
ret = tegra_hwpm_update_get_put_ioctl(hwpm,
(struct tegra_soc_hwpm_update_get_put *)buf);
break;
default:
tegra_hwpm_err(hwpm, "Unknown IOCTL command");
ret = -ENOTTY;
goto fail;
}
if ((ret == 0) && (_IOC_DIR(cmd) & _IOC_READ)) {
if (copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd))) {
tegra_hwpm_err(hwpm, "Copy buffer to user failed");
ret = -EFAULT;
goto fail;
}
}
fail:
if (buf) {
kfree(buf);
}
if (ret < 0) {
tegra_hwpm_err(hwpm, "IOCTL cmd %d failed(%d)!", cmd, ret);
} else {
tegra_hwpm_dbg(hwpm, hwpm_info,
"IOCTL cmd %d completed successfully!", cmd);
}
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;
tegra_hwpm_fn(hwpm, " ");
/* Initialize driver on first open call only */
if (!atomic_add_unless(&hwpm->hwpm_in_use, 1U, 1U)) {
return -EAGAIN;
}
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, err = 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;
}
tegra_hwpm_fn(hwpm, " ");
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");
err = ret;
}
/* Disable and release reserved IPs */
ret = tegra_hwpm_release_resources(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to release IP apertures");
err = ret;
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");
err = ret;
goto fail;
}
ret = tegra_hwpm_release_hw(hwpm);
if (ret < 0) {
tegra_hwpm_err(hwpm, "Failed to release hw");
err = ret;
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");
err = ret;
goto fail;
}
ret = reset_control_assert(hwpm->la_rst);
if (ret < 0) {
tegra_hwpm_err(hwpm, "la reset assert failed");
err = ret;
goto fail;
}
clk_disable_unprepare(hwpm->la_clk);
}
/* De-init driver on last close call only */
if (!atomic_dec_and_test(&hwpm->hwpm_in_use)) {
return 0;
}
hwpm->device_opened = false;
fail:
return err;
}
/* 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
};