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; 178 bool found = false; 179 180 mutex_lock(&unit_mutex); 181 182 list_for_each_entry(unit, &unit_list, list_entry) 183 if (unit->private_data == private_data) { 184 found = true; 185 break; 186 } 187 188 if (!found) { 189 dev_err(dev, "Weird bug: Failed to find unit\n"); 190 mutex_unlock(&unit_mutex); 191 return; 192 } 193 194 for (minor = unit->lowest_minor; 195 minor < (unit->lowest_minor + unit->num_nodes); 196 minor++) 197 device_destroy(xillybus_class, MKDEV(unit->major, minor)); 198 199 cdev_del(unit->cdev); 200 201 unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor), 202 unit->num_nodes); 203 204 dev_info(dev, "Removed %d device files.\n", 205 unit->num_nodes); 206 207 list_del(&unit->list_entry); 208 kfree(unit); 209 210 mutex_unlock(&unit_mutex); 211 } 212 EXPORT_SYMBOL(xillybus_cleanup_chrdev); 213 214 int xillybus_find_inode(struct inode *inode, 215 void **private_data, int *index) 216 { 217 int minor = iminor(inode); 218 int major = imajor(inode); 219 struct xilly_unit *unit; 220 bool found = false; 221 222 mutex_lock(&unit_mutex); 223 224 list_for_each_entry(unit, &unit_list, list_entry) 225 if (unit->major == major && 226 minor >= unit->lowest_minor && 227 minor < (unit->lowest_minor + unit->num_nodes)) { 228 found = true; 229 break; 230 } 231 232 mutex_unlock(&unit_mutex); 233 234 if (!found) 235 return -ENODEV; 236 237 *private_data = unit->private_data; 238 *index = minor - unit->lowest_minor; 239 240 return 0; 241 } 242 EXPORT_SYMBOL(xillybus_find_inode); 243 244 static int __init xillybus_class_init(void) 245 { 246 xillybus_class = class_create(THIS_MODULE, "xillybus"); 247 248 if (IS_ERR(xillybus_class)) { 249 pr_warn("Failed to register xillybus class\n"); 250 251 return PTR_ERR(xillybus_class); 252 } 253 return 0; 254 } 255 256 static void __exit xillybus_class_exit(void) 257 { 258 class_destroy(xillybus_class); 259 } 260 261 module_init(xillybus_class_init); 262 module_exit(xillybus_class_exit); 263