| // SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) |
| /* Copyright(c) 2014 - 2020 Intel Corporation */ |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/bitops.h> |
| #include <linux/pci.h> |
| #include <linux/cdev.h> |
| #include <linux/uaccess.h> |
| #include <linux/crypto.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" |
| |
| #define DEVICE_NAME "qat_adf_ctl" |
| |
| static DEFINE_MUTEX(adf_ctl_lock); |
| static long adf_ctl_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); |
| |
| static const struct file_operations adf_ctl_ops = { |
| .owner = THIS_MODULE, |
| .unlocked_ioctl = adf_ctl_ioctl, |
| .compat_ioctl = compat_ptr_ioctl, |
| }; |
| |
| struct adf_ctl_drv_info { |
| unsigned int major; |
| struct cdev drv_cdev; |
| struct class *drv_class; |
| }; |
| |
| static struct adf_ctl_drv_info adf_ctl_drv; |
| |
| static void adf_chr_drv_destroy(void) |
| { |
| device_destroy(adf_ctl_drv.drv_class, MKDEV(adf_ctl_drv.major, 0)); |
| cdev_del(&adf_ctl_drv.drv_cdev); |
| class_destroy(adf_ctl_drv.drv_class); |
| unregister_chrdev_region(MKDEV(adf_ctl_drv.major, 0), 1); |
| } |
| |
| static int adf_chr_drv_create(void) |
| { |
| dev_t dev_id; |
| struct device *drv_device; |
| |
| if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) { |
| pr_err("QAT: unable to allocate chrdev region\n"); |
| return -EFAULT; |
| } |
| |
| adf_ctl_drv.drv_class = class_create(THIS_MODULE, DEVICE_NAME); |
| if (IS_ERR(adf_ctl_drv.drv_class)) { |
| pr_err("QAT: class_create failed for adf_ctl\n"); |
| goto err_chrdev_unreg; |
| } |
| adf_ctl_drv.major = MAJOR(dev_id); |
| cdev_init(&adf_ctl_drv.drv_cdev, &adf_ctl_ops); |
| if (cdev_add(&adf_ctl_drv.drv_cdev, dev_id, 1)) { |
| pr_err("QAT: cdev add failed\n"); |
| goto err_class_destr; |
| } |
| |
| drv_device = device_create(adf_ctl_drv.drv_class, NULL, |
| MKDEV(adf_ctl_drv.major, 0), |
| NULL, DEVICE_NAME); |
| if (IS_ERR(drv_device)) { |
| pr_err("QAT: failed to create device\n"); |
| goto err_cdev_del; |
| } |
| return 0; |
| err_cdev_del: |
| cdev_del(&adf_ctl_drv.drv_cdev); |
| err_class_destr: |
| class_destroy(adf_ctl_drv.drv_class); |
| err_chrdev_unreg: |
| unregister_chrdev_region(dev_id, 1); |
| return -EFAULT; |
| } |
| |
| static int adf_ctl_alloc_resources(struct adf_user_cfg_ctl_data **ctl_data, |
| unsigned long arg) |
| { |
| struct adf_user_cfg_ctl_data *cfg_data; |
| |
| cfg_data = kzalloc(sizeof(*cfg_data), GFP_KERNEL); |
| if (!cfg_data) |
| return -ENOMEM; |
| |
| /* Initialize device id to NO DEVICE as 0 is a valid device id */ |
| cfg_data->device_id = ADF_CFG_NO_DEVICE; |
| |
| if (copy_from_user(cfg_data, (void __user *)arg, sizeof(*cfg_data))) { |
| pr_err("QAT: failed to copy from user cfg_data.\n"); |
| kfree(cfg_data); |
| return -EIO; |
| } |
| |
| *ctl_data = cfg_data; |
| return 0; |
| } |
| |
| static int adf_add_key_value_data(struct adf_accel_dev *accel_dev, |
| const char *section, |
| const struct adf_user_cfg_key_val *key_val) |
| { |
| if (key_val->type == ADF_HEX) { |
| long *ptr = (long *)key_val->val; |
| long val = *ptr; |
| |
| if (adf_cfg_add_key_value_param(accel_dev, section, |
| key_val->key, (void *)val, |
| key_val->type)) { |
| dev_err(&GET_DEV(accel_dev), |
| "failed to add hex keyvalue.\n"); |
| return -EFAULT; |
| } |
| } else { |
| if (adf_cfg_add_key_value_param(accel_dev, section, |
| key_val->key, key_val->val, |
| key_val->type)) { |
| dev_err(&GET_DEV(accel_dev), |
| "failed to add keyvalue.\n"); |
| return -EFAULT; |
| } |
| } |
| return 0; |
| } |
| |
| static int adf_copy_key_value_data(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_key_val *params_head; |
| struct adf_user_cfg_section section, *section_head; |
| |
| section_head = ctl_data->config_section; |
| |
| while (section_head) { |
| if (copy_from_user(§ion, (void __user *)section_head, |
| sizeof(*section_head))) { |
| dev_err(&GET_DEV(accel_dev), |
| "failed to copy section info\n"); |
| goto out_err; |
| } |
| |
| if (adf_cfg_section_add(accel_dev, section.name)) { |
| dev_err(&GET_DEV(accel_dev), |
| "failed to add section.\n"); |
| goto out_err; |
| } |
| |
| params_head = section.params; |
| |
| while (params_head) { |
| if (copy_from_user(&key_val, (void __user *)params_head, |
| sizeof(key_val))) { |
| dev_err(&GET_DEV(accel_dev), |
| "Failed to copy keyvalue.\n"); |
| goto out_err; |
| } |
| if (adf_add_key_value_data(accel_dev, section.name, |
| &key_val)) { |
| goto out_err; |
| } |
| params_head = key_val.next; |
| } |
| section_head = section.next; |
| } |
| return 0; |
| out_err: |
| adf_cfg_del_all(accel_dev); |
| return -EFAULT; |
| } |
| |
| static int adf_ctl_ioctl_dev_config(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int ret; |
| 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) { |
| ret = -EFAULT; |
| goto out; |
| } |
| |
| if (adf_dev_started(accel_dev)) { |
| ret = -EFAULT; |
| goto out; |
| } |
| |
| if (adf_copy_key_value_data(accel_dev, ctl_data)) { |
| ret = -EFAULT; |
| goto out; |
| } |
| set_bit(ADF_STATUS_CONFIGURED, &accel_dev->status); |
| out: |
| kfree(ctl_data); |
| return ret; |
| } |
| |
| static int adf_ctl_is_device_in_use(int id) |
| { |
| struct adf_accel_dev *dev; |
| |
| list_for_each_entry(dev, adf_devmgr_get_head(), list) { |
| if (id == dev->accel_id || id == ADF_CFG_ALL_DEVICES) { |
| if (adf_devmgr_in_reset(dev) || adf_dev_in_use(dev)) { |
| dev_info(&GET_DEV(dev), |
| "device qat_dev%d is busy\n", |
| dev->accel_id); |
| return -EBUSY; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static void adf_ctl_stop_devices(u32 id) |
| { |
| struct adf_accel_dev *accel_dev; |
| |
| list_for_each_entry(accel_dev, adf_devmgr_get_head(), list) { |
| if (id == accel_dev->accel_id || id == ADF_CFG_ALL_DEVICES) { |
| if (!adf_dev_started(accel_dev)) |
| continue; |
| |
| /* First stop all VFs */ |
| if (!accel_dev->is_vf) |
| continue; |
| |
| adf_dev_stop(accel_dev); |
| adf_dev_shutdown(accel_dev); |
| } |
| } |
| |
| list_for_each_entry(accel_dev, adf_devmgr_get_head(), list) { |
| if (id == accel_dev->accel_id || id == ADF_CFG_ALL_DEVICES) { |
| if (!adf_dev_started(accel_dev)) |
| continue; |
| |
| adf_dev_stop(accel_dev); |
| adf_dev_shutdown(accel_dev); |
| } |
| } |
| } |
| |
| static int adf_ctl_ioctl_dev_stop(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int ret; |
| struct adf_user_cfg_ctl_data *ctl_data; |
| |
| ret = adf_ctl_alloc_resources(&ctl_data, arg); |
| if (ret) |
| return ret; |
| |
| if (adf_devmgr_verify_id(ctl_data->device_id)) { |
| pr_err("QAT: Device %d not found\n", ctl_data->device_id); |
| ret = -ENODEV; |
| goto out; |
| } |
| |
| ret = adf_ctl_is_device_in_use(ctl_data->device_id); |
| if (ret) |
| goto out; |
| |
| if (ctl_data->device_id == ADF_CFG_ALL_DEVICES) |
| pr_info("QAT: Stopping all acceleration devices.\n"); |
| else |
| pr_info("QAT: Stopping acceleration device qat_dev%d.\n", |
| ctl_data->device_id); |
| |
| adf_ctl_stop_devices(ctl_data->device_id); |
| |
| out: |
| kfree(ctl_data); |
| return ret; |
| } |
| |
| static int adf_ctl_ioctl_dev_start(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int ret; |
| 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; |
| |
| ret = -ENODEV; |
| accel_dev = adf_devmgr_get_dev_by_id(ctl_data->device_id); |
| if (!accel_dev) |
| goto out; |
| |
| if (!adf_dev_started(accel_dev)) { |
| dev_info(&GET_DEV(accel_dev), |
| "Starting acceleration device qat_dev%d.\n", |
| ctl_data->device_id); |
| ret = adf_dev_init(accel_dev); |
| if (!ret) |
| ret = adf_dev_start(accel_dev); |
| } else { |
| dev_info(&GET_DEV(accel_dev), |
| "Acceleration device qat_dev%d already started.\n", |
| ctl_data->device_id); |
| } |
| if (ret) { |
| dev_err(&GET_DEV(accel_dev), "Failed to start qat_dev%d\n", |
| ctl_data->device_id); |
| adf_dev_stop(accel_dev); |
| adf_dev_shutdown(accel_dev); |
| } |
| out: |
| kfree(ctl_data); |
| return ret; |
| } |
| |
| static int adf_ctl_ioctl_get_num_devices(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| u32 num_devices = 0; |
| |
| adf_devmgr_get_num_dev(&num_devices); |
| if (copy_to_user((void __user *)arg, &num_devices, sizeof(num_devices))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| static int adf_ctl_ioctl_get_status(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct adf_hw_device_data *hw_data; |
| struct adf_dev_status_info dev_info; |
| struct adf_accel_dev *accel_dev; |
| |
| if (copy_from_user(&dev_info, (void __user *)arg, |
| sizeof(struct adf_dev_status_info))) { |
| pr_err("QAT: failed to copy from user.\n"); |
| return -EFAULT; |
| } |
| |
| 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 = accel_to_pci_dev(accel_dev)->bus->number; |
| dev_info.dev = PCI_SLOT(accel_to_pci_dev(accel_dev)->devfn); |
| dev_info.fun = PCI_FUNC(accel_to_pci_dev(accel_dev)->devfn); |
| |
| if (copy_to_user((void __user *)arg, &dev_info, |
| sizeof(struct adf_dev_status_info))) { |
| dev_err(&GET_DEV(accel_dev), "failed to copy status.\n"); |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| static long adf_ctl_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) |
| { |
| int ret; |
| |
| if (mutex_lock_interruptible(&adf_ctl_lock)) |
| return -EFAULT; |
| |
| switch (cmd) { |
| case IOCTL_CONFIG_SYS_RESOURCE_PARAMETERS: |
| ret = adf_ctl_ioctl_dev_config(fp, cmd, arg); |
| break; |
| |
| case IOCTL_STOP_ACCEL_DEV: |
| ret = adf_ctl_ioctl_dev_stop(fp, cmd, arg); |
| break; |
| |
| case IOCTL_START_ACCEL_DEV: |
| ret = adf_ctl_ioctl_dev_start(fp, cmd, arg); |
| break; |
| |
| case IOCTL_GET_NUM_DEVICES: |
| ret = adf_ctl_ioctl_get_num_devices(fp, cmd, arg); |
| break; |
| |
| case IOCTL_STATUS_ACCEL_DEV: |
| ret = adf_ctl_ioctl_get_status(fp, cmd, arg); |
| break; |
| default: |
| pr_err("QAT: Invalid ioctl\n"); |
| ret = -EFAULT; |
| break; |
| } |
| mutex_unlock(&adf_ctl_lock); |
| return ret; |
| } |
| |
| static int __init adf_register_ctl_device_driver(void) |
| { |
| if (adf_chr_drv_create()) |
| goto err_chr_dev; |
| |
| if (adf_init_aer()) |
| goto err_aer; |
| |
| if (adf_init_pf_wq()) |
| goto err_pf_wq; |
| |
| if (adf_init_vf_wq()) |
| goto err_vf_wq; |
| |
| if (qat_crypto_register()) |
| goto err_crypto_register; |
| |
| return 0; |
| |
| err_crypto_register: |
| adf_exit_vf_wq(); |
| err_vf_wq: |
| adf_exit_pf_wq(); |
| err_pf_wq: |
| adf_exit_aer(); |
| err_aer: |
| adf_chr_drv_destroy(); |
| err_chr_dev: |
| mutex_destroy(&adf_ctl_lock); |
| return -EFAULT; |
| } |
| |
| static void __exit adf_unregister_ctl_device_driver(void) |
| { |
| adf_chr_drv_destroy(); |
| adf_exit_aer(); |
| adf_exit_vf_wq(); |
| adf_exit_pf_wq(); |
| qat_crypto_unregister(); |
| adf_clean_vf_map(false); |
| mutex_destroy(&adf_ctl_lock); |
| } |
| |
| module_init(adf_register_ctl_device_driver); |
| module_exit(adf_unregister_ctl_device_driver); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_AUTHOR("Intel"); |
| MODULE_DESCRIPTION("Intel(R) QuickAssist Technology"); |
| MODULE_ALIAS_CRYPTO("intel_qat"); |
| MODULE_VERSION(ADF_DRV_VERSION); |