Comment reprogrammer une EEPROM sous Linux (2)

eepromDans un premier billet, nous avons vu qu’il était très simple de reprogrammer une mémoire de type EEPROM sous linux. Malheureusement, nous avions rencontré des soucis d’accès au périphérique que nous devons maintenant contourner. Dans ce second billet, je vous propose donc d’étudier les outils des débogage I2C Tools et leur utilisation sur un système embarqué.

Permission denied

« Permission denied » est probablement l’erreur la plus courante sous Linux. C’est aussi la moins déconcertante: si make me a sandwich ne fonctionne pas, sudo make me a sandwich le devrait. En embarqué, la commande sudo n’a pas de sens puisque nous sommes déjà root. Alors, si ce n’est pas un problème d’utilisateur, pourquoi le système ne nous laisse-t-il pas écrire dans l’EEPROM?

Use the source, Luke!

Allons jeter un œil au code source du pilote. Celui-ci dispose bien des routines pour lire et écrire dans l’EEPROM, mais il y a un élément plus intéressant : l’option AT24_FLAG_READONLY. Le pilote permet en effet aux développeurs de supprimer l’accès en écriture. En définissant cette option, le développeur empêche les applications d’utilisateur de modifier le contenu de la mémoire. L’option peut être déclarée dans des platform_data ou propriété « read-only » pour les systèmes supportant l’OpenFirmware (CONFIG_OF).

Pour faire court, sous Linux, nous pouvons classer les périphériques en deux types : les périphériques découvrables par le matériel (souris USB, carte PCI, …) et ceux qui doivent être « figés dans le code » au niveau du kernel (I2C, SPI, …). Notre mémoire EEPROM I2C tombe dans la deuxième catégorie.

I2C Tools

Si le pilote ne nous permet pas d’écrire, nous avons néanmoins une alternative : envoyer des commandes d’écriture directement sur le bus I2C. C’est la raison d’être de la chaîne d’outils I2C Tools, qui comprend quatre binaires:

  • i2cset envoie des commandes d’écriture à un périphérique,
  • i2cget envoie des commandes de lecture,
  • i2cprobe détecte les périphériques présents sur le bus,
  • et i2cdump lit l’intégralité des registres d’un périphérique.

Problème : je n’ai pas accès au système de compilation de ce système et l’ingénieur matériel qui m’accompagne n’en a pas connaissance non plus. Réfléchissons : sur quel système travaillons-nous?

# cat /proc/cpuinfo
Processor : ARM926EJ-S rev 5 (v5l)

Un ARM9, formidable! Je travaille présentement sur un autre projet équipé de ce CPU. Je devrais pouvoir utiliser la version d’i2c-tools compilée pour ARM de mon projet sur cet équipement. La version de Linux n’est pas la même (2.6.30 contre 3.2.27), mais l’API i2c.h n’a probablement pas bougé entre les deux. Essayons :

# i2cget -y 2 0x54
Error: Could not set address to 0x54: Device or resource busy

Le binaire fonctionne, l’API n’a pas dû bouger.

Opération à coeur ouvert

Évidemment le pilote de l’EEPROM a le contrôle sur ce périphérique et Linux ne nous permet pas de le hacker. Les outils I2C peuvent forcer la porte avec l’option -f, mais il y a un risque de contention du bus. Par exemple, si deux accès concurrents s’entrelacent, ils risquent de corrompre notre périphérique I2C. Nous allons plutôt utiliser la méthode élégante : déconnecter le lien entre le pilote et le périphérique. Cette méthode est disponible depuis linux-2.6.13 et est décrite sur LWN. L’idée est de supprimer le lien qui existe entre l’EEPROM et son pilote : at24. Même de cette façon, il y a un risque de corruption comme le décrit cet échange de la liste de discussion du kernel. Dans notre cas, au pire, une application accédant à l’EEPROM va planter. Jetons un œil à ce lien entre notre EEPROM et son pilote :

# cd /sys/devices/platform/i2c_davinci.2/i2c-2/2-0054/driver
# ls -l
lrwxrwxrwx 1 root root 0 Feb 1 03:43 0-0050 -> ../../../../devices/platform/i2c_davinci.0/i2c-0/0-0050
lrwxrwxrwx 1 root root 0 Feb 1 03:43 2-0054 -> ../../../../devices/platform/i2c_davinci.2/i2c-2/2-0054
--w------- 1 root root 4096 Feb 1 03:43 bind
--w------- 1 root root 4096 Feb 1 03:43 uevent
--w------- 1 root root 4096 Feb 1 03:43 unbind

Ici, on peut voir que le pilote est associé à deux mémoires EEPROM différentes : L’une à l’adresse 0x50 sur le bus i2c-0. L’autre à l’adresse 0x54 sur le bus i2c-2. C’est la deuxième qui nous intéresse. Essayons de retirer le pilote :

