Skip to contentSkip to conversation

media: Fix locking issues in rc core

PATCH Superseded Sean Young 3 participants 27 messages Sep 8 – Sep 11

Subsystems (8)

v4 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Sean Young

author maintainer
#0 lore raw

v4 cover media: Fix locking issues in rc core

Fix issues found by Sashiko and claude, and other cleanups.

Changes since v3:
- serial_ir: Fix race condition where timer can be re-armed This introduced a new race condition; reworked
- rc: After rc_unregister_device() timers can be re-armed Sashiko suggested a simpler implementation with timer_shutdown_sync()
- Add meson-ir-tx: Validate carrier and duty_cycle
- Add ir_toy: Validate the maximum tx carrier

Changes since v2:
- Cosmetic changes in: Add missing locking for keymap
- Patch 7 to 17 are new issues found

Changes since v1:
- Fix issue where input device was still leaked
- Add: Fix race between bpf(BPG_PROG_ATTACH) and device unregister
- Add: Fix inconsistent locking of keylock

Sean Young (19):
media: rc: Ensure registered is cleared in error path
media: rc: Ensure that rc_unregister_device() does not free input
device
media: rc: Fix ABBA deadlock by making locks more fine grained
media: rc: Add missing locking for keymap
media: rc: Fix race between bpf(BPG_PROG_ATTACH) and device unregister
media: rc: mce_kbd: Fix inconsistent locking of keylock
media: ene_ir: Ensure teardown is done in the correct order
media: rc: Use binary search for adding or updating a scancode
media: rc: imon: Bind both interfaces via usb_driver_claim_interface()
media: ir_toy: Remove unused struct field
media: nuvoton-cir: Remove unused struct field
media: ite-cir: Removed unused struct field
media: fintek-cir: Remove unused fields
media: mceusb: Remove unused field
media: serial_ir: Fix race condition where timer can be re-armed
media: rc: After rc_unregister_device() timers can be re-armed
media: rc: Validate carrier range in LIRC_SET_REC_CARRIER ioctl
media: ir_toy: Validate the maximum tx carrier
media: meson-ir-tx: Validate carrier and duty_cycle

 drivers/media/rc/ene_ir.c             |   4 +-
 drivers/media/rc/fintek-cir.c         |  13 --
 drivers/media/rc/fintek-cir.h         |  22 ----
 drivers/media/rc/imon.c               | 166 +++++++++++--------------
 drivers/media/rc/ir-mce_kbd-decoder.c |   5 +-
 drivers/media/rc/ir_toy.c             |   4 +-
 drivers/media/rc/ite-cir.c            |   1 -
 drivers/media/rc/ite-cir.h            |   1 -
 drivers/media/rc/lirc_dev.c           |   2 +
 drivers/media/rc/mceusb.c             |   1 -
 drivers/media/rc/meson-ir-tx.c        |  54 ++++++--
 drivers/media/rc/nuvoton-cir.h        |   3 -
 drivers/media/rc/rc-ir-raw.c          |  39 +++---
 drivers/media/rc/rc-loopback.c        |   5 -
 drivers/media/rc/rc-main.c            | 169 ++++++++++++++++----------
 drivers/media/rc/serial_ir.c          |   2 +-
 include/media/rc-map.h                |   2 -
 17 files changed, 251 insertions(+), 242 deletions(-)
Signature · 1 line
2.55.0
author maintainer
#1 lore raw

PATCH v4 1/19 media: rc: Ensure registered is cleared in error path

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")
---
 drivers/media/rc/rc-main.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/media/rc/rc-main.c +4 -1
index d93e98189c1a..1f99540456f1 100644
@@ -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);
Signature · 1 line
2.55.0
#2 lore raw

