Skip to content

Commit c18e276

Browse files
Uwe Kleine-Königgregkh
authored andcommitted
counter: Provide alternative counter registration functions
The current implementation gets device lifetime tracking wrong. The problem is that allocation of struct counter_device is controlled by the individual drivers but this structure contains a struct device that might have to live longer than a driver is bound. As a result a command sequence like: { sleep 5; echo bang; } > /dev/counter0 & sleep 1; echo 40000000.timer:counter > /sys/bus/platform/drivers/stm32-timer-counter/unbind can keep a reference to the struct device and unbinding results in freeing the memory occupied by this device resulting in an oops. This commit provides two new functions (plus some helpers): - counter_alloc() to allocate a struct counter_device that is automatically freed once the embedded struct device is released - counter_add() to register such a device. Note that this commit doesn't fix any issues, all drivers have to be converted to these new functions to correct the lifetime problems. Reviewed-by: Jonathan Cameron <[email protected]> Signed-off-by: Uwe Kleine-König <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent e152833 commit c18e276

File tree

2 files changed

+181
-2
lines changed

2 files changed

+181
-2
lines changed

drivers/counter/counter-core.c

Lines changed: 166 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/kdev_t.h>
1616
#include <linux/module.h>
1717
#include <linux/mutex.h>
18+
#include <linux/slab.h>
1819
#include <linux/types.h>
1920
#include <linux/wait.h>
2021

@@ -24,13 +25,26 @@
2425
/* Provides a unique ID for each counter device */
2526
static DEFINE_IDA(counter_ida);
2627

28+
struct 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+
2738
static void counter_device_release(struct device *dev)
2839
{
2940
struct counter_device *const counter =
3041
container_of(dev, struct counter_device, dev);
3142

3243
counter_chrdev_remove(counter);
3344
ida_free(&counter_ida, dev->id);
45+
46+
if (!counter->legacy_device)
47+
kfree(container_of(counter, struct counter_device_allochelper, counter));
3448
}
3549

3650
static struct device_type counter_device_type = {
@@ -53,7 +67,14 @@ static dev_t counter_devt;
5367
*/
5468
void *counter_priv(const struct counter_device *const counter)
5569
{
56-
return counter->priv;
70+
if (counter->legacy_device) {
71+
return counter->priv;
72+
} else {
73+
struct counter_device_allochelper *ch =
74+
container_of(counter, struct counter_device_allochelper, counter);
75+
76+
return &ch->privdata;
77+
}
5778
}
5879
EXPORT_SYMBOL_GPL(counter_priv);
5980

@@ -74,6 +95,8 @@ int counter_register(struct counter_device *const counter)
7495
int id;
7596
int err;
7697

98+
counter->legacy_device = true;
99+
77100
/* Acquire unique ID */
78101
id = ida_alloc(&counter_ida, GFP_KERNEL);
79102
if (id < 0)
@@ -114,6 +137,95 @@ int counter_register(struct counter_device *const counter)
114137
}
115138
EXPORT_SYMBOL_GPL(counter_register);
116139

