root/sys/dev/qat/qat_common/adf_ctl_drv.c
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright(c) 2007-2022 Intel Corporation */
#include "qat_freebsd.h"
#include "adf_cfg.h"
#include "adf_common_drv.h"
#include "adf_accel_devices.h"
#include "icp_qat_uclo.h"
#include "icp_qat_fw.h"
#include "icp_qat_fw_init_admin.h"
#include "adf_cfg_strings.h"
#include "adf_uio_control.h"
#include "adf_uio_cleanup.h"
#include "adf_uio.h"
#include "adf_transport_access_macros.h"
#include "adf_transport_internal.h"
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sx.h>
#include <sys/malloc.h>
#include <machine/atomic.h>
#include <dev/pci/pcivar.h>
#include <sys/conf.h>
#include <sys/systm.h>
#include <sys/queue.h>
#include <sys/proc.h>
#include <sys/types.h>
#include <sys/priv.h>
#include <linux/list.h>
#include "adf_accel_devices.h"
#include "adf_common_drv.h"
#include "adf_cfg.h"
#include "adf_cfg_common.h"
#include "adf_cfg_user.h"
#include "adf_heartbeat.h"
#include "adf_cfg_device.h"

#define DEVICE_NAME "qat_adf_ctl"

static struct sx adf_ctl_lock;

static d_ioctl_t adf_ctl_ioctl;

void *misc_counter;

static struct cdevsw adf_ctl_cdevsw = {
        .d_version = D_VERSION,
        .d_ioctl = adf_ctl_ioctl,
        .d_name = DEVICE_NAME,
};

static struct cdev *adf_ctl_dev;

static void adf_chr_drv_destroy(void)
{
        destroy_dev(adf_ctl_dev);
}

struct adf_user_addr_info {
        struct list_head list;
        void *user_addr;
};

static int adf_chr_drv_create(void)
{
        adf_ctl_dev = make_dev(&adf_ctl_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
                               DEVICE_NAME);

        if (!adf_ctl_dev) {
                printf("QAT: failed to create device\n");
                goto err_cdev_del;
        }
        return 0;
err_cdev_del:
        return EFAULT;
}

static int adf_ctl_alloc_resources(struct adf_user_cfg_ctl_data **ctl_data,
                                   caddr_t arg)
{
        *ctl_data = (struct adf_user_cfg_ctl_data *)arg;
        return 0;
}

static int adf_copy_keyval_to_user(struct adf_accel_dev *accel_dev,
                                   struct adf_user_cfg_ctl_data *ctl_data)
{
        struct adf_user_cfg_key_val key_val;
        struct adf_user_cfg_section section;
        char val[ADF_CFG_MAX_VAL_LEN_IN_BYTES] = {0};
        char *user_ptr;

        if (copyin(ctl_data->config_section, &section,
                   sizeof(struct adf_user_cfg_section))) {
                device_printf(GET_DEV(accel_dev),
                              "failed to copy section info\n");
                return EFAULT;
        }

        if (copyin(section.params, &key_val,
                   sizeof(struct adf_user_cfg_key_val))) {
                device_printf(GET_DEV(accel_dev), "failed to copy key val\n");
                return EFAULT;
        }

        user_ptr = ((char *)section.params) + ADF_CFG_MAX_KEY_LEN_IN_BYTES;

        if (adf_cfg_get_param_value(
                accel_dev, section.name, key_val.key, val)) {
                return EFAULT;
        }

        if (copyout(val, user_ptr,
                    ADF_CFG_MAX_VAL_LEN_IN_BYTES)) {
                device_printf(GET_DEV(accel_dev),
                              "failed to copy keyvalue to user!\n");
                return EFAULT;
        }

        return 0;
}

static int adf_ctl_ioctl_get_num_devices(unsigned int cmd,
                                         caddr_t arg)
{
        adf_devmgr_get_num_dev((uint32_t *)arg);

        return 0;
}