Re: [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path

Quoted 16 lines · Sean Young

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")
---
 drivers/media/rc/rc-main.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/media/rc/rc-main.c +0 -0
index d93e98189c1a..1f99540456f1 100644

drivers/media/rc/rc-main.c int rc_register_device(struct rc_dev *dev) · line 1938

Quoted 6 lines
drivers/media/rc/rc-main.c +2 -1
@@ -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?

drivers/media/rc/rc-main.c int rc_register_device(struct rc_dev *dev) · line 1987

Quoted 9 lines
drivers/media/rc/rc-main.c +2 -0
 	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

Quoted 3 lines
drivers/media/rc/rc-main.c +0 -0
 	ir_free_table(&dev->rc_map);
 out_raw:
 	ida_free(&rc_ida, minor);
author maintainer
#3 lore raw

Re: [PATCH v4 01/19] media: rc: Ensure registered is cleared in error path

Quoted 20 lines · Hans Verkuil
Quoted 19 lines · Sean Young

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")
---
 drivers/media/rc/rc-main.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/media/rc/rc-main.c +0 -0
index d93e98189c1a..1f99540456f1 100644
@@ -1934,7 +1934,8 @@ int rc_register_device(struct rc_dev *dev)
 			goto out_raw;
 	}
 
Quoted 5 lines

drivers/media/rc/rc-main.c int rc_register_device(struct rc_dev *dev) · line 1938

Quoted 3 lines
drivers/media/rc/rc-main.c +2 -1
-	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.

Quoted 12 lines

drivers/media/rc/rc-main.c int rc_register_device(struct rc_dev *dev) · line 1987

Quoted 8 lines
drivers/media/rc/rc-main.c +2 -0
 	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.

Quoted 1 line

The commit log mentions nothing about that change either.

You are right, the commit message needs to mention this.

Sean

author maintainer
#4 lore raw

PATCH v4 2/19 media: rc: Ensure that rc_unregister_device() does not free input device

In reply to Sean Young

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")
---
 drivers/media/rc/rc-main.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
drivers/media/rc/rc-main.c +4 -4
index 1f99540456f1..88d8f7d4aab3 100644
@@ -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);
 }
Signature · 1 line
2.55.0
author maintainer
#5 lore raw

PATCH v4 3/19 media: rc: Fix ABBA deadlock by making locks more fine grained

In reply to Sean Young

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")
---
 drivers/media/rc/rc-ir-raw.c | 39 ++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 15 deletions(-)
drivers/media/rc/rc-ir-raw.c +24 -15
index 54b323becb1f..75dee4a483d4 100644
@@ -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);
Signature · 1 line
2.55.0
author maintainer
#6 lore raw

PATCH v4 4/19 media: rc: Add missing locking for keymap

In reply to Sean Young

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")
---
 drivers/media/rc/rc-main.c | 122 ++++++++++++++++++++++---------------
 include/media/rc-map.h     |   2 -
 2 files changed, 74 insertions(+), 50 deletions(-)
drivers/media/rc/rc-main.c +74 -48
index 88d8f7d4aab3..04204559959f 100644
@@ -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)
include/media/rc-map.h +0 -2
index d95ed3e96de2..f167c37179c8 100644
@@ -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;
Signature · 1 line
2.55.0
#7 lore raw

Re: [PATCH v4 04/19] media: rc: Add missing locking for keymap

Quoted 7 lines · Sean Young

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.

Quoted 16 lines

Add lockdep assertions where locks are required.

Fixes dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
---
 drivers/media/rc/rc-main.c | 122 ++++++++++++++++++++++---------------
 include/media/rc-map.h     |   2 -
 2 files changed, 74 insertions(+), 50 deletions(-)
drivers/media/rc/rc-main.c +0 -0
index 88d8f7d4aab3..04204559959f 100644

drivers/media/rc/rc-main.c line 21

Quoted 8 lines
drivers/media/rc/rc-main.c +2 -3
@@ -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

Quoted 237 lines
drivers/media/rc/rc-main.c +72 -45
 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)
include/media/rc-map.h +0 -2
index d95ed3e96de2..f167c37179c8 100644
@@ -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;
author maintainer
#8 lore raw

Re: [PATCH v4 04/19] media: rc: Add missing locking for keymap

