2015-10-23 15 views
9

Sto lavorando a un progetto incorporato. La nostra board utilizza il kernel Linux v3.16.7. Sto lavorando per supportare un paio di LED periferici che monitorano l'attività. Ho modificato con successo la procedura di avvio su load the drivers and create sysfs entries in /sys/class/leds/, il che è ottimo. Ho anche allegato un oneshot trigger ai LED, così posso echo 1 > shot da /sys/class/leds/actled1\:green/ e il led lampeggia. Esattamente quello che voglio.Configura i parametri del trigger LED dallo spazio del kernel

Tuttavia, desidero configurare i ritardi per ciascun LED durante l'istanziazione del driver durante l'avvio e non sono chiaro su come farlo. Il driver crea voci di sysfs in /sys/class/leds/actled1\:green/ chiamate delay_on e delay_off, e posso scrivere su di esse dallo spazio utente per configurare i ritardi, ma dovrebbe essere possibile impostare i loro valori iniziali dallo spazio del kernel durante l'istanziazione. Voglio anche essere in grado di impostare il parametro invert (che è solo un'altra voce sysfs proprio come i ritardi).

Come posso configurare i parametri di un trigger a led quando istanzio il driver dallo spazio del kernel?

Di seguito viene illustrato come istanziare i LED GPIO. In primo luogo ho creato le struct richieste:

static struct gpio_led my_leds[] __initdata = { 
    { 
     .name = "actled1:green", 
     .default_trigger = "oneshot" 
     .gpio = ACTIVITY_LED_GPIO_BASE + 0, 
     .active_low = true, 
    }, 
    { 
     .name = "actled2:red", 
     .default_trigger = "oneshot" 
     .gpio = ACTIVITY_LED_GPIO_BASE + 1, 
     .active_low = true, 
    }, 
}; 

static struct gpio_led_platform_data my_leds_pdata __initdata = { 
    .num_leds = ARRAY_SIZE(my_leds), 
    .leds = my_leds, 
}; 

Poi, io chiamo questa funzione per creare i dispositivi di piattaforma:

static int __init setup_my_leds (void) 
{ 
    struct platform_device *pdev; 
    int ret; 

    pdev = platform_device_alloc("leds-gpio", -1); 
    if (!pdev) { 
     return -ENOMEM; 
    } 

    ret = platform_device_add_data(pdev, 
        &my_leds_pdata, 
        sizeof(my_leds_pdata)); 

    if (ret < 0) { 
     platform_device_put(pdev); 
     return ret; 
    } 

    ret = platform_device_add(pdev); 
    if (ret < 0) { 
     platform_device_put(pdev); 
     return ret; 
    } 

    return 0; 
} 

La definizione per il gpio_led struct è in include/linux/leds.h line 327, e la definizione per gpio_led_platform_data è in line 341 of the same file. La definizione di platform_device_add_data è drivers/base/platform.c line 284.

Potrebbe essere utile controllare la sorgente per il trigger di accensione (drivers/leds/trigger/ledtrig-oneshot.c) per rispondere alla domanda. Rilevante anche il driver "leds-gpio" (drivers/leds/leds-gpio.c).

Sospetto che la risposta sia da qualche parte in drivers/base/platform.c e lo documentation associato, ma non vedo alcuna funzione che si occupi dei dati di cui ho bisogno.


per affrontare alcune delle informazioni che ho inavvertitamente lasciato fuori:

  1. il boot loader definisce gli argomenti del kernel, e non possiamo modificare il boot loader. va bene; i valori che voglio impostare sono costanti e posso indicarli a fondo.
  2. il driver viene cotto nel kernel in fase di compilazione (e, presumo, caricato dal bootloader) anziché caricare un .ko con modprobe in seguito.
  3. Mi piacerebbe un modo generale per impostare parametri di trigger arbitrari, non solo delay_on/delay_off. ad esempio, il parametro invert di oneshot.
  4. sto completamente modificando oneshot/creando nuovi trigger. infatti, una volta che lo faccio funzionare con oneshot, ho per creare un nuovo trigger che si espande su oneshot (che è anche il motivo per cui ho bisogno di impostare parametri arbitrari).
+0

La struttura dei dispositivi, l'ACPI o le proprietà dei dispositivi incorporati sono la vostra scelta. In ogni caso devi scrivere un codice affinché sia ​​supportato dal driver. – 0andriy

risposta

3

Ci sono alcuni problemi e penso di aver trovato le soluzioni, ma anche se tu fornito una buona quantità di informazioni, mancavano alcune cose, quindi enumererò per tutti gli scenari possibili, quindi sii paziente ...

