Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | // SPDX-License-Identifier: GPL-2.0 /* * Generic Counter interface * Copyright (C) 2020 William Breathitt Gray */ #include <linux/cdev.h> #include <linux/counter.h> #include <linux/device.h> #include <linux/device/bus.h> #include <linux/export.h> #include <linux/fs.h> #include <linux/gfp.h> #include <linux/idr.h> #include <linux/init.h> #include <linux/kdev_t.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/types.h> #include <linux/wait.h> #include "counter-chrdev.h" #include "counter-sysfs.h" /* Provides a unique ID for each counter device */ static DEFINE_IDA(counter_ida); static void counter_device_release(struct device *dev) { struct counter_device *const counter = dev_get_drvdata(dev); counter_chrdev_remove(counter); ida_free(&counter_ida, dev->id); } static struct device_type counter_device_type = { .name = "counter_device", .release = counter_device_release, }; static struct bus_type counter_bus_type = { .name = "counter", .dev_name = "counter", }; static dev_t counter_devt; /** * counter_register - register Counter to the system * @counter: pointer to Counter to register * * This function registers a Counter to the system. A sysfs "counter" directory * will be created and populated with sysfs attributes correlating with the * Counter Signals, Synapses, and Counts respectively. * * RETURNS: * 0 on success, negative error number on failure. */ int counter_register(struct counter_device *const counter) { struct device *const dev = &counter->dev; int id; int err; /* Acquire unique ID */ id = ida_alloc(&counter_ida, GFP_KERNEL); if (id < 0) return id; mutex_init(&counter->ops_exist_lock); /* Configure device structure for Counter */ dev->id = id; dev->type = &counter_device_type; dev->bus = &counter_bus_type; dev->devt = MKDEV(MAJOR(counter_devt), id); if (counter->parent) { dev->parent = counter->parent; dev->of_node = counter->parent->of_node; } device_initialize(dev); dev_set_drvdata(dev, counter); err = counter_sysfs_add(counter); if (err < 0) goto err_free_id; err = counter_chrdev_add(counter); if (err < 0) goto err_free_id; err = cdev_device_add(&counter->chrdev, dev); if (err < 0) goto err_remove_chrdev; return 0; err_remove_chrdev: counter_chrdev_remove(counter); err_free_id: put_device(dev); return err; } EXPORT_SYMBOL_GPL(counter_register); /** * counter_unregister - unregister Counter from the system * @counter: pointer to Counter to unregister * * The Counter is unregistered from the system. */ void counter_unregister(struct counter_device *const counter) { if (!counter) return; cdev_device_del(&counter->chrdev, &counter->dev); mutex_lock(&counter->ops_exist_lock); counter->ops = NULL; wake_up(&counter->events_wait); mutex_unlock(&counter->ops_exist_lock); put_device(&counter->dev); } EXPORT_SYMBOL_GPL(counter_unregister); static void devm_counter_release(void *counter) { counter_unregister(counter); } /** * devm_counter_register - Resource-managed counter_register * @dev: device to allocate counter_device for * @counter: pointer to Counter to register * * Managed counter_register. The Counter registered with this function is * automatically unregistered on driver detach. This function calls * counter_register internally. Refer to that function for more information. * * RETURNS: * 0 on success, negative error number on failure. */ int devm_counter_register(struct device *dev, struct counter_device *const counter) { int err; err = counter_register(counter); if (err < 0) return err; return devm_add_action_or_reset(dev, devm_counter_release, counter); } EXPORT_SYMBOL_GPL(devm_counter_register); #define COUNTER_DEV_MAX 256 static int __init counter_init(void) { int err; err = bus_register(&counter_bus_type); if (err < 0) return err; err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX, "counter"); if (err < 0) goto err_unregister_bus; return 0; err_unregister_bus: bus_unregister(&counter_bus_type); return err; } static void __exit counter_exit(void) { unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX); bus_unregister(&counter_bus_type); } subsys_initcall(counter_init); module_exit(counter_exit); MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); MODULE_DESCRIPTION("Generic Counter interface"); MODULE_LICENSE("GPL v2"); |