* [PATCH v2] iio: adc: at91_adc: move to devm_* to fix NULL pointer deref
@ 2026-11-10 9:28 Abdelnasser Hussein
2026-11-10 9:58 ` Andy Shevchenko
0 siblings, 1 reply; 2+ messages in thread
From: Abdelnasser Hussein @ 2026-11-10 9:28 UTC (permalink / raw)
To: jic23, linux-iio
Cc: dlechner, nuno.sa, andy, nicolas.ferre, alexandre.belloni,
claudiu.beznea, mripard, linux-arm-kernel, linux-kernel,
Abdelnasser Hussein
If 'atmel,adc-use-external-triggers' is not set in the device tree,
at91_adc_trigger_init() leaves the external trigger slots in st->trig
as NULL. The error paths and remove function unconditionally called
iio_trigger_unregister() and iio_trigger_free() on them, causing a
NULL pointer dereference.
Instead of just checking for NULL, refactor the driver to use the
device-managed (devm_*) API for registration and allocation. This
modernizes the code, eliminates the need for manual cleanup functions
(at91_adc_trigger_remove, at91_adc_buffer_remove, at91_ts_unregister,
and at91_adc_remove), and inherently fixes the NULL pointer dereference
since the core handles the cleanup of properly allocated triggers.
As part of this simplification, the remaining trigger initialization
logic has been inlined into at91_adc_probe().
Suggested-by: Jonathan Cameron <jic23@kernel.org>
Fixes: 0e589d5fb317 ("ARM: AT91: IIO: Add AT91 ADC driver.")
Signed-off-by: Abdelnasser Hussein <abdelnasserhussein11@gmail.com>
---
v2:
- Dropped the simple NULL check approach.
- Refactored the driver to use devm_* API as suggested by Jonathan Cameron.
- Removed manual cleanup functions and error paths entirely.
- Inlined remaining trigger initialization into probe().
drivers/iio/adc/at91_adc.c | 146 +++++++++----------------------------
1 file changed, 33 insertions(+), 113 deletions(-)
diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c
index 0ac74ad92fca..911838150116 100644
--- a/drivers/iio/adc/at91_adc.c
+++ b/drivers/iio/adc/at91_adc.c
@@ -608,85 +608,32 @@ static const struct iio_trigger_ops at91_adc_trigger_ops = {
static struct iio_trigger *at91_adc_allocate_trigger(struct iio_dev *idev,
const struct at91_adc_trigger *trigger)
{
+ struct device *dev = idev->dev.parent;
struct iio_trigger *trig;
int ret;
- trig = iio_trigger_alloc(idev->dev.parent, "%s-dev%d-%s", idev->name,
- iio_device_id(idev), trigger->name);
- if (trig == NULL)
+ trig = devm_iio_trigger_alloc(dev, "%s-dev%d-%s", idev->name,
+ iio_device_id(idev), trigger->name);
+ if (!trig)
return NULL;
iio_trigger_set_drvdata(trig, idev);
trig->ops = &at91_adc_trigger_ops;
- ret = iio_trigger_register(trig);
- if (ret) {
- iio_trigger_free(trig);
+ ret = devm_iio_trigger_register(dev, trig);
+ if (ret)
return NULL;
- }
return trig;
}
-static int at91_adc_trigger_init(struct iio_dev *idev)
-{
- struct at91_adc_state *st = iio_priv(idev);
- int i, ret;
-
- st->trig = devm_kcalloc(&idev->dev,
- st->caps->trigger_number, sizeof(*st->trig),
- GFP_KERNEL);
-
- if (st->trig == NULL) {
- ret = -ENOMEM;
- goto error_ret;
- }
-
- for (i = 0; i < st->caps->trigger_number; i++) {
- if (st->caps->triggers[i].is_external && !(st->use_external))
- continue;
-
- st->trig[i] = at91_adc_allocate_trigger(idev,
- st->caps->triggers + i);
- if (st->trig[i] == NULL) {
- dev_err(&idev->dev,
- "Could not allocate trigger %d\n", i);
- ret = -ENOMEM;
- goto error_trigger;
- }
- }
-
- return 0;
-
-error_trigger:
- for (i--; i >= 0; i--) {
- iio_trigger_unregister(st->trig[i]);
- iio_trigger_free(st->trig[i]);
- }
-error_ret:
- return ret;
-}
-
-static void at91_adc_trigger_remove(struct iio_dev *idev)
-{
- struct at91_adc_state *st = iio_priv(idev);
- int i;
-
- for (i = 0; i < st->caps->trigger_number; i++) {
- iio_trigger_unregister(st->trig[i]);
- iio_trigger_free(st->trig[i]);
- }
-}
-
static int at91_adc_buffer_init(struct iio_dev *idev)
{
- return iio_triggered_buffer_setup(idev, &iio_pollfunc_store_time,
- &at91_adc_trigger_handler, NULL);
-}
+ struct device *dev = idev->dev.parent;
-static void at91_adc_buffer_remove(struct iio_dev *idev)
-{
- iio_triggered_buffer_cleanup(idev);
+ return devm_iio_triggered_buffer_setup(dev, idev,
+ &iio_pollfunc_store_time,
+ &at91_adc_trigger_handler, NULL);
}
static int at91_adc_read_raw(struct iio_dev *idev,
@@ -924,9 +871,8 @@ static int at91_ts_register(struct iio_dev *idev,
{
struct at91_adc_state *st = iio_priv(idev);
struct input_dev *input;
- int ret;
- input = input_allocate_device();
+ input = devm_input_allocate_device(&pdev->dev);
if (!input) {
dev_err(&idev->dev, "Failed to allocate TS device!\n");
return -ENOMEM;
@@ -951,8 +897,7 @@ static int at91_ts_register(struct iio_dev *idev,
if (st->touchscreen_type != ATMEL_ADC_TOUCHSCREEN_4WIRE) {
dev_err(&pdev->dev,
"This touchscreen controller only support 4 wires\n");
- ret = -EINVAL;
- goto err;
+ return -EINVAL;
}
input_set_abs_params(input, ABS_X, 0, (1 << MAX_RLPOS_BITS) - 1,
@@ -964,27 +909,14 @@ static int at91_ts_register(struct iio_dev *idev,
st->ts_input = input;
input_set_drvdata(input, st);
- ret = input_register_device(input);
- if (ret)
- goto err;
-
- return ret;
-
-err:
- input_free_device(input);
- return ret;
-}
-
-static void at91_ts_unregister(struct at91_adc_state *st)
-{
- input_unregister_device(st->ts_input);
+ return input_register_device(input);
}
static int at91_adc_probe(struct platform_device *pdev)
{
unsigned int prsc, mstrclk, ticks, adc_clk, adc_clk_khz, shtim;
struct device_node *node = pdev->dev.of_node;
- int ret;
+ int i, ret;
struct iio_dev *idev;
struct at91_adc_state *st;
u32 reg, prop;
@@ -1140,11 +1072,23 @@ static int at91_adc_probe(struct platform_device *pdev)
return dev_err_probe(&pdev->dev, ret,
"Couldn't initialize the buffer.\n");
- ret = at91_adc_trigger_init(idev);
- if (ret < 0) {
- dev_err(&pdev->dev, "Couldn't setup the triggers.\n");
- at91_adc_buffer_remove(idev);
- return ret;
+ st->trig = devm_kcalloc(&pdev->dev,
+ st->caps->trigger_number, sizeof(*st->trig),
+ GFP_KERNEL);
+ if (!st->trig)
+ return -ENOMEM;
+
+ for (i = 0; i < st->caps->trigger_number; i++) {
+ if (st->caps->triggers[i].is_external && !(st->use_external))
+ continue;
+
+ st->trig[i] = at91_adc_allocate_trigger(idev,
+ st->caps->triggers + i);
+ if (!st->trig[i]) {
+ dev_err(&idev->dev,
+ "Could not allocate trigger %d\n", i);
+ return -ENOMEM;
+ }
}
} else {
ret = at91_ts_register(idev, pdev);
@@ -1154,36 +1098,13 @@ static int at91_adc_probe(struct platform_device *pdev)
at91_ts_hw_init(idev, adc_clk_khz);
}
- ret = iio_device_register(idev);
+ ret = devm_iio_device_register(&pdev->dev, idev);
if (ret < 0) {
dev_err(&pdev->dev, "Couldn't register the device.\n");
- goto error_iio_device_register;
+ return ret;
}
return 0;
-
-error_iio_device_register:
- if (!st->touchscreen_type) {
- at91_adc_trigger_remove(idev);
- at91_adc_buffer_remove(idev);
- } else {
- at91_ts_unregister(st);
- }
- return ret;
-}
-
-static void at91_adc_remove(struct platform_device *pdev)
-{
- struct iio_dev *idev = platform_get_drvdata(pdev);
- struct at91_adc_state *st = iio_priv(idev);
-
- iio_device_unregister(idev);
- if (!st->touchscreen_type) {
- at91_adc_trigger_remove(idev);
- at91_adc_buffer_remove(idev);
- } else {
- at91_ts_unregister(st);
- }
}
static int at91_adc_suspend(struct device *dev)
@@ -1333,7 +1254,6 @@ MODULE_DEVICE_TABLE(of, at91_adc_dt_ids);
static struct platform_driver at91_adc_driver = {
.probe = at91_adc_probe,
- .remove = at91_adc_remove,
.driver = {
.name = DRIVER_NAME,
.of_match_table = at91_adc_dt_ids,
--
2.56.0
^ permalink raw reply [flat|nested] 2+ messages in thread* Re: [PATCH v2] iio: adc: at91_adc: move to devm_* to fix NULL pointer deref
2026-11-10 9:28 [PATCH v2] iio: adc: at91_adc: move to devm_* to fix NULL pointer deref Abdelnasser Hussein
@ 2026-11-10 9:58 ` Andy Shevchenko
0 siblings, 0 replies; 2+ messages in thread
From: Andy Shevchenko @ 2026-11-10 9:58 UTC (permalink / raw)
To: Abdelnasser Hussein
Cc: jic23, linux-iio, dlechner, nuno.sa, andy, nicolas.ferre,
alexandre.belloni, claudiu.beznea, mripard, linux-arm-kernel,
linux-kernel
On Tue, Nov 10, 2026 at 11:28:48AM +0200, Abdelnasser Hussein wrote:
> If 'atmel,adc-use-external-triggers' is not set in the device tree,
> at91_adc_trigger_init() leaves the external trigger slots in st->trig
> as NULL. The error paths and remove function unconditionally called
> iio_trigger_unregister() and iio_trigger_free() on them, causing a
> NULL pointer dereference.
>
> Instead of just checking for NULL, refactor the driver to use the
> device-managed (devm_*) API for registration and allocation. This
> modernizes the code, eliminates the need for manual cleanup functions
> (at91_adc_trigger_remove, at91_adc_buffer_remove, at91_ts_unregister,
> and at91_adc_remove), and inherently fixes the NULL pointer dereference
> since the core handles the cleanup of properly allocated triggers.
>
> As part of this simplification, the remaining trigger initialization
> logic has been inlined into at91_adc_probe().
...
> static int at91_ts_register(struct iio_dev *idev,
> {
> struct at91_adc_state *st = iio_priv(idev);
> struct input_dev *input;
> - int ret;
>
> - input = input_allocate_device();
> + input = devm_input_allocate_device(&pdev->dev);
> if (!input) {
> dev_err(&idev->dev, "Failed to allocate TS device!\n");
Side note:
This message needs to be killed, we don't print -ENOMEM errors.
> return -ENOMEM;
> if (st->touchscreen_type != ATMEL_ADC_TOUCHSCREEN_4WIRE) {
> dev_err(&pdev->dev,
> "This touchscreen controller only support 4 wires\n");
> - ret = -EINVAL;
> - goto err;
> + return -EINVAL;
Another side note: this should move to dev_err_probe() (along with similar
cases) at some point.
> }
>
> input_set_abs_params(input, ABS_X, 0, (1 << MAX_RLPOS_BITS) - 1,
...
> static int at91_adc_probe(struct platform_device *pdev)
> {
> unsigned int prsc, mstrclk, ticks, adc_clk, adc_clk_khz, shtim;
> struct device_node *node = pdev->dev.of_node;
> - int ret;
> + int i, ret;
No, don't add it here as it's local to the for-loop.
> struct iio_dev *idev;
> struct at91_adc_state *st;
> u32 reg, prop;
> - ret = at91_adc_trigger_init(idev);
> - if (ret < 0) {
> - dev_err(&pdev->dev, "Couldn't setup the triggers.\n");
> - at91_adc_buffer_remove(idev);
> - return ret;
> + st->trig = devm_kcalloc(&pdev->dev,
Add at the top
struct device *dev = &pdev->dev;
and use in the code you modified or add.
> + st->caps->trigger_number, sizeof(*st->trig),
> + GFP_KERNEL);
> + if (!st->trig)
> + return -ENOMEM;
> + for (i = 0; i < st->caps->trigger_number; i++) {
for (int i = 0; i < st->caps->trigger_number; i++) {
// align the type of i based on the type of trigger_number
> + if (st->caps->triggers[i].is_external && !(st->use_external))
> + continue;
> +
> + st->trig[i] = at91_adc_allocate_trigger(idev,
> + st->caps->triggers + i);
> + if (!st->trig[i]) {
> + dev_err(&idev->dev,
> + "Could not allocate trigger %d\n", i);
> + return -ENOMEM;
> + }
> }
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-10 9:58 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 9:28 [PATCH v2] iio: adc: at91_adc: move to devm_* to fix NULL pointer deref Abdelnasser Hussein
2026-11-10 9:58 ` Andy Shevchenko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®