The current implementation gets device lifetime tracking wrong. TheNice work!
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.
[...]
@@ -24,6 +25,11 @@
/* Provides a unique ID for each counter device */
static DEFINE_IDA(counter_ida);
+struct counter_device_allochelper {
+ struct counter_device counter;
+ unsigned long privdata[];
[...]I'd add a parent parameter here since with the devm_ variant we have to pass it anyway and this allows to slightly reduce the boilerplate code in the driver where the parent field of the counter has to be initialized.
+struct counter_device *counter_alloc(size_t sizeof_priv)
+{There is a inconsistency whether braces are used for single statement `if`s in this patch.
+ struct counter_device_allochelper *ch;
+ struct counter_device *counter;
+ struct device *dev;
+ int id, err;
+
+ ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
+ if (!ch) {
+ err = -ENOMEM;
+ goto err_alloc_ch;
+ }
+
+ 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;
+
+ err = counter_chrdev_add(counter);
+ if (err < 0)
+ goto err_chrdev_add;
+
+ device_initialize(dev);
+ /* Configure device structure for Counter */
+ dev->type = &counter_device_type;
+ dev->bus = &counter_bus_type;
+ dev->devt = MKDEV(MAJOR(counter_devt), id);
+
+ mutex_init(&counter->ops_exist_lock);
+
+ return counter;
+
+err_chrdev_add:
+
+ ida_free(&counter_ida, dev->id);
+err_ida_alloc:
+
+ kfree(ch);
+err_alloc_ch:
+
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(counter_alloc);
+
+void counter_put(struct counter_device *counter)
+{
+ put_device(&counter->dev);
+}
+
+/**
+ * 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;
+
+ get_device(&counter->dev);
+return cdev_device_add(...).
+ 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) */
+ err = cdev_device_add(&counter->chrdev, dev);
+
+ return err;
+struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)API Documentation?
+{API Documentation?
+ struct counter_device *counter;
+ int err;
+
+ counter = counter_alloc(sizeof_priv);
+ if (IS_ERR(counter))
+ return counter;
+
+ err = devm_add_action_or_reset(dev, devm_counter_put, counter);
+ if (err < 0)
+ return ERR_PTR(err);
+
+ return counter;
+}
+EXPORT_SYMBOL_GPL(devm_counter_alloc);
+
+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