140+
/**
141+
* counter_alloc - allocate a counter_device
142+
* @sizeof_priv: size of the driver private data
143+
*
144+
* This is part one of counter registration. The structure is allocated
145+
* dynamically to ensure the right lifetime for the embedded struct device.
146+
*
147+
* If this succeeds, call counter_put() to get rid of the counter_device again.
148+
*/
149+
struct counter_device *counter_alloc(size_t sizeof_priv)
150+
{
151+
struct counter_device_allochelper *ch;
152+
struct counter_device *counter;
153+
struct device *dev;
154+
int err;
155+
156+
ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
157+
if (!ch) {
158+
err = -ENOMEM;
159+
goto err_alloc_ch;
160+
}
161+
162+
counter = &ch->counter;
163+
dev = &counter->dev;
164+
165+
/* Acquire unique ID */
166+
err = ida_alloc(&counter_ida, GFP_KERNEL);
167+
if (err < 0)
168+
goto err_ida_alloc;
169+
dev->id = err;
170+
171+
mutex_init(&counter->ops_exist_lock);
172+
dev->type = &counter_device_type;
173+
dev->bus = &counter_bus_type;
174+
dev->devt = MKDEV(MAJOR(counter_devt), dev->id);
175+
176+
err = counter_chrdev_add(counter);
177+
if (err < 0)
178+
goto err_chrdev_add;
179+
180+
device_initialize(dev);
181+
182+
return counter;
183+
184+
err_chrdev_add:
185+
186+
ida_free(&counter_ida, dev->id);
187+
err_ida_alloc:
188+
189+
kfree(ch);
190+
err_alloc_ch:
191+
192+
return ERR_PTR(err);
193+
}
194+
EXPORT_SYMBOL_GPL(counter_alloc);
195+
196+
void counter_put(struct counter_device *counter)
197+
{
198+
put_device(&counter->dev);
199+
}
200+
EXPORT_SYMBOL_GPL(counter_put);
201+
202+
/**
203+
* counter_add - complete registration of a counter
204+
* @counter: the counter to add
205+
*
206+
* This is part two of counter registration.
207+
*
208+
* If this succeeds, call counter_unregister() to get rid of the counter_device again.
209+
*/
210+
int counter_add(struct counter_device *counter)
211+
{
212+
int err;
213+
struct device *dev = &counter->dev;
214+
215+
if (counter->parent) {
216+
dev->parent = counter->parent;
217+
dev->of_node = counter->parent->of_node;
218+
}
219+
220+
err = counter_sysfs_add(counter);
221+
if (err < 0)
222+
return err;
223+
224+
/* implies device_add(dev) */
225+
return cdev_device_add(&counter->chrdev, dev);
226+
}
227+
EXPORT_SYMBOL_GPL(counter_add);
228+
117229
/**
118230
* counter_unregister - unregister Counter from the system
119231
* @counter: pointer to Counter to unregister
@@ -134,7 +246,8 @@ void counter_unregister(struct counter_device *const counter)
134246

135247
mutex_unlock(&counter->ops_exist_lock);
136248

137-
put_device(&counter->dev);
249+
if (counter->legacy_device)
250+
put_device(&counter->dev);
138251
}
139252
EXPORT_SYMBOL_GPL(counter_unregister);
140253

@@ -168,6 +281,57 @@ int devm_counter_register(struct device *dev,
168281
}
169282
EXPORT_SYMBOL_GPL(devm_counter_register);
170283

284+
static void devm_counter_put(void *counter)
285+
{
286+
counter_put(counter);
287+
}
288+
289+
/**
290+
* devm_counter_alloc - allocate a counter_device
291+
* @dev: the device to register the release callback for
292+
* @sizeof_priv: size of the driver private data
293+
*
294+
* This is the device managed version of counter_add(). It registers a cleanup
295+
* callback to care for calling counter_put().
296+
*/
297+
struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)
298+
{
299+
struct counter_device *counter;
300+
int err;
301+
302+
counter = counter_alloc(sizeof_priv);
303+
if (IS_ERR(counter))
304+
return counter;
305+
306+
err = devm_add_action_or_reset(dev, devm_counter_put, counter);
307+
if (err < 0)
308+
return ERR_PTR(err);
309+
310+
return counter;
311+
}
312+
EXPORT_SYMBOL_GPL(devm_counter_alloc);
313+
314+
/**
315+
* devm_counter_add - complete registration of a counter
316+
* @dev: the device to register the release callback for
317+
* @counter: the counter to add
318+
*
319+
* This is the device managed version of counter_add(). It registers a cleanup
320+
* callback to care for calling counter_unregister().
321+
*/
322+
int devm_counter_add(struct device *dev,
323+
struct counter_device *const counter)
324+
{
325+
int err;
326+
327+
err = counter_add(counter);
328+
if (err < 0)
329+
return err;
330+
331+
return devm_add_action_or_reset(dev, devm_counter_release, counter);
332+
}
333+
EXPORT_SYMBOL_GPL(devm_counter_add);
334+
171335
#define COUNTER_DEV_MAX 256
172336

173337
static int __init counter_init(void)

include/linux/counter.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,14 +327,29 @@ struct counter_device {
327327
spinlock_t events_in_lock;
328328
struct mutex events_out_lock;
329329
struct mutex ops_exist_lock;
330+
331+
/*
332+
* This can go away once all drivers are converted to
333+
* counter_alloc()/counter_add().
334+
*/
335+
bool legacy_device;
330336
};
331337

332338
void *counter_priv(const struct counter_device *const counter);
333339

334340
int counter_register(struct counter_device *const counter);
341+
342+
struct counter_device *counter_alloc(size_t sizeof_priv);
343+
void counter_put(struct counter_device *const counter);
344+
int counter_add(struct counter_device *const counter);
345+
335346
void counter_unregister(struct counter_device *const counter);
336347
int devm_counter_register(struct device *dev,
337348
struct counter_device *const counter);
349+
struct counter_device *devm_counter_alloc(struct device *dev,
350+
size_t sizeof_priv);
351+
int devm_counter_add(struct device *dev,
352+
struct counter_device *const counter);
338353
void counter_push_event(struct counter_device *const counter, const u8 event,
339354
const u8 channel);
340355

0 commit comments

Comments
 (0)