// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2021 Xillybus Ltd, http://xillybus.com * * Driver for the Xillybus class */ #include #include #include #include #include #include #include #include #include "xillybus_class.h" MODULE_DESCRIPTION("Driver for Xillybus class"); MODULE_AUTHOR("Eli Billauer, Xillybus Ltd."); MODULE_ALIAS("xillybus_class"); MODULE_LICENSE("GPL v2"); static DEFINE_MUTEX(unit_mutex); static LIST_HEAD(unit_list); static struct class *xillybus_class; #define UNITNAMELEN 16 struct xilly_unit { struct list_head list_entry; void *private_data; struct cdev *cdev; char name[UNITNAMELEN]; int major; int lowest_minor; int num_nodes; }; int xillybus_init_chrdev(struct device *dev, const struct file_operations *fops, struct module *owner, void *private_data, unsigned char *idt, unsigned int len, int num_nodes, const char *prefix, bool enumerate) { int rc; dev_t mdev; int i; char devname[48]; struct device *device; size_t namelen; struct xilly_unit *unit, *u; unit = kzalloc(sizeof(*unit), GFP_KERNEL); if (!unit) return -ENOMEM; mutex_lock(&unit_mutex); if (!enumerate) snprintf(unit->name, UNITNAMELEN, "%s", prefix); for (i = 0; enumerate; i++) { snprintf(unit->name, UNITNAMELEN, "%s_%02d", prefix, i); enumerate = false; list_for_each_entry(u, &unit_list, list_entry) if (!strcmp(unit->name, u->name)) { enumerate = true; break; } } rc = alloc_chrdev_region(&mdev, 0, num_nodes, unit->name); if (rc) { dev_warn(dev, "Failed to obtain major/minors"); goto fail_obtain; } unit->major = MAJOR(mdev); unit->lowest_minor = MINOR(mdev); unit->num_nodes = num_nodes; unit->private_data = private_data; unit->cdev = cdev_alloc(); if (!unit->cdev) { rc = -ENOMEM; goto unregister_chrdev; } unit->cdev->ops = fops; unit->cdev->owner = owner; rc = cdev_add(unit->cdev, MKDEV(unit->major, unit->lowest_minor), unit->num_nodes); if (rc) { dev_err(dev, "Failed to add cdev.\n"); /* kobject_put() is normally done by cdev_del() */ kobject_put(&unit->cdev->kobj); goto unregister_chrdev; } for (i = 0; i < num_nodes; i++) { namelen = strnlen(idt, len); if (namelen == len) { dev_err(dev, "IDT's list of names is too short. This is exceptionally weird, because its CRC is OK\n"); rc = -ENODEV; goto unroll_device_create; } snprintf(devname, sizeof(devname), "%s_%s", unit->name, idt); len -= namelen + 1; idt += namelen + 1; device = device_create(xillybus_class, NULL, MKDEV(unit->major, i + unit->lowest_minor), NULL, "%s", devname); if (IS_ERR(device)) { dev_err(dev, "Failed to create %s device. Aborting.\n", devname); rc = -ENODEV; goto unroll_device_create; } } if (len) { dev_err(dev, "IDT's list of names is too long. This is exceptionally weird, because its CRC is OK\n"); rc = -ENODEV; goto unroll_device_create; } list_add_tail(&unit->list_entry, &unit_list); dev_info(dev, "Created %d device files.\n", num_nodes); mutex_unlock(&unit_mutex); return 0; unroll_device_create: for (i--; i >= 0; i--) device_destroy(xillybus_class, MKDEV(unit->major, i + unit->lowest_minor)); cdev_del(unit->cdev); unregister_chrdev: unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor), unit->num_nodes); fail_obtain: mutex_unlock(&unit_mutex); kfree(unit); return rc; } EXPORT_SYMBOL(xillybus_init_chrdev); void xillybus_cleanup_chrdev(void *private_data, struct device *dev) { int minor; struct xilly_unit *unit; bool found = false; mutex_lock(&unit_mutex); list_for_each_entry(unit, &unit_list, list_entry) if (unit->private_data == private_data) { found = true; break; } if (!found) { dev_err(dev, "Weird bug: Failed to find unit\n"); mutex_unlock(&unit_mutex); return; } for (minor = unit->lowest_minor; minor < (unit->lowest_minor + unit->num_nodes); minor++) device_destroy(xillybus_class, MKDEV(unit->major, minor)); cdev_del(unit->cdev); unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor), unit->num_nodes); dev_info(dev, "Removed %d device files.\n", unit->num_nodes); list_del(&unit->list_entry); kfree(unit); mutex_unlock(&unit_mutex); } EXPORT_SYMBOL(xillybus_cleanup_chrdev); int xillybus_find_inode(struct inode *inode, void **private_data, int *index) { int minor = iminor(inode); int major = imajor(inode); struct xilly_unit *unit; bool found = false; mutex_lock(&unit_mutex); list_for_each_entry(unit, &unit_list, list_entry) if (unit->major == major && minor >= unit->lowest_minor && minor < (unit->lowest_minor + unit->num_nodes)) { found = true; break; } mutex_unlock(&unit_mutex); if (!found) return -ENODEV; *private_data = unit->private_data; *index = minor - unit->lowest_minor; return 0; } EXPORT_SYMBOL(xillybus_find_inode); static int __init xillybus_class_init(void) { xillybus_class = class_create(THIS_MODULE, "xillybus"); if (IS_ERR(xillybus_class)) { pr_warn("Failed to register xillybus class\n"); return PTR_ERR(xillybus_class); } return 0; } static void __exit xillybus_class_exit(void) { class_destroy(xillybus_class); } module_init(xillybus_class_init); module_exit(xillybus_class_exit);