static int adf_ctl_ioctl_get_status(unsigned int cmd,
                                    caddr_t arg)
{
        struct adf_hw_device_data *hw_data;
        struct adf_dev_status_info *dev_info;
        struct adf_accel_dev *accel_dev;

        dev_info = (struct adf_dev_status_info *)arg;

        accel_dev = adf_devmgr_get_dev_by_id(dev_info->accel_id);
        if (!accel_dev)
                return ENODEV;

        hw_data = accel_dev->hw_device;
        dev_info->state = adf_dev_started(accel_dev) ? DEV_UP : DEV_DOWN;
        dev_info->num_ae = hw_data->get_num_aes(hw_data);
        dev_info->num_accel = hw_data->get_num_accels(hw_data);
        dev_info->num_logical_accel = hw_data->num_logical_accel;
        dev_info->banks_per_accel = hw_data->num_banks
        / hw_data->num_logical_accel;
        strlcpy(dev_info->name, hw_data->dev_class->name,
                sizeof(dev_info->name));
        dev_info->instance_id = hw_data->instance_id;
        dev_info->type = hw_data->dev_class->type;
        dev_info->bus = pci_get_bus(accel_to_pci_dev(accel_dev));
        dev_info->dev = pci_get_slot(accel_to_pci_dev(accel_dev));
        dev_info->fun = pci_get_function(accel_to_pci_dev(accel_dev));
        dev_info->domain = pci_get_domain(accel_to_pci_dev(accel_dev));

        dev_info->pci_device_id = pci_get_device(accel_to_pci_dev(accel_dev));

        dev_info->node_id = accel_dev->accel_pci_dev.node;
        dev_info->sku = accel_dev->accel_pci_dev.sku;

        dev_info->device_mem_available = accel_dev->aram_info ?
                accel_dev->aram_info->inter_buff_aram_region_size : 0;

        return 0;
}

static int adf_ctl_ioctl_heartbeat(unsigned int cmd,
                                   caddr_t arg)
{
        int ret = 0;
        struct adf_accel_dev *accel_dev;
        struct adf_dev_heartbeat_status_ctl *hb_status;

        hb_status = (struct adf_dev_heartbeat_status_ctl *)arg;

        accel_dev = adf_devmgr_get_dev_by_id(hb_status->device_id);
        if (!accel_dev)
                return ENODEV;

        if (adf_heartbeat_status(accel_dev, &hb_status->status)) {
                device_printf(GET_DEV(accel_dev),
                              "failed to get heartbeat status\n");
                return EAGAIN;
        }
        return ret;
}

static int adf_ctl_ioctl_dev_get_value(unsigned int cmd,
                                       caddr_t arg)
{
        int ret = 0;
        struct adf_user_cfg_ctl_data *ctl_data;
        struct adf_accel_dev *accel_dev;

        ret = adf_ctl_alloc_resources(&ctl_data, arg);
        if (ret)
                return ret;

        accel_dev = adf_devmgr_get_dev_by_id(ctl_data->device_id);
        if (!accel_dev) {
                printf("QAT: Device %d not found\n", ctl_data->device_id);
                ret = ENODEV;
                goto out;
        }

        ret = adf_copy_keyval_to_user(accel_dev, ctl_data);
        if (ret) {
                ret = ENODEV;
                goto out;
        }
out:
        return ret;
}

static struct adf_uio_control_bundle
        *adf_ctl_ioctl_bundle(struct adf_user_reserve_ring reserve)
{
        struct adf_accel_dev *accel_dev;
        struct adf_uio_control_accel *accel;
        struct adf_uio_control_bundle *bundle = NULL;
        u8 num_rings_per_bank = 0;

        accel_dev = adf_devmgr_get_dev_by_id(reserve.accel_id);
        if (!accel_dev) {
                pr_err("QAT: Failed to get accel_dev\n");
                return NULL;
        }
        num_rings_per_bank = accel_dev->hw_device->num_rings_per_bank;

        accel = accel_dev->accel;
        if (!accel) {
                pr_err("QAT: Failed to get accel\n");
                return NULL;
        }

        if (reserve.bank_nr >= GET_MAX_BANKS(accel_dev)) {
                pr_err("QAT: Invalid bank number %d\n", reserve.bank_nr);
                return NULL;
        }
        if (reserve.ring_mask & ~((1 << num_rings_per_bank) - 1)) {
                pr_err("QAT: Invalid ring mask %0X\n", reserve.ring_mask);
                return NULL;
        }
        if (accel->num_ker_bundles > reserve.bank_nr) {
                pr_err("QAT: Invalid user reserved bank\n");
                return NULL;
        }
        bundle = &accel->bundle[reserve.bank_nr];

        return bundle;
}