Quoted 11 lines · Hans Verkuil
Quoted 7 lines · Sean Young

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.

Quoted 27 lines
Quoted 15 lines

Add lockdep assertions where locks are required.

Fixes dccc0c3ddf8f ("media: rc: fix race between unregister and urb/irq callbacks")
---
 drivers/media/rc/rc-main.c | 122 ++++++++++++++++++++++---------------
 include/media/rc-map.h     |   2 -
 2 files changed, 74 insertions(+), 50 deletions(-)
drivers/media/rc/rc-main.c +0 -0
index 88d8f7d4aab3..04204559959f 100644

drivers/media/rc/rc-main.c line 21

Quoted 8 lines
drivers/media/rc/rc-main.c +2 -3
@@ -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.

Quoted 3 lines

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

Quoted 243 lines

Regards,

Hans

Quoted 237 lines
drivers/media/rc/rc-main.c +72 -45
 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)
include/media/rc-map.h +0 -2
index d95ed3e96de2..f167c37179c8 100644
@@ -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;
author maintainer
#9 lore raw

PATCH v4 5/19 media: rc: Fix race between bpf(BPG_PROG_ATTACH) and device unregister

In reply to Sean Young

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")
---
 drivers/media/rc/rc-main.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
drivers/media/rc/rc-main.c +6 -6
index 04204559959f..924b13fff753 100644
@@ -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);
 
 	/*
Signature · 1 line
2.55.0
author maintainer
#10 lore raw

PATCH v4 6/19 media: rc: mce_kbd: Fix inconsistent locking of keylock

In reply to Sean Young

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")
---
 drivers/media/rc/ir-mce_kbd-decoder.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/media/rc/ir-mce_kbd-decoder.c +3 -2
index bb2d7c37c263..d1c9263511b0 100644
@@ -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:
Signature · 1 line
2.55.0
author maintainer
#11 lore raw

PATCH v4 7/19 media: ene_ir: Ensure teardown is done in the correct order

In reply to Sean Young

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")
---
 drivers/media/rc/ene_ir.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/media/rc/ene_ir.c +2 -2
index 6f7dccc965e7..f98d76277b62 100644
@@ -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);
 }
 
Signature · 1 line
2.55.0
author maintainer
#12 lore raw

PATCH v4 8/19 media: rc: Use binary search for adding or updating a scancode

In reply to Sean Young

We can have up to 1024 scancodes entries which are always sorted, so make this a little faster.

---
 drivers/media/rc/rc-main.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)
drivers/media/rc/rc-main.c +13 -7
index 924b13fff753..795ac0fe4858 100644
@@ -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) {
Signature · 1 line
2.55.0
#13 lore raw

Re: [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode

Quoted 36 lines · Sean Young

We can have up to 1024 scancodes entries which are always sorted, so make this a little faster.

---
 drivers/media/rc/rc-main.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)
drivers/media/rc/rc-main.c +8 -3
index 924b13fff753..795ac0fe4858 100644
@@ -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;

drivers/media/rc/rc-main.c static unsigned int ir_establish_scancode(struct rc_dev *dev, · line 423

Quoted 10 lines
drivers/media/rc/rc-main.c +5 -4
-
-		/* 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

Quoted 3 lines
drivers/media/rc/rc-main.c +0 -0
 	/* No previous mapping found, we might need to grow the table */
 	if (rc_map->size == rc_map->len) {
author maintainer
#14 lore raw

Re: [PATCH v4 08/19] media: rc: Use binary search for adding or updating a scancode

Quoted 41 lines · Hans Verkuil
Quoted 40 lines · Sean Young

We can have up to 1024 scancodes entries which are always sorted, so make this a little faster.

---
 drivers/media/rc/rc-main.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)
drivers/media/rc/rc-main.c +8 -7
index 924b13fff753..795ac0fe4858 100644
@@ -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;
Quoted 8 lines