# echo 2-0054 > unbind

# i2cdump -y 2 0x54
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 4b 5a 20 20 20 20 20 20 20 20 20 20 20 20 20 20 KZ
10: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
30: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 4e 50 NP
40: 45 4e 30 31 20 20 20 20 20 20 20 20 20 20 20 20 EN01

Bingo! Essayons maintenant d’écrire un octet à l’adresse 0x44, par exemple :

# i2cset -y 2 0x54 0x44 0x5a

# i2cget -y 2 0x54 0x44
0x5a

Bingo! Quoi de plus simple? Désormais nous pouvons écrire un petit script shell ou programme C pour réécrire les octets qui nous intéressent spécifiquement et l’exécuter sur chaque équipement présentant le défaut initial. N’oublions pas de recoudre le patient, c’est à dire de reconnecter le pilote :

# echo 2-0054 > bind
[ 643.972940] at24 2-0054: 512 byte 24c04 EEPROM, writable, 1 bytes/write

Et vérifions que le patient est bien guéri en lisant l’octet modifié :

# hexdump -C 2-0054/eeprom -n 1 -s 0x44
00000044 5a |Z|

Mission réussie grâce à un petit « hack » rapide et efficace. Inutile de brancher un JTAG ou de recompiler quoi que ce soit. Reste à convaincre le chef de projet que la solution logicielle vaut le coup, ce qui risque d’être une autre histoire…

Comment reprogrammer une EEPROM sous Linux

EEPROMDans ce premier billet, je vous propose de démontrer qu’il est très simple de lire et écrire une EEPROM sous Linux. Malheureusement, en embarqué, les problèmes les plus simples se compliquent toujours. C’est pourquoi, dans un 2e billet, nous verrons comment contourner les petits soucis à l’aide de quelques connaissances au niveau de l’architecture matérielle, des pilotes linux et du bus i2c. Tout cela sans aucune compilation, mais avec un peu d’huile de coude.

Un problème?

Il y a quelques jours, un ingénieur matériel m’expose un problème délicat. Deux cent équipements ont été déployés mais ne fonctionnent pas car ils sont mal configurés. La configuration en question se trouve sur une mémoire non-volatile programmable de type EEPROM. Habituellement, les techniciens suivent la procédure qui suit:

  1. Démonter l’équipement de son support
  2. Démonter le boîtier
  3. Connecter un émulateur JTAG à l’équipement
  4. Reprogrammer la mémoire à l’aide de la connexion JTAG
  5. Remonter le boîtier
  6. Remonter l’équipement sur son support
  7. goto 1 et répéter pour les 200 équipements.

Ouch! Voilà qui risque d’être long! Bonne nouvelle, cependant : le système roule un Linux plutôt récent (2.6.30). On devrait donc pouvoir faire plus subtil en utilisant la méthode logicielle.

I-squared-C

Les EEPROM sont le plus souvent interfacées sur un bus i2c. L’i2c est un bus à 2 fils, normalisé par Philips en 1992, dont le protocole de communication maître/esclave est simple et très bien supporté par Linux. Dans un système Linux, les périphériques i2c se trouvent dans /sys/bus/i2c/devices.

# ls -l /sys/bus/i2c/devices
total 0
lrwxrwxrwx 1 root root 0 Feb 11 01:49 0-004c -> ../../../devices/e0000000.soc/e0003000.i2c/i2c-0/0-004c
lrwxrwxrwx 1 root root 0 Feb 11 01:49 0-0050 -> ../../../devices/e0000000.soc/e0003000.i2c/i2c-0/0-0050
lrwxrwxrwx 1 root root 0 Feb 11 01:49 0-0068 -> ../../../devices/e0000000.soc/e0003000.i2c/i2c-0/0-0068
lrwxrwxrwx 1 root root 0 Feb 11 01:49 1-0070 -> ../../../devices/e0000000.soc/e0003100.i2c/i2c-1/1-0070
lrwxrwxrwx 1 root root 0 Feb 11 01:49 2-0024 -> ../../../devices/e0000000.soc/e0003100.i2c/i2c-1/i2c-2/2-0024
lrwxrwxrwx 1 root root 0 Feb 11 01:49 2-004c -> ../../../devices/e0000000.soc/e0003100.i2c/i2c-1/i2c-2/2-004c
lrwxrwxrwx 1 root root 0 Feb 11 01:49 2-0054 -> ../../../devices/e0000000.soc/e0003100.i2c/i2c-1/i2c-2/2-0054
lrwxrwxrwx 1 root root 0 Feb 11 01:49 2-0055 -> ../../../devices/e0000000.soc/e0003100.i2c/i2c-1/i2c-2/2-0055
lrwxrwxrwx 1 root root 0 Feb 11 01:49 i2c-0 -> ../../../devices/e0000000.soc/e0003000.i2c/i2c-0
lrwxrwxrwx 1 root root 0 Feb 11 01:49 i2c-1 -> ../../../devices/e0000000.soc/e0003100.i2c/i2c-1
lrwxrwxrwx 1 root root 0 Feb 11 01:49 i2c-2 -> ../../../devices/e0000000.soc/e0003100.i2c/i2c-1/i2c-2

 

