xref: /linux/drivers/misc/issei/cdev.c (revision c16ce856e422e73a54c41131e0332de1afe09b8b)
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/cleanup.h>
6 #include <linux/device.h>
7 #include <linux/err.h>
8 #include <linux/export.h>
9 #include <linux/fs.h>
10 #include <linux/issei.h>
11 #include <linux/kobject.h>
12 #include <linux/list.h>
13 #include <linux/module.h>
14 #include <linux/mutex.h>
15 #include <linux/poll.h>
16 #include <linux/sched/signal.h>
17 #include <linux/slab.h>
18 #include <linux/types.h>
19 #include <linux/wait.h>
20 #include <linux/xarray.h>
21 
22 #include "issei_dev.h"
23 #include "host_client.h"
24 #include "cdev.h"
25 
26 struct class *issei_class;
27 static dev_t issei_devt;
28 
29 #define ISSEI_MAX_DEVS MINORMASK
30 
31 static DEFINE_XARRAY_ALLOC(issei_minor_xa);
32 
33 static int issei_open(struct inode *inode, struct file *fp)
34 {
35 	struct issei_host_client *cl;
36 	struct issei_device *idev;
37 
38 	xa_lock(&issei_minor_xa);
39 	idev = xa_load(&issei_minor_xa, iminor(inode));
40 	if (idev)
41 		get_device(&idev->dev);
42 	xa_unlock(&issei_minor_xa);
43 	if (!idev)
44 		return -ENODEV;
45 
46 	cl = issei_cl_create(idev, fp);
47 	if (IS_ERR(cl)) {
48 		put_device(&idev->dev);
49 		return PTR_ERR(cl);
50 	}
51 	fp->private_data = cl;
52 
53 	return nonseekable_open(inode, fp);
54 }
55 
56 static int issei_release(struct inode *inode, struct file *fp)
57 {
58 	struct issei_host_client *cl = fp->private_data;
59 	struct issei_device *idev = cl->idev;
60 
61 	issei_cl_remove(cl);
62 	put_device(&idev->dev);
63 
64 	return 0;
65 }
66 
67 static long issei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
68 {
69 	struct issei_host_client *cl = file->private_data;
70 	struct issei_connect_client_data conn;
71 	struct issei_device *idev = cl->idev;
72 	int ret;
73 
74 	switch (cmd) {
75 	case IOCTL_ISSEI_CONNECT_CLIENT:
76 		dev_dbg(&idev->dev, "IOCTL_ISSEI_CONNECT_CLIENT\n");
77 
78 		if (idev->rst_state != ISSEI_RST_STATE_DONE) {
79 			dev_dbg(&idev->dev, "Device is in transition\n");
80 			return -ENODEV;
81 		}
82 
83 		if (copy_from_user(&conn, (char __user *)data, sizeof(conn))) {
84 			dev_dbg(&idev->dev, "failed to copy data from userland\n");
85 			return -EFAULT;
86 		}
87 
88 		ret = issei_cl_connect(cl, (uuid_t *)&conn.in_client_uuid,
89 				       &conn.out_client_properties.max_msg_length,
90 				       &conn.out_client_properties.protocol_version,
91 				       &conn.out_client_properties.flags);
92 		if (ret)
93 			return ret;
94 
95 		if (copy_to_user((char __user *)data, &conn, sizeof(conn))) {
96 			dev_dbg(&idev->dev, "failed to copy data to userland\n");
97 			issei_cl_disconnect(cl);
98 			return -EFAULT;
99 		}
100 		return 0;
101 
102 	case IOCTL_ISSEI_DISCONNECT_CLIENT:
103 		dev_dbg(&idev->dev, "IOCTL_ISSEI_DISCONNECT_CLIENT\n");
104 
105 		if (idev->rst_state != ISSEI_RST_STATE_DONE) {
106 			dev_dbg(&idev->dev, "Device is in transition\n");
107 			return -ENODEV;
108 		}
109 
110 		return issei_cl_disconnect(cl);
111 
112 	default:
113 		return -ENOIOCTLCMD;
114 	}
115 }
116 
117 static ssize_t issei_write(struct file *file, const char __user *ubuf,
118 			   size_t length, loff_t *offset)
119 {
120 	struct issei_host_client *cl = file->private_data;
121 	struct issei_device *idev = cl->idev;
122 	ssize_t ret;
123 
124 	if (!length)
125 		return 0;
126 
127 	if (idev->rst_state != ISSEI_RST_STATE_DONE) {
128 		dev_dbg(&idev->dev, "Device is in transition\n");
129 		return -EBUSY;
130 	}
131 
132 	/* sanity check */
133 	if (length > idev->dma.length.h2f) {
134 		dev_dbg(&idev->dev, "Write is too big %zu > %zu\n",
135 			length, idev->dma.length.h2f);
136 		return -EFBIG;
137 	}
138 
139 	u8 *buf __free(kfree) = memdup_user(ubuf, length);
140 	if (IS_ERR(buf)) {
141 		dev_dbg(&idev->dev, "failed to copy data from userland\n");
142 		return PTR_ERR(buf);
143 	}
144 
145 	do {
146 		ret = issei_cl_write(cl, buf, length);
147 		if (ret < 0 && ret != -EAGAIN)
148 			return ret;
149 		/* buf is consumed by issei_cl_write on success */
150 		if (ret >= 0)
151 			retain_and_null_ptr(buf);
152 		if (wait_event_interruptible(cl->write_wait, issei_cl_check_write(cl) != 1)) {
153 			issei_cl_clean_all_wbuf(cl);
154 			if (signal_pending(current))
155 				return -EINTR;
156 			return -ERESTARTSYS;
157 		}
158 	} while (ret == -EAGAIN);
159 
160 	return ret;
161 }
162 
163 static ssize_t issei_read(struct file *file, char __user *ubuf,
164 			  size_t length, loff_t *offset)
165 {
166 	struct issei_host_client *cl = file->private_data;
167 	struct issei_device *idev = cl->idev;
168 	u8 *data = NULL;
169 	ssize_t ret;
170 
171 	if (!length)
172 		return 0;
173 
174 	if (idev->rst_state != ISSEI_RST_STATE_DONE) {
175 		dev_dbg(&idev->dev, "Device is in transition\n");
176 		return -EBUSY;
177 	}
178 
179 	/* sanity check */
180 	if (length > idev->dma.length.f2h) {
181 		dev_dbg(&idev->dev, "Read is too big %zu > %zu\n",
182 			length, idev->dma.length.f2h);
183 		return -EFBIG;
184 	}
185 
186 	ret = issei_cl_read(cl, &data, length);
187 	if (ret < 0) {
188 		if (ret != -ENOENT)
189 			return ret;
190 
191 		if (wait_event_interruptible(cl->read_wait, issei_cl_check_read(cl) != 0)) {
192 			if (signal_pending(current))
193 				return -EINTR;
194 			return -ERESTARTSYS;
195 		}
196 
197 		ret = issei_cl_read(cl, &data, length);
198 		if (ret < 0)
199 			return ret;
200 	}
201 
202 	if (copy_to_user(ubuf, data, ret)) {
203 		dev_dbg(&idev->dev, "failed to copy data to userland\n");
204 		ret = -EFAULT;
205 	} else {
206 		*offset = 0;
207 	}
208 
209 	kfree(data);
210 
211 	return ret;
212 }
213 
214 static __poll_t issei_poll(struct file *file, poll_table *wait)
215 {
216 	__poll_t req_events = poll_requested_events(wait);
217 	struct issei_host_client *cl = file->private_data;
218 	struct issei_device *idev = cl->idev;
219 	__poll_t mask = 0;
220 	int ret;
221 
222 	if (idev->rst_state != ISSEI_RST_STATE_DONE) {
223 		dev_dbg(&idev->dev, "Device is in transition\n");
224 		return EPOLLERR;
225 	}
226 
227 	if (req_events & (EPOLLIN | EPOLLRDNORM)) {
228 		poll_wait(file, &cl->read_wait, wait);
229 		ret = issei_cl_check_read(cl);
230 		if (ret == 1)
231 			mask |= EPOLLIN | EPOLLRDNORM;
232 		else if (ret < 0)
233 			mask |= EPOLLERR;
234 	}
235 
236 	if (req_events & (EPOLLOUT | EPOLLWRNORM)) {
237 		poll_wait(file, &cl->write_wait, wait);
238 		ret = issei_cl_check_write(cl);
239 		if (ret == 0)
240 			mask |= EPOLLOUT | EPOLLWRNORM;
241 		else if (ret < 0)
242 			mask |= EPOLLERR;
243 	}
244 
245 	return mask;
246 }
247 
248 static ssize_t fw_ver_show(struct device *device,
249 			   struct device_attribute *attr, char *buf)
250 {
251 	struct issei_device *idev = dev_get_drvdata(device);
252 
253 	return sysfs_emit(buf, "%u.%u.%u.%u\n", idev->fw_version[0], idev->fw_version[1],
254 			  idev->fw_version[2], idev->fw_version[3]);
255 }
256 static DEVICE_ATTR_RO(fw_ver);
257 
258 static struct attribute *issei_attrs[] = {
259 	&dev_attr_fw_ver.attr,
260 	NULL
261 };
262 ATTRIBUTE_GROUPS(issei);
263 
264 static const struct file_operations issei_fops = {
265 	.owner = THIS_MODULE,
266 	.open = issei_open,
267 	.unlocked_ioctl = issei_ioctl,
268 	.compat_ioctl = compat_ptr_ioctl,
269 	.write = issei_write,
270 	.read = issei_read,
271 	.release = issei_release,
272 	.poll = issei_poll,
273 };
274 
275 static void issei_device_release(struct device *dev)
276 {
277 	kfree(dev_get_drvdata(dev));
278 }
279 
280 static void issei_device_init(struct issei_device *idev, struct device *parent,
281 			      const struct issei_dma_length *dma_length,
282 			      const struct issei_hw_ops *ops)
283 {
284 	idev->parent = parent;
285 	idev->power_down = false;
286 	init_waitqueue_head(&idev->wait_has_data);
287 	idev->has_data = false;
288 	init_waitqueue_head(&idev->wait_rst_state);
289 	idev->rst_state = ISSEI_RST_STATE_INIT;
290 
291 	mutex_init(&idev->client_lock);
292 	INIT_LIST_HEAD(&idev->host_client_list);
293 	idev->host_client_last_id = 0;
294 	idev->host_client_count = 0;
295 	INIT_LIST_HEAD(&idev->fw_client_list);
296 	INIT_LIST_HEAD(&idev->write_queue);
297 	idev->last_write_ts = 0;
298 
299 	idev->dma.length = *dma_length;
300 
301 	idev->ops = ops;
302 }
303 
304 /**
305  * issei_register: register issei character device
306  * @hw_size: size of the hardware structure to allocate
307  * @parent: parent device
308  * @dma_length: structure with DMA sizes
309  * @ops: hardware-related operations
310  *
311  * Return: pointer allocated to issei_device structure, error on failure
312  */
313 struct issei_device *issei_register(size_t hw_size, struct device *parent,
314 				    const struct issei_dma_length *dma_length,
315 				    const struct issei_hw_ops *ops)
316 {
317 	struct issei_device *idev;
318 	u32 minor;
319 	int ret, devno;
320 
321 	idev = kzalloc(sizeof(*idev) + hw_size, GFP_KERNEL);
322 	if (!idev)
323 		return ERR_PTR(-ENOMEM);
324 
325 	issei_device_init(idev, parent, dma_length, ops);
326 
327 	ret = xa_alloc(&issei_minor_xa, &minor, idev, XA_LIMIT(0, ISSEI_MAX_DEVS), GFP_KERNEL);
328 	if (ret < 0) {
329 		dev_err(&idev->dev, "Failed to allocate minor. ret = %d\n", ret);
330 		kfree(idev);
331 		return ERR_PTR(ret);
332 	}
333 
334 	idev->minor = minor;
335 	devno = MKDEV(MAJOR(issei_devt), idev->minor);
336 
337 	device_initialize(&idev->dev);
338 	idev->dev.devt = devno;
339 	idev->dev.class = issei_class;
340 	idev->dev.parent = parent;
341 	idev->dev.groups = issei_groups;
342 	idev->dev.release = issei_device_release;
343 	dev_set_drvdata(&idev->dev, idev);
344 
345 	idev->cdev = cdev_alloc();
346 	if (!idev->cdev) {
347 		ret = -ENOMEM;
348 		goto err;
349 	}
350 	idev->cdev->ops = &issei_fops;
351 	if (parent->driver)
352 		idev->cdev->owner = parent->driver->owner;
353 	cdev_set_parent(idev->cdev, &idev->dev.kobj);
354 
355 	ret = cdev_add(idev->cdev, devno, 1);
356 	if (ret) {
357 		dev_err(parent, "unable to add device %d:%u ret = %d\n",
358 			MAJOR(issei_devt), idev->minor, ret);
359 		goto err_del_cdev;
360 	}
361 
362 	ret = dev_set_name(&idev->dev, "issei%u", idev->minor);
363 	if (ret) {
364 		dev_err(parent, "unable to set name to device %d:%u ret = %d\n",
365 			MAJOR(issei_devt), idev->minor, ret);
366 		goto err_del_cdev;
367 	}
368 
369 	ret = device_add(&idev->dev);
370 	if (ret) {
371 		dev_err(parent, "unable to add device %d:%u ret = %d\n",
372 			MAJOR(issei_devt), idev->minor, ret);
373 		goto err_del_cdev;
374 	}
375 
376 	idev->fw_clients = kset_create_and_add("fw_clients", NULL, &idev->dev.kobj);
377 	if (!idev->fw_clients) {
378 		ret = -ENOMEM;
379 		goto err_del_dev;
380 	}
381 
382 	return idev;
383 
384 err_del_dev:
385 	device_del(&idev->dev);
386 err_del_cdev:
387 	cdev_del(idev->cdev);
388 err:
389 	put_device(&idev->dev);
390 	xa_erase(&issei_minor_xa, minor);
391 
392 	return ERR_PTR(ret);
393 }
394 EXPORT_SYMBOL_GPL(issei_register);
395 
396 /**
397  * issei_deregister: remove issei character device
398  * @idev: the device structure
399  */
400 void issei_deregister(struct issei_device *idev)
401 {
402 	u32 minor = idev->minor;
403 
404 	cdev_del(idev->cdev);
405 
406 	kset_unregister(idev->fw_clients);
407 
408 	device_del(&idev->dev);
409 
410 	put_device(&idev->dev);
411 
412 	xa_erase(&issei_minor_xa, minor);
413 }
414 EXPORT_SYMBOL_GPL(issei_deregister);
415 
416 static int __init issei_cdev_init(void)
417 {
418 	int ret;
419 
420 	issei_class = class_create("issei");
421 	if (IS_ERR(issei_class)) {
422 		pr_err("couldn't create class\n");
423 		return PTR_ERR(issei_class);
424 	}
425 
426 	ret = alloc_chrdev_region(&issei_devt, 0, ISSEI_MAX_DEVS, "issei");
427 	if (ret < 0) {
428 		pr_err("unable to allocate char dev region\n");
429 		class_destroy(issei_class);
430 		return ret;
431 	}
432 
433 	return 0;
434 }
435 
436 static void __exit issei_cdev_exit(void)
437 {
438 	unregister_chrdev_region(issei_devt, ISSEI_MAX_DEVS);
439 	class_destroy(issei_class);
440 }
441 
442 module_init(issei_cdev_init);
443 module_exit(issei_cdev_exit);
444 
445 MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface");
446 MODULE_LICENSE("GPL");
447