257 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright 2021 Xillybus Ltd, http://xillybus.com4 *5 * Driver for the Xillybus class6 */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 const struct class xillybus_class = {27 .name = "xillybus",28};29 30#define UNITNAMELEN 1631 32struct xilly_unit {33 struct list_head list_entry;34 void *private_data;35 36 struct cdev *cdev;37 char name[UNITNAMELEN];38 int major;39 int lowest_minor;40 int num_nodes;41};42 43int xillybus_init_chrdev(struct device *dev,44 const struct file_operations *fops,45 struct module *owner,46 void *private_data,47 unsigned char *idt, unsigned int len,48 int num_nodes,49 const char *prefix, bool enumerate)50{51 int rc;52 dev_t mdev;53 int i;54 char devname[48];55 56 struct device *device;57 size_t namelen;58 struct xilly_unit *unit, *u;59 60 unit = kzalloc(sizeof(*unit), GFP_KERNEL);61 62 if (!unit)63 return -ENOMEM;64 65 mutex_lock(&unit_mutex);66 67 if (!enumerate)68 snprintf(unit->name, UNITNAMELEN, "%s", prefix);69 70 for (i = 0; enumerate; i++) {71 snprintf(unit->name, UNITNAMELEN, "%s_%02d",72 prefix, i);73 74 enumerate = false;75 list_for_each_entry(u, &unit_list, list_entry)76 if (!strcmp(unit->name, u->name)) {77 enumerate = true;78 break;79 }80 }81 82 rc = alloc_chrdev_region(&mdev, 0, num_nodes, unit->name);83 84 if (rc) {85 dev_warn(dev, "Failed to obtain major/minors");86 goto fail_obtain;87 }88 89 unit->major = MAJOR(mdev);90 unit->lowest_minor = MINOR(mdev);91 unit->num_nodes = num_nodes;92 unit->private_data = private_data;93 94 unit->cdev = cdev_alloc();95 if (!unit->cdev) {96 rc = -ENOMEM;97 goto unregister_chrdev;98 }99 unit->cdev->ops = fops;100 unit->cdev->owner = owner;101 102 rc = cdev_add(unit->cdev, MKDEV(unit->major, unit->lowest_minor),103 unit->num_nodes);104 if (rc) {105 dev_err(dev, "Failed to add cdev.\n");106 /* kobject_put() is normally done by cdev_del() */107 kobject_put(&unit->cdev->kobj);108 goto unregister_chrdev;109 }110 111 for (i = 0; i < num_nodes; i++) {112 namelen = strnlen(idt, len);113 114 if (namelen == len) {115 dev_err(dev, "IDT's list of names is too short. This is exceptionally weird, because its CRC is OK\n");116 rc = -ENODEV;117 goto unroll_device_create;118 }119 120 snprintf(devname, sizeof(devname), "%s_%s",121 unit->name, idt);122 123 len -= namelen + 1;124 idt += namelen + 1;125 126 device = device_create(&xillybus_class,127 NULL,128 MKDEV(unit->major,129 i + unit->lowest_minor),130 NULL,131 "%s", devname);132 133 if (IS_ERR(device)) {134 dev_err(dev, "Failed to create %s device. Aborting.\n",135 devname);136 rc = -ENODEV;137 goto unroll_device_create;138 }139 }140 141 if (len) {142 dev_err(dev, "IDT's list of names is too long. This is exceptionally weird, because its CRC is OK\n");143 rc = -ENODEV;144 goto unroll_device_create;145 }146 147 list_add_tail(&unit->list_entry, &unit_list);148 149 dev_info(dev, "Created %d device files.\n", num_nodes);150 151 mutex_unlock(&unit_mutex);152 153 return 0;154 155unroll_device_create:156 for (i--; i >= 0; i--)157 device_destroy(&xillybus_class, MKDEV(unit->major,158 i + unit->lowest_minor));159 160 cdev_del(unit->cdev);161 162unregister_chrdev:163 unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor),164 unit->num_nodes);165 166fail_obtain:167 mutex_unlock(&unit_mutex);168 169 kfree(unit);170 171 return rc;172}173EXPORT_SYMBOL(xillybus_init_chrdev);174 175void xillybus_cleanup_chrdev(void *private_data,176 struct device *dev)177{178 int minor;179 struct xilly_unit *unit = NULL, *iter;180 181 mutex_lock(&unit_mutex);182 183 list_for_each_entry(iter, &unit_list, list_entry)184 if (iter->private_data == private_data) {185 unit = iter;186 break;187 }188 189 if (!unit) {190 dev_err(dev, "Weird bug: Failed to find unit\n");191 mutex_unlock(&unit_mutex);192 return;193 }194 195 for (minor = unit->lowest_minor;196 minor < (unit->lowest_minor + unit->num_nodes);197 minor++)198 device_destroy(&xillybus_class, MKDEV(unit->major, minor));199 200 cdev_del(unit->cdev);201 202 unregister_chrdev_region(MKDEV(unit->major, unit->lowest_minor),203 unit->num_nodes);204 205 dev_info(dev, "Removed %d device files.\n",206 unit->num_nodes);207 208 list_del(&unit->list_entry);209 kfree(unit);210 211 mutex_unlock(&unit_mutex);212}213EXPORT_SYMBOL(xillybus_cleanup_chrdev);214 215int xillybus_find_inode(struct inode *inode,216 void **private_data, int *index)217{218 int minor = iminor(inode);219 int major = imajor(inode);220 struct xilly_unit *unit = NULL, *iter;221 222 mutex_lock(&unit_mutex);223 224 list_for_each_entry(iter, &unit_list, list_entry)225 if (iter->major == major &&226 minor >= iter->lowest_minor &&227 minor < (iter->lowest_minor + iter->num_nodes)) {228 unit = iter;229 break;230 }231 232 if (!unit) {233 mutex_unlock(&unit_mutex);234 return -ENODEV;235 }236 237 *private_data = unit->private_data;238 *index = minor - unit->lowest_minor;239 240 mutex_unlock(&unit_mutex);241 return 0;242}243EXPORT_SYMBOL(xillybus_find_inode);244 245static int __init xillybus_class_init(void)246{247 return class_register(&xillybus_class);248}249 250static void __exit xillybus_class_exit(void)251{252 class_unregister(&xillybus_class);253}254 255module_init(xillybus_class_init);256module_exit(xillybus_class_exit);257