Guida Programmare nel kernel Linux [parte 2]

U

Utente cancellato 277320

Ultima modifica da un moderatore:
In questa guida scriviamo un piccolo driver.

Quando si vuole imparare a scrivere un driver, in genere, si prova a leggere qualche libro, famosi quelli di Rubini, o qualche tutorial.
Il problema e' che il kernel cambia di continuo, talvolta anche i layer che stanno sotto al driver-framework. Quando una parte del kernel connessa ad altre parti viene aggiornata, assieme ad essa vengono aggiornate le parti soprastanti, che continuano a funzionare. Copiando il codice "vecchio" da libri e tutorial invece, spesso appena compilato, ci sono errori di compilazione di vario genere, causando spesso l'abbandono all'argomento.

Questo tutorial e' scritto con l'idea di orientare i lettori alla modalita' di sviluppo corretta, e illustrare il modo corretto di contribuire al kernel.

Requisiti: conocenza git, fluent linux, buone conoscenze C.

1) Come scoprire se un particolare driver manca. Per esempio, esiste il driver per una memoria eeprom 1-wire ds2430 ?

Scaricare la testa dello sviluppo, ovvero il kernel mainline, detto anche vanilla.
Per questa operazione eventualemente vedere la guida parte 1.

Codice:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

Codice:
cd drivers
# o se si conosce la famiglia di dispositivi, cd drivers/w1
grep -iR ds2430

Nota: un alternativa allo scaricare il codice, che e' un processo piuttosto lungo per chi non ha la fibra, e' sfogliarlo online, ma a mio avviso meno comoda per ricerche: codice kernel

Se con grep non trovo nulla, ma vedo che nella famiglia delle eeprom 1-wire ce ne sono altre simili, questo e' ottimo.
In genere e' diffile che un druver manchi nel kernel, ma non e' fondamentale che manchi, puo anche esisterne uno simile da cui per altro prendere esempio, questa la situazione migliore e in genere la piu comune.

2) Impostazione del driver

Non si scirve quasi mai da zero, simile c'e' il driver w1_ds2431.c, copiamo da li_

quindi, dall radice dei sorgenti linux, posizionarsi in:
Codice:
drivers/w1/slaves

quindi copio il driver simile con:
Codice:
cp w1_ds2431.c  w1_ds2430.c

Ora tuttavia il nostro w1_ds2430.c non sara' compilato, fino a che non lo aggiungiamo al makefile:

Diff:
-------------------------- drivers/w1/slaves/Kconfig --------------------------
index b7847636501d..687753889c34 100644
@@ -74,6 +74,14 @@ config W1_SLAVE_DS2805
           organized as 7 pages of 16 bytes each with 64bit
           unique number. Requires OverDrive Speed to talk to.
 
+config W1_SLAVE_DS2430
+    tristate "256b EEPROM family support (DS2430)"
+    help
+      Say Y here if you want to use a 1-wire 256bit EEPROM
+      family device (DS2430).
+      This EEPROM is organized as one page of 32 bytes for random
+      access.
+
 config W1_SLAVE_DS2431
     tristate "1kb EEPROM family support (DS2431)"
     help
e
Diff:
-------------------------- drivers/w1/slaves/Makefile --------------------------
index 8e9655eaa478..278bcf2a9bfd 100644
@@ -10,6 +10,7 @@ obj-$(CONFIG_W1_SLAVE_DS2408)    += w1_ds2408.o
 obj-$(CONFIG_W1_SLAVE_DS2413)    += w1_ds2413.o
 obj-$(CONFIG_W1_SLAVE_DS2406)    += w1_ds2406.o
 obj-$(CONFIG_W1_SLAVE_DS2423)    += w1_ds2423.o
+obj-$(CONFIG_W1_SLAVE_DS2430)    += w1_ds2430.o
 obj-$(CONFIG_W1_SLAVE_DS2431)    += w1_ds2431.o
 obj-$(CONFIG_W1_SLAVE_DS2805)    += w1_ds2805.o
 obj-$(CONFIG_W1_SLAVE_DS2433)    += w1_ds2433.o

Affinche' sia compilato, va cmq poi selezionato tramite "make menuconfig" .

3) Compilazione

Ora, prima di iniziare le modifiche, serve verificare che il nostro drriver venga compilato senza errori.
Si puo' compilare o cross-compilare l'intero kernel, o il solo driver. Per ora spiego la compilazione completa.

