linux/drivers/char/xillybus/xillybus_class.c
<<
>>
Prefs
   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
  19MODULE_DESCRIPTION("Driver for Xillybus class");
  20MODULE_AUTHOR("Eli Billauer, Xillybus Ltd.");
  21MODULE_ALIAS("xillybus_class");
  22MODULE_LICENSE("GPL v2");
  23
  24static DEFINE_MUTEX(unit_mutex);
  25static LIST_HEAD(unit_list);
  26static struct class *xillybus_class;
  27
  28#define UNITNAMELEN 16
  29
  30struct 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
  41int 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
 153unroll_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
 160unregister_chrdev:
 161        unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor),
 162                                 unit->num_nodes);
 163
 164fail_obtain:
 165        mutex_unlock(&unit_mutex);
 166
 167        kfree(unit);
 168
 169        return rc;
 170}
 171EXPORT_SYMBOL(xillybus_init_chrdev);
 172
 173void 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}
 212EXPORT_SYMBOL(xillybus_cleanup_chrdev);
 213
 214int 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}
 242EXPORT_SYMBOL(xillybus_find_inode);
 243
 244static 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
 256static void __exit xillybus_class_exit(void)
 257{
 258        class_destroy(xillybus_class);
 259}
 260
 261module_init(xillybus_class_init);
 262module_exit(xillybus_class_exit);
 263