* [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path
[not found] <cover.1788882189.git.sean@mess.org>
@ 2026-09-08 15:51 ` Sean Young
2026-09-11 7:53 ` Hans Verkuil
2026-09-08 15:51 ` [PATCH v4 02/19] media: rc: Ensure that rc_unregister_device() does not free input device Sean Young
` (17 subsequent siblings)
18 siblings, 1 reply; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab, Hans Verkuil,
Patrice Chotard
Cc: Rik van Riel, stable, linux-kernel
If rc_register_device() fails, ensure that registered is not set to true.
If lirc_register() succeeded, then userspace could have an open file
descriptor open. This leads to a use-after-free.
Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/rc-main.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
index d93e98189c1a..1f99540456f1 100644
--- a/drivers/media/rc/rc-main.c
+++ b/drivers/media/rc/rc-main.c
@@ -1934,7 +1934,8 @@ int rc_register_device(struct rc_dev *dev)
goto out_raw;
}
- dev->registered = true;
+ scoped_guard(mutex, &dev->lock)
+ dev->registered = true;
rc = device_add(&dev->dev);
if (rc)
@@ -1982,6 +1983,8 @@ int rc_register_device(struct rc_dev *dev)
out_dev:
device_del(&dev->dev);
out_rx_free:
+ scoped_guard(mutex, &dev->lock)
+ dev->registered = false;
ir_free_table(&dev->rc_map);
out_raw:
ida_free(&rc_ida, minor);
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path
2026-09-08 15:51 ` [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path Sean Young
@ 2026-09-11 7:53 ` Hans Verkuil
2026-09-11 8:45 ` Sean Young
0 siblings, 1 reply; 25+ messages in thread
From: Hans Verkuil @ 2026-09-11 7:53 UTC (permalink / raw)
To: Sean Young, linux-media, Mauro Carvalho Chehab, Patrice Chotard
Cc: Rik van Riel, stable, linux-kernel
On 08/09/2026 17:51, Sean Young wrote:
> If rc_register_device() fails, ensure that registered is not set to true.
> If lirc_register() succeeded, then userspace could have an open file
> descriptor open. This leads to a use-after-free.
>
> Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
> Signed-off-by: Sean Young <sean@mess.org>
> Cc: stable@vger.kernel.org
> ---
> drivers/media/rc/rc-main.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
> index d93e98189c1a..1f99540456f1 100644
> --- a/drivers/media/rc/rc-main.c
> +++ b/drivers/media/rc/rc-main.c
> @@ -1934,7 +1934,8 @@ int rc_register_device(struct rc_dev *dev)
> goto out_raw;
> }
>
> - dev->registered = true;
> + scoped_guard(mutex, &dev->lock)
> + dev->registered = true;
Is the guard actually needed?
>
> rc = device_add(&dev->dev);
> if (rc)
> @@ -1982,6 +1983,8 @@ int rc_register_device(struct rc_dev *dev)
> out_dev:
> device_del(&dev->dev);
> out_rx_free:
> + scoped_guard(mutex, &dev->lock)
> + dev->registered = false;
Same question here. I think the real fix is setting dev->registered to
false, but I'm not sure the guard makes a difference.
The commit log mentions nothing about that change either.
Regards,
Hans
> ir_free_table(&dev->rc_map);
> out_raw:
> ida_free(&rc_ida, minor);
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path
2026-09-11 7:53 ` Hans Verkuil
@ 2026-09-11 8:45 ` Sean Young
0 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-11 8:45 UTC (permalink / raw)
To: Hans Verkuil
Cc: linux-media, Mauro Carvalho Chehab, Patrice Chotard,
Rik van Riel, stable, linux-kernel
On Fri, Sep 11, 2026 at 09:53:10AM +0200, Hans Verkuil wrote:
> On 08/09/2026 17:51, Sean Young wrote:
> > If rc_register_device() fails, ensure that registered is not set to true.
> > If lirc_register() succeeded, then userspace could have an open file
> > descriptor open. This leads to a use-after-free.
> >
> > Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
> > Signed-off-by: Sean Young <sean@mess.org>
> > Cc: stable@vger.kernel.org
> > ---
> > drivers/media/rc/rc-main.c | 5 ++++-
> > 1 file changed, 4 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
> > index d93e98189c1a..1f99540456f1 100644
> > --- a/drivers/media/rc/rc-main.c
> > +++ b/drivers/media/rc/rc-main.c
> > @@ -1934,7 +1934,8 @@ int rc_register_device(struct rc_dev *dev)
> > goto out_raw;
> > }
> >
> > - dev->registered = true;
> > + scoped_guard(mutex, &dev->lock)
> > + dev->registered = true;
>
> Is the guard actually needed?
Yes, we read registered under lock in e.g. in lirc_open(). It s
racey without.
> > rc = device_add(&dev->dev);
> > if (rc)
> > @@ -1982,6 +1983,8 @@ int rc_register_device(struct rc_dev *dev)
> > out_dev:
> > device_del(&dev->dev);
> > out_rx_free:
> > + scoped_guard(mutex, &dev->lock)
> > + dev->registered = false;
>
> Same question here. I think the real fix is setting dev->registered to
> false, but I'm not sure the guard makes a difference.
>
> The commit log mentions nothing about that change either.
You are right, the commit message needs to mention this.
Sean
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v4 02/19] media: rc: Ensure that rc_unregister_device() does not free input device
[not found] <cover.1788882189.git.sean@mess.org>
2026-09-08 15:51 ` [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:51 ` [PATCH v4 03/19] media: rc: Fix ABBA deadlock by making locks more fine grained Sean Young
` (16 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab, Hans Verkuil,
Patrice Chotard
Cc: Rik van Riel, stable, linux-kernel
During or after rc_unregister_device(), IR may still be reported which
results in a input event being reported. This could result in a null
pointer deref in rc_keydown() or a use-after-free of the input device if
the pointer was read before it is set to NULL.
Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/rc-main.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
index 1f99540456f1..88d8f7d4aab3 100644
--- a/drivers/media/rc/rc-main.c
+++ b/drivers/media/rc/rc-main.c
@@ -1753,7 +1753,7 @@ void rc_free_device(struct rc_dev *dev)
if (!dev)
return;
- input_free_device(dev->input_dev);
+ input_put_device(dev->input_dev);
put_device(&dev->dev);
@@ -1865,6 +1865,8 @@ static int rc_setup_rx_device(struct rc_dev *dev)
if (rc)
return rc;
+ input_get_device(dev->input_dev);
+
/*
* Default delay of 250ms is too short for some protocols, especially
* since the timeout is currently set to 250ms. Increase it to 500ms,
@@ -1891,10 +1893,8 @@ static void rc_free_rx_device(struct rc_dev *dev)
if (!dev)
return;
- if (dev->input_dev) {
+ if (dev->input_dev)
input_unregister_device(dev->input_dev);
- dev->input_dev = NULL;
- }
ir_free_table(&dev->rc_map);
}
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 03/19] media: rc: Fix ABBA deadlock by making locks more fine grained
[not found] <cover.1788882189.git.sean@mess.org>
2026-09-08 15:51 ` [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path Sean Young
2026-09-08 15:51 ` [PATCH v4 02/19] media: rc: Ensure that rc_unregister_device() does not free input device Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:51 ` [PATCH v4 04/19] media: rc: Add missing locking for keymap Sean Young
` (15 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab, Heiner Kallweit
Cc: Rik van Riel, stable, linux-kernel
The ir_raw_event_unregister() function takes the ir_raw_handler_lock
first and then the rc_dev->lock. Other functions like change_protocol
do this in the reverse order.
This means that an ir decoder module unload and writing to the protocol
sysfs file can cause a deadlock.
ir_raw_handler_lock protects the client list, amongst other things.
Split this out into a separate lock so we can ensure the locking
order is always the same.
Fixes: 93cffffc18f6 ("[media] media: rc: fix decoder module unloading")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/rc-ir-raw.c | 39 ++++++++++++++++++++++--------------
1 file changed, 24 insertions(+), 15 deletions(-)
diff --git a/drivers/media/rc/rc-ir-raw.c b/drivers/media/rc/rc-ir-raw.c
index 54b323becb1f..75dee4a483d4 100644
--- a/drivers/media/rc/rc-ir-raw.c
+++ b/drivers/media/rc/rc-ir-raw.c
@@ -10,7 +10,8 @@
#include <linux/sched.h>
#include "rc-core-priv.h"
-/* Used to keep track of IR raw clients, protected by ir_raw_handler_lock */
+/* Used to keep track of IR raw clients, protected by ir_raw_client_lock */
+static DEFINE_MUTEX(ir_raw_client_lock);
static LIST_HEAD(ir_raw_client_list);
/* Used to handle IR raw handler extensions */
@@ -273,13 +274,6 @@ static int change_protocol(struct rc_dev *dev, u64 *rc_proto)
return 0;
}
-static void ir_raw_disable_protocols(struct rc_dev *dev, u64 protocols)
-{
- mutex_lock(&dev->lock);
- dev->enabled_protocols &= ~protocols;
- mutex_unlock(&dev->lock);
-}
-
/**
* ir_raw_gen_manchester() - Encode data with Manchester (bi-phase) modulation.
* @ev: Pointer to pointer to next free event. *@ev is incremented for
@@ -615,15 +609,18 @@ int ir_raw_event_register(struct rc_dev *dev)
{
struct task_struct *thread;
+ /* Holding dev->lock could result in a dead-lock */
+ lockdep_assert_not_held(&dev->lock);
+
thread = kthread_run(ir_raw_event_thread, dev->raw, "rc%u", dev->minor);
if (IS_ERR(thread))
return PTR_ERR(thread);
dev->raw->thread = thread;
- mutex_lock(&ir_raw_handler_lock);
+ mutex_lock(&ir_raw_client_lock);
list_add_tail(&dev->raw->list, &ir_raw_client_list);
- mutex_unlock(&ir_raw_handler_lock);
+ mutex_unlock(&ir_raw_client_lock);
return 0;
}
@@ -656,16 +653,19 @@ void ir_raw_event_unregister(struct rc_dev *dev)
kthread_stop(dev->raw->thread);
timer_delete_sync(&dev->raw->edge_handle);
- mutex_lock(&ir_raw_handler_lock);
+ mutex_lock(&ir_raw_client_lock);
list_del(&dev->raw->list);
+
+ mutex_lock(&ir_raw_handler_lock);
list_for_each_entry(handler, &ir_raw_handler_list, list)
if (handler->raw_unregister &&
(handler->protocols & dev->enabled_protocols))
handler->raw_unregister(dev);
lirc_bpf_free(dev);
-
mutex_unlock(&ir_raw_handler_lock);
+
+ mutex_unlock(&ir_raw_client_lock);
}
/*
@@ -688,15 +688,24 @@ void ir_raw_handler_unregister(struct ir_raw_handler *ir_raw_handler)
struct ir_raw_event_ctrl *raw;
u64 protocols = ir_raw_handler->protocols;
+ mutex_lock(&ir_raw_client_lock);
+
mutex_lock(&ir_raw_handler_lock);
list_del(&ir_raw_handler->list);
+ atomic64_andnot(protocols, &available_protocols);
+ mutex_unlock(&ir_raw_handler_lock);
+
list_for_each_entry(raw, &ir_raw_client_list, list) {
+ mutex_lock(&raw->dev->lock);
+ mutex_lock(&ir_raw_handler_lock);
if (ir_raw_handler->raw_unregister &&
(raw->dev->enabled_protocols & protocols))
ir_raw_handler->raw_unregister(raw->dev);
- ir_raw_disable_protocols(raw->dev, protocols);
+ raw->dev->enabled_protocols &= ~protocols;
+ mutex_unlock(&ir_raw_handler_lock);
+ mutex_unlock(&raw->dev->lock);
}
- atomic64_andnot(protocols, &available_protocols);
- mutex_unlock(&ir_raw_handler_lock);
+
+ mutex_unlock(&ir_raw_client_lock);
}
EXPORT_SYMBOL(ir_raw_handler_unregister);
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 04/19] media: rc: Add missing locking for keymap
[not found] <cover.1788882189.git.sean@mess.org>
` (2 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 03/19] media: rc: Fix ABBA deadlock by making locks more fine grained Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-11 8:08 ` Hans Verkuil
2026-09-08 15:51 ` [PATCH v4 05/19] media: rc: Fix race between bpf(BPG_PROG_ATTACH) and device unregister Sean Young
` (14 subsequent siblings)
18 siblings, 1 reply; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab, Hans Verkuil,
Patrice Chotard
Cc: Rik van Riel, stable, linux-kernel
When the rc_map for an rc_dev gets updated, locking is required but is
missing in places. A concurrent scancode lookup and a call to
rc_{register,unregistered}_device() could result in a use-after-free;
this could happen if IR is decoded during those function calls.
We also fix some ugliness like open-coded krealloc() and removing the
pointless alloc member of rc_map.
Add lockdep assertions where locks are required.
Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/rc-main.c | 122 ++++++++++++++++++++++---------------
include/media/rc-map.h | 2 -
2 files changed, 74 insertions(+), 50 deletions(-)
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
index 88d8f7d4aab3..04204559959f 100644
--- a/drivers/media/rc/rc-main.c
+++ b/drivers/media/rc/rc-main.c
@@ -17,9 +17,8 @@
#include <linux/module.h>
#include "rc-core-priv.h"
-/* Sizes are in bytes, 256 bytes allows for 32 entries on x64 */
-#define IR_TAB_MIN_SIZE 256
-#define IR_TAB_MAX_SIZE 8192
+#define IR_TAB_MIN_SIZE 32
+#define IR_TAB_MAX_SIZE 1024
static const struct {
const char *name;
@@ -105,7 +104,6 @@ static struct rc_map_list *seek_rc_map(const char *name)
struct rc_map *rc_map_get(const char *name)
{
-
struct rc_map_list *map;
map = seek_rc_map(name);
@@ -202,7 +200,7 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode)
* ir_create_table() - initializes a scancode table
* @dev: the rc_dev device
* @rc_map: the rc_map to initialize
- * @name: name to assign to the table
+ * @map_name: name to assign to the table
* @rc_proto: ir type to assign to the new table
* @size: initial size of the table
*
@@ -212,23 +210,33 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode)
* return: zero on success or a negative error code
*/
static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map,
- const char *name, u64 rc_proto, size_t size)
+ const char *map_name, u64 rc_proto, size_t size)
{
- rc_map->name = kstrdup(name, GFP_KERNEL);
- if (!rc_map->name)
+ struct rc_map_table *scan;
+ unsigned int alloc;
+ char *name;
+
+ name = kstrdup(map_name, GFP_KERNEL);
+ if (!name)
return -ENOMEM;
- rc_map->rc_proto = rc_proto;
- rc_map->alloc = roundup_pow_of_two(size * sizeof(struct rc_map_table));
- rc_map->size = rc_map->alloc / sizeof(struct rc_map_table);
- rc_map->scan = kmalloc(rc_map->alloc, GFP_KERNEL);
- if (!rc_map->scan) {
- kfree(rc_map->name);
- rc_map->name = NULL;
+
+ alloc = roundup_pow_of_two(size);
+ scan = kmalloc_objs(struct rc_map_table, alloc, GFP_KERNEL);
+ if (!scan) {
+ kfree(name);
return -ENOMEM;
}
- dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%u bytes)\n",
- rc_map->size, rc_map->alloc);
+ scoped_guard(spinlock_irqsave, &rc_map->lock) {
+ rc_map->name = name;
+ rc_map->scan = scan;
+ rc_map->rc_proto = rc_proto;
+ rc_map->len = 0;
+ rc_map->size = alloc;
+ }
+
+ dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%zu bytes)\n",
+ alloc, alloc * sizeof(struct rc_map_table));
return 0;
}
@@ -236,16 +244,26 @@ static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map,
* ir_free_table() - frees memory allocated by a scancode table
* @rc_map: the table whose mappings need to be freed
*
- * This routine will free memory alloctaed for key mappings used by given
+ * This routine will free memory allocated for key mappings used by given
* scancode table.
*/
static void ir_free_table(struct rc_map *rc_map)
{
- rc_map->size = 0;
- kfree(rc_map->name);
- rc_map->name = NULL;
- kfree(rc_map->scan);
- rc_map->scan = NULL;
+ struct rc_map_table *scan;
+ const char *name;
+
+ scoped_guard(spinlock_irqsave, &rc_map->lock) {
+ name = rc_map->name;
+ scan = rc_map->scan;
+
+ rc_map->size = 0;
+ rc_map->len = 0;
+ rc_map->name = NULL;
+ rc_map->scan = NULL;
+ }
+
+ kfree(name);
+ kfree(scan);
}
/**
@@ -262,38 +280,38 @@ static void ir_free_table(struct rc_map *rc_map)
static int ir_resize_table(struct rc_dev *dev, struct rc_map *rc_map,
gfp_t gfp_flags)
{
- unsigned int oldalloc = rc_map->alloc;
- unsigned int newalloc = oldalloc;
- struct rc_map_table *oldscan = rc_map->scan;
+ unsigned int newsize = rc_map->size;
struct rc_map_table *newscan;
+ lockdep_assert_held(&rc_map->lock);
+
if (rc_map->size == rc_map->len) {
/* All entries in use -> grow keytable */
- if (rc_map->alloc >= IR_TAB_MAX_SIZE)
+ if (newsize >= IR_TAB_MAX_SIZE)
return -ENOMEM;
- newalloc *= 2;
- dev_dbg(&dev->dev, "Growing table to %u bytes\n", newalloc);
+ newsize *= 2;
+
+ dev_dbg(&dev->dev, "Growing table to %u entries\n", newsize);
}
- if ((rc_map->len * 3 < rc_map->size) && (oldalloc > IR_TAB_MIN_SIZE)) {
+ if (rc_map->len * 3 < rc_map->size && rc_map->size > IR_TAB_MIN_SIZE) {
/* Less than 1/3 of entries in use -> shrink keytable */
- newalloc /= 2;
- dev_dbg(&dev->dev, "Shrinking table to %u bytes\n", newalloc);
+ newsize /= 2;
+ dev_dbg(&dev->dev, "Shrinking table to %u entries\n", newsize);
}
- if (newalloc == oldalloc)
+ if (newsize == rc_map->size)
return 0;
- newscan = kmalloc(newalloc, gfp_flags);
+ newscan = krealloc_array(rc_map->scan, newsize,
+ sizeof(struct rc_map_table), gfp_flags);
if (!newscan)
return -ENOMEM;
- memcpy(newscan, rc_map->scan, rc_map->len * sizeof(struct rc_map_table));
rc_map->scan = newscan;
- rc_map->alloc = newalloc;
- rc_map->size = rc_map->alloc / sizeof(struct rc_map_table);
- kfree(oldscan);
+ rc_map->size = newsize;
+
return 0;
}
@@ -318,6 +336,8 @@ static unsigned int ir_update_mapping(struct rc_dev *dev,
int old_keycode = rc_map->scan[index].keycode;
int i;
+ lockdep_assert_held(&rc_map->lock);
+
/* Did the user wish to remove the mapping? */
if (new_keycode == KEY_RESERVED || new_keycode == KEY_UNKNOWN) {
dev_dbg(&dev->dev, "#%d: Deleting scan 0x%04llx\n",
@@ -373,6 +393,8 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
{
unsigned int i;
+ lockdep_assert_held(&rc_map->lock);
+
/*
* Unfortunately, some hardware-based IR decoders don't provide
* all bits for the complete IR code. In general, they provide only
@@ -397,7 +419,7 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
/* No previous mapping found, we might need to grow the table */
if (rc_map->size == rc_map->len) {
if (!resize || ir_resize_table(dev, rc_map, GFP_ATOMIC))
- return -1U;
+ return UINT_MAX;
}
/* i is the proper index to insert our new keycode */
@@ -479,16 +501,18 @@ static int ir_setkeytable(struct rc_dev *dev, const struct rc_map *from)
if (rc)
return rc;
- for (i = 0; i < from->size; i++) {
- index = ir_establish_scancode(dev, rc_map,
- from->scan[i].scancode, false);
- if (index >= rc_map->len) {
- rc = -ENOMEM;
- break;
- }
+ scoped_guard(spinlock_irqsave, &rc_map->lock) {
+ for (i = 0; i < from->size; i++) {
+ index = ir_establish_scancode(dev, rc_map,
+ from->scan[i].scancode, false);
+ if (index >= rc_map->len) {
+ rc = -ENOMEM;
+ break;
+ }
- ir_update_mapping(dev, rc_map, index,
- from->scan[i].keycode);
+ ir_update_mapping(dev, rc_map, index,
+ from->scan[i].keycode);
+ }
}
if (rc)
@@ -524,6 +548,8 @@ static unsigned int ir_lookup_by_scancode(const struct rc_map *rc_map,
{
struct rc_map_table *res;
+ lockdep_assert_held(&rc_map->lock);
+
res = bsearch(&scancode, rc_map->scan, rc_map->len,
sizeof(struct rc_map_table), rc_map_cmp);
if (!res)
diff --git a/include/media/rc-map.h b/include/media/rc-map.h
index d95ed3e96de2..f167c37179c8 100644
--- a/include/media/rc-map.h
+++ b/include/media/rc-map.h
@@ -148,7 +148,6 @@ struct rc_map_table {
* @scan: pointer to struct &rc_map_table
* @size: Max number of entries
* @len: Number of entries that are in use
- * @alloc: size of \*scan, in bytes
* @rc_proto: type of the remote controller protocol, as defined at
* enum &rc_proto
* @name: name of the key map table
@@ -158,7 +157,6 @@ struct rc_map {
struct rc_map_table *scan;
unsigned int size;
unsigned int len;
- unsigned int alloc;
enum rc_proto rc_proto;
const char *name;
spinlock_t lock;
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH v4 04/19] media: rc: Add missing locking for keymap
2026-09-08 15:51 ` [PATCH v4 04/19] media: rc: Add missing locking for keymap Sean Young
@ 2026-09-11 8:08 ` Hans Verkuil
2026-09-11 8:47 ` Sean Young
0 siblings, 1 reply; 25+ messages in thread
From: Hans Verkuil @ 2026-09-11 8:08 UTC (permalink / raw)
To: Sean Young, linux-media, Mauro Carvalho Chehab, Patrice Chotard
Cc: Rik van Riel, stable, linux-kernel
On 08/09/2026 17:51, Sean Young wrote:
> When the rc_map for an rc_dev gets updated, locking is required but is
> missing in places. A concurrent scancode lookup and a call to
> rc_{register,unregistered}_device() could result in a use-after-free;
> this could happen if IR is decoded during those function calls.
>
> We also fix some ugliness like open-coded krealloc() and removing the
> pointless alloc member of rc_map.
I would recommend splitting off the krealloc changes etc. into a separate
patch. It made it hard to review this patch with that change thrown in.
>
> Add lockdep assertions where locks are required.
>
> Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
> Signed-off-by: Sean Young <sean@mess.org>
> Cc: stable@vger.kernel.org
> ---
> drivers/media/rc/rc-main.c | 122 ++++++++++++++++++++++---------------
> include/media/rc-map.h | 2 -
> 2 files changed, 74 insertions(+), 50 deletions(-)
>
> diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
> index 88d8f7d4aab3..04204559959f 100644
> --- a/drivers/media/rc/rc-main.c
> +++ b/drivers/media/rc/rc-main.c
> @@ -17,9 +17,8 @@
> #include <linux/module.h>
> #include "rc-core-priv.h"
>
> -/* Sizes are in bytes, 256 bytes allows for 32 entries on x64 */
> -#define IR_TAB_MIN_SIZE 256
> -#define IR_TAB_MAX_SIZE 8192
> +#define IR_TAB_MIN_SIZE 32
> +#define IR_TAB_MAX_SIZE 1024
It doesn't hurt to keep a comment mentioning that these are the
min and max number of entries.
In general I found it a bit confusing that 'size' refers to number
of elements in variable names, when it is usually the number of
entries. Perhaps something to address in a future patch.
Regards,
Hans
>
> static const struct {
> const char *name;
> @@ -105,7 +104,6 @@ static struct rc_map_list *seek_rc_map(const char *name)
>
> struct rc_map *rc_map_get(const char *name)
> {
> -
> struct rc_map_list *map;
>
> map = seek_rc_map(name);
> @@ -202,7 +200,7 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode)
> * ir_create_table() - initializes a scancode table
> * @dev: the rc_dev device
> * @rc_map: the rc_map to initialize
> - * @name: name to assign to the table
> + * @map_name: name to assign to the table
> * @rc_proto: ir type to assign to the new table
> * @size: initial size of the table
> *
> @@ -212,23 +210,33 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode)
> * return: zero on success or a negative error code
> */
> static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map,
> - const char *name, u64 rc_proto, size_t size)
> + const char *map_name, u64 rc_proto, size_t size)
> {
> - rc_map->name = kstrdup(name, GFP_KERNEL);
> - if (!rc_map->name)
> + struct rc_map_table *scan;
> + unsigned int alloc;
> + char *name;
> +
> + name = kstrdup(map_name, GFP_KERNEL);
> + if (!name)
> return -ENOMEM;
> - rc_map->rc_proto = rc_proto;
> - rc_map->alloc = roundup_pow_of_two(size * sizeof(struct rc_map_table));
> - rc_map->size = rc_map->alloc / sizeof(struct rc_map_table);
> - rc_map->scan = kmalloc(rc_map->alloc, GFP_KERNEL);
> - if (!rc_map->scan) {
> - kfree(rc_map->name);
> - rc_map->name = NULL;
> +
> + alloc = roundup_pow_of_two(size);
> + scan = kmalloc_objs(struct rc_map_table, alloc, GFP_KERNEL);
> + if (!scan) {
> + kfree(name);
> return -ENOMEM;
> }
>
> - dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%u bytes)\n",
> - rc_map->size, rc_map->alloc);
> + scoped_guard(spinlock_irqsave, &rc_map->lock) {
> + rc_map->name = name;
> + rc_map->scan = scan;
> + rc_map->rc_proto = rc_proto;
> + rc_map->len = 0;
> + rc_map->size = alloc;
> + }
> +
> + dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%zu bytes)\n",
> + alloc, alloc * sizeof(struct rc_map_table));
> return 0;
> }
>
> @@ -236,16 +244,26 @@ static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map,
> * ir_free_table() - frees memory allocated by a scancode table
> * @rc_map: the table whose mappings need to be freed
> *
> - * This routine will free memory alloctaed for key mappings used by given
> + * This routine will free memory allocated for key mappings used by given
> * scancode table.
> */
> static void ir_free_table(struct rc_map *rc_map)
> {
> - rc_map->size = 0;
> - kfree(rc_map->name);
> - rc_map->name = NULL;
> - kfree(rc_map->scan);
> - rc_map->scan = NULL;
> + struct rc_map_table *scan;
> + const char *name;
> +
> + scoped_guard(spinlock_irqsave, &rc_map->lock) {
> + name = rc_map->name;
> + scan = rc_map->scan;
> +
> + rc_map->size = 0;
> + rc_map->len = 0;
> + rc_map->name = NULL;
> + rc_map->scan = NULL;
> + }
> +
> + kfree(name);
> + kfree(scan);
> }
>
> /**
> @@ -262,38 +280,38 @@ static void ir_free_table(struct rc_map *rc_map)
> static int ir_resize_table(struct rc_dev *dev, struct rc_map *rc_map,
> gfp_t gfp_flags)
> {
> - unsigned int oldalloc = rc_map->alloc;
> - unsigned int newalloc = oldalloc;
> - struct rc_map_table *oldscan = rc_map->scan;
> + unsigned int newsize = rc_map->size;
> struct rc_map_table *newscan;
>
> + lockdep_assert_held(&rc_map->lock);
> +
> if (rc_map->size == rc_map->len) {
> /* All entries in use -> grow keytable */
> - if (rc_map->alloc >= IR_TAB_MAX_SIZE)
> + if (newsize >= IR_TAB_MAX_SIZE)
> return -ENOMEM;
>
> - newalloc *= 2;
> - dev_dbg(&dev->dev, "Growing table to %u bytes\n", newalloc);
> + newsize *= 2;
> +
> + dev_dbg(&dev->dev, "Growing table to %u entries\n", newsize);
> }
>
> - if ((rc_map->len * 3 < rc_map->size) && (oldalloc > IR_TAB_MIN_SIZE)) {
> + if (rc_map->len * 3 < rc_map->size && rc_map->size > IR_TAB_MIN_SIZE) {
> /* Less than 1/3 of entries in use -> shrink keytable */
> - newalloc /= 2;
> - dev_dbg(&dev->dev, "Shrinking table to %u bytes\n", newalloc);
> + newsize /= 2;
> + dev_dbg(&dev->dev, "Shrinking table to %u entries\n", newsize);
> }
>
> - if (newalloc == oldalloc)
> + if (newsize == rc_map->size)
> return 0;
>
> - newscan = kmalloc(newalloc, gfp_flags);
> + newscan = krealloc_array(rc_map->scan, newsize,
> + sizeof(struct rc_map_table), gfp_flags);
> if (!newscan)
> return -ENOMEM;
>
> - memcpy(newscan, rc_map->scan, rc_map->len * sizeof(struct rc_map_table));
> rc_map->scan = newscan;
> - rc_map->alloc = newalloc;
> - rc_map->size = rc_map->alloc / sizeof(struct rc_map_table);
> - kfree(oldscan);
> + rc_map->size = newsize;
> +
> return 0;
> }
>
> @@ -318,6 +336,8 @@ static unsigned int ir_update_mapping(struct rc_dev *dev,
> int old_keycode = rc_map->scan[index].keycode;
> int i;
>
> + lockdep_assert_held(&rc_map->lock);
> +
> /* Did the user wish to remove the mapping? */
> if (new_keycode == KEY_RESERVED || new_keycode == KEY_UNKNOWN) {
> dev_dbg(&dev->dev, "#%d: Deleting scan 0x%04llx\n",
> @@ -373,6 +393,8 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> {
> unsigned int i;
>
> + lockdep_assert_held(&rc_map->lock);
> +
> /*
> * Unfortunately, some hardware-based IR decoders don't provide
> * all bits for the complete IR code. In general, they provide only
> @@ -397,7 +419,7 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> /* No previous mapping found, we might need to grow the table */
> if (rc_map->size == rc_map->len) {
> if (!resize || ir_resize_table(dev, rc_map, GFP_ATOMIC))
> - return -1U;
> + return UINT_MAX;
> }
>
> /* i is the proper index to insert our new keycode */
> @@ -479,16 +501,18 @@ static int ir_setkeytable(struct rc_dev *dev, const struct rc_map *from)
> if (rc)
> return rc;
>
> - for (i = 0; i < from->size; i++) {
> - index = ir_establish_scancode(dev, rc_map,
> - from->scan[i].scancode, false);
> - if (index >= rc_map->len) {
> - rc = -ENOMEM;
> - break;
> - }
> + scoped_guard(spinlock_irqsave, &rc_map->lock) {
> + for (i = 0; i < from->size; i++) {
> + index = ir_establish_scancode(dev, rc_map,
> + from->scan[i].scancode, false);
> + if (index >= rc_map->len) {
> + rc = -ENOMEM;
> + break;
> + }
>
> - ir_update_mapping(dev, rc_map, index,
> - from->scan[i].keycode);
> + ir_update_mapping(dev, rc_map, index,
> + from->scan[i].keycode);
> + }
> }
>
> if (rc)
> @@ -524,6 +548,8 @@ static unsigned int ir_lookup_by_scancode(const struct rc_map *rc_map,
> {
> struct rc_map_table *res;
>
> + lockdep_assert_held(&rc_map->lock);
> +
> res = bsearch(&scancode, rc_map->scan, rc_map->len,
> sizeof(struct rc_map_table), rc_map_cmp);
> if (!res)
> diff --git a/include/media/rc-map.h b/include/media/rc-map.h
> index d95ed3e96de2..f167c37179c8 100644
> --- a/include/media/rc-map.h
> +++ b/include/media/rc-map.h
> @@ -148,7 +148,6 @@ struct rc_map_table {
> * @scan: pointer to struct &rc_map_table
> * @size: Max number of entries
> * @len: Number of entries that are in use
> - * @alloc: size of \*scan, in bytes
> * @rc_proto: type of the remote controller protocol, as defined at
> * enum &rc_proto
> * @name: name of the key map table
> @@ -158,7 +157,6 @@ struct rc_map {
> struct rc_map_table *scan;
> unsigned int size;
> unsigned int len;
> - unsigned int alloc;
> enum rc_proto rc_proto;
> const char *name;
> spinlock_t lock;
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH v4 04/19] media: rc: Add missing locking for keymap
2026-09-11 8:08 ` Hans Verkuil
@ 2026-09-11 8:47 ` Sean Young
0 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-11 8:47 UTC (permalink / raw)
To: Hans Verkuil
Cc: linux-media, Mauro Carvalho Chehab, Patrice Chotard,
Rik van Riel, stable, linux-kernel
On Fri, Sep 11, 2026 at 10:08:03AM +0200, Hans Verkuil wrote:
> On 08/09/2026 17:51, Sean Young wrote:
> > When the rc_map for an rc_dev gets updated, locking is required but is
> > missing in places. A concurrent scancode lookup and a call to
> > rc_{register,unregistered}_device() could result in a use-after-free;
> > this could happen if IR is decoded during those function calls.
> >
> > We also fix some ugliness like open-coded krealloc() and removing the
> > pointless alloc member of rc_map.
>
> I would recommend splitting off the krealloc changes etc. into a separate
> patch. It made it hard to review this patch with that change thrown in.
You're right, this commit has become to big and needs splitting.
> > Add lockdep assertions where locks are required.
> >
> > Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
> > Signed-off-by: Sean Young <sean@mess.org>
> > Cc: stable@vger.kernel.org
> > ---
> > drivers/media/rc/rc-main.c | 122 ++++++++++++++++++++++---------------
> > include/media/rc-map.h | 2 -
> > 2 files changed, 74 insertions(+), 50 deletions(-)
> >
> > diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
> > index 88d8f7d4aab3..04204559959f 100644
> > --- a/drivers/media/rc/rc-main.c
> > +++ b/drivers/media/rc/rc-main.c
> > @@ -17,9 +17,8 @@
> > #include <linux/module.h>
> > #include "rc-core-priv.h"
> >
> > -/* Sizes are in bytes, 256 bytes allows for 32 entries on x64 */
> > -#define IR_TAB_MIN_SIZE 256
> > -#define IR_TAB_MAX_SIZE 8192
> > +#define IR_TAB_MIN_SIZE 32
> > +#define IR_TAB_MAX_SIZE 1024
>
> It doesn't hurt to keep a comment mentioning that these are the
> min and max number of entries.
>
> In general I found it a bit confusing that 'size' refers to number
> of elements in variable names, when it is usually the number of
> entries. Perhaps something to address in a future patch.
That is confusing. Let me re-write it.
Thanks,
Sean
>
> Regards,
>
> Hans
>
> >
> > static const struct {
> > const char *name;
> > @@ -105,7 +104,6 @@ static struct rc_map_list *seek_rc_map(const char *name)
> >
> > struct rc_map *rc_map_get(const char *name)
> > {
> > -
> > struct rc_map_list *map;
> >
> > map = seek_rc_map(name);
> > @@ -202,7 +200,7 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode)
> > * ir_create_table() - initializes a scancode table
> > * @dev: the rc_dev device
> > * @rc_map: the rc_map to initialize
> > - * @name: name to assign to the table
> > + * @map_name: name to assign to the table
> > * @rc_proto: ir type to assign to the new table
> > * @size: initial size of the table
> > *
> > @@ -212,23 +210,33 @@ static int scancode_to_u64(const struct input_keymap_entry *ke, u64 *scancode)
> > * return: zero on success or a negative error code
> > */
> > static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map,
> > - const char *name, u64 rc_proto, size_t size)
> > + const char *map_name, u64 rc_proto, size_t size)
> > {
> > - rc_map->name = kstrdup(name, GFP_KERNEL);
> > - if (!rc_map->name)
> > + struct rc_map_table *scan;
> > + unsigned int alloc;
> > + char *name;
> > +
> > + name = kstrdup(map_name, GFP_KERNEL);
> > + if (!name)
> > return -ENOMEM;
> > - rc_map->rc_proto = rc_proto;
> > - rc_map->alloc = roundup_pow_of_two(size * sizeof(struct rc_map_table));
> > - rc_map->size = rc_map->alloc / sizeof(struct rc_map_table);
> > - rc_map->scan = kmalloc(rc_map->alloc, GFP_KERNEL);
> > - if (!rc_map->scan) {
> > - kfree(rc_map->name);
> > - rc_map->name = NULL;
> > +
> > + alloc = roundup_pow_of_two(size);
> > + scan = kmalloc_objs(struct rc_map_table, alloc, GFP_KERNEL);
> > + if (!scan) {
> > + kfree(name);
> > return -ENOMEM;
> > }
> >
> > - dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%u bytes)\n",
> > - rc_map->size, rc_map->alloc);
> > + scoped_guard(spinlock_irqsave, &rc_map->lock) {
> > + rc_map->name = name;
> > + rc_map->scan = scan;
> > + rc_map->rc_proto = rc_proto;
> > + rc_map->len = 0;
> > + rc_map->size = alloc;
> > + }
> > +
> > + dev_dbg(&dev->dev, "Allocated space for %u keycode entries (%zu bytes)\n",
> > + alloc, alloc * sizeof(struct rc_map_table));
> > return 0;
> > }
> >
> > @@ -236,16 +244,26 @@ static int ir_create_table(struct rc_dev *dev, struct rc_map *rc_map,
> > * ir_free_table() - frees memory allocated by a scancode table
> > * @rc_map: the table whose mappings need to be freed
> > *
> > - * This routine will free memory alloctaed for key mappings used by given
> > + * This routine will free memory allocated for key mappings used by given
> > * scancode table.
> > */
> > static void ir_free_table(struct rc_map *rc_map)
> > {
> > - rc_map->size = 0;
> > - kfree(rc_map->name);
> > - rc_map->name = NULL;
> > - kfree(rc_map->scan);
> > - rc_map->scan = NULL;
> > + struct rc_map_table *scan;
> > + const char *name;
> > +
> > + scoped_guard(spinlock_irqsave, &rc_map->lock) {
> > + name = rc_map->name;
> > + scan = rc_map->scan;
> > +
> > + rc_map->size = 0;
> > + rc_map->len = 0;
> > + rc_map->name = NULL;
> > + rc_map->scan = NULL;
> > + }
> > +
> > + kfree(name);
> > + kfree(scan);
> > }
> >
> > /**
> > @@ -262,38 +280,38 @@ static void ir_free_table(struct rc_map *rc_map)
> > static int ir_resize_table(struct rc_dev *dev, struct rc_map *rc_map,
> > gfp_t gfp_flags)
> > {
> > - unsigned int oldalloc = rc_map->alloc;
> > - unsigned int newalloc = oldalloc;
> > - struct rc_map_table *oldscan = rc_map->scan;
> > + unsigned int newsize = rc_map->size;
> > struct rc_map_table *newscan;
> >
> > + lockdep_assert_held(&rc_map->lock);
> > +
> > if (rc_map->size == rc_map->len) {
> > /* All entries in use -> grow keytable */
> > - if (rc_map->alloc >= IR_TAB_MAX_SIZE)
> > + if (newsize >= IR_TAB_MAX_SIZE)
> > return -ENOMEM;
> >
> > - newalloc *= 2;
> > - dev_dbg(&dev->dev, "Growing table to %u bytes\n", newalloc);
> > + newsize *= 2;
> > +
> > + dev_dbg(&dev->dev, "Growing table to %u entries\n", newsize);
> > }
> >
> > - if ((rc_map->len * 3 < rc_map->size) && (oldalloc > IR_TAB_MIN_SIZE)) {
> > + if (rc_map->len * 3 < rc_map->size && rc_map->size > IR_TAB_MIN_SIZE) {
> > /* Less than 1/3 of entries in use -> shrink keytable */
> > - newalloc /= 2;
> > - dev_dbg(&dev->dev, "Shrinking table to %u bytes\n", newalloc);
> > + newsize /= 2;
> > + dev_dbg(&dev->dev, "Shrinking table to %u entries\n", newsize);
> > }
> >
> > - if (newalloc == oldalloc)
> > + if (newsize == rc_map->size)
> > return 0;
> >
> > - newscan = kmalloc(newalloc, gfp_flags);
> > + newscan = krealloc_array(rc_map->scan, newsize,
> > + sizeof(struct rc_map_table), gfp_flags);
> > if (!newscan)
> > return -ENOMEM;
> >
> > - memcpy(newscan, rc_map->scan, rc_map->len * sizeof(struct rc_map_table));
> > rc_map->scan = newscan;
> > - rc_map->alloc = newalloc;
> > - rc_map->size = rc_map->alloc / sizeof(struct rc_map_table);
> > - kfree(oldscan);
> > + rc_map->size = newsize;
> > +
> > return 0;
> > }
> >
> > @@ -318,6 +336,8 @@ static unsigned int ir_update_mapping(struct rc_dev *dev,
> > int old_keycode = rc_map->scan[index].keycode;
> > int i;
> >
> > + lockdep_assert_held(&rc_map->lock);
> > +
> > /* Did the user wish to remove the mapping? */
> > if (new_keycode == KEY_RESERVED || new_keycode == KEY_UNKNOWN) {
> > dev_dbg(&dev->dev, "#%d: Deleting scan 0x%04llx\n",
> > @@ -373,6 +393,8 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> > {
> > unsigned int i;
> >
> > + lockdep_assert_held(&rc_map->lock);
> > +
> > /*
> > * Unfortunately, some hardware-based IR decoders don't provide
> > * all bits for the complete IR code. In general, they provide only
> > @@ -397,7 +419,7 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> > /* No previous mapping found, we might need to grow the table */
> > if (rc_map->size == rc_map->len) {
> > if (!resize || ir_resize_table(dev, rc_map, GFP_ATOMIC))
> > - return -1U;
> > + return UINT_MAX;
> > }
> >
> > /* i is the proper index to insert our new keycode */
> > @@ -479,16 +501,18 @@ static int ir_setkeytable(struct rc_dev *dev, const struct rc_map *from)
> > if (rc)
> > return rc;
> >
> > - for (i = 0; i < from->size; i++) {
> > - index = ir_establish_scancode(dev, rc_map,
> > - from->scan[i].scancode, false);
> > - if (index >= rc_map->len) {
> > - rc = -ENOMEM;
> > - break;
> > - }
> > + scoped_guard(spinlock_irqsave, &rc_map->lock) {
> > + for (i = 0; i < from->size; i++) {
> > + index = ir_establish_scancode(dev, rc_map,
> > + from->scan[i].scancode, false);
> > + if (index >= rc_map->len) {
> > + rc = -ENOMEM;
> > + break;
> > + }
> >
> > - ir_update_mapping(dev, rc_map, index,
> > - from->scan[i].keycode);
> > + ir_update_mapping(dev, rc_map, index,
> > + from->scan[i].keycode);
> > + }
> > }
> >
> > if (rc)
> > @@ -524,6 +548,8 @@ static unsigned int ir_lookup_by_scancode(const struct rc_map *rc_map,
> > {
> > struct rc_map_table *res;
> >
> > + lockdep_assert_held(&rc_map->lock);
> > +
> > res = bsearch(&scancode, rc_map->scan, rc_map->len,
> > sizeof(struct rc_map_table), rc_map_cmp);
> > if (!res)
> > diff --git a/include/media/rc-map.h b/include/media/rc-map.h
> > index d95ed3e96de2..f167c37179c8 100644
> > --- a/include/media/rc-map.h
> > +++ b/include/media/rc-map.h
> > @@ -148,7 +148,6 @@ struct rc_map_table {
> > * @scan: pointer to struct &rc_map_table
> > * @size: Max number of entries
> > * @len: Number of entries that are in use
> > - * @alloc: size of \*scan, in bytes
> > * @rc_proto: type of the remote controller protocol, as defined at
> > * enum &rc_proto
> > * @name: name of the key map table
> > @@ -158,7 +157,6 @@ struct rc_map {
> > struct rc_map_table *scan;
> > unsigned int size;
> > unsigned int len;
> > - unsigned int alloc;
> > enum rc_proto rc_proto;
> > const char *name;
> > spinlock_t lock;
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v4 05/19] media: rc: Fix race between bpf(BPG_PROG_ATTACH) and device unregister
[not found] <cover.1788882189.git.sean@mess.org>
` (3 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 04/19] media: rc: Add missing locking for keymap Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:51 ` [PATCH v4 06/19] media: rc: mce_kbd: Fix inconsistent locking of keylock Sean Young
` (13 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab, Hans Verkuil,
Patrice Chotard
Cc: Rik van Riel, stable, linux-kernel, bpf
Mark the device as unregistered before tearing down raw IR/bpf
state, so that racing bpf(BPF_PROG_{ATTACH,DETACH,QUERY}) calls
cannot slip in between ir_raw_event_unregister() completing and
dev->registered being cleared.
Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/rc-main.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
index 04204559959f..924b13fff753 100644
--- a/drivers/media/rc/rc-main.c
+++ b/drivers/media/rc/rc-main.c
@@ -2050,18 +2050,18 @@ void rc_unregister_device(struct rc_dev *dev)
if (!dev)
return;
+ mutex_lock(&dev->lock);
+ dev->registered = false;
+ if (dev->users && dev->close)
+ dev->close(dev);
+ mutex_unlock(&dev->lock);
+
if (dev->driver_type == RC_DRIVER_IR_RAW)
ir_raw_event_unregister(dev);
timer_delete_sync(&dev->timer_keyup);
timer_delete_sync(&dev->timer_repeat);
- mutex_lock(&dev->lock);
- if (dev->users && dev->close)
- dev->close(dev);
- dev->registered = false;
- mutex_unlock(&dev->lock);
-
rc_free_rx_device(dev);
/*
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 06/19] media: rc: mce_kbd: Fix inconsistent locking of keylock
[not found] <cover.1788882189.git.sean@mess.org>
` (4 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 05/19] media: rc: Fix race between bpf(BPG_PROG_ATTACH) and device unregister Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:51 ` [PATCH v4 07/19] media: ene_ir: Ensure teardown is done in the correct order Sean Young
` (12 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab
Cc: Rik van Riel, stable, linux-kernel
data->keylock is taken with spin_lock() in ir_mce_kbd_decode(), which
runs in the raw IR decode kthread with local interrupts enabled, but
with spin_lock_irqsave() in mce_kbd_rx_timeout(), the rx_timeout timer
callback, which runs in softirq context.
If the decode kthread is holding keylock via spin_lock() when a timer
interrupt fires on the same CPU and the softirq runs
mce_kbd_rx_timeout(), the softirq spins forever waiting for a lock
that only the now-preempted kthread can release, while the kthread
cannot run again until the softirq gives up the CPU. This deadlocks
the CPU.
Fixes: 53a62800efb2 ("media: rc: mce_kbd decoder: fix race condition")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/ir-mce_kbd-decoder.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/media/rc/ir-mce_kbd-decoder.c b/drivers/media/rc/ir-mce_kbd-decoder.c
index bb2d7c37c263..d1c9263511b0 100644
--- a/drivers/media/rc/ir-mce_kbd-decoder.c
+++ b/drivers/media/rc/ir-mce_kbd-decoder.c
@@ -219,6 +219,7 @@ static int ir_mce_kbd_decode(struct rc_dev *dev, struct ir_raw_event ev)
struct mce_kbd_dec *data = &dev->raw->mce_kbd;
u32 scancode;
unsigned long delay;
+ unsigned long flags;
struct lirc_scancode lsc = {};
if (!is_timing_event(ev)) {
@@ -319,7 +320,7 @@ static int ir_mce_kbd_decode(struct rc_dev *dev, struct ir_raw_event ev)
scancode = data->body & 0xffffff;
dev_dbg(&dev->dev, "keyboard data 0x%08x\n",
data->body);
- spin_lock(&data->keylock);
+ spin_lock_irqsave(&data->keylock, flags);
if (scancode) {
delay = usecs_to_jiffies(dev->timeout) +
msecs_to_jiffies(100);
@@ -329,7 +330,7 @@ static int ir_mce_kbd_decode(struct rc_dev *dev, struct ir_raw_event ev)
}
/* Pass data to keyboard buffer parser */
ir_mce_kbd_process_keyboard_data(dev, scancode);
- spin_unlock(&data->keylock);
+ spin_unlock_irqrestore(&data->keylock, flags);
lsc.rc_proto = RC_PROTO_MCIR2_KBD;
break;
case MCIR2_MOUSE_NBITS:
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 07/19] media: ene_ir: Ensure teardown is done in the correct order
[not found] <cover.1788882189.git.sean@mess.org>
` (5 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 06/19] media: rc: mce_kbd: Fix inconsistent locking of keylock Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:51 ` [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode Sean Young
` (11 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Maxim Levitsky, Sean Young, Mauro Carvalho Chehab,
Patrice Chotard, Hans Verkuil
Cc: Rik van Riel, stable, linux-kernel
The timer can be re-armed from the irq, so ensure that the timers are
deleted after the irq and that we only free the rc device once we are
sure that the irq has been silenced.
Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
Cc: stable@vger.kernel.org
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/ene_ir.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/media/rc/ene_ir.c b/drivers/media/rc/ene_ir.c
index 6f7dccc965e7..f98d76277b62 100644
--- a/drivers/media/rc/ene_ir.c
+++ b/drivers/media/rc/ene_ir.c
@@ -1103,15 +1103,15 @@ static void ene_remove(struct pnp_dev *pnp_dev)
unsigned long flags;
rc_unregister_device(dev->rdev);
- timer_delete_sync(&dev->tx_sim_timer);
spin_lock_irqsave(&dev->hw_lock, flags);
ene_rx_disable(dev);
ene_rx_restore_hw_buffer(dev);
spin_unlock_irqrestore(&dev->hw_lock, flags);
- rc_free_device(dev->rdev);
free_irq(dev->irq, dev);
+ timer_delete_sync(&dev->tx_sim_timer);
release_region(dev->hw_io, ENE_IO_SIZE);
+ rc_free_device(dev->rdev);
kfree(dev);
}
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode
[not found] <cover.1788882189.git.sean@mess.org>
` (6 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 07/19] media: ene_ir: Ensure teardown is done in the correct order Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-11 8:10 ` Hans Verkuil
2026-09-08 15:51 ` [PATCH v4 09/19] media: rc: imon: Bind both interfaces via usb_driver_claim_interface() Sean Young
` (10 subsequent siblings)
18 siblings, 1 reply; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
We can have up to 1024 scancodes entries which are always sorted, so make
this a little faster.
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/rc-main.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
index 924b13fff753..795ac0fe4858 100644
--- a/drivers/media/rc/rc-main.c
+++ b/drivers/media/rc/rc-main.c
@@ -391,7 +391,7 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
struct rc_map *rc_map,
u64 scancode, bool resize)
{
- unsigned int i;
+ unsigned int i, lo, hi;
lockdep_assert_held(&rc_map->lock);
@@ -406,15 +406,21 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
if (dev->scancode_mask)
scancode &= dev->scancode_mask;
- /* First check if we already have a mapping for this ir command */
- for (i = 0; i < rc_map->len; i++) {
+ /*
+ * Binary search for an existing mapping for this ir command.
+ */
+ lo = 0;
+ hi = rc_map->len;
+ while (lo < hi) {
+ i = lo + (hi - lo) / 2;
if (rc_map->scan[i].scancode == scancode)
return i;
-
- /* Keytable is sorted from lowest to highest scancode */
- if (rc_map->scan[i].scancode >= scancode)
- break;
+ if (rc_map->scan[i].scancode < scancode)
+ lo = i + 1;
+ else
+ hi = i;
}
+ i = lo;
/* No previous mapping found, we might need to grow the table */
if (rc_map->size == rc_map->len) {
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode
2026-09-08 15:51 ` [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode Sean Young
@ 2026-09-11 8:10 ` Hans Verkuil
2026-09-11 8:42 ` Sean Young
0 siblings, 1 reply; 25+ messages in thread
From: Hans Verkuil @ 2026-09-11 8:10 UTC (permalink / raw)
To: Sean Young, linux-media, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
On 08/09/2026 17:51, Sean Young wrote:
> We can have up to 1024 scancodes entries which are always sorted, so make
> this a little faster.
>
> Signed-off-by: Sean Young <sean@mess.org>
> ---
> drivers/media/rc/rc-main.c | 20 +++++++++++++-------
> 1 file changed, 13 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
> index 924b13fff753..795ac0fe4858 100644
> --- a/drivers/media/rc/rc-main.c
> +++ b/drivers/media/rc/rc-main.c
> @@ -391,7 +391,7 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> struct rc_map *rc_map,
> u64 scancode, bool resize)
> {
> - unsigned int i;
> + unsigned int i, lo, hi;
>
> lockdep_assert_held(&rc_map->lock);
>
> @@ -406,15 +406,21 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> if (dev->scancode_mask)
> scancode &= dev->scancode_mask;
>
> - /* First check if we already have a mapping for this ir command */
> - for (i = 0; i < rc_map->len; i++) {
> + /*
> + * Binary search for an existing mapping for this ir command.
> + */
> + lo = 0;
> + hi = rc_map->len;
> + while (lo < hi) {
> + i = lo + (hi - lo) / 2;
> if (rc_map->scan[i].scancode == scancode)
> return i;
> -
> - /* Keytable is sorted from lowest to highest scancode */
> - if (rc_map->scan[i].scancode >= scancode)
> - break;
> + if (rc_map->scan[i].scancode < scancode)
> + lo = i + 1;
> + else
> + hi = i;
> }
> + i = lo;
Can you use bsearch() for this? (lib/bsearch.c)
Regards,
Hans
>
> /* No previous mapping found, we might need to grow the table */
> if (rc_map->size == rc_map->len) {
^ permalink raw reply [flat|nested] 25+ messages in thread* Re: [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode
2026-09-11 8:10 ` Hans Verkuil
@ 2026-09-11 8:42 ` Sean Young
0 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-11 8:42 UTC (permalink / raw)
To: Hans Verkuil
Cc: linux-media, Mauro Carvalho Chehab, Rik van Riel, linux-kernel
On Fri, Sep 11, 2026 at 10:10:52AM +0200, Hans Verkuil wrote:
> On 08/09/2026 17:51, Sean Young wrote:
> > We can have up to 1024 scancodes entries which are always sorted, so make
> > this a little faster.
> >
> > Signed-off-by: Sean Young <sean@mess.org>
> > ---
> > drivers/media/rc/rc-main.c | 20 +++++++++++++-------
> > 1 file changed, 13 insertions(+), 7 deletions(-)
> >
> > diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
> > index 924b13fff753..795ac0fe4858 100644
> > --- a/drivers/media/rc/rc-main.c
> > +++ b/drivers/media/rc/rc-main.c
> > @@ -391,7 +391,7 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> > struct rc_map *rc_map,
> > u64 scancode, bool resize)
> > {
> > - unsigned int i;
> > + unsigned int i, lo, hi;
> >
> > lockdep_assert_held(&rc_map->lock);
> >
> > @@ -406,15 +406,21 @@ static unsigned int ir_establish_scancode(struct rc_dev *dev,
> > if (dev->scancode_mask)
> > scancode &= dev->scancode_mask;
> >
> > - /* First check if we already have a mapping for this ir command */
> > - for (i = 0; i < rc_map->len; i++) {
> > + /*
> > + * Binary search for an existing mapping for this ir command.
> > + */
> > + lo = 0;
> > + hi = rc_map->len;
> > + while (lo < hi) {
> > + i = lo + (hi - lo) / 2;
> > if (rc_map->scan[i].scancode == scancode)
> > return i;
> > -
> > - /* Keytable is sorted from lowest to highest scancode */
> > - if (rc_map->scan[i].scancode >= scancode)
> > - break;
> > + if (rc_map->scan[i].scancode < scancode)
> > + lo = i + 1;
> > + else
> > + hi = i;
> > }
> > + i = lo;
>
> Can you use bsearch() for this? (lib/bsearch.c)
We want to insert an entry if it does not already exist. bsearch()
does not give us an index if there is no match.
Arguably this should be an extension of lib/bsearch.c but I'm not
clear what form that would take.
Thanks,
Sean
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v4 09/19] media: rc: imon: Bind both interfaces via usb_driver_claim_interface()
[not found] <cover.1788882189.git.sean@mess.org>
` (7 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:51 ` [PATCH v4 10/19] media: ir_toy: Remove unused struct field Sean Young
` (9 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
iMON devices expose two USB interfaces: interface 0 (IR, and
the display/knob on older devices) and interface 1 (touchscreen/
display on newer devices).
The driver currently pieces the interface together and this has to
deal with various race conditions. Restrict the id_table to interface 0
only (USB_DEVICE_INTERFACE_NUMBER) so imon_probe() is only ever entered
once per device and then we find the second interface with
usb_driver_claim_interface(). This makes the driver much simpler.
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/imon.c | 166 +++++++++++++++++-----------------------
1 file changed, 72 insertions(+), 94 deletions(-)
diff --git a/drivers/media/rc/imon.c b/drivers/media/rc/imon.c
index 621554005f2f..ef288cbe2b99 100644
--- a/drivers/media/rc/imon.c
+++ b/drivers/media/rc/imon.c
@@ -96,7 +96,6 @@ struct imon_context {
bool dev_present_intf1; /* USB device presence, interface 1 */
struct mutex lock; /* to lock this object */
- wait_queue_head_t remove_ok; /* For unexpected USB disconnects */
struct usb_endpoint_descriptor *rx_endpoint_intf0;
struct usb_endpoint_descriptor *rx_endpoint_intf1;
@@ -378,7 +377,7 @@ static const struct usb_device_id imon_usb_id_table[] = {
* SoundGraph iMON PAD (IR & LCD)
* SoundGraph iMON Knob (IR only)
*/
- { USB_DEVICE(0x15c2, 0xffdc),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0xffdc, 0),
.driver_info = (unsigned long)&imon_default_table },
/*
@@ -387,61 +386,61 @@ static const struct usb_device_id imon_usb_id_table[] = {
* Need user input to fill in details on unknown devices.
*/
/* SoundGraph iMON OEM Touch LCD (IR & 7" VGA LCD) */
- { USB_DEVICE(0x15c2, 0x0034),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0034, 0),
.driver_info = (unsigned long)&imon_DH102 },
/* SoundGraph iMON OEM Touch LCD (IR & 4.3" VGA LCD) */
- { USB_DEVICE(0x15c2, 0x0035),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0035, 0),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON OEM VFD (IR & VFD) */
- { USB_DEVICE(0x15c2, 0x0036),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0036, 0),
.driver_info = (unsigned long)&imon_OEM_VFD },
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x0037),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0037, 0),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON OEM LCD (IR & LCD) */
- { USB_DEVICE(0x15c2, 0x0038),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0038, 0),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON UltraBay (IR & LCD) */
- { USB_DEVICE(0x15c2, 0x0039),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0039, 0),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x003a),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003a, 0),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x003b),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003b, 0),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON OEM Inside (IR only) */
- { USB_DEVICE(0x15c2, 0x003c),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003c, 0),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x003d),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003d, 0),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x003e),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003e, 0),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x003f),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x003f, 0),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x0040),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0040, 0),
.driver_info = (unsigned long)&imon_default_table},
/* SoundGraph iMON MINI (IR only) */
- { USB_DEVICE(0x15c2, 0x0041),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0041, 0),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station EZ External (IR only) */
- { USB_DEVICE(0x15c2, 0x0042),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0042, 0),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station Basic Internal (IR only) */
- { USB_DEVICE(0x15c2, 0x0043),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0043, 0),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station Elite (IR & VFD) */
- { USB_DEVICE(0x15c2, 0x0044),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0044, 0),
.driver_info = (unsigned long)&imon_default_table},
/* Antec Veris Multimedia Station Premiere (IR & LCD) */
- { USB_DEVICE(0x15c2, 0x0045),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0045, 0),
.driver_info = (unsigned long)&imon_default_table},
/* device specifics unknown */
- { USB_DEVICE(0x15c2, 0x0046),
+ { USB_DEVICE_INTERFACE_NUMBER(0x15c2, 0x0046, 0),
.driver_info = (unsigned long)&imon_default_table},
{}
};
@@ -2334,9 +2333,8 @@ static struct imon_context *imon_init_intf1(struct usb_interface *intf,
mutex_lock(&ictx->lock);
- if (ictx->display_type == IMON_DISPLAY_TYPE_VGA) {
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA)
timer_setup(&ictx->ttimer, imon_touch_display_timeout, 0);
- }
ictx->usbdev_intf1 = interface_to_usbdev(intf);
ictx->rx_urb_intf1 = rx_urb;
@@ -2363,20 +2361,32 @@ static struct imon_context *imon_init_intf1(struct usb_interface *intf,
ret = usb_submit_urb(ictx->rx_urb_intf1, GFP_KERNEL);
if (ret) {
- pr_err("usb_submit_urb failed for intf1 (%d)\n", ret);
+ dev_err(ictx->dev, "usb_submit_urb failed for intf1 (%d)\n", ret);
goto urb_submit_failed;
}
+ ret = usb_driver_claim_interface(&imon_driver, intf, ictx);
+
+ if (ret) {
+ dev_err(ictx->dev, "failed to claim second interface (%d)\n", ret);
+ goto usb_claim_failed;
+ }
+
ictx->dev_present_intf1 = true;
mutex_unlock(&ictx->lock);
+
return ictx;
+usb_claim_failed:
+ usb_kill_urb(rx_urb);
urb_submit_failed:
if (ictx->touch)
input_unregister_device(ictx->touch);
touch_setup_failed:
find_endpoint_failed:
+ if (ictx->display_type == IMON_DISPLAY_TYPE_VGA)
+ timer_delete_sync(&ictx->ttimer);
ictx->usbdev_intf1 = NULL;
mutex_unlock(&ictx->lock);
usb_free_urb(rx_urb);
@@ -2416,90 +2426,50 @@ static void imon_init_display(struct imon_context *ictx,
static int imon_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
- struct usb_device *usbdev = NULL;
- struct usb_host_interface *iface_desc = NULL;
- struct usb_interface *first_if;
+ struct usb_device *usbdev;
+ struct usb_interface *second_if;
struct device *dev = &interface->dev;
- int ifnum, sysfs_err;
- int ret = 0;
- struct imon_context *ictx = NULL;
+ int sysfs_err;
+ struct imon_context *ictx;
u16 vendor, product;
- usbdev = interface_to_usbdev(interface);
- iface_desc = interface->cur_altsetting;
- ifnum = iface_desc->desc.bInterfaceNumber;
- vendor = le16_to_cpu(usbdev->descriptor.idVendor);
- product = le16_to_cpu(usbdev->descriptor.idProduct);
-
- dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n",
- __func__, vendor, product, ifnum);
+ if (interface->cur_altsetting->desc.bInterfaceNumber != 0)
+ return -ENODEV;
- first_if = usb_ifnum_to_if(usbdev, 0);
- if (!first_if) {
- ret = -ENODEV;
- goto fail;
- }
+ usbdev = interface_to_usbdev(interface);
+ vendor = le16_to_cpu(usbdev->descriptor.idVendor);
+ product = le16_to_cpu(usbdev->descriptor.idProduct);
- if (first_if->dev.driver != interface->dev.driver) {
- dev_err(&interface->dev, "inconsistent driver matching\n");
- ret = -EINVAL;
- goto fail;
- }
+ dev_dbg(dev, "found iMON device (%04x:%04x)\n", vendor, product);
- if (ifnum == 0) {
- ictx = imon_init_intf0(interface, id);
- if (!ictx) {
- pr_err("failed to initialize context!\n");
- ret = -ENODEV;
- goto fail;
- }
- refcount_set(&ictx->users, 1);
+ ictx = imon_init_intf0(interface, id);
+ if (!ictx)
+ return -ENODEV;
- } else {
- /* this is the secondary interface on the device */
- struct imon_context *first_if_ctx = usb_get_intfdata(first_if);
+ refcount_set(&ictx->users, 1);
- /* fail early if first intf failed to register */
- if (!first_if_ctx) {
- ret = -ENODEV;
- goto fail;
- }
+ usb_set_intfdata(interface, ictx);
- ictx = imon_init_intf1(interface, first_if_ctx);
- if (!ictx) {
- pr_err("failed to attach to context!\n");
- ret = -ENODEV;
- goto fail;
- }
+ /* Newer devices export a second interface for display/touchscreen */
+ second_if = usb_ifnum_to_if(usbdev, 1);
+ if (second_if && imon_init_intf1(second_if, ictx))
refcount_inc(&ictx->users);
+ if (product == 0xffdc && ictx->rf_device) {
+ sysfs_err = sysfs_create_group(&interface->dev.kobj,
+ &imon_rf_attr_group);
+ if (sysfs_err)
+ pr_err("Could not create RF sysfs entries(%d)\n",
+ sysfs_err);
}
- usb_set_intfdata(interface, ictx);
-
- if (ifnum == 0) {
- if (product == 0xffdc && ictx->rf_device) {
- sysfs_err = sysfs_create_group(&interface->dev.kobj,
- &imon_rf_attr_group);
- if (sysfs_err)
- pr_err("Could not create RF sysfs entries(%d)\n",
- sysfs_err);
- }
-
- if (ictx->display_supported)
- imon_init_display(ictx, interface);
- }
+ if (ictx->display_supported)
+ imon_init_display(ictx, interface);
- dev_info(dev, "iMON device (%04x:%04x, intf%d) on usb<%d:%d> initialized\n",
- vendor, product, ifnum,
- usbdev->bus->busnum, usbdev->devnum);
+ dev_info(dev, "iMON device (%04x:%04x) on usb<%d:%d> initialized\n",
+ vendor, product, usbdev->bus->busnum, usbdev->devnum);
return 0;
-
-fail:
- dev_err(dev, "unable to register, err %d\n", ret);
-
- return ret;
}
/*
@@ -2507,11 +2477,15 @@ static int imon_probe(struct usb_interface *interface,
*/
static void imon_disconnect(struct usb_interface *interface)
{
+ struct usb_device *usbdev = interface_to_usbdev(interface);
+ struct usb_interface *other_if;
struct imon_context *ictx;
struct device *dev;
int ifnum;
ictx = usb_get_intfdata(interface);
+ if (!ictx)
+ return;
mutex_lock(&ictx->lock);
ictx->disconnected = true;
@@ -2547,6 +2521,7 @@ static void imon_disconnect(struct usb_interface *interface)
else if (ictx->display_type == IMON_DISPLAY_TYPE_VFD)
usb_deregister_dev(interface, &imon_vfd_class);
}
+ other_if = usb_ifnum_to_if(usbdev, 1);
} else {
ictx->dev_present_intf1 = false;
usb_kill_urb(ictx->rx_urb_intf1);
@@ -2554,13 +2529,16 @@ static void imon_disconnect(struct usb_interface *interface)
timer_delete_sync(&ictx->ttimer);
input_unregister_device(ictx->touch);
}
+ other_if = usb_ifnum_to_if(usbdev, 0);
}
+ if (other_if)
+ usb_driver_release_interface(&imon_driver, other_if);
+
if (refcount_dec_and_test(&ictx->users))
free_imon_context(ictx);
- dev_dbg(dev, "%s: iMON device (intf%d) disconnected\n",
- __func__, ifnum);
+ dev_dbg(dev, "iMON device (intf%d) disconnected\n", ifnum);
}
static int imon_suspend(struct usb_interface *intf, pm_message_t message)
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 10/19] media: ir_toy: Remove unused struct field
[not found] <cover.1788882189.git.sean@mess.org>
` (8 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 09/19] media: rc: imon: Bind both interfaces via usb_driver_claim_interface() Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:51 ` [PATCH v4 11/19] media: nuvoton-cir: " Sean Young
` (8 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
Never assigned or read.
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/ir_toy.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/media/rc/ir_toy.c b/drivers/media/rc/ir_toy.c
index ee645882dd5a..286c8543aa5a 100644
--- a/drivers/media/rc/ir_toy.c
+++ b/drivers/media/rc/ir_toy.c
@@ -55,7 +55,6 @@ enum state {
struct irtoy {
struct device *dev;
- struct usb_device *usbdev;
struct rc_dev *rc;
struct urb *urb_in, *urb_out;
@@ -441,7 +440,6 @@ static int irtoy_probe(struct usb_interface *intf,
irtoy_out_callback, irtoy);
irtoy->dev = &intf->dev;
- irtoy->usbdev = usbdev;
irtoy->rc = rc;
irtoy->urb_out = urb;
irtoy->pulse = true;
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 11/19] media: nuvoton-cir: Remove unused struct field
[not found] <cover.1788882189.git.sean@mess.org>
` (9 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 10/19] media: ir_toy: Remove unused struct field Sean Young
@ 2026-09-08 15:51 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 12/19] media: ite-cir: Removed " Sean Young
` (7 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:51 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
Never assigned or read.
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/nuvoton-cir.h | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/media/rc/nuvoton-cir.h b/drivers/media/rc/nuvoton-cir.h
index ed7d93beaa28..e66e3cfedeec 100644
--- a/drivers/media/rc/nuvoton-cir.h
+++ b/drivers/media/rc/nuvoton-cir.h
@@ -77,9 +77,6 @@ struct nvt_dev {
/* hardware id */
u8 chip_major;
u8 chip_minor;
-
- /* carrier period = 1 / frequency */
- u32 carrier;
};
/* buffer packet constants */
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 12/19] media: ite-cir: Removed unused struct field
[not found] <cover.1788882189.git.sean@mess.org>
` (10 preceding siblings ...)
2026-09-08 15:51 ` [PATCH v4 11/19] media: nuvoton-cir: " Sean Young
@ 2026-09-08 15:52 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 13/19] media: fintek-cir: Remove unused fields Sean Young
` (6 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
Never assigned or read.
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/ite-cir.c | 1 -
drivers/media/rc/ite-cir.h | 1 -
2 files changed, 2 deletions(-)
diff --git a/drivers/media/rc/ite-cir.c b/drivers/media/rc/ite-cir.c
index 1fbafcd8219e..d0e98095d1b6 100644
--- a/drivers/media/rc/ite-cir.c
+++ b/drivers/media/rc/ite-cir.c
@@ -1359,7 +1359,6 @@ static int ite_probe(struct pnp_dev *pdev, const struct pnp_device_id
/* set driver data into the pnp device */
pnp_set_drvdata(pdev, itdev);
- itdev->pdev = pdev;
/* initialize waitqueues for transmission */
init_waitqueue_head(&itdev->tx_queue);
diff --git a/drivers/media/rc/ite-cir.h b/drivers/media/rc/ite-cir.h
index 4b4294d77555..a8382aa9dc35 100644
--- a/drivers/media/rc/ite-cir.h
+++ b/drivers/media/rc/ite-cir.h
@@ -76,7 +76,6 @@ struct ite_dev_params {
/* ITE CIR device structure */
struct ite_dev {
- struct pnp_dev *pdev;
struct rc_dev *rdev;
/* sync data */
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 13/19] media: fintek-cir: Remove unused fields
[not found] <cover.1788882189.git.sean@mess.org>
` (11 preceding siblings ...)
2026-09-08 15:52 ` [PATCH v4 12/19] media: ite-cir: Removed " Sean Young
@ 2026-09-08 15:52 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 14/19] media: mceusb: Remove unused field Sean Young
` (5 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
Some of these fields are vestigal from transmit, which never worked.
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/fintek-cir.c | 13 -------------
drivers/media/rc/fintek-cir.h | 22 ----------------------
2 files changed, 35 deletions(-)
diff --git a/drivers/media/rc/fintek-cir.c b/drivers/media/rc/fintek-cir.c
index c196ee923ecd..d4368181fef6 100644
--- a/drivers/media/rc/fintek-cir.c
+++ b/drivers/media/rc/fintek-cir.c
@@ -140,17 +140,6 @@ static int fintek_hw_detect(struct fintek_dev *fintek)
ir_class = fintek_cir_reg_read(fintek, CIR_CR_CLASS);
fit_dbg("ir_class reg: 0x%02x", ir_class);
- switch (ir_class) {
- case CLASS_RX_2TX:
- case CLASS_RX_1TX:
- fintek->hw_tx_capable = true;
- break;
- case CLASS_RX_ONLY:
- default:
- fintek->hw_tx_capable = false;
- break;
- }
-
chip_major = fintek_cr_read(fintek, GCR_CHIP_ID_HI);
chip_minor = fintek_cr_read(fintek, GCR_CHIP_ID_LO);
chip = chip_major << 8 | chip_minor;
@@ -169,7 +158,6 @@ static int fintek_hw_detect(struct fintek_dev *fintek)
spin_lock_irqsave(&fintek->fintek_lock, flags);
fintek->chip_major = chip_major;
fintek->chip_minor = chip_minor;
- fintek->chip_vendor = vendor;
/*
* Newer reviews of this chipset uses port 8 instead of 5
@@ -496,7 +484,6 @@ static int fintek_probe(struct pnp_dev *pdev, const struct pnp_device_id *dev_id
spin_lock_init(&fintek->fintek_lock);
pnp_set_drvdata(pdev, fintek);
- fintek->pdev = pdev;
ret = fintek_hw_detect(fintek);
if (ret)
diff --git a/drivers/media/rc/fintek-cir.h b/drivers/media/rc/fintek-cir.h
index 20696359e9ba..9ade9d7e7bfb 100644
--- a/drivers/media/rc/fintek-cir.h
+++ b/drivers/media/rc/fintek-cir.h
@@ -40,11 +40,9 @@ static int debug;
KBUILD_MODNAME ": " text "\n" , ## __VA_ARGS__)
-#define TX_BUF_LEN 256
#define RX_BUF_LEN 32
struct fintek_dev {
- struct pnp_dev *pdev;
struct rc_dev *rdev;
spinlock_t fintek_lock;
@@ -53,14 +51,6 @@ struct fintek_dev {
u8 buf[RX_BUF_LEN];
unsigned int pkts;
- struct {
- spinlock_t lock;
- u8 buf[TX_BUF_LEN];
- unsigned int buf_count;
- unsigned int cur_buf_num;
- wait_queue_head_t queue;
- } tx;
-
/* Config register index/data port pair */
u32 cr_ip;
u32 cr_dp;
@@ -73,17 +63,8 @@ struct fintek_dev {
/* hardware id */
u8 chip_major;
u8 chip_minor;
- u16 chip_vendor;
u8 logical_dev_cir;
- /* hardware features */
- bool hw_learning_capable;
- bool hw_tx_capable;
-
- /* rx settings */
- bool learning_enabled;
- bool carrier_detect_enabled;
-
enum {
CMD_HEADER = 0,
SUBCMD,
@@ -92,9 +73,6 @@ struct fintek_dev {
} parser_state;
u8 cmd, rem;
-
- /* carrier period = 1 / frequency */
- u32 carrier;
};
/* buffer packet constants, largely identical to mceusb.c */
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 14/19] media: mceusb: Remove unused field
[not found] <cover.1788882189.git.sean@mess.org>
` (12 preceding siblings ...)
2026-09-08 15:52 ` [PATCH v4 13/19] media: fintek-cir: Remove unused fields Sean Young
@ 2026-09-08 15:52 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 15/19] media: serial_ir: Fix race condition where timer can be re-armed Sean Young
` (4 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
Never assigned or read.
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/mceusb.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/media/rc/mceusb.c b/drivers/media/rc/mceusb.c
index 39ba7f6a2549..356a54f91d05 100644
--- a/drivers/media/rc/mceusb.c
+++ b/drivers/media/rc/mceusb.c
@@ -484,7 +484,6 @@ struct mceusb_dev {
u8 cmd, rem; /* Remaining IR data bytes in packet */
struct {
- u32 connected:1;
u32 tx_mask_normal:1;
u32 microsoft_gen1:1;
u32 no_tx:1;
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 15/19] media: serial_ir: Fix race condition where timer can be re-armed
[not found] <cover.1788882189.git.sean@mess.org>
` (13 preceding siblings ...)
2026-09-08 15:52 ` [PATCH v4 14/19] media: mceusb: Remove unused field Sean Young
@ 2026-09-08 15:52 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 16/19] media: rc: After rc_unregister_device() timers " Sean Young
` (3 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab
Cc: Rik van Riel, stable, linux-kernel
Ensure the timer cannot be re-armed.
Fixes: 2940c7e49775 ("[media] serial_ir: generate timeout")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/serial_ir.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/media/rc/serial_ir.c b/drivers/media/rc/serial_ir.c
index 992fff82b524..f6702b3df9a0 100644
--- a/drivers/media/rc/serial_ir.c
+++ b/drivers/media/rc/serial_ir.c
@@ -798,7 +798,7 @@ static int __init serial_ir_init_module(void)
static void __exit serial_ir_exit_module(void)
{
- timer_delete_sync(&serial_ir.timeout_timer);
+ timer_shutdown_sync(&serial_ir.timeout_timer);
serial_ir_exit();
}
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 16/19] media: rc: After rc_unregister_device() timers can be re-armed
[not found] <cover.1788882189.git.sean@mess.org>
` (14 preceding siblings ...)
2026-09-08 15:52 ` [PATCH v4 15/19] media: serial_ir: Fix race condition where timer can be re-armed Sean Young
@ 2026-09-08 15:52 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 17/19] media: rc: Validate carrier range in LIRC_SET_REC_CARRIER ioctl Sean Young
` (2 subsequent siblings)
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab, Hans Verkuil,
Patrice Chotard
Cc: Rik van Riel, stable, linux-kernel
If rc_keydown() is called after rc_register_device(), then the timers
can be re-armed.
Fixes: dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/rc-main.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
index 795ac0fe4858..088d935d8347 100644
--- a/drivers/media/rc/rc-main.c
+++ b/drivers/media/rc/rc-main.c
@@ -2065,8 +2065,8 @@ void rc_unregister_device(struct rc_dev *dev)
if (dev->driver_type == RC_DRIVER_IR_RAW)
ir_raw_event_unregister(dev);
- timer_delete_sync(&dev->timer_keyup);
- timer_delete_sync(&dev->timer_repeat);
+ timer_shutdown_sync(&dev->timer_keyup);
+ timer_shutdown_sync(&dev->timer_repeat);
rc_free_rx_device(dev);
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 17/19] media: rc: Validate carrier range in LIRC_SET_REC_CARRIER ioctl
[not found] <cover.1788882189.git.sean@mess.org>
` (15 preceding siblings ...)
2026-09-08 15:52 ` [PATCH v4 16/19] media: rc: After rc_unregister_device() timers " Sean Young
@ 2026-09-08 15:52 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 18/19] media: ir_toy: Validate the maximum tx carrier Sean Young
2026-09-08 15:52 ` [PATCH v4 19/19] media: meson-ir-tx: Validate carrier and duty_cycle Sean Young
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab; +Cc: Rik van Riel, linux-kernel
Ensure that the low value of the carrier range is smaller than the
high value. This fixes an underflow in ite_set_rx_carrier_range().
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/lirc_dev.c | 2 ++
drivers/media/rc/rc-loopback.c | 5 -----
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c
index 183f1939b941..d9b6ecca563f 100644
--- a/drivers/media/rc/lirc_dev.c
+++ b/drivers/media/rc/lirc_dev.c
@@ -492,6 +492,8 @@ static long lirc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
ret = -ENOTTY;
else if (val <= 0)
ret = -EINVAL;
+ else if (fh->carrier_low && fh->carrier_low > val)
+ ret = -EINVAL;
else
ret = dev->s_rx_carrier_range(dev, fh->carrier_low,
val);
diff --git a/drivers/media/rc/rc-loopback.c b/drivers/media/rc/rc-loopback.c
index 53d0540717b3..9d56e77c9351 100644
--- a/drivers/media/rc/rc-loopback.c
+++ b/drivers/media/rc/rc-loopback.c
@@ -74,11 +74,6 @@ static int loop_set_rx_carrier_range(struct rc_dev *dev, u32 min, u32 max)
{
struct loopback_dev *lodev = dev->priv;
- if (min < 1 || min > max) {
- dev_dbg(&dev->dev, "invalid rx carrier range %u to %u\n", min, max);
- return -EINVAL;
- }
-
dev_dbg(&dev->dev, "setting rx carrier range %u to %u\n", min, max);
lodev->rxcarriermin = min;
lodev->rxcarriermax = max;
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 18/19] media: ir_toy: Validate the maximum tx carrier
[not found] <cover.1788882189.git.sean@mess.org>
` (16 preceding siblings ...)
2026-09-08 15:52 ` [PATCH v4 17/19] media: rc: Validate carrier range in LIRC_SET_REC_CARRIER ioctl Sean Young
@ 2026-09-08 15:52 ` Sean Young
2026-09-08 15:52 ` [PATCH v4 19/19] media: meson-ir-tx: Validate carrier and duty_cycle Sean Young
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab
Cc: Rik van Riel, stable, Mauro Carvalho Chehab, linux-kernel
Reject any carrier the hardware cannot generate. This also fixes
a divide by zero if a carrier of U32_MAX/16 is set.
Fixes: 8bff1386d62d ("media: ir_toy: allow tx carrier to be set")
Cc: stable@vger.kernel.org
Signed-off-by: Sean Young <sean@mess.org>
---
drivers/media/rc/ir_toy.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/media/rc/ir_toy.c b/drivers/media/rc/ir_toy.c
index 286c8543aa5a..58b23d08a897 100644
--- a/drivers/media/rc/ir_toy.c
+++ b/drivers/media/rc/ir_toy.c
@@ -372,7 +372,7 @@ static int irtoy_tx_carrier(struct rc_dev *rc, uint32_t carrier)
u8 buf[3];
int err;
- if (carrier < 11800)
+ if (carrier < 11800 || carrier > 3000000)
return -EINVAL;
buf[0] = 0x06;
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH v4 19/19] media: meson-ir-tx: Validate carrier and duty_cycle
[not found] <cover.1788882189.git.sean@mess.org>
` (17 preceding siblings ...)
2026-09-08 15:52 ` [PATCH v4 18/19] media: ir_toy: Validate the maximum tx carrier Sean Young
@ 2026-09-08 15:52 ` Sean Young
18 siblings, 0 replies; 25+ messages in thread
From: Sean Young @ 2026-09-08 15:52 UTC (permalink / raw)
To: linux-media, Sean Young, Mauro Carvalho Chehab, Neil Armstrong,
Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Viktor Prutyanov
Cc: Rik van Riel, stable, Mauro Carvalho Chehab, linux-arm-kernel,
linux-amlogic, linux-kernel
It is possible to set a combination of carrier and duty cycle that do
not produce a correct signal. In addition, setting a carrier larger
than USEC_PER_SEC will result in a divide by zero in
meson_irtx_prepare_pulse() during transmit.
Fixes: 49be1c78d575 ("media: rc: introduce Meson IR TX driver")
Signed-off-by: Sean Young <sean@mess.org>
Cc: stable@vger.kernel.org
---
drivers/media/rc/meson-ir-tx.c | 54 +++++++++++++++++++++++++++-------
1 file changed, 44 insertions(+), 10 deletions(-)
diff --git a/drivers/media/rc/meson-ir-tx.c b/drivers/media/rc/meson-ir-tx.c
index 1ab898069e67..a410464ede22 100644
--- a/drivers/media/rc/meson-ir-tx.c
+++ b/drivers/media/rc/meson-ir-tx.c
@@ -76,11 +76,24 @@ struct meson_irtx {
unsigned long clk_rate;
};
-static void meson_irtx_set_mod(struct meson_irtx *ir)
+static bool meson_irtx_calc_mod(unsigned long clk_rate, u32 carrier,
+ u32 duty_cycle, unsigned int *pulse_cnt,
+ unsigned int *space_cnt)
{
- unsigned int cnt = DIV_ROUND_CLOSEST(ir->clk_rate, ir->carrier);
- unsigned int pulse_cnt = DIV_ROUND_CLOSEST(cnt * ir->duty_cycle, 100);
- unsigned int space_cnt = cnt - pulse_cnt;
+ unsigned int cnt;
+
+ cnt = DIV_ROUND_CLOSEST(clk_rate, carrier);
+ *pulse_cnt = DIV_ROUND_CLOSEST(cnt * duty_cycle, 100);
+ *space_cnt = cnt - *pulse_cnt;
+
+ return *pulse_cnt >= 1 && *pulse_cnt <= 65536 &&
+ *space_cnt >= 1 && *space_cnt <= 65536;
+}
+
+static void meson_irtx_write_mod(struct meson_irtx *ir, unsigned int pulse_cnt,
+ unsigned int space_cnt)
+{
+ unsigned int cnt = pulse_cnt + space_cnt;
dev_dbg(ir->dev, "F_mod = %uHz, T_mod = %luns, duty_cycle = %u%%\n",
ir->carrier, NSEC_PER_SEC / ir->clk_rate * cnt,
@@ -90,8 +103,10 @@ static void meson_irtx_set_mod(struct meson_irtx *ir)
ir->reg_base + IRB_ADDR1);
}
-static void meson_irtx_setup(struct meson_irtx *ir, unsigned int clk_nr)
+static int meson_irtx_setup(struct meson_irtx *ir, unsigned int clk_nr)
{
+ unsigned int pulse_cnt, space_cnt;
+
/*
* Disable the TX, set modulator clock tick and set initialize
* output to be high. Set up carrier frequency and duty cycle. Then
@@ -100,13 +115,18 @@ static void meson_irtx_setup(struct meson_irtx *ir, unsigned int clk_nr)
*/
writel(~IRB_ENABLE & (IRB_MOD_CLK(clk_nr) | IRB_INIT_HIGH),
ir->reg_base + IRB_ADDR0);
- meson_irtx_set_mod(ir);
+ if (!meson_irtx_calc_mod(ir->clk_rate, ir->carrier, ir->duty_cycle,
+ &pulse_cnt, &space_cnt))
+ return -EINVAL;
+ meson_irtx_write_mod(ir, pulse_cnt, space_cnt);
writel(readl(ir->reg_base + IRB_ADDR0) & ~IRB_INIT_HIGH,
ir->reg_base + IRB_ADDR0);
writel(IRB_FIFO_IRQ_ENABLE | MIRTX_FIFO_THD,
ir->reg_base + IRB_ADDR3);
writel(readl(ir->reg_base + IRB_ADDR0) | IRB_ENABLE,
ir->reg_base + IRB_ADDR0);
+
+ return 0;
}
static u32 meson_irtx_prepare_pulse(struct meson_irtx *ir, unsigned int time)
@@ -215,12 +235,17 @@ static irqreturn_t meson_irtx_irqhandler(int irq, void *data)
static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier)
{
struct meson_irtx *ir = rc->priv;
+ unsigned int pulse_cnt, space_cnt;
+
+ if (!carrier)
+ return -EINVAL;
- if (carrier == 0)
+ if (!meson_irtx_calc_mod(ir->clk_rate, carrier, ir->duty_cycle,
+ &pulse_cnt, &space_cnt))
return -EINVAL;
ir->carrier = carrier;
- meson_irtx_set_mod(ir);
+ meson_irtx_write_mod(ir, pulse_cnt, space_cnt);
return 0;
}
@@ -228,9 +253,14 @@ static int meson_irtx_set_carrier(struct rc_dev *rc, u32 carrier)
static int meson_irtx_set_duty_cycle(struct rc_dev *rc, u32 duty_cycle)
{
struct meson_irtx *ir = rc->priv;
+ unsigned int pulse_cnt, space_cnt;
+
+ if (!meson_irtx_calc_mod(ir->clk_rate, ir->carrier, duty_cycle,
+ &pulse_cnt, &space_cnt))
+ return -EINVAL;
ir->duty_cycle = duty_cycle;
- meson_irtx_set_mod(ir);
+ meson_irtx_write_mod(ir, pulse_cnt, space_cnt);
return 0;
}
@@ -337,7 +367,11 @@ static int meson_irtx_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "modulator clock setup failed\n");
- meson_irtx_setup(ir, clk_nr);
+ ret = meson_irtx_setup(ir, clk_nr);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "carrier %u, duty_cycle %u%% not supported at clk_rate %luHz\n",
+ ir->carrier, ir->duty_cycle, ir->clk_rate);
ret = devm_request_irq(dev, irq,
meson_irtx_irqhandler,
--
2.55.0
^ permalink raw reply [flat|nested] 25+ messages in thread