xref: /linux/drivers/misc/issei/cdev.c (revision 65f0ecb9ec85cb3a2e2372a6427dcc100691fc57)
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