L'essenziale della compilazione del kernel dai suoi sorgenti si risolve in poche righe:
Bash:
# configurazione gia pronta
make board_defconfig
# oppure
make menuconfig
make

E' possibile crearsi una propria configurazione minimale (make menuconfig) e
salvarsela, per sesempio se sto compilando per architettura x86:
Bash:
make savedefconfig
cp defconfig arch/x86/configs/miascheda_defconfig
# quindi poi, da questa volta in avanti, solo
make miascheda_defconfig
make

Il nuovo kernel si puo poi testare, eseguendolo tramite un emulatore, ad esempio qemu, o crearsi
una menu entry grub per testarlo. Per un driver, tuttavia, e' piu semplice utilizzare una scheda
embedded tipo raspberry e cross-compilare il kernel, copiarlo su scheda SD e testarlo.

Allego un paio di script per la compilazione nativa e cross.

4) Personalizzazione / implementazione

Per questo driver, e' stato necessario modificare alcune funzioni, ecco le differneza rispetto
all'esistene w1_ds2431.c.:
(la maggiorparte delle differenze col driver precedente e' solo nella nomenclatura delel funzioni e dei defines)

Git:
--- w1_ds2431.c    2020-10-18 20:41:33.428847678 +0200
+++ w1_ds2430.c    2020-10-18 20:41:33.428847678 +0200
@@ -1,10 +1,12 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * w1_ds2431.c - w1 family 2d (DS2431) driver
+ * w1_ds2430.c - w1 family 14 (DS2430) driver
+ **
+ * Copyright (c) 2019 Angelo Dureghello <[email protected]>
  *
+ * Cloned and modified from ds2431
  * Copyright (c) 2008 Bernhard Weirich <[email protected]>
  *
- * Heavily inspired by w1_DS2433 driver from Ben Gardner <[email protected]>
  */
 
 #include <linux/kernel.h>
@@ -16,34 +18,33 @@
 
 #include <linux/w1.h>
 
-#define W1_EEPROM_DS2431    0x2D
+#define W1_EEPROM_DS2430    0x14
 
-#define W1_F2D_EEPROM_SIZE        128
-#define W1_F2D_PAGE_COUNT        4
-#define W1_F2D_PAGE_BITS        5
-#define W1_F2D_PAGE_SIZE        (1<<W1_F2D_PAGE_BITS)
-#define W1_F2D_PAGE_MASK        0x1F
-
-#define W1_F2D_SCRATCH_BITS  3
-#define W1_F2D_SCRATCH_SIZE  (1<<W1_F2D_SCRATCH_BITS)
-#define W1_F2D_SCRATCH_MASK  (W1_F2D_SCRATCH_SIZE-1)
-
-#define W1_F2D_READ_EEPROM    0xF0
-#define W1_F2D_WRITE_SCRATCH    0x0F
-#define W1_F2D_READ_SCRATCH    0xAA
-#define W1_F2D_COPY_SCRATCH    0x55
-
-
-#define W1_F2D_TPROG_MS        11
-
-#define W1_F2D_READ_RETRIES        10
-#define W1_F2D_READ_MAXLEN        8
+#define W1_F14_EEPROM_SIZE    32
+#define W1_F14_PAGE_COUNT    1
+#define W1_F14_PAGE_BITS    5
+#define W1_F14_PAGE_SIZE    (1 << W1_F14_PAGE_BITS)
+#define W1_F14_PAGE_MASK    0x1F
+
+#define W1_F14_SCRATCH_BITS    5
+#define W1_F14_SCRATCH_SIZE    (1 << W1_F14_SCRATCH_BITS)
+#define W1_F14_SCRATCH_MASK    (W1_F14_SCRATCH_SIZE-1)
+
+#define W1_F14_READ_EEPROM    0xF0
+#define W1_F14_WRITE_SCRATCH    0x0F
+#define W1_F14_READ_SCRATCH    0xAA
+#define W1_F14_COPY_SCRATCH    0x55
+#define W1_F14_VALIDATION_KEY    0xa5
+
+#define W1_F14_TPROG_MS        11
+#define W1_F14_READ_RETRIES    10
+#define W1_F14_READ_MAXLEN    W1_F14_SCRATCH_SIZE
 
 /*
  * Check the file size bounds and adjusts count as needed.
  * This would not be needed if the file size didn't reset to 0 after a write.
  */
-static inline size_t w1_f2d_fix_count(loff_t off, size_t count, size_t size)
+static inline size_t w1_f14_fix_count(loff_t off, size_t count, size_t size)
 {
     if (off > size)
         return 0;
@@ -57,31 +58,30 @@
 /*
  * Read a block from W1 ROM two times and compares the results.
  * If they are equal they are returned, otherwise the read
- * is repeated W1_F2D_READ_RETRIES times.
+ * is repeated W1_F14_READ_RETRIES times.
  *
- * count must not exceed W1_F2D_READ_MAXLEN.
+ * count must not exceed W1_F14_READ_MAXLEN.
  */
-static int w1_f2d_readblock(struct w1_slave *sl, int off, int count, char *buf)
+static int w1_f14_readblock(struct w1_slave *sl, int off, int count, char *buf)
 {
-    u8 wrbuf[3];
-    u8 cmp[W1_F2D_READ_MAXLEN];
-    int tries = W1_F2D_READ_RETRIES;
+    u8 wrbuf[2];
+    u8 cmp[W1_F14_READ_MAXLEN];
+    int tries = W1_F14_READ_RETRIES;
 
     do {
-        wrbuf[0] = W1_F2D_READ_EEPROM;
+        wrbuf[0] = W1_F14_READ_EEPROM;
         wrbuf[1] = off & 0xff;
-        wrbuf[2] = off >> 8;
 
         if (w1_reset_select_slave(sl))
             return -1;
 
-        w1_write_block(sl->master, wrbuf, 3);
+        w1_write_block(sl->master, wrbuf, 2);
         w1_read_block(sl->master, buf, count);
 
         if (w1_reset_select_slave(sl))
             return -1;
 
-        w1_write_block(sl->master, wrbuf, 3);
+        w1_write_block(sl->master, wrbuf, 2);
         w1_read_block(sl->master, cmp, count);
 
         if (!memcmp(cmp, buf, count))
@@ -89,7 +89,7 @@
     } while (--tries);
 
     dev_err(&sl->dev, "proof reading failed %d times\n",
-            W1_F2D_READ_RETRIES);
+            W1_F14_READ_RETRIES);
 
     return -1;
 }
@@ -101,27 +101,27 @@
     struct w1_slave *sl = kobj_to_w1_slave(kobj);
     int todo = count;
 
-    count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE);
+    count = w1_f14_fix_count(off, count, W1_F14_EEPROM_SIZE);
     if (count == 0)
         return 0;
 
     mutex_lock(&sl->master->bus_mutex);
 
