GNU Linux-libre 6.7.9-gnu
[releases.git] / drivers / char / xillybus / xillybus_class.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright 2021 Xillybus Ltd, http://xillybus.com
4  *
5  * Driver for the Xillybus class
6  */
7
8 #include <linux/types.h>
9 #include <linux/module.h>
10 #include <linux/device.h>
11 #include <linux/fs.h>
12 #include <linux/cdev.h>
13 #include <linux/slab.h>
14 #include <linux/list.h>
15 #include <linux/mutex.h>
16
17 #include "xillybus_class.h"
18
19 MODULE_DESCRIPTION("Driver for Xillybus class");
20 MODULE_AUTHOR("Eli Billauer, Xillybus Ltd.");
21 MODULE_ALIAS("xillybus_class");
22 MODULE_LICENSE("GPL v2");
23
24 static DEFINE_MUTEX(unit_mutex);
25 static LIST_HEAD(unit_list);
26 static const struct class xillybus_class = {
27         .name = "xillybus",
28 };
29
30 #define UNITNAMELEN 16
31
32 struct xilly_unit {
33         struct list_head list_entry;
34         void *private_data;
35
36         struct cdev *cdev;
37         char name[UNITNAMELEN];
38         int major;
39         int lowest_minor;
40         int num_nodes;
41 };
42
43 int xillybus_init_chrdev(struct device *dev,
44                          const struct file_operations *fops,
45                          struct module *owner,
46                          void *private_data,
47                          unsigned char *idt, unsigned int len,
48                          int num_nodes,
49                          const char *prefix, bool enumerate)
50 {
51         int rc;
52         dev_t mdev;
53         int i;
54         char devname[48];
55
56         struct device *device;
57         size_t namelen;
58         struct xilly_unit *unit, *u;
59
60         unit = kzalloc(sizeof(*unit), GFP_KERNEL);
61
62         if (!unit)
63                 return -ENOMEM;
64
65         mutex_lock(&unit_mutex);
66
67         if (!enumerate)
68                 snprintf(unit->name, UNITNAMELEN, "%s", prefix);
69
70         for (i = 0; enumerate; i++) {
71                 snprintf(unit->name, UNITNAMELEN, "%s_%02d",
72                          prefix, i);
73
74                 enumerate = false;
75                 list_for_each_entry(u, &unit_list, list_entry)
76                         if (!strcmp(unit->name, u->name)) {
77                                 enumerate = true;
78                                 break;
79                         }
80         }
81
82         rc = alloc_chrdev_region(&mdev, 0, num_nodes, unit->name);
83
84         if (rc) {
85                 dev_warn(dev, "Failed to obtain major/minors");
86                 goto fail_obtain;
87         }
88
89         unit->major = MAJOR(mdev);
90         unit->lowest_minor = MINOR(mdev);
91         unit->num_nodes = num_nodes;
92         unit->private_data = private_data;
93
94         unit->cdev = cdev_alloc();
95         if (!unit->cdev) {
96                 rc = -ENOMEM;
97                 goto unregister_chrdev;
98         }
99         unit->cdev->ops = fops;
100         unit->cdev->owner = owner;
101
102         rc = cdev_add(unit->cdev, MKDEV(unit->major, unit->lowest_minor),
103                       unit->num_nodes);
104         if (rc) {
105                 dev_err(dev, "Failed to add cdev.\n");
106                 /* kobject_put() is normally done by cdev_del() */
107                 kobject_put(&unit->cdev->kobj);
108                 goto unregister_chrdev;
109         }
110
111         for (i = 0; i < num_nodes; i++) {
112                 namelen = strnlen(idt, len);
113
114                 if (namelen == len) {
115                         dev_err(dev, "IDT's list of names is too short. This is exceptionally weird, because its CRC is OK\n");
116                         rc = -ENODEV;
117                         goto unroll_device_create;
118                 }
119
120                 snprintf(devname, sizeof(devname), "%s_%s",
121                          unit->name, idt);
122
123                 len -= namelen + 1;
124                 idt += namelen + 1;
125
126                 device = device_create(&xillybus_class,
127                                        NULL,
128                                        MKDEV(unit->major,
129                                              i + unit->lowest_minor),
130                                        NULL,
131                                        "%s", devname);
132
133                 if (IS_ERR(device)) {
134                         dev_err(dev, "Failed to create %s device. Aborting.\n",
135                                 devname);
136                         rc = -ENODEV;
137                         goto unroll_device_create;
138                 }
139         }
140
141         if (len) {
142                 dev_err(dev, "IDT's list of names is too long. This is exceptionally weird, because its CRC is OK\n");
143                 rc = -ENODEV;
144                 goto unroll_device_create;
145         }
146
147         list_add_tail(&unit->list_entry, &unit_list);
148
149         dev_info(dev, "Created %d device files.\n", num_nodes);
150
151         mutex_unlock(&unit_mutex);
152
153         return 0;
154
155 unroll_device_create:
156         for (i--; i >= 0; i--)
157                 device_destroy(&xillybus_class, MKDEV(unit->major,
158                                                      i + unit->lowest_minor));
159
160         cdev_del(unit->cdev);
161
162 unregister_chrdev:
163         unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor),
164                                  unit->num_nodes);
165
166 fail_obtain:
167         mutex_unlock(&unit_mutex);
168
169         kfree(unit);
170
171         return rc;
172 }
173 EXPORT_SYMBOL(xillybus_init_chrdev);
174
175 void xillybus_cleanup_chrdev(void *private_data,
176                              struct device *dev)
177 {
178         int minor;
179         struct xilly_unit *unit = NULL, *iter;
180
181         mutex_lock(&unit_mutex);
182
183         list_for_each_entry(iter, &unit_list, list_entry)
184                 if (iter->private_data == private_data) {
185                         unit = iter;
186                         break;
187                 }
188
189         if (!unit) {
190                 dev_err(dev, "Weird bug: Failed to find unit\n");
191                 mutex_unlock(&unit_mutex);
192                 return;
193         }
194
195         for (minor = unit->lowest_minor;
196              minor < (unit->lowest_minor + unit->num_nodes);
197              minor++)
198                 device_destroy(&xillybus_class, MKDEV(unit->major, minor));
199
200         cdev_del(unit->cdev);
201
202         unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor),
203                                  unit->num_nodes);
204
205         dev_info(dev, "Removed %d device files.\n",
206                  unit->num_nodes);
207
208         list_del(&unit->list_entry);
209         kfree(unit);
210
211         mutex_unlock(&unit_mutex);
212 }
213 EXPORT_SYMBOL(xillybus_cleanup_chrdev);
214
215 int xillybus_find_inode(struct inode *inode,
216                         void **private_data, int *index)
217 {
218         int minor = iminor(inode);
219         int major = imajor(inode);
220         struct xilly_unit *unit = NULL, *iter;
221
222         mutex_lock(&unit_mutex);
223
224         list_for_each_entry(iter, &unit_list, list_entry)
225                 if (iter->major == major &&
226                     minor >= iter->lowest_minor &&
227                     minor < (iter->lowest_minor + iter->num_nodes)) {
228                         unit = iter;
229                         break;
230                 }
231
232         if (!unit) {
233                 mutex_unlock(&unit_mutex);
234                 return -ENODEV;
235         }
236
237         *private_data = unit->private_data;
238         *index = minor - unit->lowest_minor;
239
240         mutex_unlock(&unit_mutex);
241         return 0;
242 }
243 EXPORT_SYMBOL(xillybus_find_inode);
244
245 static int __init xillybus_class_init(void)
246 {
247         return class_register(&xillybus_class);
248 }
249
250 static void __exit xillybus_class_exit(void)
251 {
252         class_unregister(&xillybus_class);
253 }
254
255 module_init(xillybus_class_init);
256 module_exit(xillybus_class_exit);