static int adf_ctl_ioctl_reserve_ring(caddr_t arg)
{
        struct adf_user_reserve_ring reserve = {0};
        struct adf_uio_control_bundle *bundle;
        struct adf_uio_instance_rings *instance_rings;
        int pid_entry_found = 0;

        reserve = *((struct adf_user_reserve_ring *)arg);

        bundle = adf_ctl_ioctl_bundle(reserve);
        if (!bundle) {
                pr_err("QAT: Failed to get bundle\n");
                return -EINVAL;
        }

        mutex_lock(&bundle->lock);
        if (bundle->rings_used & reserve.ring_mask) {
                pr_err("QAT: Bundle %d, rings 0x%04X already reserved\n",
                       reserve.bank_nr,
                       reserve.ring_mask);
                mutex_unlock(&bundle->lock);
                return -EINVAL;
        }

        /* Find the list entry for this process */
        mutex_lock(&bundle->list_lock);
        list_for_each_entry(instance_rings, &bundle->list, list) {
                if (instance_rings->user_pid == curproc->p_pid) {
                        pid_entry_found = 1;
                        break;
                }
        }
        mutex_unlock(&bundle->list_lock);

        if (!pid_entry_found) {
                pr_err("QAT: process %d not found\n", curproc->p_pid);
                mutex_unlock(&bundle->lock);
                return -EINVAL;
        }

        instance_rings->ring_mask |= reserve.ring_mask;
        bundle->rings_used |= reserve.ring_mask;
        mutex_unlock(&bundle->lock);

        return 0;
}

static int adf_ctl_ioctl_release_ring(caddr_t arg)
{
        struct adf_user_reserve_ring reserve;
        struct adf_uio_control_bundle *bundle;
        struct adf_uio_instance_rings *instance_rings;
        int pid_entry_found;

        reserve = *((struct adf_user_reserve_ring *)arg);

        bundle = adf_ctl_ioctl_bundle(reserve);
        if (!bundle) {
                pr_err("QAT: Failed to get bundle\n");
                return -EINVAL;
        }

        /* Find the list entry for this process */
        pid_entry_found = 0;
        mutex_lock(&bundle->list_lock);
        list_for_each_entry(instance_rings, &bundle->list, list) {
                if (instance_rings->user_pid == curproc->p_pid) {
                        pid_entry_found = 1;
                        break;
                }
        }
        mutex_unlock(&bundle->list_lock);

        if (!pid_entry_found) {
                pr_err("QAT: No ring reservation found for PID %d\n",
                       curproc->p_pid);
                return -EINVAL;
        }

        if ((instance_rings->ring_mask & reserve.ring_mask) !=
            reserve.ring_mask) {
                pr_err("QAT: Attempt to release rings not reserved by this process\n");
                return -EINVAL;
        }

        instance_rings->ring_mask &= ~reserve.ring_mask;
        mutex_lock(&bundle->lock);
        bundle->rings_used &= ~reserve.ring_mask;
        mutex_unlock(&bundle->lock);

        return 0;
}

static int adf_ctl_ioctl_enable_ring(caddr_t arg)
{
        struct adf_user_reserve_ring reserve;
        struct adf_uio_control_bundle *bundle;

        reserve = *((struct adf_user_reserve_ring *)arg);

        bundle = adf_ctl_ioctl_bundle(reserve);
        if (!bundle) {
                pr_err("QAT: Failed to get bundle\n");
                return -EINVAL;
        }

        mutex_lock(&bundle->lock);
        bundle->rings_enabled |= reserve.ring_mask;
        adf_update_uio_ring_arb(bundle);
        mutex_unlock(&bundle->lock);

        return 0;
}