(1) Ottenere i valori iniziali che si desidera impostare. Presumo che tu l'abbia già capito, ma ... Puoi ottenere questi parametri dall'analisi del kernel cmdline (ad esempio aggiungi i valori a /boot/grub2/grub.cfg come myleds.delay_on=.... Se stai caricando tramite modprobe, imposti un . modulo parametro Questi potrebbero anche essere un file di configurazione come in myleds.config_file=/etc/sysconfig/myleds.conf

(2) li È possibile impostare all'interno del vostro setup_my_leds [fatta eccezione per la riluttanza di oneshot_trig_activate - che ci occuperemo presto] Da drivers/base/platform.c:.

/** 
* arch_setup_pdev_archdata - Allow manipulation of archdata before its used 
* @pdev: platform device 
* 
* This is called before platform_device_add() such that any pdev_archdata may 
* be setup before the platform_notifier is called. So if a user needs to 
* manipulate any relevant information in the pdev_archdata they can do: 
* 
* platform_device_alloc() 
* ... manipulate ... 
* platform_device_add() 
* 
* And if they don't care they can just call platform_device_register() and 
* everything will just work out. 
*/ 

Così, con questo in mente, cambiamo un po 'la funzione di impostazione:

(3) Sfortunatamente, dal momento che si utilizza .default_trigger = "oneshot", i dati sopra riportati verranno cancellati da oneshot_trig_activate in drivers/leds/trigger/ledtrig-oneshot.c. Quindi, abbiamo bisogno di affrontarlo.

Opzione (A): Supponendo che è possibile ricostruire l'intero kernel come si sceglie, è sufficiente modificare oneshot_trig_activate in ledtrig-oneshot.c e rimuovere le linee che utilizzano DEFAULT_DELAY. Questo è veramente utile solo se tu conosci che è non utilizzato da qualcos'altro nel tuo sistema che potrebbe aver bisogno dei valori predefiniti.

Opzione (B): Se non ti è permesso di modificare ledtrig-oneshot.c, ma il permesso di aggiungere nuovi trigger per drivers/leds/trigger, copiare il file (ad esempio) ledtrig-oneshot2.c e fare le modifiche lì. Dovrai modificare lo .name in .name = "oneshot2". Il modo semplice [in vi, ovviamente :-)] è :%s/oneshot/oneshot2/g. Dovrai anche aggiungere una nuova voce in Kconfig e Makefile per questo. Quindi, cambiare la tua definizione struct per utilizzare il nuovo driver: .default_trigger = "oneshot2"

Opzione (C): Supponendo che non è possibile [o non si vuole] toccare la directory drivers/leds/trigger, copiare ledtrig-oneshot.c nella directory del driver [rinomina a seconda dei casi ]. Fai le modifiche dall'opzione (B) qui sopra. Con alcuni trucchi nel tuo Makefile, puoi farlo per creare sia my_led_driver.koeledtrig-oneshot2.ko. Avrai bisogno di modificare il tuo Kconfig, eventualmente aggiungendo un depends on LED_TRIGGERS per il driver di trigger a led. Si potrebbe anche mettere i due in sottodirectory separate e il Makefile individuo/Kconfig potrebbe essere più semplice: my_led/my_driver e my_led/my_trigger

Opzione (C) sarebbe più lavoro in attacco, ma potrebbe essere più pulito e più portabile nel lungo periodo. Naturalmente, si potrebbe fare l'opzione (A) per la prova di concetto, quindi fare l'opzione (B), e fare la "nave finale" come opzione (C).

Un modo alternativo per quando si impostano i valori iniziali: Ricordare il commento per my_leds_get_init_values è stato possibly storing away for later use. È possibile modificare oneshot2_trig_activate per chiamarlo invece di utilizzare DEFAULT_DELAY. Non mi piace così tanto e preferisco le soluzioni che semplicemente neutralizzano il comportamento offensivo di oneshot_trig_activate. Ma con alcuni test, potresti scoprire che questo è il modo in cui devi farlo.

Speriamo che quanto sopra funzionerà. In caso contrario, modifica la tua domanda con ulteriori informazioni e/o restrizioni [e inviami un commento], e sarò lieto di aggiornare la mia risposta [Ho fatto driver per 40+].

UPDATE: OK, qui è un driver di trigger a LED completamente annotato e modificato che è possibile utilizzare come sostituzione in sostituzione di drivers/led/trigger/ledtrig-oneshot.c.

