Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

media: rc: fix race condition in ir_raw_event_store_edge() handling

There is a possible race condition between the IR timeout being generated
from the timer, and new IR arriving. This could result in the timeout
being added to the kfifo after new IR arrives. On top of that, there is
concurrent write access to the kfifo from ir_raw_event_store_edge() and
the timer.

Signed-off-by: Sean Young <sean@mess.org>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>

authored by

Sean Young and committed by
Mauro Carvalho Chehab
e3e389f9 5817b3d1

+24 -5
+3 -2
drivers/media/rc/rc-core-priv.h
··· 50 50 DECLARE_KFIFO(kfifo, struct ir_raw_event, MAX_IR_EVENT_SIZE); 51 51 ktime_t last_event; /* when last event occurred */ 52 52 struct rc_dev *dev; /* pointer to the parent rc_dev */ 53 - /* edge driver */ 54 - struct timer_list edge_handle; 53 + /* handle delayed ir_raw_event_store_edge processing */ 54 + spinlock_t edge_spinlock; 55 + struct timer_list edge_handle; 55 56 56 57 /* raw decoder state follows */ 57 58 struct ir_raw_event prev_ev;
+21 -3
drivers/media/rc/rc-ir-raw.c
··· 101 101 ev.duration = ktime_to_ns(ktime_sub(now, dev->raw->last_event)); 102 102 ev.pulse = !pulse; 103 103 104 + spin_lock(&dev->raw->edge_spinlock); 104 105 rc = ir_raw_event_store(dev, &ev); 105 106 106 107 dev->raw->last_event = now; ··· 113 112 mod_timer(&dev->raw->edge_handle, 114 113 jiffies + msecs_to_jiffies(15)); 115 114 } 115 + spin_unlock(&dev->raw->edge_spinlock); 116 116 117 117 return rc; 118 118 } ··· 464 462 } 465 463 EXPORT_SYMBOL(ir_raw_encode_scancode); 466 464 467 - static void edge_handle(struct timer_list *t) 465 + /** 466 + * ir_raw_edge_handle() - Handle ir_raw_event_store_edge() processing 467 + * 468 + * @t: timer_list 469 + * 470 + * This callback is armed by ir_raw_event_store_edge(). It does two things: 471 + * first of all, rather than calling ir_raw_event_handle() for each 472 + * edge and waking up the rc thread, 15 ms after the first edge 473 + * ir_raw_event_handle() is called. Secondly, generate a timeout event 474 + * no more IR is received after the rc_dev timeout. 475 + */ 476 + static void ir_raw_edge_handle(struct timer_list *t) 468 477 { 469 478 struct ir_raw_event_ctrl *raw = from_timer(raw, t, edge_handle); 470 479 struct rc_dev *dev = raw->dev; 471 - ktime_t interval = ktime_sub(ktime_get(), dev->raw->last_event); 480 + unsigned long flags; 481 + ktime_t interval; 472 482 483 + spin_lock_irqsave(&dev->raw->edge_spinlock, flags); 484 + interval = ktime_sub(ktime_get(), dev->raw->last_event); 473 485 if (ktime_to_ns(interval) >= dev->timeout) { 474 486 DEFINE_IR_RAW_EVENT(ev); 475 487 ··· 496 480 jiffies + nsecs_to_jiffies(dev->timeout - 497 481 ktime_to_ns(interval))); 498 482 } 483 + spin_unlock_irqrestore(&dev->raw->edge_spinlock, flags); 499 484 500 485 ir_raw_event_handle(dev); 501 486 } ··· 545 528 546 529 dev->raw->dev = dev; 547 530 dev->change_protocol = change_protocol; 548 - timer_setup(&dev->raw->edge_handle, edge_handle, 0); 531 + spin_lock_init(&dev->raw->edge_spinlock); 532 + timer_setup(&dev->raw->edge_handle, ir_raw_edge_handle, 0); 549 533 INIT_KFIFO(dev->raw->kfifo); 550 534 551 535 return 0;