1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (C) 2023-2026 Intel Corporation */ 3 #include <linux/atomic.h> 4 #include <linux/cdev.h> 5 #include <linux/device.h> 6 #include <linux/err.h> 7 #include <linux/export.h> 8 #include <linux/fs.h> 9 #include <linux/issei.h> 10 #include <linux/kobject.h> 11 #include <linux/list.h> 12 #include <linux/module.h> 13 #include <linux/mutex.h> 14 #include <linux/types.h> 15 #include <linux/wait.h> 16 #include <linux/xarray.h> 17 18 #include "issei_dev.h" 19 #include "cdev.h" 20 21 struct class *issei_class; 22 static dev_t issei_devt; 23 24 #define ISSEI_MAX_DEVS MINORMASK 25 26 static DEFINE_XARRAY_ALLOC(issei_minor_xa); 27 28 static ssize_t fw_ver_show(struct device *device, 29 struct device_attribute *attr, char *buf) 30 { 31 struct issei_device *idev = dev_get_drvdata(device); 32 33 return sysfs_emit(buf, "%u.%u.%u.%u\n", idev->fw_version[0], idev->fw_version[1], 34 idev->fw_version[2], idev->fw_version[3]); 35 } 36 static DEVICE_ATTR_RO(fw_ver); 37 38 static struct attribute *issei_attrs[] = { 39 &dev_attr_fw_ver.attr, 40 NULL 41 }; 42 ATTRIBUTE_GROUPS(issei); 43 44 static const struct file_operations issei_fops = { 45 .owner = THIS_MODULE, 46 }; 47 48 static void issei_device_release(struct device *dev) 49 { 50 kfree(dev_get_drvdata(dev)); 51 } 52 53 static void issei_device_init(struct issei_device *idev, struct device *parent, 54 const struct issei_dma_length *dma_length, 55 const struct issei_hw_ops *ops) 56 { 57 idev->parent = parent; 58 idev->power_down = false; 59 init_waitqueue_head(&idev->wait_has_data); 60 idev->has_data = false; 61 init_waitqueue_head(&idev->wait_rst_state); 62 idev->rst_state = ISSEI_RST_STATE_INIT; 63 64 mutex_init(&idev->client_lock); 65 INIT_LIST_HEAD(&idev->host_client_list); 66 idev->host_client_last_id = 0; 67 idev->host_client_count = 0; 68 INIT_LIST_HEAD(&idev->fw_client_list); 69 INIT_LIST_HEAD(&idev->write_queue); 70 idev->last_write_ts = 0; 71 72 idev->dma.length = *dma_length; 73 74 idev->ops = ops; 75 } 76 77 /** 78 * issei_register: register issei character device 79 * @hw_size: size of the hardware structure to allocate 80 * @parent: parent device 81 * @dma_length: structure with DMA sizes 82 * @ops: hardware-related operations 83 * 84 * Return: pointer allocated to issei_device structure, error on failure 85 */ 86 struct issei_device *issei_register(size_t hw_size, struct device *parent, 87 const struct issei_dma_length *dma_length, 88 const struct issei_hw_ops *ops) 89 { 90 struct issei_device *idev; 91 u32 minor; 92 int ret, devno; 93 94 idev = kzalloc(sizeof(*idev) + hw_size, GFP_KERNEL); 95 if (!idev) 96 return ERR_PTR(-ENOMEM); 97 98 issei_device_init(idev, parent, dma_length, ops); 99 100 ret = xa_alloc(&issei_minor_xa, &minor, idev, XA_LIMIT(0, ISSEI_MAX_DEVS), GFP_KERNEL); 101 if (ret < 0) { 102 dev_err(&idev->dev, "Failed to allocate minor. ret = %d\n", ret); 103 kfree(idev); 104 return ERR_PTR(ret); 105 } 106 107 idev->minor = minor; 108 devno = MKDEV(MAJOR(issei_devt), idev->minor); 109 110 device_initialize(&idev->dev); 111 idev->dev.devt = devno; 112 idev->dev.class = issei_class; 113 idev->dev.parent = parent; 114 idev->dev.groups = issei_groups; 115 idev->dev.release = issei_device_release; 116 dev_set_drvdata(&idev->dev, idev); 117 118 idev->cdev = cdev_alloc(); 119 if (!idev->cdev) { 120 ret = -ENOMEM; 121 goto err; 122 } 123 idev->cdev->ops = &issei_fops; 124 if (parent->driver) 125 idev->cdev->owner = parent->driver->owner; 126 cdev_set_parent(idev->cdev, &idev->dev.kobj); 127 128 ret = cdev_add(idev->cdev, devno, 1); 129 if (ret) { 130 dev_err(parent, "unable to add device %d:%u ret = %d\n", 131 MAJOR(issei_devt), idev->minor, ret); 132 goto err_del_cdev; 133 } 134 135 ret = dev_set_name(&idev->dev, "issei%u", idev->minor); 136 if (ret) { 137 dev_err(parent, "unable to set name to device %d:%u ret = %d\n", 138 MAJOR(issei_devt), idev->minor, ret); 139 goto err_del_cdev; 140 } 141 142 ret = device_add(&idev->dev); 143 if (ret) { 144 dev_err(parent, "unable to add device %d:%u ret = %d\n", 145 MAJOR(issei_devt), idev->minor, ret); 146 goto err_del_cdev; 147 } 148 149 idev->fw_clients = kset_create_and_add("fw_clients", NULL, &idev->dev.kobj); 150 if (!idev->fw_clients) { 151 ret = -ENOMEM; 152 goto err_del_dev; 153 } 154 155 return idev; 156 157 err_del_dev: 158 device_del(&idev->dev); 159 err_del_cdev: 160 cdev_del(idev->cdev); 161 err: 162 put_device(&idev->dev); 163 xa_erase(&issei_minor_xa, minor); 164 165 return ERR_PTR(ret); 166 } 167 EXPORT_SYMBOL_GPL(issei_register); 168 169 /** 170 * issei_deregister: remove issei character device 171 * @idev: the device structure 172 */ 173 void issei_deregister(struct issei_device *idev) 174 { 175 u32 minor = idev->minor; 176 177 cdev_del(idev->cdev); 178 179 kset_unregister(idev->fw_clients); 180 181 device_del(&idev->dev); 182 183 put_device(&idev->dev); 184 185 xa_erase(&issei_minor_xa, minor); 186 } 187 EXPORT_SYMBOL_GPL(issei_deregister); 188 189 static int __init issei_cdev_init(void) 190 { 191 int ret; 192 193 issei_class = class_create("issei"); 194 if (IS_ERR(issei_class)) { 195 pr_err("couldn't create class\n"); 196 return PTR_ERR(issei_class); 197 } 198 199 ret = alloc_chrdev_region(&issei_devt, 0, ISSEI_MAX_DEVS, "issei"); 200 if (ret < 0) { 201 pr_err("unable to allocate char dev region\n"); 202 class_destroy(issei_class); 203 return ret; 204 } 205 206 return 0; 207 } 208 209 static void __exit issei_cdev_exit(void) 210 { 211 unregister_chrdev_region(issei_devt, ISSEI_MAX_DEVS); 212 class_destroy(issei_class); 213 } 214 215 module_init(issei_cdev_init); 216 module_exit(issei_cdev_exit); 217 218 MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface"); 219 MODULE_LICENSE("GPL"); 220