Le premier chiffre renseigne le numéro du bus sur lequel le périphérique est connecté (4). Le nombre suivant représente l’adresse hexadécimale (0x54) du périphérique. Celui-ci va répondre aux commandes contenant cette adresse et ignorer les commandes contenant une adresse différente.

Hexadécimal après la virgule

Habituellement les EEPROM sont programmées en hexadécimal, c’est à dire que leur contenu n’est pas lisible par un éditeur de texte classique. Une telle lecture ne retournera pas grand chose d’utile. Par exemple :

# cat /sys/bus/i2c/devices/4-0054/eeprom
♦®♦™♦©♦

 

Il existe cependant de nombreux outils sous Linux pour lire l’hexadécimal, comme hexdumpod ou xxd:

# od -x eeprom
0000000 4b5a 2020 2020 2020 2020 2020 2020 2020
0000020 2020 2020 2020 2020 2020 2020 2020 2020
0000040 2020 2020 2020 2020 2020 2020 2020 4e50
0000080 454e 3031 2020 2020 2020 2020 2020 2020

# hexdump -C eeprom
00000000  4b 5a 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |KZ              |
00000010  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
00000020  20 20 20 20 20 20 20 20  20 20 20 20 20 20 4e 50  |              NP|
00000030  45 4e 30 31 20 20 20 20  20 20 20 20 20 20 20 20  |EN01            |

 

Pour éditer le contenu, il faudrait un éditeur hexadécimal. Malheureusement :

  1. Il est plutôt rare d’avoir ce genre d’outil disponible sur un Linux embarqué
  2. Certaines (vieilles) EEPROM ne peuvent pas être reprogrammées partiellement. On ne peut que tout effacer et tout réécrire.

Nous allons donc employer la méthode suivante :

  • Faire une copie locale
  • La transférer sur la machine de travail
  • Modifier la copie à l’aide d’un éditeur hexadécimal
  • Transférer la copie modifiée sur l’équipement
  • Reprogrammer l’EEPROM à l’aide du fichier modifié.

Pour la copie locale, on va utiliser dd :

# dd if=/sys/bus/i2c/devices/4-0054/eeprom of=/tmp/eeprom.orig conv=direct

 

C’est l’utilisation la plus simple de dd: on copie le contenu du fichier d’entrée (if: input file) vers le fichier de sortie (of: output file). L’option conv=direct empêche l’utilisation de mémoire tampon (cache) pour le transfert. Notez qu’on aurait aussi pu utiliser un simple cp, ici.

Passons au transfert sur la machine de travail. L’équipement est connecté en ethernet et dispose d’un client FTP embarqué (busybox) qui permet de transférer le fichier facilement. Si votre équipement est équipé d’un serveur SSH (dropbear), pensez aussi à scp. Dernièrement, je suis tombé sur un équipement ne disposant ni d’un serveur, ni d’un client ssh ou FTP. Eh oui, ça existe! Heureusement, netcat était présent et la copie a pu se faire ainsi :

(target) $ nc -w 3 192.168.1.22 1234 < ~/tmp/eeprom.orig
(host)   # nc -l -p 1234 > ~/eeprom.orig

 

Place à la modification. Si votre poste de travail est sous Linux, vous pouvez éditer le fichier en mode hexadécimal avec vim etxxd ou l’outil graphique bless. Sous Windows, jetez un oeil à frhed. Sous Mac OS, regardez HexEdit. La modification terminée, on ramène le fichier modifié eeprom.new sur l’équipement. Puis on réécrit l’EEPROM, toujours avec dd:

# dd if=/tmp/eeprom.new of=/sys/bus/i2c/devices/4-0054/eeprom conv=direct

 

Ecran bleu sous Linux

Vous pouvez essayer chez vous, c’est sans danger et cela devrait fonctionner. Dans mon cas, j’ai eu droit à une jolie error: permission denied. Réfléchissons. Nous sommes connectés en tant que root, essayons (naïvement) de changer les droits d’accès:

# chmod 777 /sys/bus/i2c/devices/4-0054/eeprom
# ls -l /sys/bus/i2c/devices/4-0054/eeprom
-rwxrwxrwx 1 root root    0 fév 28 21:57 eeprom

# dd if=/tmp/eeprom.new of=/sys/bus/i2c/devices/4-0054/eeprom conv=direct
error: permission denied

 

ideeOn dirait que cela ne va pas être aussi simple que prévu.

Comment s’en sortir dans ce cas-là?
Avez-vous une idée?

Solution au prochain épisode!