123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright 2021 Xillybus Ltd, http://xillybus.com
- *
- * Driver for the Xillybus class
- */
- #include <linux/types.h>
- #include <linux/module.h>
- #include <linux/device.h>
- #include <linux/fs.h>
- #include <linux/cdev.h>
- #include <linux/slab.h>
- #include <linux/list.h>
- #include <linux/mutex.h>
- #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 = NULL, *iter;
- mutex_lock(&unit_mutex);
- list_for_each_entry(iter, &unit_list, list_entry)
- if (iter->private_data == private_data) {
- unit = iter;
- break;
- }
- if (!unit) {
- 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 = NULL, *iter;
- mutex_lock(&unit_mutex);
- list_for_each_entry(iter, &unit_list, list_entry)
- if (iter->major == major &&
- minor >= iter->lowest_minor &&
- minor < (iter->lowest_minor + iter->num_nodes)) {
- unit = iter;
- break;
- }
- mutex_unlock(&unit_mutex);
- if (!unit)
- 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);
|