mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	Naming the counter device provides a convenient way to identify it in devres_log events and similar situations. This patch names the counter device by combining the prefix "counter" with the counter device's unique ID. Link: https://lore.kernel.org/r/20220204084551.16397-1-vilhelm.gray@gmail.com Acked-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: William Breathitt Gray <vilhelm.gray@gmail.com> Link: https://lore.kernel.org/r/87cc8eb4c84f49f89290577dc9231b2e4d7d3e8c.1647373009.git.vilhelm.gray@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
		
			
				
	
	
		
			282 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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/slab.h>
 | 
						|
#include <linux/types.h>
 | 
						|
#include <linux/wait.h>
 | 
						|
 | 
						|
#include "counter-chrdev.h"
 | 
						|
#include "counter-sysfs.h"
 | 
						|
 | 
						|
#define COUNTER_NAME	"counter"
 | 
						|
 | 
						|
/* Provides a unique ID for each counter device */
 | 
						|
static DEFINE_IDA(counter_ida);
 | 
						|
 | 
						|
struct counter_device_allochelper {
 | 
						|
	struct counter_device counter;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * This is cache line aligned to ensure private data behaves like if it
 | 
						|
	 * were kmalloced separately.
 | 
						|
	 */
 | 
						|
	unsigned long privdata[] ____cacheline_aligned;
 | 
						|
};
 | 
						|
 | 
						|
static void counter_device_release(struct device *dev)
 | 
						|
{
 | 
						|
	struct counter_device *const counter =
 | 
						|
		container_of(dev, struct counter_device, dev);
 | 
						|
 | 
						|
	counter_chrdev_remove(counter);
 | 
						|
	ida_free(&counter_ida, dev->id);
 | 
						|
 | 
						|
	kfree(container_of(counter, struct counter_device_allochelper, counter));
 | 
						|
}
 | 
						|
 | 
						|
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_priv - access counter device private data
 | 
						|
 * @counter: counter device
 | 
						|
 *
 | 
						|
 * Get the counter device private data
 | 
						|
 */
 | 
						|