drivers/media/rc/rc-main.c static unsigned int ir_establish_scancode(struct rc_dev *dev, · line 423

Quoted 6 lines
drivers/media/rc/rc-main.c +5 -0
+		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

author maintainer
#15 lore raw

PATCH v4 9/19 media: rc: imon: Bind both interfaces via usb_driver_claim_interface()

In reply to Sean Young

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.

---
 drivers/media/rc/imon.c | 166 +++++++++++++++++-----------------------
 1 file changed, 72 insertions(+), 94 deletions(-)
drivers/media/rc/imon.c +72 -94
index 621554005f2f..ef288cbe2b99 100644
@@ -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)
Signature · 1 line
2.55.0
author maintainer
#16 lore raw

PATCH v4 10/19 media: ir_toy: Remove unused struct field

In reply to Sean Young

Never assigned or read.

---
 drivers/media/rc/ir_toy.c | 2 --
 1 file changed, 2 deletions(-)
drivers/media/rc/ir_toy.c +0 -2
index ee645882dd5a..286c8543aa5a 100644
@@ -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;
Signature · 1 line
2.55.0
author maintainer
#17 lore raw

PATCH v4 11/19 media: nuvoton-cir: Remove unused struct field

In reply to Sean Young

Never assigned or read.

---
 drivers/media/rc/nuvoton-cir.h | 3 ---
 1 file changed, 3 deletions(-)
drivers/media/rc/nuvoton-cir.h +0 -3
index ed7d93beaa28..e66e3cfedeec 100644
@@ -77,9 +77,6 @@ struct nvt_dev {
 	/* hardware id */
 	u8 chip_major;
 	u8 chip_minor;
-
-	/* carrier period = 1 / frequency */
-	u32 carrier;
 };
 
 /* buffer packet constants */
Signature · 1 line
2.55.0
author maintainer
#18 lore raw

PATCH v4 12/19 media: ite-cir: Removed unused struct field

In reply to Sean Young

Never assigned or read.

---
 drivers/media/rc/ite-cir.c | 1 -
 drivers/media/rc/ite-cir.h | 1 -
 2 files changed, 2 deletions(-)
drivers/media/rc/ite-cir.c +0 -1
index 1fbafcd8219e..d0e98095d1b6 100644
@@ -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);
drivers/media/rc/ite-cir.h +0 -1
index 4b4294d77555..a8382aa9dc35 100644
@@ -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 */
Signature · 1 line
2.55.0
author maintainer
#19 lore raw

PATCH v4 13/19 media: fintek-cir: Remove unused fields

In reply to Sean Young

Some of these fields are vestigal from transmit, which never worked.

---
 drivers/media/rc/fintek-cir.c | 13 -------------
 drivers/media/rc/fintek-cir.h | 22 ----------------------
 2 files changed, 35 deletions(-)
drivers/media/rc/fintek-cir.c +0 -13
index c196ee923ecd..d4368181fef6 100644
@@ -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)
drivers/media/rc/fintek-cir.h +0 -22
index 20696359e9ba..9ade9d7e7bfb 100644
@@ -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 */
Signature · 1 line
2.55.0
author maintainer
#20 lore raw

PATCH v4 14/19 media: mceusb: Remove unused field

In reply to Sean Young

Never assigned or read.

---
 drivers/media/rc/mceusb.c | 1 -
 1 file changed, 1 deletion(-)
drivers/media/rc/mceusb.c +0 -1
index 39ba7f6a2549..356a54f91d05 100644
@@ -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;
Signature · 1 line
2.55.0
author maintainer
#21 lore raw

PATCH v4 15/19 media: serial_ir: Fix race condition where timer can be re-armed

In reply to Sean Young

Ensure the timer cannot be re-armed.

Fixes 2940c7e49775 ("[media] serial_ir: generate timeout")
---
 drivers/media/rc/serial_ir.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/media/rc/serial_ir.c +1 -1
index 992fff82b524..f6702b3df9a0 100644
@@ -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();
 }
 
Signature · 1 line
2.55.0
author maintainer
#22 lore raw

