284 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Generic Counter interface4 * Copyright (C) 2020 William Breathitt Gray5 */6#include <linux/cdev.h>7#include <linux/counter.h>8#include <linux/device.h>9#include <linux/device/bus.h>10#include <linux/export.h>11#include <linux/fs.h>12#include <linux/gfp.h>13#include <linux/idr.h>14#include <linux/init.h>15#include <linux/kdev_t.h>16#include <linux/module.h>17#include <linux/mutex.h>18#include <linux/slab.h>19#include <linux/types.h>20#include <linux/wait.h>21 22#include "counter-chrdev.h"23#include "counter-sysfs.h"24 25#define COUNTER_NAME "counter"26 27/* Provides a unique ID for each counter device */28static DEFINE_IDA(counter_ida);29 30struct counter_device_allochelper {31 struct counter_device counter;32 33 /*34 * This ensures private data behaves like if it were kmalloced35 * separately. Also ensures the minimum alignment for safe DMA36 * operations (which may or may not mean cache alignment).37 */38 unsigned long privdata[] __aligned(ARCH_DMA_MINALIGN);39};40 41static void counter_device_release(struct device *dev)42{43 struct counter_device *const counter =44 container_of(dev, struct counter_device, dev);45 46 counter_chrdev_remove(counter);47 ida_free(&counter_ida, dev->id);48 49 kfree(container_of(counter, struct counter_device_allochelper, counter));50}51 52static const struct device_type counter_device_type = {53 .name = "counter_device",54 .release = counter_device_release,55};56 57static const struct bus_type counter_bus_type = {58 .name = "counter",59 .dev_name = "counter",60};61 62static dev_t counter_devt;63 64/**65 * counter_priv - access counter device private data66 * @counter: counter device67 *68 * Get the counter device private data69 */70void *counter_priv(const struct counter_device *const counter)71{72 struct counter_device_allochelper *ch =73 container_of(counter, struct counter_device_allochelper, counter);74 75 return &ch->privdata;76}77EXPORT_SYMBOL_NS_GPL(counter_priv, COUNTER);78 79/**80 * counter_alloc - allocate a counter_device81 * @sizeof_priv: size of the driver private data82 *83 * This is part one of counter registration. The structure is allocated84 * dynamically to ensure the right lifetime for the embedded struct device.85 *86 * If this succeeds, call counter_put() to get rid of the counter_device again.87 */88struct counter_device *counter_alloc(size_t sizeof_priv)89{90 struct counter_device_allochelper *ch;91 struct counter_device *counter;92 struct device *dev;93 int err;94 95 ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);96 if (!ch)97 return NULL;98 99 counter = &ch->counter;100 dev = &counter->dev;101 102 /* Acquire unique ID */103 err = ida_alloc(&counter_ida, GFP_KERNEL);104 if (err < 0)105 goto err_ida_alloc;106 dev->id = err;107 108 mutex_init(&counter->ops_exist_lock);109 dev->type = &counter_device_type;110 dev->bus = &counter_bus_type;111 dev->devt = MKDEV(MAJOR(counter_devt), dev->id);112 113 err = counter_chrdev_add(counter);114 if (err < 0)115 goto err_chrdev_add;116 117 device_initialize(dev);118 119 err = dev_set_name(dev, COUNTER_NAME "%d", dev->id);120 if (err)121 goto err_dev_set_name;122 123 return counter;124 125err_dev_set_name:126 127 counter_chrdev_remove(counter);128err_chrdev_add:129 130 ida_free(&counter_ida, dev->id);131err_ida_alloc:132 133 kfree(ch);134 135 return NULL;136}137EXPORT_SYMBOL_NS_GPL(counter_alloc, COUNTER);138 139void counter_put(struct counter_device *counter)140{141 put_device(&counter->dev);142}143EXPORT_SYMBOL_NS_GPL(counter_put, COUNTER);144 145/**146 * counter_add - complete registration of a counter147 * @counter: the counter to add148 *149 * This is part two of counter registration.150 *151 * If this succeeds, call counter_unregister() to get rid of the counter_device again.152 */153int counter_add(struct counter_device *counter)154{155 int err;156 struct device *dev = &counter->dev;157 158 if (counter->parent) {159 dev->parent = counter->parent;160 dev->of_node = counter->parent->of_node;161 }162 163 err = counter_sysfs_add(counter);164 if (err < 0)165 return err;166 167 /* implies device_add(dev) */168 return cdev_device_add(&counter->chrdev, dev);169}170EXPORT_SYMBOL_NS_GPL(counter_add, COUNTER);171 172/**173 * counter_unregister - unregister Counter from the system174 * @counter: pointer to Counter to unregister175 *176 * The Counter is unregistered from the system.177 */178void counter_unregister(struct counter_device *const counter)179{180 if (!counter)181 return;182 183 cdev_device_del(&counter->chrdev, &counter->dev);184 185 mutex_lock(&counter->ops_exist_lock);186 187 counter->ops = NULL;188 wake_up(&counter->events_wait);189 190 mutex_unlock(&counter->ops_exist_lock);191}192EXPORT_SYMBOL_NS_GPL(counter_unregister, COUNTER);193 194static void devm_counter_release(void *counter)195{196 counter_unregister(counter);197}198 199static void devm_counter_put(void *counter)200{201 counter_put(counter);202}203 204/**205 * devm_counter_alloc - allocate a counter_device206 * @dev: the device to register the release callback for207 * @sizeof_priv: size of the driver private data208 *209 * This is the device managed version of counter_add(). It registers a cleanup210 * callback to care for calling counter_put().211 */212struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)213{214 struct counter_device *counter;215 int err;216 217 counter = counter_alloc(sizeof_priv);218 if (!counter)219 return NULL;220 221 err = devm_add_action_or_reset(dev, devm_counter_put, counter);222 if (err < 0)223 return NULL;224 225 return counter;226}227EXPORT_SYMBOL_NS_GPL(devm_counter_alloc, COUNTER);228 229/**230 * devm_counter_add - complete registration of a counter231 * @dev: the device to register the release callback for232 * @counter: the counter to add233 *234 * This is the device managed version of counter_add(). It registers a cleanup235 * callback to care for calling counter_unregister().236 */237int devm_counter_add(struct device *dev,238 struct counter_device *const counter)239{240 int err;241 242 err = counter_add(counter);243 if (err < 0)244 return err;245 246 return devm_add_action_or_reset(dev, devm_counter_release, counter);247}248EXPORT_SYMBOL_NS_GPL(devm_counter_add, COUNTER);249 250#define COUNTER_DEV_MAX 256251 252static int __init counter_init(void)253{254 int err;255 256 err = bus_register(&counter_bus_type);257 if (err < 0)258 return err;259 260 err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX,261 COUNTER_NAME);262 if (err < 0)263 goto err_unregister_bus;264 265 return 0;266 267err_unregister_bus:268 bus_unregister(&counter_bus_type);269 return err;270}271 272static void __exit counter_exit(void)273{274 unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);275 bus_unregister(&counter_bus_type);276}277 278subsys_initcall(counter_init);279module_exit(counter_exit);280 281MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");282MODULE_DESCRIPTION("Generic Counter interface");283MODULE_LICENSE("GPL v2");284