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