PATCH v4 16/19 media: rc: After rc_unregister_device() timers can be re-armed

In reply to Sean Young

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")
---
 drivers/media/rc/rc-main.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/media/rc/rc-main.c +2 -2
index 795ac0fe4858..088d935d8347 100644
@@ -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);
 
Signature · 1 line
2.55.0
author maintainer
#23 lore raw

PATCH v4 17/19 media: rc: Validate carrier range in LIRC_SET_REC_CARRIER ioctl

In reply to Sean Young

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().

---
 drivers/media/rc/lirc_dev.c    | 2 ++
 drivers/media/rc/rc-loopback.c | 5 -----
 2 files changed, 2 insertions(+), 5 deletions(-)
drivers/media/rc/lirc_dev.c +2 -0
index 183f1939b941..d9b6ecca563f 100644
@@ -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);
drivers/media/rc/rc-loopback.c +0 -5
index 53d0540717b3..9d56e77c9351 100644
@@ -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;
Signature · 1 line
2.55.0
author maintainer
#24 lore raw

PATCH v4 18/19 media: ir_toy: Validate the maximum tx carrier

In reply to Sean Young

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")
---
 drivers/media/rc/ir_toy.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/media/rc/ir_toy.c +1 -1
index 286c8543aa5a..58b23d08a897 100644
@@ -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;
Signature · 1 line
2.55.0
author maintainer
#25 lore raw

PATCH v4 19/19 media: meson-ir-tx: Validate carrier and duty_cycle

In reply to Sean Young

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")
---
 drivers/media/rc/meson-ir-tx.c | 54 +++++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 10 deletions(-)
drivers/media/rc/meson-ir-tx.c +44 -10
index 1ab898069e67..a410464ede22 100644
@@ -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,
Signature · 1 line
2.55.0
#26 lore raw

Re: [PATCH v4 19/19] media: meson-ir-tx: Validate carrier and duty_cycle

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The patch claims to fix a divide-by-zero error for carrier frequencies larger than USEC_PER_SEC…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The patch claims to fix a divide-by-zero error for carrier frequencies larger than USEC_PER_SEC, but the implemented bounds check mathematically fails to prevent it. --

commit e3f11b02aad6109d1dcf403fdfe01c4cc2db70e1
Author: Sean Young <[email protected]>

media: meson-ir-tx: Validate carrier and duty_cycle

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.

Quoted 4 lines
drivers/media/rc/meson-ir-tx.c +0 -0
index 1ab898069e67f..a410464ede224 100644

[ ... ]

Quoted 11 lines
drivers/media/rc/meson-ir-tx.c +3 -3
@@ -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);

drivers/media/rc/meson-ir-tx.c struct meson_irtx { · line 91

Quoted 10 lines
drivers/media/rc/meson-ir-tx.c +9 -1
-	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;
+}

[Severity: Medium]
Does this validation check adequately prevent the divide-by-zero condition mentioned in the commit message?

For example, if the carrier frequency is set to 3,000,000 Hz and the clock rate is 8,000,000 Hz, cnt evaluates to 3. Depending on the duty cycle, pulse_cnt and space_cnt could be valid (e.g., 2 and 1), allowing this function to return true despite the carrier being larger than USEC_PER_SEC.

Because of this, callers like meson_irtx_set_carrier() can still accept these excessively high carrier frequencies, allowing tb_us to drop to 0 in meson_irtx_prepare_pulse():

	unsigned int tb_us = DIV_ROUND_CLOSEST(USEC_PER_SEC, ir->carrier);
	delay = (DIV_ROUND_CLOSEST(time, tb_us) - 1) & IRB_DELAY_MASK;

While a pre-existing bounds check in meson_irtx_check_buf() seems to coincidentally reject transmissions when carrier > USEC_PER_SEC (since its calculated max_tb_us becomes 0), should this new logic also explicitly check for carrier <= USEC_PER_SEC to fully enforce the constraint stated in the commit message?

Signature · 6 lines
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19

_______________________________________________
linux-amlogic mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-amlogic