-    /* read directly from the EEPROM in chunks of W1_F2D_READ_MAXLEN */
+    /* read directly from the EEPROM in chunks of W1_F14_READ_MAXLEN */
     while (todo > 0) {
         int block_read;
 
-        if (todo >= W1_F2D_READ_MAXLEN)
-            block_read = W1_F2D_READ_MAXLEN;
+        if (todo >= W1_F14_READ_MAXLEN)
+            block_read = W1_F14_READ_MAXLEN;
         else
             block_read = todo;
 
-        if (w1_f2d_readblock(sl, off, block_read, buf) < 0)
+        if (w1_f14_readblock(sl, off, block_read, buf) < 0)
             count = -EIO;
 
-        todo -= W1_F2D_READ_MAXLEN;
-        buf += W1_F2D_READ_MAXLEN;
-        off += W1_F2D_READ_MAXLEN;
+        todo -= W1_F14_READ_MAXLEN;
+        buf += W1_F14_READ_MAXLEN;
+        off += W1_F14_READ_MAXLEN;
     }
 
     mutex_unlock(&sl->master->bus_mutex);
@@ -132,22 +132,21 @@
 /*
  * Writes to the scratchpad and reads it back for verification.
  * Then copies the scratchpad to EEPROM.
- * The data must be aligned at W1_F2D_SCRATCH_SIZE bytes and
- * must be W1_F2D_SCRATCH_SIZE bytes long.
+ * The data must be aligned at W1_F14_SCRATCH_SIZE bytes and
+ * must be W1_F14_SCRATCH_SIZE bytes long.
  * The master must be locked.
  *
  * @param sl    The slave structure
  * @param addr    Address for the write
- * @param len   length must be <= (W1_F2D_PAGE_SIZE - (addr & W1_F2D_PAGE_MASK))
+ * @param len   length must be <= (W1_F14_PAGE_SIZE - (addr & W1_F14_PAGE_MASK))
  * @param data    The data to write
  * @return    0=Success -1=failure
  */
