1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Generic Counter interface |
4 | * Copyright (C) 2020 William Breathitt Gray |
5 | */ |
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 */ |
28 | static DEFINE_IDA(counter_ida); |
29 | |
30 | struct counter_device_allochelper { |
31 | struct counter_device counter; |
32 | |
33 | /* |
34 | * This is cache line aligned to ensure private data behaves like if it |
35 | * were kmalloced separately. |
36 | */ |
37 | unsigned long privdata[] ____cacheline_aligned; |
38 | }; |
39 | |
40 | static void counter_device_release(struct device *dev) |
41 | { |
42 | struct counter_device *const counter = |
43 | container_of(dev, struct counter_device, dev); |
44 | |
45 | counter_chrdev_remove(counter); |
46 | ida_free(&counter_ida, id: dev->id); |
47 | |
48 | kfree(container_of(counter, struct counter_device_allochelper, counter)); |
49 | } |
50 | |
51 | static struct device_type counter_device_type = { |
52 | .name = "counter_device" , |
53 | .release = counter_device_release, |
54 | }; |
55 | |
56 | static struct bus_type counter_bus_type = { |
57 | .name = "counter" , |
58 | .dev_name = "counter" , |
59 | }; |
60 | |
61 | static dev_t counter_devt; |
62 | |
63 | /** |
64 | * counter_priv - access counter device private data |
65 | * @counter: counter device |
66 | * |
67 | * Get the counter device private data |
68 | */ |
69 | void *counter_priv(const struct counter_device *const counter) |
70 | { |
71 | struct counter_device_allochelper *ch = |
72 | container_of(counter, struct counter_device_allochelper, counter); |
73 | |
74 | return &ch->privdata; |
75 | } |
76 | EXPORT_SYMBOL_NS_GPL(counter_priv, COUNTER); |
77 | |
78 | /** |
79 | * counter_alloc - allocate a counter_device |
80 | * @sizeof_priv: size of the driver private data |
81 | * |
82 | * This is part one of counter registration. The structure is allocated |
83 | * dynamically to ensure the right lifetime for the embedded struct device. |
84 | * |
85 | * If this succeeds, call counter_put() to get rid of the counter_device again. |
86 | */ |
87 | struct counter_device *counter_alloc(size_t sizeof_priv) |
88 | { |
89 | struct counter_device_allochelper *ch; |
90 | struct counter_device *counter; |
91 | struct device *dev; |
92 | int err; |
93 | |
94 | ch = kzalloc(size: sizeof(*ch) + sizeof_priv, GFP_KERNEL); |
95 | if (!ch) |
96 | return NULL; |
97 | |
98 | counter = &ch->counter; |
99 | dev = &counter->dev; |
100 | |
101 | /* Acquire unique ID */ |
102 | err = ida_alloc(ida: &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 | err = dev_set_name(dev, COUNTER_NAME "%d" , dev->id); |
119 | if (err) |
120 | goto err_dev_set_name; |
121 | |
122 | return counter; |
123 | |
124 | err_dev_set_name: |
125 | |
126 | counter_chrdev_remove(counter); |
127 | err_chrdev_add: |
128 | |
129 | ida_free(&counter_ida, id: dev->id); |
130 | err_ida_alloc: |
131 | |
132 | kfree(objp: ch); |
133 | |
134 | return NULL; |
135 | } |
136 | EXPORT_SYMBOL_NS_GPL(counter_alloc, COUNTER); |
137 | |
138 | void counter_put(struct counter_device *counter) |
139 | { |
140 | put_device(dev: &counter->dev); |
141 | } |
142 | EXPORT_SYMBOL_NS_GPL(counter_put, COUNTER); |
143 | |
144 | /** |
145 | * counter_add - complete registration of a counter |
146 | * @counter: the counter to add |
147 | * |
148 | * This is part two of counter registration. |
149 | * |
150 | * If this succeeds, call counter_unregister() to get rid of the counter_device again. |
151 | */ |
152 | int counter_add(struct counter_device *counter) |
153 | { |
154 | int err; |
155 | struct device *dev = &counter->dev; |
156 | |
157 | if (counter->parent) { |
158 | dev->parent = counter->parent; |
159 | dev->of_node = counter->parent->of_node; |
160 | } |
161 | |
162 | err = counter_sysfs_add(counter); |
163 | if (err < 0) |
164 | return err; |
165 | |
166 | /* implies device_add(dev) */ |
167 | return cdev_device_add(cdev: &counter->chrdev, dev); |
168 | } |
169 | EXPORT_SYMBOL_NS_GPL(counter_add, COUNTER); |
170 | |
171 | /** |
172 | * counter_unregister - unregister Counter from the system |
173 | * @counter: pointer to Counter to unregister |
174 | * |
175 | * The Counter is unregistered from the system. |
176 | */ |
177 | void counter_unregister(struct counter_device *const counter) |
178 | { |
179 | if (!counter) |
180 | return; |
181 | |
182 | cdev_device_del(cdev: &counter->chrdev, dev: &counter->dev); |
183 | |
184 | mutex_lock(&counter->ops_exist_lock); |
185 | |
186 | counter->ops = NULL; |
187 | wake_up(&counter->events_wait); |
188 | |
189 | mutex_unlock(lock: &counter->ops_exist_lock); |
190 | } |
191 | EXPORT_SYMBOL_NS_GPL(counter_unregister, COUNTER); |
192 | |
193 | static void devm_counter_release(void *counter) |
194 | { |
195 | counter_unregister(counter); |
196 | } |
197 | |
198 | static void devm_counter_put(void *counter) |
199 | { |
200 | counter_put(counter); |
201 | } |
202 | |
203 | /** |
204 | * devm_counter_alloc - allocate a counter_device |
205 | * @dev: the device to register the release callback for |
206 | * @sizeof_priv: size of the driver private data |
207 | * |
208 | * This is the device managed version of counter_add(). It registers a cleanup |
209 | * callback to care for calling counter_put(). |
210 | */ |
211 | struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv) |
212 | { |
213 | struct counter_device *counter; |
214 | int err; |
215 | |
216 | counter = counter_alloc(sizeof_priv); |
217 | if (!counter) |
218 | return NULL; |
219 | |
220 | err = devm_add_action_or_reset(dev, devm_counter_put, counter); |
221 | if (err < 0) |
222 | return NULL; |
223 | |
224 | return counter; |
225 | } |
226 | EXPORT_SYMBOL_NS_GPL(devm_counter_alloc, COUNTER); |
227 | |
228 | /** |
229 | * devm_counter_add - complete registration of a counter |
230 | * @dev: the device to register the release callback for |
231 | * @counter: the counter to add |
232 | * |
233 | * This is the device managed version of counter_add(). It registers a cleanup |
234 | * callback to care for calling counter_unregister(). |
235 | */ |
236 | int devm_counter_add(struct device *dev, |
237 | struct counter_device *const counter) |
238 | { |
239 | int err; |
240 | |
241 | err = counter_add(counter); |
242 | if (err < 0) |
243 | return err; |
244 | |
245 | return devm_add_action_or_reset(dev, devm_counter_release, counter); |
246 | } |
247 | EXPORT_SYMBOL_NS_GPL(devm_counter_add, COUNTER); |
248 | |
249 | #define COUNTER_DEV_MAX 256 |
250 | |
251 | static int __init counter_init(void) |
252 | { |
253 | int err; |
254 | |
255 | err = bus_register(bus: &counter_bus_type); |
256 | if (err < 0) |
257 | return err; |
258 | |
259 | err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX, |
260 | COUNTER_NAME); |
261 | if (err < 0) |
262 | goto err_unregister_bus; |
263 | |
264 | return 0; |
265 | |
266 | err_unregister_bus: |
267 | bus_unregister(bus: &counter_bus_type); |
268 | return err; |
269 | } |
270 | |
271 | static void __exit counter_exit(void) |
272 | { |
273 | unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX); |
274 | bus_unregister(bus: &counter_bus_type); |
275 | } |
276 | |
277 | subsys_initcall(counter_init); |
278 | module_exit(counter_exit); |
279 | |
280 | MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>" ); |
281 | MODULE_DESCRIPTION("Generic Counter interface" ); |
282 | MODULE_LICENSE("GPL v2" ); |
283 | |