0001
0002
0003
0004
0005
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
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);