Периодически вызывается spi_write в драйвере Linux

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

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

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

Основываясь на следующем сообщении: Как решить «BUG: планирование при атомарном: swapper / 0x00000103 / 0, CPU # 0»? в драйвере TSC2007?

«Планирование в то время как атомарное» означает, что вы пытались спать где-то, что вам не нужно, например, в критическом разделе, защищенном от спин-блокировки, или обработчике прерываний.

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

 [] (schedule_timeout) from [] (wait_for_common+0x114/0x15c) [] (wait_for_common) from [] (spi_sync+0x70/0x88) [] (spi_sync) from [] (plt_lcd_send_toggle_comin_cmd+0x7c/0x84 [plt_lcd_spi]) [] (plt_lcd_send_toggle_comin_cmd [plt_lcd_spi]) from [] (plt_lcd_timer_handler+0xc/0x2c [plt_lcd_spi]) [] (plt_lcd_timer_handler [plt_lcd_spi]) from [] (call_timer_fn.isra.26+0x20/0x30) [] (call_timer_fn.isra.26) from [] (run_timer_softirq+0x1ec/0x21c) [] (run_timer_softirq) from [] (__do_softirq+0xe0/0x1c8) [] (__do_softirq) from [] (irq_exit+0x58/0xac) [] (irq_exit) from [] (__handle_domain_irq+0x80/0xa0) [] (__handle_domain_irq) from [] (gic_handle_irq+0x38/0x5c) [] (gic_handle_irq) from [] (__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); } 

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

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

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

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

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

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

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