static int adf_ctl_ioctl_disable_ring(caddr_t arg)
{
        struct adf_user_reserve_ring reserve;
        struct adf_uio_control_bundle *bundle;

        reserve = *((struct adf_user_reserve_ring *)arg);

        bundle = adf_ctl_ioctl_bundle(reserve);
        if (!bundle) {
                pr_err("QAT: Failed to get bundle\n");
                return -EINVAL;
        }

        mutex_lock(&bundle->lock);
        bundle->rings_enabled &= ~reserve.ring_mask;
        adf_update_uio_ring_arb(bundle);
        mutex_unlock(&bundle->lock);

        return 0;
}

static int adf_ctl_ioctl(struct cdev *dev,
                         u_long cmd,
                         caddr_t arg,
                         int fflag,
                         struct thread *td)
{
        int ret = 0;
        bool allowed = false;
        int i;
        static const unsigned int unrestricted_cmds[] = {
                IOCTL_GET_NUM_DEVICES,     IOCTL_STATUS_ACCEL_DEV,
                IOCTL_HEARTBEAT_ACCEL_DEV, IOCTL_GET_CFG_VAL,
                IOCTL_RESERVE_RING,     IOCTL_RELEASE_RING,
                IOCTL_ENABLE_RING,       IOCTL_DISABLE_RING,
        };

        if (priv_check(curthread, PRIV_DRIVER)) {
                for (i = 0; i < ARRAY_SIZE(unrestricted_cmds); i++) {
                        if (cmd == unrestricted_cmds[i]) {
                                allowed = true;
                                break;
                        }
                }
                if (!allowed)
                        return EACCES;
        }

        /* All commands have an argument */
        if (!arg)
                return EFAULT;

        if (sx_xlock_sig(&adf_ctl_lock))
                return EINTR;

        switch (cmd) {
        case IOCTL_GET_NUM_DEVICES:
                ret = adf_ctl_ioctl_get_num_devices(cmd, arg);
                break;
        case IOCTL_STATUS_ACCEL_DEV:
                ret = adf_ctl_ioctl_get_status(cmd, arg);
                break;
        case IOCTL_GET_CFG_VAL:
                ret = adf_ctl_ioctl_dev_get_value(cmd, arg);
                break;
        case IOCTL_RESERVE_RING:
                ret = adf_ctl_ioctl_reserve_ring(arg);
                break;
        case IOCTL_RELEASE_RING:
                ret = adf_ctl_ioctl_release_ring(arg);
                break;
        case IOCTL_ENABLE_RING:
                ret = adf_ctl_ioctl_enable_ring(arg);
                break;
        case IOCTL_DISABLE_RING:
                ret = adf_ctl_ioctl_disable_ring(arg);
                break;
        case IOCTL_HEARTBEAT_ACCEL_DEV:
                ret = adf_ctl_ioctl_heartbeat(cmd, arg);
                break;
        default:
                printf("QAT: Invalid ioctl\n");
                ret = ENOTTY;
                break;
        }
        sx_xunlock(&adf_ctl_lock);
        return ret;
}

int
adf_register_ctl_device_driver(void)
{
        sx_init(&adf_ctl_lock, "adf ctl");

        if (adf_chr_drv_create())
                goto err_chr_dev;

        adf_state_init();
        if (adf_processes_dev_register() != 0)
                goto err_processes_dev_register;
        return 0;

err_processes_dev_register:
        adf_chr_drv_destroy();
err_chr_dev:
        sx_destroy(&adf_ctl_lock);
        return EFAULT;
}

void
adf_unregister_ctl_device_driver(void)
{
        adf_processes_dev_unregister();
        adf_state_destroy();
        adf_chr_drv_destroy();
        adf_clean_vf_map(false);
        sx_destroy(&adf_ctl_lock);
}