-static int w1_f2d_write(struct w1_slave *sl, int addr, int len, const u8 *data)
+static int w1_f14_write(struct w1_slave *sl, int addr, int len, const u8 *data)
 {
-    int tries = W1_F2D_READ_RETRIES;
-    u8 wrbuf[4];
-    u8 rdbuf[W1_F2D_SCRATCH_SIZE + 3];
-    u8 es = (addr + len - 1) % W1_F2D_SCRATCH_SIZE;
+    int tries = W1_F14_READ_RETRIES;
+    u8 wrbuf[2];
+    u8 rdbuf[W1_F14_SCRATCH_SIZE + 3];
 
 retry:
 
@@ -155,30 +154,32 @@
     if (w1_reset_select_slave(sl))
         return -1;
 
-    wrbuf[0] = W1_F2D_WRITE_SCRATCH;
+    wrbuf[0] = W1_F14_WRITE_SCRATCH;
     wrbuf[1] = addr & 0xff;
-    wrbuf[2] = addr >> 8;
 
-    w1_write_block(sl->master, wrbuf, 3);
+    w1_write_block(sl->master, wrbuf, 2);
     w1_write_block(sl->master, data, len);
 
     /* Read the scratchpad and verify */
     if (w1_reset_select_slave(sl))
         return -1;
 
-    w1_write_8(sl->master, W1_F2D_READ_SCRATCH);
-    w1_read_block(sl->master, rdbuf, len + 3);
+    w1_write_8(sl->master, W1_F14_READ_SCRATCH);
+    w1_read_block(sl->master, rdbuf, len + 2);
 
-    /* Compare what was read against the data written */
-    if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) ||
-       (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) {
+    /*
+    * Compare what was read against the data written
+    * Note: on read scratchpad, device returns 2 bulk 0xff bytes,
+    * to be discarded.
+    */
+    if ((memcmp(data, &rdbuf[2], len) != 0)) {
 
         if (--tries)
             goto retry;
 
         dev_err(&sl->dev,
             "could not write to eeprom, scratchpad compare failed %d times\n",
-            W1_F2D_READ_RETRIES);
+            W1_F14_READ_RETRIES);
 
         return -1;
     }
@@ -187,12 +188,12 @@
     if (w1_reset_select_slave(sl))
         return -1;
 
-    wrbuf[0] = W1_F2D_COPY_SCRATCH;
-    wrbuf[3] = es;
-    w1_write_block(sl->master, wrbuf, 4);
+    wrbuf[0] = W1_F14_COPY_SCRATCH;
+    wrbuf[1] = W1_F14_VALIDATION_KEY;
+    w1_write_block(sl->master, wrbuf, 2);
 
     /* Sleep for tprog ms to wait for the write to complete */
-    msleep(W1_F2D_TPROG_MS);
+    msleep(W1_F14_TPROG_MS);
 
     /* Reset the bus to wake up the EEPROM  */
     w1_reset_bus(sl->master);
@@ -208,7 +209,7 @@
     int addr, len;
     int copy;
 
-    count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE);
+    count = w1_f14_fix_count(off, count, W1_F14_EEPROM_SIZE);
     if (count == 0)
         return 0;
 