Poiché il parametro invert può non essere passato direttamente attraverso qualsiasi struct di serie si ha accesso al vostro funzione di impostazione [vale a dire è memorizzato in una struttura privata all'interno del driver di trigger], rimuovere "Choice (1)" e "Choice (2)". Li metteremo tutti insieme nello [modificato] oneshot_trig_activate.

Inoltre, i parametri di init che si desidera devono essere impostati e memorizzati come globali dal my_leds_get_init_values in modo che il trigger trigger possa trovarli. Cioè, non c'è modo di farlo in modo pulito (ad esempio con un puntatore a una struttura privata che viene passata in giro) poiché le strutture a cui hai accesso in fase di installazione non hanno un campo per questo. Vedere la parte superiore del driver del trigger per la discussione su questo.

Il mio primo passo è stato annotare il driver di base con commenti descrittivi. Non ci sono stati commenti in esso, eccetto per lo stile K & R per copyright e un singolo liner. Le mie annotazioni sono commenti ANSI ("//").

Se dovessi assumere il driver, aggiungerei questi e lasciarli in. Tuttavia, il mio livello di commenti potrebbe essere considerato "eccessivo commento" in base alla guida allo stile del kernel e potrebbe essere considerato "cruft", in particolare per un guidatore che è così semplice.

Il passaggio successivo è stato aggiungere le modifiche necessarie. Tutte le posizioni che hanno aggiunte/modifiche sono contrassegnate da un blocco di commenti che inizia con "C:". Questi sono i posti importanti da guardare. Si noti che questi commenti sono legittimi candidati a lasciare in. In altri driver più complessi, il livello di commenti spetta all'autore. La "C:" è solo per evidenziare i posti per te.

Con le annotazioni, una lettura in linea retta potrebbe essere più semplice ora. Potrebbe anche essere utile un diff -u. Se hai tutto sotto git, tanto meglio.

A causa di tutto ciò, rimuoverei "Opzione (A)" [modifica diretta del file originale] e "Solo Opzione (B)" o "Opzione (C)".

Il driver di trigger utilizza tutte le definizioni static, quindi la modifica globale che ho suggerito prima è non necessaria. Ho fatto .name = "myled_oneshot";, quindi dovrai abbinarlo a .default_trigger = "myled_oneshot";. Sentiti libero di usare my_leds_whatever per essere coerente con la tua convenzione di denominazione esistente. Quando faccio questo per me stesso, io di solito uso le mie iniziali, in modo che diventi ce_leds_whatever --YMMV

Comunque, ecco il driver grilletto modificato intera. Nota che ho eseguito il montaggio, ma ho provato a compilarlo/compilarlo con non.

/* 
* One-shot LED Trigger 
* 
* Copyright 2012, Fabio Baltieri <[email protected]> 
* 
* Based on ledtrig-timer.c by Richard Purdie <[email protected]> 
* 
* This program is free software; you can redistribute it and/or modify 
* it under the terms of the GNU General Public License version 2 as 
* published by the Free Software Foundation. 
* 
*/ 

#include <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/init.h> 
#include <linux/device.h> 
#include <linux/ctype.h> 
#include <linux/slab.h> 
#include <linux/leds.h> 
#include "../leds.h" 

// C: we need to get access to the init data populated by the setup function 
// we have the "clean way" with a struct definition inside a header file and 
// the "dirty way" using three separate int globals 
// in either case, the externs referenced here must be defined in the "my_leds" 
// driver as global 

// C: the "clean way" 
// (1) requires that we have a path to the .h (e.g. -I<whatever) 
// (2) this would be easier/preferable for the "Option (C)" 
// (3) once done, easily extensible [probably not a consideration here] 
#ifdef MYLED_USESTRUCT 
#include "whatever/myled_init.h" 
extern struct myled_init myled_init; 

// C: the "ugly way" 
// (1) no need to use a separate .h file 
// (2) three separate global variables is wasteful 
// (3) more than three, and we really should consider the "struct" 
#else 
extern int myled_init_delay_on; 
extern int myled_init_delay_off; 
extern int myled_init_invert; 
#endif 

#define DEFAULT_DELAY 100 

// oneshot trigger driver private data 
struct oneshot_trig_data { 
    unsigned int invert;    // current invert state 
}; 

// arm oneshot sequence from sysfs write to shot file 
static ssize_t led_shot(struct device *dev, 
     struct device_attribute *attr, const char *buf, size_t size) 
{ 
    struct led_classdev *led_cdev = dev_get_drvdata(dev); 
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; 

    led_blink_set_oneshot(led_cdev, 
      &led_cdev->blink_delay_on, &led_cdev->blink_delay_off, 
      oneshot_data->invert); 

    /* content is ignored */ 
    return size; 
} 

// show invert state for "cat invert" 
static ssize_t led_invert_show(struct device *dev, 
     struct device_attribute *attr, char *buf) 
{ 
    struct led_classdev *led_cdev = dev_get_drvdata(dev); 
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; 

    return sprintf(buf, "%u\n", oneshot_data->invert); 
} 

// set invert from sysfs write to invert file (e.g. echo 1 > invert) 
static ssize_t led_invert_store(struct device *dev, 
     struct device_attribute *attr, const char *buf, size_t size) 
{ 
    struct led_classdev *led_cdev = dev_get_drvdata(dev); 
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; 
    unsigned long state; 
    int ret; 

    ret = kstrtoul(buf, 0, &state); 
    if (ret) 
     return ret; 

    oneshot_data->invert = !!state; 

    if (oneshot_data->invert) 
     led_set_brightness_async(led_cdev, LED_FULL); 
    else 
     led_set_brightness_async(led_cdev, LED_OFF); 

    return size; 
} 

// show delay_on state for "cat delay_on" 
static ssize_t led_delay_on_show(struct device *dev, 
     struct device_attribute *attr, char *buf) 
{ 
    struct led_classdev *led_cdev = dev_get_drvdata(dev); 

    return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); 
} 

