brintos

brintos / linux-shallow public Read only

0
0
Text · 5.3 KiB · c92a628 Raw
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