projects/linux·blob master · refs

446 lines · 10.3 KiB

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
26struct class *issei_class;
27static dev_t issei_devt;
28
29#define ISSEI_MAX_DEVS MINORMASK
30
31static DEFINE_XARRAY_ALLOC(issei_minor_xa);
32
33static 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
56static 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
67static 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
117static 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
163static 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
214static __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
248static 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}
256static DEVICE_ATTR_RO(fw_ver);
257
258static struct attribute *issei_attrs[] = {
259 &dev_attr_fw_ver.attr,
260 NULL
261};
262ATTRIBUTE_GROUPS(issei);
263
264static 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
275static void issei_device_release(struct device *dev)
276{
277 kfree(dev_get_drvdata(dev));
278}
279
280static 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 */
313struct 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
384err_del_dev:
385 device_del(&idev->dev);
386err_del_cdev:
387 cdev_del(idev->cdev);
388err:
389 put_device(&idev->dev);
390 xa_erase(&issei_minor_xa, minor);
391
392 return ERR_PTR(ret);
393}
394EXPORT_SYMBOL_GPL(issei_register);
395
396/**
397 * issei_deregister: remove issei character device
398 * @idev: the device structure
399 */
400void 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}
414EXPORT_SYMBOL_GPL(issei_deregister);
415
416static 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
436static void __exit issei_cdev_exit(void)
437{
438 unregister_chrdev_region(issei_devt, ISSEI_MAX_DEVS);
439 class_destroy(issei_class);
440}
441
442module_init(issei_cdev_init);
443module_exit(issei_cdev_exit);
444
445MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface");
446MODULE_LICENSE("GPL");