// set delay_on from sysfs write to delay_on file (e.g. echo 20 > delay_on) 
static ssize_t led_delay_on_store(struct device *dev, 
     struct device_attribute *attr, const char *buf, size_t size) 
{ 
    struct led_classdev *led_cdev = dev_get_drvdata(dev); 
    unsigned long state; 
    int ret; 

    ret = kstrtoul(buf, 0, &state); 
    if (ret) 
     return ret; 

    led_cdev->blink_delay_on = state; 

    return size; 
} 

// show delay_off state for "cat delay_off" 
static ssize_t led_delay_off_show(struct device *dev, 
     struct device_attribute *attr, char *buf) 
{ 
    struct led_classdev *led_cdev = dev_get_drvdata(dev); 

    return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); 
} 

// set delay_off from sysfs write to delay_off file (e.g. echo 20 > delay_off) 
static ssize_t led_delay_off_store(struct device *dev, 
     struct device_attribute *attr, const char *buf, size_t size) 
{ 
    struct led_classdev *led_cdev = dev_get_drvdata(dev); 
    unsigned long state; 
    int ret; 

    ret = kstrtoul(buf, 0, &state); 
    if (ret) 
     return ret; 

    led_cdev->blink_delay_off = state; 

    return size; 
} 

// these are the "attribute" definitions -- one for each sysfs entry 
// pointers to these show up in the above functions as the "attr" argument 
static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); 
static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); 
static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); 
static DEVICE_ATTR(shot, 0200, NULL, led_shot); 

// activate the trigger device 
static void oneshot_trig_activate(struct led_classdev *led_cdev) 
{ 
    struct oneshot_trig_data *oneshot_data; 
    int rc; 

    // create an instance of the private data we need 
    oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL); 
    if (!oneshot_data) 
     return; 

    // save the pointer in the led class struct so it's available to other 
    // functions above 
    led_cdev->trigger_data = oneshot_data; 

    // attach the sysfs entries 
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); 
    if (rc) 
     goto err_out_trig_data; 
    rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); 
    if (rc) 
     goto err_out_delayon; 
    rc = device_create_file(led_cdev->dev, &dev_attr_invert); 
    if (rc) 
     goto err_out_delayoff; 
    rc = device_create_file(led_cdev->dev, &dev_attr_shot); 
    if (rc) 
     goto err_out_invert; 

    // C: this is what the driver used to do 
#if 0 
    led_cdev->blink_delay_on = DEFAULT_DELAY; 
    led_cdev->blink_delay_off = DEFAULT_DELAY; 
#endif 

    led_cdev->activated = true; 

    // C: from here to the return is what the modified driver must do 

#ifdef MYLED_USESTRUCT 
    led_cdev->blink_delay_on = myled_init.delay_on; 
    led_cdev->blink_delay_off = myled_init.delay_off; 
    oneshot_data->invert = myled_init.invert; 
#else 
    led_cdev->blink_delay_on = myled_init_delay_on; 
    led_cdev->blink_delay_off = myled_init_delay_off; 
    oneshot_data->invert = myled_init_invert; 
