2016-05-10 2 views
0

Я пишу драйвер для ЖК-дисплея. Согласно примечанию к приложению, мне нужно написать фиктивный SPI для записи в команду, чтобы максимизировать его контраст. Для этого я настроил таймер и попытался написать двухминутную фиктивную команду с контрастностью с помощью обработчика таймера.Вызов spi_write периодически в драйвере Linux

Однако что-то идет не так, потому что spi_write функция вызывает полный крах ядра со следующей ошибкой:

BUG: scheduling while atomic: swapper/1/0/0x00000102 

Основываясь на следующем сообщении: 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.

Может быть, вызов spi_write вызывает какое-то поведение сна. Это имело бы смысл, чтобы запретить спать здесь, потому что на основе трассировки стека, я вижу, что код находится в мягком IRQ состоянии:

[<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) 

Мой вопрос: что такое правильный способ реализации такого периодического поведения , где транзакция SPI должна периодически возникать?

Ниже приводится краткое описание обработчика таймера (хотя и с некоторыми изменениями вручную, чтобы сделать имена более общий характер - я мог бы вставить некоторые опечатки в процессе)

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); 
} 

Спасибо!

EDIT: Вот что я придумал после выполнения рекомендаций из нижеприведенного ответа. Работает хорошо, но использование delayed_work связано с необходимостью прыгать через несколько обручей.

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); 
} 
+0

Если 'spi_write' может спать, я думаю, вам нужно запланировать рабочий элемент, чтобы сделать это: https://www.kernel.org/doc/Documentation/workqueue.txt –

ответ

1

Если посмотреть на spi_write исходный код, он вызывает spi_sync, и если посмотреть на первые строки spi_sync ->mutex_lock, так spi_write не может быть запущена внутри прерывания, , и это не может быть исправлено с помощью .config или sysfs.

My question is: what is the right way to implement such periodic behavior, where > an SPI transaction needs to occur periodically?

Ответ зависит от вашего оборудования, как часто вы хотите отправить данные через SPI, , что задержка вы принимаете и т.д.

вы можете использовать spi_write внутри workqueue обратного вызова см https://www.safaribooksonline.com/library/view/understanding-the-linux/0596005652/ch04s08.html

workqueue специально предназначенные для такого рода вещей (запуск чего-то, что нельзя запускать в контексте прерывания),

также вы можете использовать spi_async, чтобы запланировать запись через spi. spy_async можно вызвать внутри обработчика прерываний.

также вы перемещаете вещи в пользовательское пространство, если задержка не имеет значения, и записывайте в SPI через интерфейс spidev.

+0

Успех! Спасибо! Стоит отметить: В следующей статье описываются изменения, внесенные в интерфейс workqueue в 2007 году.Старая документация относится к прототипам различных функций, и мне потребовалось немного времени, чтобы понять это. http://lwn.net/Articles/211279/ – BareMetalCoder

Смежные вопросы