void *counter_priv(const struct counter_device *const counter)
 | 
						|
{
 | 
						|
	struct counter_device_allochelper *ch =
 | 
						|
		container_of(counter, struct counter_device_allochelper, counter);
 | 
						|
 | 
						|
	return &ch->privdata;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(counter_priv);
 | 
						|
 | 
						|
/**
 | 
						|
 * counter_alloc - allocate a counter_device
 | 
						|
 * @sizeof_priv: size of the driver private data
 | 
						|
 *
 | 
						|
 * This is part one of counter registration. The structure is allocated
 | 
						|
 * dynamically to ensure the right lifetime for the embedded struct device.
 | 
						|
 *
 | 
						|
 * If this succeeds, call counter_put() to get rid of the counter_device again.
 | 
						|
 */
 | 
						|
struct counter_device *counter_alloc(size_t sizeof_priv)
 | 
						|
{
 | 
						|
	struct counter_device_allochelper *ch;
 | 
						|
	struct counter_device *counter;
 | 
						|
	struct device *dev;
 | 
						|
	int err;
 | 
						|
 | 
						|
	ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
 | 
						|
	if (!ch)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	counter = &ch->counter;
 | 
						|
	dev = &counter->dev;
 | 
						|
 | 
						|
	/* Acquire unique ID */
 | 
						|
	err = ida_alloc(&counter_ida, GFP_KERNEL);
 | 
						|
	if (err < 0)
 | 
						|
		goto err_ida_alloc;
 | 
						|
	dev->id = err;
 | 
						|
 | 
						|
	mutex_init(&counter->ops_exist_lock);
 | 
						|
	dev->type = &counter_device_type;
 | 
						|
	dev->bus = &counter_bus_type;
 | 
						|
	dev->devt = MKDEV(MAJOR(counter_devt), dev->id);
 | 
						|
 | 
						|
	err = counter_chrdev_add(counter);
 | 
						|
	if (err < 0)
 | 
						|
		goto err_chrdev_add;
 | 
						|
 | 
						|
	device_initialize(dev);
 | 
						|
 | 
						|
	err = dev_set_name(dev, COUNTER_NAME "%d", dev->id);
 | 
						|
	if (err)
 | 
						|
		goto err_dev_set_name;
 | 
						|
 | 
						|
	return counter;
 | 
						|
 | 
						|
err_dev_set_name:
 | 
						|
 | 
						|
	counter_chrdev_remove(counter);
 | 
						|
err_chrdev_add:
 | 
						|
 | 
						|
	ida_free(&counter_ida, dev->id);
 | 
						|
err_ida_alloc:
 | 
						|
 | 
						|
	kfree(ch);
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(counter_alloc);
 | 
						|
 | 
						|
void counter_put(struct counter_device *counter)
 | 
						|
{
 | 
						|
	put_device(&counter->dev);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(counter_put);
 | 
						|
 | 
						|
/**
 | 
						|
 * counter_add - complete registration of a counter
 | 
						|
 * @counter: the counter to add
 | 
						|
 *
 | 
						|
 * This is part two of counter registration.
 | 
						|
 *
 | 
						|
 * If this succeeds, call counter_unregister() to get rid of the counter_device again.
 | 
						|
 */
 | 
						|
int counter_add(struct counter_device *counter)
 | 
						|
{
 | 
						|
	int err;
 | 
						|
	struct device *dev = &counter->dev;
 | 
						|
 | 
						|
	if (counter->parent) {
 | 
						|
		dev->parent = counter->parent;
 | 
						|
		dev->of_node = counter->parent->of_node;
 | 
						|
	}
 | 
						|
 | 
						|
	err = counter_sysfs_add(counter);
 | 
						|
	if (err < 0)
 | 
						|
		return err;
 | 
						|
 | 
						|
	/* implies device_add(dev) */
 | 
						|
	return cdev_device_add(&counter->chrdev, dev);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(counter_add);
 | 
						|
 | 
						|
/**
 | 
						|
 * 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);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(counter_unregister);
 | 
						|
 | 
						|
static void devm_counter_release(void *counter)
 | 
						|
{
 | 
						|
	counter_unregister(counter);
 | 
						|
}
 | 
						|
 | 
						|
static void devm_counter_put(void *counter)
 | 
						|
{
 | 
						|
	counter_put(counter);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * devm_counter_alloc - allocate a counter_device
 | 
						|
 * @dev: the device to register the release callback for
 | 
						|
 * @sizeof_priv: size of the driver private data
 | 
						|
 *
 | 
						|
 * This is the device managed version of counter_add(). It registers a cleanup
 | 
						|
 * callback to care for calling counter_put().
 | 
						|
 */
 | 
						|
struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)
 | 
						|
{
 | 
						|
	struct counter_device *counter;
 | 
						|
	int err;
 | 
						|
 | 
						|
	counter = counter_alloc(sizeof_priv);
 | 
						|
	if (!counter)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	err = devm_add_action_or_reset(dev, devm_counter_put, counter);
 | 
						|
	if (err < 0)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	return counter;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devm_counter_alloc);
 | 
						|
 | 
						|
/**
 | 
						|
 * devm_counter_add - complete registration of a counter
 | 
						|
 * @dev: the device to register the release callback for
 | 
						|
 * @counter: the counter to add
 | 
						|
 *
 | 
						|
 * This is the device managed version of counter_add(). It registers a cleanup
 | 
						|
 * callback to care for calling counter_unregister().
 | 
						|
 */
 | 
						|
int devm_counter_add(struct device *dev,
 | 
						|
		     struct counter_device *const counter)
 | 
						|
{
 | 
						|
	int err;
 | 
						|
 | 
						|
	err = counter_add(counter);
 | 
						|
	if (err < 0)
 | 
						|
		return err;
 | 
						|
 | 
						|
	return devm_add_action_or_reset(dev, devm_counter_release, counter);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devm_counter_add);
 | 
						|
 | 
						|
#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_NAME);
 | 
						|
	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");
 |