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

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

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

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

На основе следующего сообщения: Как решить ОШИБКУ: планирование при атомарном: swapper /0x00000103/0, CPU#0? в драйвере TSC2007?

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

Возможно, вызов spi_write вызывает какое-то поведение сна. Здесь имеет смысл запретить спящий режим, потому что по трассировке стека я вижу, что код находится в состоянии soft 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);
}

Спасибо!

РЕДАКТИРОВАТЬ: вот что я придумал после выполнения рекомендаций из ответа ниже. Работает хорошо, но использование 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);
}

person BareMetalCoder    schedule 10.05.2016    source источник
comment
Если spi_write может спать, я думаю, вам нужно запланировать рабочий элемент для этого: kernel.org/doc/Documentation/workqueue.txt   -  person Jonathon Reinhart    schedule 11.05.2016


Ответы (1)


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

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

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

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

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

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

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

person fghj    schedule 10.05.2016
comment
Успех! Благодарю вас! Стоит отметить: в следующей статье описываются изменения, внесенные в интерфейс рабочей очереди примерно в 2007 году. Старая документация относится к другим прототипам функций, и мне потребовалось некоторое время, чтобы понять это. lwn.net/Articles/211279 - person BareMetalCoder; 13.05.2016