Appel périodique de spi_write dans un pilote linux

J’écris un pilote pour un écran LCD. Selon la note d’application, j’ai besoin d’écrire une fausse écriture SPI dans la commande périodiquement pour maximiser son contraste. Pour ce faire, j’ai configuré une timer et tenté d’écrire la commande fictive à 2 octets maximisant le contraste à partir du gestionnaire de timer.

Cependant, quelque chose ne va pas, car la fonction spi_write provoque un crash complet du kernel avec l’erreur suivante:

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

Basé sur le post suivant: Comment résoudre “BOGUE: planification en atomique: swapper / 0x00000103 / 0, CPU # 0″? dans le pilote TSC2007?

“Planification en mode atomique” indique que vous avez essayé de dormir quelque part que vous ne devriez pas – comme dans une section critique protégée par un verrou tournant ou un gestionnaire d’interruptions.

Peut-être que l’appel à spi_write déclenche une sorte de comportement de sumil. Il serait logique d’interdire dormir ici, car, d’après la trace de la stack, je vois que le code est dans un état IRQ souple:

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

Ma question est la suivante: quel est le bon moyen de mettre en œuvre un tel comportement périodique lorsqu’une transaction SPI doit avoir lieu périodiquement?

Ce qui suit est un résumé du gestionnaire de timer (avec quelques modifications manuelles pour rendre les noms plus génériques – j’aurais peut-être inséré des fautes de frappe dans le processus)

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

Merci!

EDIT: Voici ce que je suis venu après la mise en œuvre des recommandations de la réponse ci-dessous. Fonctionne bien, mais avec delay_work, il fallait passer par quelques cerceaux.

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

Si vous regardez le code source de spi_write , il appelle spi_sync , et si vous regardez les premières lignes de spi_sync -> mutex_lock , spi_write ne peut donc pas être exécuté dans interruption et ne peut être corrigé via .config ou sysfs .

Ma question est la suivante: quel est le bon moyen de mettre en œuvre un tel comportement périodique, où> une transaction SPI doit avoir lieu périodiquement?

La réponse dépend de votre matériel, de la fréquence à laquelle vous souhaitez envoyer des données via SPI, de la latence que vous acceptez, etc.

vous pouvez utiliser spi_write dans le rappel de la file de travail, voir https://www.safaribooksonline.com/library/view/understanding-the-linux/0596005652/ch04s08.html

une file de travail spécialement conçue pour ce genre de choses (exécuter quelque chose qui ne peut pas être exécuté dans un contexte d’interruption),

Vous pouvez également utiliser spi_async pour planifier l’écriture via spi. spy_async peut être appelé à l’intérieur du gestionnaire d’interruptions.

De plus, vous déplacez les éléments dans l’espace utilisateur si la latence n’a pas d’importance et vous écrivez dans SPI via l’interface spidev .