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