Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Copyright 2021 Xillybus Ltd, http://xillybus.com
0004  *
0005  * Driver for the Xillybus class
0006  */
0007 
0008 #include <linux/types.h>
0009 #include <linux/module.h>
0010 #include <linux/device.h>
0011 #include <linux/fs.h>
0012 #include <linux/cdev.h>
0013 #include <linux/slab.h>
0014 #include <linux/list.h>
0015 #include <linux/mutex.h>
0016 
0017 #include "xillybus_class.h"
0018 
0019 MODULE_DESCRIPTION("Driver for Xillybus class");
0020 MODULE_AUTHOR("Eli Billauer, Xillybus Ltd.");
0021 MODULE_ALIAS("xillybus_class");
0022 MODULE_LICENSE("GPL v2");
0023 
0024 static DEFINE_MUTEX(unit_mutex);
0025 static LIST_HEAD(unit_list);
0026 static struct class *xillybus_class;
0027 
0028 #define UNITNAMELEN 16
0029 
0030 struct xilly_unit {
0031     struct list_head list_entry;
0032     void *private_data;
0033 
0034     struct cdev *cdev;
0035     char name[UNITNAMELEN];
0036     int major;
0037     int lowest_minor;
0038     int num_nodes;
0039 };
0040 
0041 int xillybus_init_chrdev(struct device *dev,
0042              const struct file_operations *fops,
0043              struct module *owner,
0044              void *private_data,
0045              unsigned char *idt, unsigned int len,
0046              int num_nodes,
0047              const char *prefix, bool enumerate)
0048 {
0049     int rc;
0050     dev_t mdev;
0051     int i;
0052     char devname[48];
0053 
0054     struct device *device;
0055     size_t namelen;
0056     struct xilly_unit *unit, *u;
0057 
0058     unit = kzalloc(sizeof(*unit), GFP_KERNEL);
0059 
0060     if (!unit)
0061         return -ENOMEM;
0062 
0063     mutex_lock(&unit_mutex);
0064 
0065     if (!enumerate)
0066         snprintf(unit->name, UNITNAMELEN, "%s", prefix);
0067 
0068     for (i = 0; enumerate; i++) {
0069         snprintf(unit->name, UNITNAMELEN, "%s_%02d",
0070              prefix, i);
0071 
0072         enumerate = false;
0073         list_for_each_entry(u, &unit_list, list_entry)
0074             if (!strcmp(unit->name, u->name)) {
0075                 enumerate = true;
0076                 break;
0077             }
0078     }
0079 
0080     rc = alloc_chrdev_region(&mdev, 0, num_nodes, unit->name);
0081 
0082     if (rc) {
0083         dev_warn(dev, "Failed to obtain major/minors");
0084         goto fail_obtain;
0085     }
0086 
0087     unit->major = MAJOR(mdev);
0088     unit->lowest_minor = MINOR(mdev);
0089     unit->num_nodes = num_nodes;
0090     unit->private_data = private_data;
0091 
0092     unit->cdev = cdev_alloc();
0093     if (!unit->cdev) {
0094         rc = -ENOMEM;
0095         goto unregister_chrdev;
0096     }
0097     unit->cdev->ops = fops;
0098     unit->cdev->owner = owner;
0099 
0100     rc = cdev_add(unit->cdev, MKDEV(unit->major, unit->lowest_minor),
0101               unit->num_nodes);
0102     if (rc) {
0103         dev_err(dev, "Failed to add cdev.\n");
0104         /* kobject_put() is normally done by cdev_del() */
0105         kobject_put(&unit->cdev->kobj);
0106         goto unregister_chrdev;
0107     }
0108 
0109     for (i = 0; i < num_nodes; i++) {
0110         namelen = strnlen(idt, len);
0111 
0112         if (namelen == len) {
0113             dev_err(dev, "IDT's list of names is too short. This is exceptionally weird, because its CRC is OK\n");
0114             rc = -ENODEV;
0115             goto unroll_device_create;
0116         }
0117 
0118         snprintf(devname, sizeof(devname), "%s_%s",
0119              unit->name, idt);
0120 
0121         len -= namelen + 1;
0122         idt += namelen + 1;
0123 
0124         device = device_create(xillybus_class,
0125                        NULL,
0126                        MKDEV(unit->major,
0127                          i + unit->lowest_minor),
0128                        NULL,
0129                        "%s", devname);
0130 
0131         if (IS_ERR(device)) {
0132             dev_err(dev, "Failed to create %s device. Aborting.\n",
0133                 devname);
0134             rc = -ENODEV;
0135             goto unroll_device_create;
0136         }
0137     }
0138 
0139     if (len) {
0140         dev_err(dev, "IDT's list of names is too long. This is exceptionally weird, because its CRC is OK\n");
0141         rc = -ENODEV;
0142         goto unroll_device_create;
0143     }
0144 
0145     list_add_tail(&unit->list_entry, &unit_list);
0146 
0147     dev_info(dev, "Created %d device files.\n", num_nodes);
0148 
0149     mutex_unlock(&unit_mutex);
0150 
0151     return 0;
0152 
0153 unroll_device_create:
0154     for (i--; i >= 0; i--)
0155         device_destroy(xillybus_class, MKDEV(unit->major,
0156                              i + unit->lowest_minor));
0157 
0158     cdev_del(unit->cdev);
0159 
0160 unregister_chrdev:
0161     unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor),
0162                  unit->num_nodes);
0163 
0164 fail_obtain:
0165     mutex_unlock(&unit_mutex);
0166 
0167     kfree(unit);
0168 
0169     return rc;
0170 }
0171 EXPORT_SYMBOL(xillybus_init_chrdev);
0172 
0173 void xillybus_cleanup_chrdev(void *private_data,
0174                  struct device *dev)
0175 {
0176     int minor;
0177     struct xilly_unit *unit = NULL, *iter;
0178 
0179     mutex_lock(&unit_mutex);
0180 
0181     list_for_each_entry(iter, &unit_list, list_entry)
0182         if (iter->private_data == private_data) {
0183             unit = iter;
0184             break;
0185         }
0186 
0187     if (!unit) {
0188         dev_err(dev, "Weird bug: Failed to find unit\n");
0189         mutex_unlock(&unit_mutex);
0190         return;
0191     }
0192 
0193     for (minor = unit->lowest_minor;
0194          minor < (unit->lowest_minor + unit->num_nodes);
0195          minor++)
0196         device_destroy(xillybus_class, MKDEV(unit->major, minor));
0197 
0198     cdev_del(unit->cdev);
0199 
0200     unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor),
0201                  unit->num_nodes);
0202 
0203     dev_info(dev, "Removed %d device files.\n",
0204          unit->num_nodes);
0205 
0206     list_del(&unit->list_entry);
0207     kfree(unit);
0208 
0209     mutex_unlock(&unit_mutex);
0210 }
0211 EXPORT_SYMBOL(xillybus_cleanup_chrdev);
0212 
0213 int xillybus_find_inode(struct inode *inode,
0214             void **private_data, int *index)
0215 {
0216     int minor = iminor(inode);
0217     int major = imajor(inode);
0218     struct xilly_unit *unit = NULL, *iter;
0219 
0220     mutex_lock(&unit_mutex);
0221 
0222     list_for_each_entry(iter, &unit_list, list_entry)
0223         if (iter->major == major &&
0224             minor >= iter->lowest_minor &&
0225             minor < (iter->lowest_minor + iter->num_nodes)) {
0226             unit = iter;
0227             break;
0228         }
0229 
0230     mutex_unlock(&unit_mutex);
0231 
0232     if (!unit)
0233         return -ENODEV;
0234 
0235     *private_data = unit->private_data;
0236     *index = minor - unit->lowest_minor;
0237 
0238     return 0;
0239 }
0240 EXPORT_SYMBOL(xillybus_find_inode);
0241 
0242 static int __init xillybus_class_init(void)
0243 {
0244     xillybus_class = class_create(THIS_MODULE, "xillybus");
0245 
0246     if (IS_ERR(xillybus_class)) {
0247         pr_warn("Failed to register xillybus class\n");
0248 
0249         return PTR_ERR(xillybus_class);
0250     }
0251     return 0;
0252 }
0253 
0254 static void __exit xillybus_class_exit(void)
0255 {
0256     class_destroy(xillybus_class);
0257 }
0258 
0259 module_init(xillybus_class_init);
0260 module_exit(xillybus_class_exit);