#endif 

    // C: if invert is off, nothing to do -- just like before 
    // if invert is set, we implement this as if we just got an instantaneous 
    // write to the sysfs "invert" file (which would call led_invert_store 
    // above) 

    // C: this is a direct rip-off of the above led_invert_store function which 
    // we can _not_ call here directly because we don't have access to the 
    // data it needs for its arguments [at least, not conveniently] 
    // so, we extract the one line we actually need 
    if (oneshot_data->invert) 
     led_set_brightness_async(led_cdev, LED_FULL); 

    return; 

    // release everything if an error occurs 
err_out_invert: 
    device_remove_file(led_cdev->dev, &dev_attr_invert); 
err_out_delayoff: 
    device_remove_file(led_cdev->dev, &dev_attr_delay_off); 
err_out_delayon: 
    device_remove_file(led_cdev->dev, &dev_attr_delay_on); 
err_out_trig_data: 
    kfree(led_cdev->trigger_data); 
} 

// deactivate the trigger device 
static void oneshot_trig_deactivate(struct led_classdev *led_cdev) 
{ 
    struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; 

    // release/destroy all the sysfs entries [and free the private data] 
    if (led_cdev->activated) { 
     device_remove_file(led_cdev->dev, &dev_attr_delay_on); 
     device_remove_file(led_cdev->dev, &dev_attr_delay_off); 
     device_remove_file(led_cdev->dev, &dev_attr_invert); 
     device_remove_file(led_cdev->dev, &dev_attr_shot); 
     kfree(oneshot_data); 
     led_cdev->activated = false; 
    } 

    /* Stop blinking */ 
    led_set_brightness(led_cdev, LED_OFF); 
} 

// definition/control for trigger device registration 
// C: changed the name to "myled_oneshot" 
static struct led_trigger oneshot_led_trigger = { 
    .name  = "myled_oneshot", 
    .activate = oneshot_trig_activate, 
    .deactivate = oneshot_trig_deactivate, 
}; 

// module init function -- register the trigger device 
static int __init oneshot_trig_init(void) 
{ 
    return led_trigger_register(&oneshot_led_trigger); 
} 

// module exit function -- unregister the trigger device 
static void __exit oneshot_trig_exit(void) 
{ 
    led_trigger_unregister(&oneshot_led_trigger); 
} 

module_init(oneshot_trig_init); 
module_exit(oneshot_trig_exit); 

MODULE_AUTHOR("Fabio Baltieri <[email protected]>"); 
MODULE_DESCRIPTION("One-shot LED trigger"); 
MODULE_LICENSE("GPL"); 
+0

sei un vero toccasana. questa risposta è estremamente informativa esaminerò alcune delle cose che hai menzionato e riferirò. –

+0

okay, quindi vedo che questo mi permette di impostare 'delay_on' e' delay_off' usando i dati della piattaforma. posso impostare altri parametri come 'invert' usando questa tecnica? –

+0

chiedo perché sembra che quello che sta succedendo è che sta impostando direttamente i campi 'delay_on' e' delay_off' esplicitamente menzionati di ['gpio_led_platform_data'] (http://lxr.free-electrons.com/source/include/ linux/leds.h # L348) struct, ma il campo 'invert' non fa parte di quella struttura (né sono parametri arbitrari di altri trigger). quindi questo metodo si limita a configurare solo i parametri 'delay_on' e' delay_off'? –

0

Come si può vedere in ledtrig-oneshot.c, il ritardo viene sempre inizializzato con DEFAULT_DELAY.Sfortunatamente, se vuoi essere in grado di configurare un valore diverso all'avvio, questo è un meccanismo che dovrai implementare.

+0

Sarei felice di scrivere la mia modifica su oneshot, ma se scrivessi una nuova funzione (diciamo, set_delay_on (int) '), come la chiamerei dal mio codice di istanziazione dello spazio del kernel? in realtà non ho una maniglia per l'autista o altro. –

0

Come Craig ha risposto che dovrebbe essere tra le opzioni della riga di comando del kernel, ma ci potrebbe essere un problema con i sistemi embedded dove il boot-loader passa i parametri della riga di comando e boot-loader non possono essere modificati, sono solitamente OTP. In questo caso vedo solo 2 opzioni

  1. codifica duro in funzione del kernel init

  2. come indirizzo MAC è memorizzato in EEPROM per il driver nic di leggere, se i valori possono essere memorizzati in un lampo (né) e il valore letto all'avvio. Questo può essere fatto dopo aver creato le partizioni mtd durante l'avvio del kernel.

+0

questo è vero nel mio caso, e lo codificherò nella funzione init, che va bene. grazie per l'heads-up –