I am writing a driver for an LCD display. According to the application note, I need to write a dummy SPI write to the command periodically to maximize its contrast. To accomplish this, I set up a timer and attempt to write the contrast-maximizing 2-byte dummy command from the timer handler.
However, something goes wrong because the spi_write function causes a complete kernel crash with the following error:
BUG: scheduling while atomic: swapper/1/0/0x00000102
Based on the following post: How to solve "BUG: scheduling while atomic: swapper /0x00000103/0, CPU#0"? in TSC2007 Driver?
"Scheduling while atomic" indicates that you've tried to sleep somewhere that you shouldn't - like within a spinlock-protected critical section or an interrupt handler.
Maybe the call to spi_write triggers some sort of sleep behavior. It would make sense to disallow sleeping here, because based on the stack trace, I see that the code is in a soft IRQ state:
[<404ec600>] (schedule_timeout) from [<404eac3c>] (wait_for_common+0x114/0x15c)
[<404eac3c>] (wait_for_common) from [<4031c7a4>] (spi_sync+0x70/0x88)
[<4031c7a4>] (spi_sync) from [<3f08a6b0>] (plt_lcd_send_toggle_comin_cmd+0x7c/0x84 [plt_lcd_spi])
[<3f08a6b0>] (plt_lcd_send_toggle_comin_cmd [plt_lcd_spi]) from [<3f08a6c4>] (plt_lcd_timer_handler+0xc/0x2c [plt_lcd_spi])
[<3f08a6c4>] (plt_lcd_timer_handler [plt_lcd_spi]) from [<40058818>] (call_timer_fn.isra.26+0x20/0x30)
[<40058818>] (call_timer_fn.isra.26) from [<40058f30>] (run_timer_softirq+0x1ec/0x21c)
[<40058f30>] (run_timer_softirq) from [<40023414>] (__do_softirq+0xe0/0x1c8)
[<40023414>] (__do_softirq) from [<400236f0>] (irq_exit+0x58/0xac)
[<400236f0>] (irq_exit) from [<4004ee4c>] (__handle_domain_irq+0x80/0xa0)
[<4004ee4c>] (__handle_domain_irq) from [<400085ac>] (gic_handle_irq+0x38/0x5c)
[<400085ac>] (gic_handle_irq) from [<40011740>] (__irq_svc+0x40/0x74)
My question is: what is the right way to implement such periodic behavior, where an SPI transaction needs to occur periodically?
The following is a summary of the timer handler (albeit with some manual modifications to make the names more generic -- I might have inserted some typos in the process)
static void lcd_timer_handler(unsigned long data)
{
// priv is a private structure that contains private info for the
// driver: timer structure, timer timeout, context for the dummy command
lcd_priv * const priv = (memlcd_priv *) data;
unsigned char dummy[2];
dummy[0] = get_dummy_command_code(priv);
dummy[1] = 0; // command must be terminated by a 0.
// This is the call that causes the failure.
// priv->spi is a struct spi_device *
spi_write(priv->spi, ((const void *) dummy), 2);
// Re-arm the timer
mod_timer(&priv->timer, jiffies + priv->timer_timeout);
}
Thanks!
EDIT: Here is what I came up with after implementing the recommendations from the answer below. Works nicely, but using delayed_work involved having to jump through a few hoops.
typedef struct lcd_priv {
/* private stuff: */
/* ... */
/* workqueue stuff: */
struct workqueue_struct * wq;
struct delayed_work periodic_work;
} lcd_priv;
void lcd_periodic_work(struct work_struct * work_struct_ptr)
{
/*
* Old documentation refers to a "data" pointer, but the API
* no longer supports it. The developer is invited to put the work_struct
* inside what would have been pointed to by "data" and to use container_of()
* to recover this master struct.
* See http://lwn.net/Articles/211279/ for more info.
*/
struct delayed_work * delayed = container_of(work_struct_ptr, struct delayed_work, work);
lcd_priv * priv = container_of(delayed, lcd_priv, periodic_work);
/* (prepare spi buffer in priv->spi_buf) */
/* ... */
/* This could be any activity that goes to sleep: */
spi_write(priv->spi, ((const void *) &priv->spi_buf[0]), 2);
queue_delayed_work(priv->wq, &priv->periodic_work, TOGGLE_FREQUENCY);
}
static void lcd_start_workqueue(lcd_priv * const priv) {
priv->wq = create_singlethread_workqueue("lcd_periodic_st_wq");
INIT_DELAYED_WORK(&priv->periodic_work, lcd_periodic_work);
queue_delayed_work(priv->wq, &priv->periodic_work, TOGGLE_FREQUENCY);
}
static void lcd_stop_workqueue(lcd_priv * const priv) {
destroy_workqueue(priv->wq);
}
If look at
spi_write
source code, it callsspi_sync
, and if look at first lines ofspi_sync
->mutex_lock
, sospi_write
can not be run inside interrupt, and it can not be fixed via.config
orsysfs
.Answer depend on your hardware, how often you want send data via SPI, what latency you accept etc.
you can use
spi_write
inside workqueue callback, see https://www.safaribooksonline.com/library/view/understanding-the-linux/0596005652/ch04s08.htmlworkqueue specially designed for such kind of things (running something that can not be run in interrupt context),
also you can use
spi_async
to schedule write via spi.spy_async
can be called inside interrupt handler.also you move things to userspace if latency not matter, and write to SPI via
spidev
interface.