blob: 7e0957eea094672eaf7b9934a6db7852d86c802e [file] [log] [blame]
William Breathitt Grayaaec1a02021-08-27 12:47:47 +09001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Generic Counter interface
4 * Copyright (C) 2020 William Breathitt Gray
5 */
William Breathitt Grayb6c50af2021-09-29 12:15:59 +09006#include <linux/cdev.h>
William Breathitt Grayaaec1a02021-08-27 12:47:47 +09007#include <linux/counter.h>
8#include <linux/device.h>
William Breathitt Grayb6c50af2021-09-29 12:15:59 +09009#include <linux/device/bus.h>
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090010#include <linux/export.h>
William Breathitt Grayb6c50af2021-09-29 12:15:59 +090011#include <linux/fs.h>
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090012#include <linux/gfp.h>
13#include <linux/idr.h>
14#include <linux/init.h>
William Breathitt Grayb6c50af2021-09-29 12:15:59 +090015#include <linux/kdev_t.h>
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090016#include <linux/module.h>
William Breathitt Grayb6c50af2021-09-29 12:15:59 +090017#include <linux/mutex.h>
Uwe Kleine-Königc18e2762021-12-30 16:02:50 +010018#include <linux/slab.h>
William Breathitt Grayb6c50af2021-09-29 12:15:59 +090019#include <linux/types.h>
20#include <linux/wait.h>
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090021
William Breathitt Grayb6c50af2021-09-29 12:15:59 +090022#include "counter-chrdev.h"
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090023#include "counter-sysfs.h"
24
25/* Provides a unique ID for each counter device */
26static DEFINE_IDA(counter_ida);
27
Uwe Kleine-Königc18e2762021-12-30 16:02:50 +010028struct counter_device_allochelper {
29 struct counter_device counter;
30
31 /*
32 * This is cache line aligned to ensure private data behaves like if it
33 * were kmalloced separately.
34 */
35 unsigned long privdata[] ____cacheline_aligned;
36};
37
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090038static void counter_device_release(struct device *dev)
39{
Uwe Kleine-Königb56346d2021-12-30 16:02:38 +010040 struct counter_device *const counter =
41 container_of(dev, struct counter_device, dev);
William Breathitt Grayb6c50af2021-09-29 12:15:59 +090042
43 counter_chrdev_remove(counter);
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090044 ida_free(&counter_ida, dev->id);
Uwe Kleine-Königc18e2762021-12-30 16:02:50 +010045
Uwe Kleine-Königf2ee4752021-12-30 16:03:00 +010046 kfree(container_of(counter, struct counter_device_allochelper, counter));
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090047}
48
49static struct device_type counter_device_type = {
50 .name = "counter_device",
51 .release = counter_device_release,
52};
53
54static struct bus_type counter_bus_type = {
55 .name = "counter",
56 .dev_name = "counter",
57};
58
William Breathitt Grayb6c50af2021-09-29 12:15:59 +090059static dev_t counter_devt;
60
William Breathitt Grayaaec1a02021-08-27 12:47:47 +090061/**
Uwe Kleine-König5207fb22021-12-30 16:02:41 +010062 * counter_priv - access counter device private data
63 * @counter: counter device
64 *
65 * Get the counter device private data
66 */
67void *counter_priv(const struct counter_device *const counter)
68{
Uwe Kleine-Königf2ee4752021-12-30 16:03:00 +010069 struct counter_device_allochelper *ch =
70 container_of(counter, struct counter_device_allochelper, counter);
Uwe Kleine-Königc18e2762021-12-30 16:02:50 +010071
Uwe Kleine-Königf2ee4752021-12-30 16:03:00 +010072 return &ch->privdata;
Uwe Kleine-König5207fb22021-12-30 16:02:41 +010073}
74EXPORT_SYMBOL_GPL(counter_priv);
75
76/**
Uwe Kleine-Königc18e2762021-12-30 16:02:50 +010077 * counter_alloc - allocate a counter_device
78 * @sizeof_priv: size of the driver private data
79 *
80 * This is part one of counter registration. The structure is allocated
81 * dynamically to ensure the right lifetime for the embedded struct device.
82 *
83 * If this succeeds, call counter_put() to get rid of the counter_device again.
84 */
85struct counter_device *counter_alloc(size_t sizeof_priv)
86{
87 struct counter_device_allochelper *ch;
88 struct counter_device *counter;
89 struct device *dev;
90 int err;
91
92 ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
93 if (!ch) {
94 err = -ENOMEM;
95 goto err_alloc_ch;
96 }
97
98 counter = &ch->counter;
99 dev = &counter->dev;
100
101 /* Acquire unique ID */
102 err = ida_alloc(&counter_ida, GFP_KERNEL);
103 if (err < 0)
104 goto err_ida_alloc;
105 dev->id = err;
106
107 mutex_init(&counter->ops_exist_lock);
108 dev->type = &counter_device_type;
109 dev->bus = &counter_bus_type;
110 dev->devt = MKDEV(MAJOR(counter_devt), dev->id);
111
112 err = counter_chrdev_add(counter);
113 if (err < 0)
114 goto err_chrdev_add;
115
116 device_initialize(dev);
117
118 return counter;
119
120err_chrdev_add:
121
122 ida_free(&counter_ida, dev->id);
123err_ida_alloc:
124
125 kfree(ch);
126err_alloc_ch:
127
128 return ERR_PTR(err);
129}
130EXPORT_SYMBOL_GPL(counter_alloc);
131
132void counter_put(struct counter_device *counter)
133{
134 put_device(&counter->dev);
135}
136EXPORT_SYMBOL_GPL(counter_put);
137
138/**
139 * counter_add - complete registration of a counter
140 * @counter: the counter to add
141 *
142 * This is part two of counter registration.
143 *
144 * If this succeeds, call counter_unregister() to get rid of the counter_device again.
145 */
146int counter_add(struct counter_device *counter)
147{
148 int err;
149 struct device *dev = &counter->dev;
150
151 if (counter->parent) {
152 dev->parent = counter->parent;
153 dev->of_node = counter->parent->of_node;
154 }
155
156 err = counter_sysfs_add(counter);
157 if (err < 0)
158 return err;
159
160 /* implies device_add(dev) */
161 return cdev_device_add(&counter->chrdev, dev);
162}
163EXPORT_SYMBOL_GPL(counter_add);
164
165/**
William Breathitt Grayaaec1a02021-08-27 12:47:47 +0900166 * counter_unregister - unregister Counter from the system
167 * @counter: pointer to Counter to unregister
168 *
169 * The Counter is unregistered from the system.
170 */
171void counter_unregister(struct counter_device *const counter)
172{
173 if (!counter)
174 return;
175
William Breathitt Grayb6c50af2021-09-29 12:15:59 +0900176 cdev_device_del(&counter->chrdev, &counter->dev);
177
178 mutex_lock(&counter->ops_exist_lock);
179
180 counter->ops = NULL;
181 wake_up(&counter->events_wait);
182
183 mutex_unlock(&counter->ops_exist_lock);
William Breathitt Grayaaec1a02021-08-27 12:47:47 +0900184}
185EXPORT_SYMBOL_GPL(counter_unregister);
186
187static void devm_counter_release(void *counter)
188{
189 counter_unregister(counter);
190}
191
Uwe Kleine-Königc18e2762021-12-30 16:02:50 +0100192static void devm_counter_put(void *counter)
193{
194 counter_put(counter);
195}
196
197/**
198 * devm_counter_alloc - allocate a counter_device
199 * @dev: the device to register the release callback for
200 * @sizeof_priv: size of the driver private data
201 *
202 * This is the device managed version of counter_add(). It registers a cleanup
203 * callback to care for calling counter_put().
204 */
205struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)
206{
207 struct counter_device *counter;
208 int err;
209
210 counter = counter_alloc(sizeof_priv);
211 if (IS_ERR(counter))
212 return counter;
213
214 err = devm_add_action_or_reset(dev, devm_counter_put, counter);
215 if (err < 0)
216 return ERR_PTR(err);
217
218 return counter;
219}
220EXPORT_SYMBOL_GPL(devm_counter_alloc);
221
222/**
223 * devm_counter_add - complete registration of a counter
224 * @dev: the device to register the release callback for
225 * @counter: the counter to add
226 *
227 * This is the device managed version of counter_add(). It registers a cleanup
228 * callback to care for calling counter_unregister().
229 */
230int devm_counter_add(struct device *dev,
231 struct counter_device *const counter)
232{
233 int err;
234
235 err = counter_add(counter);
236 if (err < 0)
237 return err;
238
239 return devm_add_action_or_reset(dev, devm_counter_release, counter);
240}
241EXPORT_SYMBOL_GPL(devm_counter_add);
242
William Breathitt Grayb6c50af2021-09-29 12:15:59 +0900243#define COUNTER_DEV_MAX 256
244
William Breathitt Grayaaec1a02021-08-27 12:47:47 +0900245static int __init counter_init(void)
246{
William Breathitt Grayb6c50af2021-09-29 12:15:59 +0900247 int err;
248
249 err = bus_register(&counter_bus_type);
250 if (err < 0)
251 return err;
252
253 err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX, "counter");
254 if (err < 0)
255 goto err_unregister_bus;
256
257 return 0;
258
259err_unregister_bus:
260 bus_unregister(&counter_bus_type);
261 return err;
William Breathitt Grayaaec1a02021-08-27 12:47:47 +0900262}
263
264static void __exit counter_exit(void)
265{
William Breathitt Grayb6c50af2021-09-29 12:15:59 +0900266 unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);
William Breathitt Grayaaec1a02021-08-27 12:47:47 +0900267 bus_unregister(&counter_bus_type);
268}
269
270subsys_initcall(counter_init);
271module_exit(counter_exit);
272
273MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
274MODULE_DESCRIPTION("Generic Counter interface");
275MODULE_LICENSE("GPL v2");