@@ -220,33 +221,33 @@
     while (len > 0) {
 
         /* if len too short or addr not aligned */
-        if (len < W1_F2D_SCRATCH_SIZE || addr & W1_F2D_SCRATCH_MASK) {
-            char tmp[W1_F2D_SCRATCH_SIZE];
+        if (len < W1_F14_SCRATCH_SIZE || addr & W1_F14_SCRATCH_MASK) {
+            char tmp[W1_F14_SCRATCH_SIZE];
 
             /* read the block and update the parts to be written */
-            if (w1_f2d_readblock(sl, addr & ~W1_F2D_SCRATCH_MASK,
-                    W1_F2D_SCRATCH_SIZE, tmp)) {
+            if (w1_f14_readblock(sl, addr & ~W1_F14_SCRATCH_MASK,
+                    W1_F14_SCRATCH_SIZE, tmp)) {
                 count = -EIO;
                 goto out_up;
             }
 
             /* copy at most to the boundary of the PAGE or len */
-            copy = W1_F2D_SCRATCH_SIZE -
-                (addr & W1_F2D_SCRATCH_MASK);
+            copy = W1_F14_SCRATCH_SIZE -
+                (addr & W1_F14_SCRATCH_MASK);
 
             if (copy > len)
                 copy = len;
 
-            memcpy(&tmp[addr & W1_F2D_SCRATCH_MASK], buf, copy);
-            if (w1_f2d_write(sl, addr & ~W1_F2D_SCRATCH_MASK,
-                    W1_F2D_SCRATCH_SIZE, tmp) < 0) {
+            memcpy(&tmp[addr & W1_F14_SCRATCH_MASK], buf, copy);
+            if (w1_f14_write(sl, addr & ~W1_F14_SCRATCH_MASK,
+                    W1_F14_SCRATCH_SIZE, tmp) < 0) {
                 count = -EIO;
                 goto out_up;
             }
         } else {
 
-            copy = W1_F2D_SCRATCH_SIZE;
-            if (w1_f2d_write(sl, addr, copy, buf) < 0) {
+            copy = W1_F14_SCRATCH_SIZE;
+            if (w1_f14_write(sl, addr, copy, buf) < 0) {
                 count = -EIO;
                 goto out_up;
             }
@@ -262,33 +263,33 @@
     return count;
 }
 
-static BIN_ATTR_RW(eeprom, W1_F2D_EEPROM_SIZE);
+static BIN_ATTR_RW(eeprom, W1_F14_EEPROM_SIZE);
 
-static struct bin_attribute *w1_f2d_bin_attrs[] = {
+static struct bin_attribute *w1_f14_bin_attrs[] = {
     &bin_attr_eeprom,
     NULL,
 };
 
-static const struct attribute_group w1_f2d_group = {
-    .bin_attrs = w1_f2d_bin_attrs,
+static const struct attribute_group w1_f14_group = {
+    .bin_attrs = w1_f14_bin_attrs,
 };
 
-static const struct attribute_group *w1_f2d_groups[] = {
-    &w1_f2d_group,
+static const struct attribute_group *w1_f14_groups[] = {
+    &w1_f14_group,
     NULL,
 };
 
-static const struct w1_family_ops w1_f2d_fops = {
-    .groups        = w1_f2d_groups,
+static const struct w1_family_ops w1_f14_fops = {
+    .groups    = w1_f14_groups,
 };
 
-static struct w1_family w1_family_2d = {
-    .fid = W1_EEPROM_DS2431,
-    .fops = &w1_f2d_fops,
+static struct w1_family w1_family_14 = {
+    .fid = W1_EEPROM_DS2430,
+    .fops = &w1_f14_fops,
 };
-module_w1_family(w1_family_2d);
+module_w1_family(w1_family_14);
 
-MODULE_AUTHOR("Bernhard Weirich <[email protected]>");
-MODULE_DESCRIPTION("w1 family 2d driver for DS2431, 1kb EEPROM");
+MODULE_AUTHOR("Angelo Dureghello <[email protected]>");
+MODULE_DESCRIPTION("w1 family 14 driver for DS2430, 256b EEPROM");
 MODULE_LICENSE("GPL");
-MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2431));
+MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2430));

Come si puo notare, le differenze sono per lo piu sui nomi delle funzioni,
e un po' di codice diverso, ma minimale.

Come si legge dal commento iniziale, importante mantere copyright / autore del driver da cui si copia.

5) Git commit e controllo

Ora, committare il codice, in genere,

Bash:
git commit -a -s
Su come titolare il commit, verificare i commit precedenti su un driver simile (git log file).

Verificare non ci siano problemi di stile, gnenerare la patch e verificarla:
Bash:
git format-patch HEAD~1
scripts/checkpatch.pl 0001-nomepatch.patch

Se vi sono problemi, sistemarli, quindi
Bash:
git add -u && git commit --amend
dunque rieseguire il controllo di stile.

6) Invio lavoro alla community

Individuare la giusta mailing list da (http://vger.kernel.org/vger-lists.html) o tramite il file MAINTAINERS che si trova nella radice
dei sorgenti, inviare sempre all'amministratore della lista come "to" e la lista in "cc". Opzionale, aggiungere in cc
ogni altro esperto di questo driver o famiglia, nomi ricavabili sempre tramite "git log" su quel file o su quella directory.

Quindi inviare la patch.
Bash:
git send-email --to [email protected] --to [email protected] --cc [email protected] -1 HEAD

Poi sostanzialmente si attendono i feedback, si sistema ogni problema spedendo patch v2, v3 fino ad approvazione finale.