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); 257