brintos

brintos / linux-shallow public Read only

0
0
Text · 6.4 KiB · 893b4f0 Raw
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