wait_event_interruptible does not wake_up
Steve Kaiser
skaiser.uci at gmail.com
Wed Mar 12 05:12:48 EST 2008
Can I ask you kernel gurus here for some advice? I am using Linux
2.4.25 on an Freescale MPC5200B IceCube development board.
I have some trouble waking a user process sent to sleep with
wait_event[_interruptible]. 99% of the time it works fine, but
sometimes when I call wake_up(), my user process does not wake up. I
can see whats happening by toggling LEDs on the board, and watching with
an oscilloscope.
<-- 50ms -->
______ _____
|___| |___| LED1 (low active)
_____ _________ ___
|| || LED4 (low active)
^ user thread is finshed here, and goes back to sleep
^ user thread is woke up here
^ interrupt tasklet finishes here
^ interrupt tasklet starts here
With some testing and hair pulling, I have discovered exactly when (but
not why) this failure to wake_up happens. If I install a 10ms kernel
timer task, and the task occurs while my hardware interrupt service
tasklet is happening (I feel maybe exactly when it is calling wake_up
but not sure), that's when the wake_up always fails.
If I eliminate the asynchronous nature of the hardware interrupt, and
simulate the hardware interrupt by installing a 10ms kernel timer task,
using the task to toggling an I/O line that I jumper over to the
hardware interrupt input, then everything works great. The wake_up()
never fails with this synchronous configuration.
Anybody heard of such a thing? Any advice is very welcome. Here's more
details and code if you have a spare moment to read:
My very small user program is put to sleep by a driver with
wait_event_interruptable() when the user program calls device_write().
I want the user program to sleep until my hardware is ready. The
hardware will interrupt when ready, every 50ms or so. The hardware is a
64k word FIFO memory chip, and the interrupt is it's half-full flag
(latched with flip flop), but that doesn't matter.
When the driver recognizes the hardware interrupt, it should burst a
chunk of data out to the hardware FIFO, and then wake the user program.
The user program writes a new chunk of data to the driver, and gets
put to sleep again. The driver holds the data in kobuf, all ready in
preparation for the next hardware interrupt.
This all works perfectly well-- 99.99% of the time. But every once in a
while, the user process does not awake. My interrupt tasklet recognized
the interrupt, did call wake_up() for sure, but the process simply did
not wake up. Sometimes the process wakes up an arbitrary time later--
hundreds of milliseconds sometimes. The interrupt service tasklet
otherwise seems to be working reliably, as on the oscilloscope I can see
the effects of it clearing a hardware flip-flop perfectly every time,
and this is the call right before wake-up().
I copy below some of the code which may explain things better. Maybe my
error is obvious and dumb and if so, I am happy. Maybe my approach is
wrong?
Steve Kaiser
static u32 kobuf[FIFO_DEPTH][2]; // output DAC buffer
static int wrq = 0; // user sleeps until hdw fifo is half empty
static DECLARE_WAIT_QUEUE_HEAD(WriteQ);
static void gpio_irq_handler (int, void*, struct pt_regs *);
static void gpio_tasklet_handler( unsigned long );
static DECLARE_TASKLET(gpio_tasklet,gpio_tasklet_handler,0);
/* ----------------------------------------------------------------------
device_open device_read device_ioctl
device_release device_write device_poll
---------------------------------------------------------------------- */
static ssize_t device_write (struct file *filp,
const char *buff, // the user buffer to copy from
size_t count, // user requested nbytes
loff_t * f_pos) // offset in the file
{
size_t len = count;
// wait for interrupt to make room for data and wake us up
wrq = 1; wait_event_interruptible(WriteQ,!wrq);
// format of user buffer is uint[FIFO_DEPTH][2],
// where for MPC5200, sizeof(uint) = 4, or 8 bytes per element
if ( copy_from_user(kobuf,buff,len) )
return -EFAULT;
return len;
}
/* ----------------------------------------------------------------------
gpio_irq_handler: called on every gpio interrupt
---------------------------------------------------------------------- */
static void gpio_irq_handler( int irq,
void *dev_id, struct pt_regs *regs )
{
struct mpc5xxx_gpio *pgpio = (struct mpc5xxx_gpio *)MPC5xxx_GPIO;
// check GPIO Simple Interrupt Status Register
if (pgpio->sint_istat & GSI2) {
// clear only GSI2 Status read-write-clear bit
// '=' instead oft '|=' to leave other bits unchanged
pgpio->sint_istat = GSI2;
// GSI2 Interrupt has occured. schedule some work
tasklet_schedule(&gpio_tasklet);
}
}
static void gpio_tasklet_handler( unsigned long data )
{
unsigned int i;
unsigned int *buf;
// refill hardware output FIFOs, assuming they are half empty
buf = &kobuf[0][0];
for ( i = 0; i < FIFO_HALFDEPTH; i++ ) {
outl(*buf++,ioaddr); // left
outl(*buf++,ioaddr + 8); // right
}
// pulse the hardware flip-flop clear pin,
// allows hardware to assert another irq when fifo is half empty
hdwrctrl &= ~kHdwrIrqAck; outl(hdwrctrl,ioaddr + 4);
hdwrctrl |= kHdwrIrqAck; outl(hdwrctrl,ioaddr + 4);
// wake up user program thread
if ( wrq ) { wrq = 0; wake_up(&WriteQ); }
}
More information about the Linuxppc-embedded
mailing list