(not on the list, please CC: replies to me)
Using atomic_* seems to cause a false sense of security in some people.
atomic_set(...,atomic_read(...)) is not atomic, and rtc.c did it everywhere.
Also, testing for a bit and later enabling it (like in rtc_open) causes races
(two proccesses in SMP could open the rtc at the same time if they got the
right timing). I patched it to use a spinlock instead.
This is my first kernel patch, so I probably missed something (spinlocks are
tricky). Also, I only tested compiling it.
--- linux-2.3.99-pre5/drivers/char/rtc.c Fri Apr 14 14:31:08 2000
+++ linux-rtcfix/drivers/char/rtc.c Thu Apr 20 10:25:57 2000
@@ -124,7 +124,10 @@
#define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */
#define RTC_TIMER_ON 0x02 /* missed irq timer active */
-static atomic_t rtc_status = ATOMIC_INIT(0); /* bitmapped status byte. */
+/* Doing read-modify-write with atomic_set(...,atomic_read(...)) is evil */
+/* rtc_status_lock > rtc_lock */
+static spinlock_t rtc_status_lock = SPIN_LOCK_UNLOCKED;
+static unsigned int rtc_status = 0; /* bitmapped status byte. */
static unsigned long rtc_freq = 0; /* Current periodic IRQ rate */
static unsigned long rtc_irq_data = 0; /* our output to the world */
@@ -167,10 +170,13 @@
wake_up_interruptible(&rtc_wait);
if (rtc_async_queue)
+ /* Is this OK on interrupt? */
kill_fasync (rtc_async_queue, SIGIO, POLL_IN);
- if (atomic_read(&rtc_status) & RTC_TIMER_ON)
+ spin_lock (&rtc_status_lock);
+ if (rtc_status & RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
+ spin_unlock (&rtc_status_lock);
}
#endif
@@ -245,11 +251,12 @@
case RTC_PIE_OFF: /* Mask periodic int. enab. bit */
{
mask_rtc_irq_bit(RTC_PIE);
- if (atomic_read(&rtc_status) & RTC_TIMER_ON) {
+ spin_lock_irqsave (&rtc_status_lock, flags);
+ if (rtc_status & RTC_TIMER_ON) {
del_timer(&rtc_irq_timer);
- atomic_set(&rtc_status,
- atomic_read(&rtc_status) & ~RTC_TIMER_ON);
+ rtc_status &= ~RTC_TIMER_ON;
}
+ spin_unlock_irqrestore (&rtc_status_lock, flags);
return 0;
}
case RTC_PIE_ON: /* Allow periodic ints */
@@ -262,13 +269,14 @@
if ((rtc_freq > 64) && (!capable(CAP_SYS_RESOURCE)))
return -EACCES;
- if (!(atomic_read(&rtc_status) & RTC_TIMER_ON)) {
- atomic_set(&rtc_status,
- atomic_read(&rtc_status) | RTC_TIMER_ON);
+ spin_lock_irqsave (&rtc_status_lock, flags);
+ if (!(rtc_status & RTC_TIMER_ON)) {
+ rtc_status |= RTC_TIMER_ON;
rtc_irq_timer.expires = jiffies + HZ/rtc_freq + 2*HZ/100;
add_timer(&rtc_irq_timer);
}
set_rtc_irq_bit(RTC_PIE);
+ spin_unlock_irqrestore (&rtc_status_lock, flags);
return 0;
}
case RTC_UIE_OFF: /* Mask ints from RTC updates. */
@@ -491,17 +499,23 @@
{
unsigned long flags;
- if(atomic_read(&rtc_status) & RTC_IS_OPEN)
- return -EBUSY;
+ spin_lock_irqsave (&rtc_status_lock, flags);
+ if(rtc_status & RTC_IS_OPEN)
+ goto out_busy;
MOD_INC_USE_COUNT;
- atomic_set(&rtc_status, atomic_read(&rtc_status) | RTC_IS_OPEN);
+ rtc_status |= RTC_IS_OPEN;
- spin_lock_irqsave (&rtc_lock, flags);
+ spin_lock (&rtc_lock); /* Already in irqsave */
rtc_irq_data = 0;
- spin_unlock_irqrestore (&rtc_lock, flags);
+ spin_unlock (&rtc_lock);
+ spin_unlock_irqrestore (&rtc_status_lock, flags);
return 0;
+
+out_busy:
+ spin_unlock_irqrestore (&rtc_status_lock, flags);
+ return -EBUSY;
}
static int rtc_fasync (int fd, struct file *filp, int on)
@@ -514,24 +528,27 @@
{
unsigned long flags;
#ifndef __alpha__
+ unsigned char tmp;
+#endif
+
+ spin_lock_irqsave (&rtc_status_lock, flags);
+#ifndef __alpha__
/*
* Turn off all interrupts once the device is no longer
* in use, and clear the data.
*/
- unsigned char tmp;
-
- spin_lock_irqsave(&rtc_lock, flags);
+ spin_lock(&rtc_lock); /* Already in irqsave */
tmp = CMOS_READ(RTC_CONTROL);
tmp &= ~RTC_PIE;
tmp &= ~RTC_AIE;
tmp &= ~RTC_UIE;
CMOS_WRITE(tmp, RTC_CONTROL);
CMOS_READ(RTC_INTR_FLAGS);
- spin_unlock_irqrestore(&rtc_lock, flags);
+ spin_unlock(&rtc_lock);
- if (atomic_read(&rtc_status) & RTC_TIMER_ON) {
- atomic_set(&rtc_status, atomic_read(&rtc_status) & ~RTC_TIMER_ON);
+ if (rtc_status & RTC_TIMER_ON) {
+ rtc_status &= ~RTC_TIMER_ON;
del_timer(&rtc_irq_timer);
}
@@ -542,10 +559,11 @@
#endif
MOD_DEC_USE_COUNT;
- spin_lock_irqsave (&rtc_lock, flags);
+ spin_lock (&rtc_lock);
rtc_irq_data = 0;
- spin_unlock_irqrestore (&rtc_lock, flags);
- atomic_set(&rtc_status, atomic_read(&rtc_status) & ~RTC_IS_OPEN);
+ spin_unlock (&rtc_lock);
+ rtc_status &= ~RTC_IS_OPEN;
+ spin_unlock_irqrestore (&rtc_status_lock, flags);
return 0;
}
@@ -699,8 +717,13 @@
{
/* interrupts and maybe timer disabled at this point by rtc_release */
- if (atomic_read(&rtc_status) & RTC_TIMER_ON)
+ /* FIXME: how rtc_open here could be avoided? */
+ /* No need for locking, if we know for sure nothing else can open() us*/
+ spin_lock (&rtc_status_lock); /* No need for irqsave; interrupts are
+ disabled (I hope) */
+ if (rtc_status & RTC_TIMER_ON)
del_timer(&rtc_irq_timer);
+ spin_unlock (&rtc_status_lock);
remove_proc_entry ("driver/rtc", NULL);
misc_deregister(&rtc_dev);
-- Cesar Eduardo Barros cesarb@web4u.com.br cesarb@dcc.ufrj.br- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/
This archive was generated by hypermail 2b29 : Sun Apr 23 2000 - 21:00:17 EST