Quantcast
Channel: Linux – Christophe Blaess
Viewing all 31 articles
Browse latest View live

Capitole du Libre 2014

$
0
0

Capitole Du Libre 2014Je présente aujourd’hui à Toulouse une conférence dans le cadre du Capitole du Libre 2014.

Ma présentation s’intitule « Du microcontrôleur au système Linux embarqué – choix d’architectures matérielles et logicielles« . Les slides sont disponibles ici.

Je serai heureux de vous rencontrer à cette occasion.


Le système Overlayfs de Linux 3.18

$
0
0

OverlayfsDans le noyau 3.18 un nouveau système de fichiers est apparu : overlayfs. Je l’avais déjà utilisé à maintes reprises sur des systèmes embarqués, mais cela nécessitait jusqu’alors l’ajout de patches supplémentaires. J’ai eu envie de vérifier si cette fonctionnalité à présent disponible dans le nouveau noyau mainline fonctionnait comme je la connaissais auparavant.

Système Overlayfs

Le système overlayfs n’est pas à proprement parler un véritable système de fichiers, mais un mécanisme de montage permettant de superposer dans un répertoire le contenu de plusieurs autres répertoires. La configuration la plus simple est celle de deux répertoires, appelons-les upper et lower que l’on peut imaginer comme des calques contenant des fichiers. Lorsque l’on veut lire le contenu d’un fichier, on regarde tout d’abord sur le calque supérieur – upper – puis, si aucun fichier ne correspond au nom recherché on ira examiner ensuite le contenu du calque inférieur.

 Overlayfs

Ce qui est particulièrement intéressant dans un contexte embarqué, est le fait que le répertoire lower est considéré comme accessible en lecture seulement. Les modifications que l’on apporte aux fichiers issus de la superposition des deux calques affecteront uniquement le calque supérieur, et jamais celui sous-jacent (qui peut lui même être un montage overlay d’autres répertoires…)

Cela implique une gestion interne assez compliquée :

  • Lorsqu’on veut modifier un fichier du répertoire lower, cela crée tout d’abord une copie dans le répertoire upper qui sera effectivement modifiée. Le fichier de lower ne sera jamais touché.
  • La suppression d’un fichier existant uniquement dans le répertoire upper est assez simple. En revanche, effacer un fichier appartenant à lower met en œuvre une mécanique complexe, car il ne faut pas modifier réellement le répertoire lower (considéré comme accessible en lecture seulement). Le système overlayfs utilise un troisième répertoire de travail, dans lequel il mémorise l’état des éléments de lower qu’il doit faire disparaître de l’empilement des calques

Dans le cas d’un système embarqué, la robustesse face aux erreurs liées aux systèmes de fichiers est une préoccupation constante. Dans ce but, on préfère généralement monter les partitions contenant les fichiers système (/bin, /lib, /etc, /usr…) en lecture seule. Toutefois, il est parfois nécessaire de modifier des éléments de configuration (par exemple le nom d’hôte stocké dans /etc/hostname). La possibilité de disposer d’une partition indépendante que l’on viendra placer en calque supérieur dans un répertoire dont le contenu reste inamovible est très intéressante.

Pour tester le nouveau système de fichiers, j’ai choisi de construire un système minimal avec Buildroot et le nouveau noyau Linux 3.18 pour une cible que je trouve plutôt sympathique : la carte OLinuXino iMX233 d’Olimex (https://www.olimex.com/Products/OLinuXino/iMX233) plus précisément dans sa version « micro ».

 Noyau Linux 3.18 sur OLinuXino iMX233

Pour préparer mon image, j’ai fait appel à Buildroot en utilisant la dernière version, celle de novembre 2014.

[~]$ wget http://buildroot.uclibc.org/downloads/buildroot-2014.11.tar.bz2
[~]$ tar xjf buildroot-2014.11.tar.bz2
[~]$ cd buildroot-2014.11/
[buildroot-2014.11]$ ls configs/
   [...]
atstk100x_defconfig            nitrogen6x_defconfig                 qmx6_defconfig
beaglebone_defconfig           olimex_imx233_olinuxino_defconfig    raspberrypi_defconfig
calao_qil_a9260_defconfig      openblocks_a6_defconfig              s6lx9_microboard_defconfig
   [...]

Nous voyons qu’il existe bien une cible olimex_imx233_olinuxino_defconfig prédéfinie pour Buildroot, vérifions sa configuration…

[buildroot-2014.11]$ make olimex_imx233_olinuxino_defconfig
  [...]
[buildroot-2014.11]$ make menuconfig

Dans le menu de configuration « Kernel« , nous voyons que la version actuellement proposée est le noyau 3.17. Il nous suffit de remplacer cette version par 3.18. Nous devons également activer le support du système overlayfs dans la configuration du noyau :

[buildroot-2014.11]$ make linux-menuconfig

Ceci s’obtient dans le menu « File Systems » en activant l’option « Overlay filesystem support« . Puis nous lançons la compilation avec un simple

[buildroot-2014.11]$ make

Au bout de quelques minutes, nous obtenons :

[buildroot-2014.11]$ ls output/images/
imx23_olinuxino_dev_linux.sb  imx23-olinuxino.dtb  rootfs.ext2  zImage

Nous devons préparer une carte micro-SD pour notre système cible. Il faut nécessairement créer au moins deux partitions, pour accueillir le système de boot et l’arborescence des fichiers. J’ai choisi de rajouter une troisième partition pour le calque supérieur.

  • Partition 1 : 16 Mo. On notera – c’est une particularité de l’OLinuXino iMX233 – qu’il faut obligatoirement que cette partition de démarrage soit dotée du type 53 (hexadécimal).
  • Partition 2 : 32 Mo ext4 pour le système en lecture-seule.
  • Partition 3 : 32 Mo ext4 pour les modifications apportées à la configuration système.

J’insère une carte SD vierge sur mon PC de développement, elle apparaît sous le nom /dev/sdb/ (à vérifier sur votre système !)

[buildroot-2014.11]$ sudo umount /dev/sdb?
[buildroot-2014.11]$ sudo fdisk /dev/sdb

Commande (m pour l'aide) : p
Disque /dev/sdb : 3924 Mo, 3924819968 octets
28 têtes, 40 secteurs/piste, 6844 cylindres, total 7665664 secteurs
Unités = secteurs de 1 * 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets
Identifiant de disque : 0x361d7260

Périphérique Amorçage  Début         Fin      Blocs    Id. Système
/dev/sdb1            2048     7665663     3831808    c  W95 FAT32 (LBA)

Commande (m pour l'aide) : d
Partition sélectionnée 1

Commande (m pour l'aide) : n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Numéro de partition (1-4, 1 par défaut) : 1
Premier secteur (2048-7665663, 2048 par défaut) : (entrée)
Utilisation de la valeur 2048 par défaut
Dernier secteur, +secteurs ou +taille{K,M,G} (2048-7665663, 7665663 par défaut) : +16M
Commande (m pour l'aide) : t
Partition sélectionnée 1
Code Hexa (taper L pour lister les codes): 53
Type système de partition modifié de 1 à 53 (OnTrack DM6 Aux3)
Commande (m pour l'aide) : n
Partition type:
   p   primary (1 primary, 0 extended, 3 free)
   e   extended
Select (default p): p
Numéro de partition (1-4, 2 par défaut) : 2
Premier secteur (34816-7665663, 34816 par défaut) : (Entrée)
Utilisation de la valeur 34816 par défaut
Dernier secteur, +secteurs ou +taille{K,M,G} (34816-7665663, 7665663 par défaut) : +32M

Commande (m pour l'aide) : n
Partition type:
   p   primary (2 primary, 0 extended, 2 free)
   e   extended
Select (default p): p
Numéro de partition (1-4, 3 par défaut) : 3
Premier secteur (100352-7665663, 100352 par défaut) : (Entrée)
Utilisation de la valeur 100352 par défaut
Dernier secteur, +secteurs ou +taille{K,M,G} (100352-7665663, 7665663 par défaut) : +32M
Commande (m pour l'aide) : p

Disque /dev/sdb : 3924 Mo, 3924819968 octets
28 têtes, 40 secteurs/piste, 6844 cylindres, total 7665664 secteurs
Unités = secteurs de 1 * 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets
Identifiant de disque : 0x361d7260

Périphérique Amorçage  Début         Fin      Blocs    Id. Système
/dev/sdb1            2048       34815       16384   53  OnTrack DM6 Aux3
/dev/sdb2           34816      100351       32768   83  Linux
/dev/sdb3          100352      165887       32768   83  Linux

Commande (m pour l'aide) : w
La table de partitions a été altérée.

Appel d'ioctl() pour relire la table de partitions.
Synchronisation des disques.
[buildroot-2014.11]$

Copions les deux images produites par Buildroot sur leurs partitions respectives, et formatons la troisième partition.

[buildroot-2014.11]$ sudo  dd  if=output/images/imx23_olinuxino_dev_linux.sb  of=/dev/sdb1  bs=512  seek=4
  [...]
[buildroot-2014.11]$ sudo  dd  if=output/images/rootfs.ext2  of=/dev/sdb2  bs=1M
  [...]
[buildroot-2014.11]$ sudo  mkfs.ext4  /dev/sdb3 
mke2fs 1.42.9 (4-Feb-2014)
Étiquette de système de fichiers=
Type de système d'exploitation : Linux
Taille de bloc=1024 (log=0)
[...]

On peut alors démarrer le système OLinuXino iMX233 en observant les traces sur la console série.

PowerPrep start initialize power...
Battery Voltage = 0.92V
No battery or bad battery detected!!!.Disabling battery
EMI_CTRL 0x1C084040
FRAC 0x92926192
init_ddr_mt46v32m16_133Mhz
power 0x00820710
Frac 0x92926192
start change cpu freq
hbus 0x00000003
cpu 0x00010001
LLLLLLLFCLJ[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 3.18.0 (cpb@TR-B-01) (gcc version 4.8.3 (Buildroot 2014.11) ) #1 Sat Dec 13 16:37:23 CET 2014
[    0.000000] CPU: ARM926EJ-S [41069265] revision 5 (ARMv5TEJ), cr=0005317f
[    0.000000] CPU: VIVT data cache, VIVT instruction cache
[    0.000000] Machine model: i.MX23 Olinuxino Low Cost Board
[    0.000000] Memory policy: Data cache writeback
[    0.000000] Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 16256
[    0.000000] Kernel command line: console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw rootwait
[    0.000000] PID hash table entries: 256 (order: -2, 1024 bytes)
[...]
[    1.730000] EXT4-fs (mmcblk0p2): re-mounted. Opts: errors=remount-ro
Starting logging: OK
Starting mdev...
Initializing random number generator... [    2.510000] random: dd urandom read with 5 bits of entropy available
done.
Starting network...

Welcome to Buildroot
buildroot login: root

Mise en place de l’overlay

Je commence par créer un point de montage spécifique pour le calque supérieur de mon montage overlay.

# mkdir /data

Je préfère remonter tout de suite ma partition système en lecture-seule.

# mount / -o ro,remount
[  129.290000] EXT4-fs (mmcblk0p2): re-mounted. Opts: errors=remount-ro

Vérifions que le contenu du répertoire /etc est accessible en lecture seulement.

# cat /etc/hostname
buildroot
# echo HELLO > /etc/hostname
-sh: can't create /etc/hostname: Read-only file system

Je vais monter la partition numéro 3 sur /data et y créer les répertoires indispensables. Sur l’OLinuXino, la carte SD est vue comme un périphérique bloc /dev/mmcblk0 contrairement au PC qui la voyait en /dev/sdb (par l’intermédiaire d’un lecteur USB).

# mount /dev/mmcblk0p3 /data/ -t ext4
[  182.390000] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null)
# mkdir /data/etc
# mkdir /data/work

Enfin nous pouvons réaliser le montage overlay de /data/etc en superposition sur /etc, tout en utilisant /data/work comme répertoire de travail interne.

# mount none -t overlayfs -o lowerdir=/etc,upperdir=/data/etc,workdir=/data/work /etc

Essayons de consulter puis modifier un fichier /etc/.

# cat /etc/hostname
buildroot
# echo OLinuXino > /etc/hostname
# cat /etc/hostname
OLinuXino

La modification semble normale. Essayons d’y ajouter un nouveau fichier.

# ls /etc/
fstab         init.d        ld.so.conf    network       protocols     services
group         inittab       ld.so.conf.d  os-release    random-seed   shadow
hostname      inputrc       mdev.conf     passwd        resolv.conf
hosts         issue         mtab          profile       securetty
# echo HELLO > /etc/new-file
# ls /etc/
fstab         init.d        ld.so.conf    network       profile       securetty
group         inittab       ld.so.conf.d  new-file      protocols     services
hostname      inputrc       mdev.conf     os-release    random-seed   shadow
hosts         issue         mtab          passwd        resolv.conf

Le fichier est bien visible. Mais qu’en est-il du répertoire /etc sous-jacent ? Pour le savoir, démontons l’architecture overlay et vérifions.

# umount /etc
# ls /etc/
fstab         init.d        ld.so.conf    network       protocols     services
group         inittab       ld.so.conf.d  os-release    random-seed   shadow
hostname      inputrc       mdev.conf     passwd        resolv.conf
hosts         issue         mtab          profile       securetty
# cat /etc/hostname
buildroot

Le répertoire inférieur est donc intact. Où sont stockées les modifications que nous avons réalisées ? Naturellement, dans /data/etc.

# ls /data/etc/
hostname  new-file
# cat /data/etc/hostname
OLinuXino
# cat /data/etc/new-file
HELLO

Montage overlay dès le boot

Je souhaite que la partition racine soit montée en lecture seule, et que le répertoire /etc soit accessible en overlay dès le boot. Pour cela je dois modifier deux fichiers système qui se trouvent dans /etc. Le premier est inittab.

/etc/inittab:
[...]
# Startup the system
null::sysinit:/bin/mount -t proc proc /proc
#null::sysinit:/bin/mount -o remount,rw /
null::sysinit:/bin/mkdir -p /dev/pts
null::sysinit:/bin/mkdir -p /dev/shm
null::sysinit:/bin/mount -a                                                   
null::sysinit:/bin/mount none -t overlay -o lowerdir=/etc,upperdir=/data/etc,workdir=/data/work /etc
null::sysinit:/bin/hostname -F /etc/hostname
[...]

J’ai ajouté un # pour mettre en commentaire la ligne qui remonte / en lecture-écriture, et une ligne de montage overlay avant celle qui invoque hostname. En outre je modifie la table des systèmes de fichiers à monter automatiquement.

/etc/fstab:
#                 
/dev/root       /              ext2     ro,noauto         0      1
proc            /proc          proc     defaults          0      0
devpts          /dev/pts       devpts   defaults,gid=5,mode=620   0      0
tmpfs           /dev/shm       tmpfs    mode=0777         0      0
tmpfs           /tmp           tmpfs    mode=1777         0      0
sysfs           /sys           sysfs    defaults          0      0
/dev/mmcblk0p3  /data          ext4     defaults          0      0

J’ai remplacé une option ‘rw‘ par ‘ro‘ dans le montage de /dev/root et une ligne pour monter la troisième partition sur /data.

Je reboote le système :

[...]
[    1.160000] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null)
Starting logging: OK
Starting mdev...
Initializing random number generator... [    1.940000] random: dd urandom read with 6 bits of entropy available
done.
Starting network...

Welcome to Buildroot
OLinuXino login:

Visiblement, le nom d’hôte (juste avant « login« )  a été fixé en lisant bien le fichier hostname du calque supérieur. Toutefois, un souci persiste.

OLinuXino login: root
# echo HELLO > /abcd
# cat /abcd
HELLO
# rm /abcd

Notre partition racine n’est pas montée en lecture seulement, malgré notre configuration. Ceci est dû aux options que le noyau reçoit au démarrage.

# cat /proc/cmdline
console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw rootwait

L’option « rw » lui indique de monter la racine en lecture-écriture. Nous devons modifier cette option.

Partition système en lecture-seule

La première chose à faire lorsqu’on désire conserver une partition système en lecture seulement, est de s’assurer que le noyau ne la montera pas automatiquement en lecture-écriture. Pour cela, retournons dans la configuration du kernel.

[buildroot-2014.11]$ make linux-menuconfig

Dans le menu « Boot Options« , je modifie l’option « Default kernel command string » pour

(console=ttyAMA0,115200 root=/dev/mmcblk0p2 ro rootwait) Default kernel command string

(on notera le ro à la place de rw). En outre, par précaution, je modifie l’option « Kernel command line type » en

 (X) Always use the default kernel command string

Après avoir quitté ce menu de configuration, je m’assure que Buildroot va recréer l’image de la partition de boot, et je relance la compilation.

[buildroot-2014.11]$ rm output/build/mxs-bootlets-10.12.01/.stamp_built
[buildroot-2014.11]$ make

Après installation de la nouvelle version, le boot est conforme à nos attentes.

[    0.970000] VFS: Mounted root (ext2 filesystem) readonly on device 179:2.
[    0.990000] devtmpfs: mounted
[    0.990000] Freeing unused kernel memory: 148K (c0527000 - c054c000)
[    1.160000] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null)
Starting logging: OK
Starting mdev...
Initializing random number generator... [    1.940000] random: dd urandom read with 6 bits of entropy available
done.
Starting network...

Welcome to Buildroot
OLinuXino login: root
# echo HELLO > abcd
-sh: can't create abcd: Read-only file system
#

Conclusion

Nous disposons ainsi facilement d’un système dont la configuration « usine » reste inamovible dans le répertoire /etc inférieur tandis que les modifications de l’utilisateur restent sur le répertoire supérieur. Le retour aux paramètres « factory defaults » se fait très facilement en effaçant (reformatant éventuellement) la partition 3.

J’ai utilisé ici un montage automatique de cette dernière via le fichier /etc/fstab. Sur un véritable système embarqué, je préférerais un montage par script qui vérifie d’abord l’intégrité de la partition et la reformate si elle est défectueuse (ou bascule sur une version de secours).

6 fois plus rapide ? vraiment ?

$
0
0

Raspberry Pi 2Le nouveau Raspberry Pi 2 modèle B est disponible depuis le début de la semaine dernière. Et les distributeurs ont été suffisamment approvisionnés pour pouvoir répondre aux demandes. J’en ai commandé deux exemplaires lundi dernier à l’annonce de sa mise sur le marché, et j’ai été livré dès mardi matin ! L’un des arguments de vente est « 4 à 6 fois plus rapide que le précédent ». C’est le genre de chose qui titille ma curiosité, j’ai voulu vérifier ce qu’il en est…

Comparaison avec le modèle précédent

Très peu de différences externes entre le modèle 2 et le modèle 1 B+. Les connecteurs et les composants actifs se trouvent aux mêmes emplacements, et même le routage des pistes électroniques a peu changé dans l’essentiel.

On remarque néanmoins un nouveau system-on-chip, le Broadcom 2836 au lieu du 2835. Celui-ci intègre un processeur Arm quadri-cœur.
2015-02-03 11.22.24

On peut observer également un bloc de mémoire Ram DDR2 situé sur le dessous du Raspberry Pi, alors que dans les précédents modèles la mémoire se trouvait collée directement sous le system-on-chip.
B8132B4PB-8D-F

Enfin, la sérigraphie indique le numéro de ce nouveau Raspberry Pi.
Raspberry Pi 2 Model B V1.1

Support sous Linux

Je n’ai essayé sur ce nouveau Raspberry Pi 2 que la distribution Raspbian 2015-01-31. Il est nécessaire de prendre la version la plus récente pour que le support du multicœur soit activé dans le noyau.

Voici quelques commandes systèmes permettant d’avoir un aperçu du support par le noyau Linux.

pi@raspberrypi ~ $ uname -a
Linux raspberrypi 3.18.5-v7+ #225 SMP PREEMPT Fri Jan 30 18:53:55 GMT 2015 armv7l GNU/Linux

Le noyau est très récent. C’est la dernière version stable (ah tiens, non, le noyau 3.19 vient de sortir ce matin !).

pi@raspberrypi ~ $ free
             total       used       free     shared    buffers     cached
Mem:        762384      60252     702132          0       8772      22420
-/+ buffers/cache:      29060     733324
Swap:       102396          0     102396

Le système est doté d’un Go de Ram (une partie est réservée par le noyau). Sur le modèle précédent, la même commande donne :

pi@raspberrypi ~ $ free
             total       used       free     shared    buffers     cached
Mem:        445772      50056     395716          0       7192      21108
-/+ buffers/cache:      21756     424016
Swap:       102396          0     102396

Voyons le processeur :

pi@raspberrypi ~ $ lscpu 
Architecture:          armv7l
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    1
Core(s) per socket:    4
Socket(s):             1
pi@raspberrypi ~ $

Un processeur, avec 4 cœurs physiques indépendants. Voyons, ses possibilités de réglage de la fréquence de fonctionnement.

pi@raspberrypi ~ $ cd /sys/devices/system/cpu/
pi@raspberrypi /sys/devices/system/cpu $ ls
cpu0  cpu1  cpu2  cpu3  cpufreq  cpuidle  kernel_max  offline  online  possible  power  present  uevent

Les quatre cœurs sont bien réglables séparément.

pi@raspberrypi /sys/devices/system/cpu $ cd cpu0/
pi@raspberrypi /sys/devices/system/cpu/cpu0 $ ls
cpufreq  crash_notes  crash_notes_size  online  power  subsystem  topology  uevent
pi@raspberrypi /sys/devices/system/cpu/cpu0 $ cd cpufreq/
pi@raspberrypi /sys/devices/system/cpu/cpu0/cpufreq $ ls
affected_cpus     cpuinfo_max_freq  cpuinfo_transition_latency  scaling_available_frequencies  scaling_cur_freq  scaling_governor  scaling_min_freq
cpuinfo_cur_freq  cpuinfo_min_freq  related_cpus                scaling_available_governors    scaling_driver    scaling_max_freq  scaling_setspeed
pi@raspberrypi /sys/devices/system/cpu/cpu0/cpufreq $ cat scaling_min_freq 
600000
pi@raspberrypi /sys/devices/system/cpu/cpu0/cpufreq $ cat scaling_max_freq
900000
pi@raspberrypi /sys/devices/system/cpu/cpu0/cpufreq $ cat scaling_cur_freq 
600000
pi@raspberrypi /sys/devices/system/cpu/cpu0/cpufreq $ cat scaling_governor
ondemand
pi@raspberrypi /sys/devices/system/cpu/cpu0/cpufreq $

Le réglage étant sur ondemand, la fréquence (actuellement à 600 MHz) évoluera automatiquement pour monter si besoin à 900 MHz.Il est possible de la faire varier manuellement, pour en savoir plus, voir cet article. Avec le précédent modèle, la fréquence était figée à 700MHz.

Il est toujours possible de faire un overclocking du processeur, comme avec le modèle précédent, en utilisant la commande sudo raspi-config. La fréquence CPU pourra alors monter jusqu’à 1GHz. Attention toutefois, il est bien précisé que cela peut réduire la durée de vie du processeur (dans la pratique je n’en suis pas persuadé).

Comparaison de puissance processeur

Il existe de nombreux benchmarks pour évaluer la puissance d’un processeur, mais j’aime bien mener mes propres tests avec des situations représentatives de ce que je fais au quotidien sur les systèmes que j’utilise.

Puissance monocœur

Pour avoir une idée de la puissance de calcul brute d’un CPU, il y a une possibilité très simple : lui demander de calculer des décimales de Pi (le nombre Pi, pas le Raspberry !). C’est extrêmement simple à faire depuis le shell en invoquant la commande bc et en lui demandant d’afficher 4 * a(1). La fonction a() de bc calcule l’arc-tangente. Or arctan(1) = PI/4.
L’intérêt de bc, c’est que l’on peut lui préciser le nombre de décimales désirées dans sa variable scale. Ici je lui en demande 10.000, et j’encadre l’invocation avec la commande date pour connaître sa durée.

pi@raspberrypi ~/rpi-kernel $ date; echo "scale=10000; 4*a(1)" | bc -l; date
Sat Feb  7 00:26:00 UTC 2015
3.141592653589793238462643383279502884197169399375105820974944592307\
81640628620899862803482534211706798214808651328230664709384460955058\
22317253594081284811174502841027019385211055596446229489549303819644\
[...]
83440869735020141020672358502007245225632651341055924019027421624843\
91403599895353945909440704691209140938700126456001623742880210927645\
79310657922955249887275846101264836999892256959688159205600101655256\
375676
Sat Feb  7 00:41:30 UTC 2015

Le calcul a duré 14 minutes et 30 secondes.

Effectuons la même opération sur un Raspberry Pi 1 modèle B+

pi@raspberrypi ~/rpi-kernel $ date; echo "scale=10000; 4*a(1)" | bc -l; date
Sun Feb  8 18:14:43 UTC 2015
3.141592653589793238462643383279502884197169399375105820974944592307\
81640628620899862803482534211706798214808651328230664709384460955058\
22317253594081284811174502841027019385211055596446229489549303819644\
[...]
83440869735020141020672358502007245225632651341055924019027421624843\
91403599895353945909440704691209140938700126456001623742880210927645\
79310657922955249887275846101264836999892256959688159205600101655256\
375676
Sun Feb  8 18:39:03 UTC 2015

Cette fois le calcul a duré 24 minutes et 20 secondes.

Pour un calcul brut monocœur, le Raspberry Pi 2 est 1,66 fois (5 / 3) plus rapide que son prédécesseur. C’est un peu meilleur que le simple rapport des fréquences CPU (900/700).

Puissance multicœur

La grande nouveauté du Raspberry Pi 2 est son aspect multicœur. Ceci peut grandement améliorer les performances lorsque plusieurs opérations doivent s’exécuter simultanément et peuvent être parallélisées. Ma référence en ce domaine est la compilation de gros projets avec un make paralléle (option -j suivie du nombre de jobs simultanés).

Fidèle à mes habitudes, j’ai testé ceci sur une compilation du noyau Linux. Il faut commencer par ajouter deux packages sur la distribution Raspbian.

pi@raspberrypi ~/rpi-kernel $ sudo apt-get install bc libncurses5-dev

Je télécharge les sources du noyau modifié pour Raspberry Pi.

pi@raspberrypi ~ $ date ; git clone http://github.com/raspberrypi/linux rpi-kernel ; date
Fri Feb  6 07:34:24 UTC 2015
Cloning into 'rpi-kernel'...
[...]
Fri Feb  6 08:21:15 UTC 2015

Le téléchargement dure un peu moins d’une heure, le facteur limitant est naturellement le débit réseau global mais également celui du contrôleur Ethernet du Raspberry Pi 2 (LAN 9514 qui n’a pas changé par rapport au modèle précédent). Avec une carte réseau et une liaison ADSL correctes, le transfert est plutôt de 10 à 15 minutes. Je configure la compilation pour un noyau standard pour Raspberry Pi.

pi@raspberrypi ~/rpi-kernel $ make bcmrpi_defconfig

Je lance la compilation en demandant un total permanent de huit jobs en parallèle (c’est une bonne habitude de compter un à deux jobs par cœur).

pi@raspberrypi ~/rpi-kernel $ date ; make -j 8 ; date
Fri Feb  6 22:16:01 UTC 2015
[...]
Sat Feb  7 00:00:56 UTC 2015
pi@raspberrypi ~/rpi-kernel $

La compilation prend 1h45. C’est long, mais il s’agit d’une configuration pour un noyau complet, avec beaucoup d’options activées.

Pour comparaison, je lance la même opération sur le Raspberry Pi 1 modèle B+.

pi@raspberrypi ~/rpi-kernel $ date; make -j2 ; date
Sat Feb  7 21:54:33 UTC 2015
[...]
Sun Feb  8 10:27:53 UTC 2015
pi@raspberrypi ~/rpi-kernel $

12h33 de compilation !

Dans ce cas, en effet le Raspberry Pi 2 a été 7 fois plus rapide que le modèle 1. La différence est due au nombre de cœurs (probablement dans un rapport 4 environ), mais aussi à la différence de mémoire RAM, ce qui influe sensiblement sur les compilations (taille des caches, etc.).

Conclusion

Mon utilisation courante du Raspberry Pi (tests de drivers, bidouilles sur les GPIO, serveurs HTTP, outils de démo pour des applications réseau, etc.) se déroule essentiellement en console texte, avec une connexion sur l’interface série ou par SSH.

Je verrai une différence de rapidité surtout lors de compilations longues qu’il m’arrive de réaliser pour des sessions de formations sur la programmation de drivers Linux.

Pour l’utilisateur qui emploie l’interface graphique du système, on peut espérer également un gain notable car cet environnement pourra alors tirer parti de la parallélisation : le navigateur, le lecteur vidéo, l’environnement graphique proprement dit, les serveurs réseau, etc. pourront s’exécuter sur des cœurs distincts et rendre le système plus fluide.

Commentaires, remarques et retours d’expériences sont les bienvenus.

Linux 4

$
0
0

Le noyau stable actuellement disponible en téléchargement sur kernel.org est le 3.19.

Linus a fait un petit sondage récemment et en conséquence le prochain noyau sera un 4.0 !

On peut d’ores et déjà télécharger sur kernel.org la version release candidate 4.0-rc1.

Il n’y a aucun changement majeur dans la structure du noyau ou dans son organisation, seule la numérotation subit ainsi un petit rafraîchissement, comme lors du passage du 2.6.18 au 3.0.

La justification de Linus, plutôt tirée par les cheveux, est qu’il souhaite pouvoir compter les versions intermédiaires sur ses doigts de mains et de pieds. Se limiter donc à vingt versions (en comptant la .0). Il voudrait d’ailleurs ne plus dépasser dix versions pour « ne plus avoir à retirer ses chaussettes pour compter les releases » (sic).

Certains attendent déjà avec impatience la version 4.1.15 que l’on aperçoit dans les traces du système Skynet de la saga Terminator.

Linux 4.1.15

Meetup Paris Embedded – 2015/03

$
0
0

Paris EmbeddedCe soir se tenait une édition des rendez-vous « Paris Embedded » à laquelle j’ai eu le plaisir de participer.

Les conférences de cette soirée :

  • Créer sa distribution embarquée avec Yocto ou Angström (Christian Charreyre)
  • Actualité de l’embarqué libre (moi-même) – Les slides commentés se trouvent ici.
  • Embedded Map : Cartographie communautaire des sociétés de l’embarqué (Yoann Sculo)
  • Twiz : outil de mesure magnetico-inertiel open source (Gabriel Pettier)
  • The seccomp() system, sandboxing system calls (Michael Kerrisk)

Comme c’est l’usage, les conférences étaient suivie d’une phase conviviale pizzas-bières pendant laquelle j’ai eu le plaisir de revoir beaucoup d’amis.

Prochain rendez-vous, le jeudi 18 juin, pour une thématique orientée vers Android.

N’hésitez pas à vous inscrire.

Conférence « Solutions pour Linux embarqué – Panorama et critères de choix »

$
0
0

J’ai le plaisir de présenter cet après-midi une conférence intitulée « Solutions pour Linux embarqué – Panorama et critères de choix« .

Organisée par Cap’tronic , elle aura lieu à Tours dans les locaux de Polytech’Tours de 14h à 17h30 environ. L’accueil est organisé à partir de 13h30.

Vous pouvez consulter les slides.

Plusieurs démonstrations utilisant des microcontrôleurs (MSP 430) ou systèmes monocartes (BeagleBone Black et Raspsberry Pi) seront présentées.

Vous pouvez télécharger également les fichiers de configuration et sources des démonstrations.

Renforcer une distribution Raspbian Jessie

$
0
0

Raspbian Jessie

Depuis quelques jours une nouvelle distribution Raspbian est disponible pour le Raspberry Pi : la version Jessie. Il s’agit de l’adaptation de la distribution Debian 8 sortie au printemps.

L’avantage de Debian est de disposer aisément d’un très large éventail d’applications, utilitaires, bibliothèques pré-configurés et faciles à installer. En outre il s’agit en quelque sorte de la distribution de référence lorsqu’on parle d’un système Linux.

L’inconvénient, à mes yeux, de Raspbian est qu’elle est prévue pour une utilisation « desktop » qui convient très bien pour un PC mais pas vraiment pour un système embarqué. Je lui reproche entre autre de ne pas être très robuste vis-à-vis des coupures d’alimentation. Mais rien ne nous empêche de la configurer comme un système embarqué classique. Essayons…

Premier boot, premières impressions

Je démarre la distribution Raspbian Jessie sur un Raspberry Pi modèle 1 B, et j’observe les traces de boot sur la console série.

[    0.000000] Linux version 4.1.7+ (dc4@dc4-XPS13-9333) (gcc version 4.8.3 20140303 (prerelease) (crosstool-NG linaro-1.13.1+bzr2650 - Linaro GCC 2014.03) ) #817 PREEMPT Sat Sep 19 15:25:36 BST 2015
[    0.000000] CPU: ARMv6-compatible processor [410fb767] revision 7 (ARMv7), cr=00c5387d
[    0.000000] CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
[    0.000000] Machine model: Raspberry Pi Model B Rev 2

Le modèle est bien identifié (Raspberry Pi Model B), et nous voyons que le noyau est plutôt récent (4.1.7), compilé le 19 septembre 2015. Il s’agit de la 817ème compilation depuis l’extraction des sources.

Je me demande si cette valeur est bien réelle. Le noyau 4.1.7 a été publié sur www.kernel.org le 13 septembre, cela représente donc une moyenne de 136 compilations par jour ce qui est très intensif, même dans le cas, d’un portage sur une nouvelle plate-forme !

Au bout de quelques secondes de boot, nous apercevons la fin des messages :

[    3.552810] systemd[1]: Starting Forward Password Requests to Wall Directory Watch.
[    3.565385] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
[    3.577346] systemd[1]: Expecting device dev-ttyAMA0.device...
[    3.588483] systemd[1]: Starting Remote File Systems (Pre).
[    3.599047] systemd[1]: Reached target Remote File Systems (Pre).
[    3.607838] systemd[1]: Starting Arbitrary Executable File Formats File System Automount Point.
[    3.624897] systemd[1]: Set up automount Arbitrary Executable File Formats File System Automount Point.
[    3.638953] systemd[1]: Starting Encrypted Volumes.
[    3.649175] systemd[1]: Reached target Encrypted Volumes.
[    3.657151] systemd[1]: Starting Swap.
[    3.666030] systemd[1]: Reached target Swap.
[    3.672566] systemd[1]: Expecting device dev-mmcblk0p1.device...
[    3.683667] systemd[1]: Starting Root Slice.
[    3.692723] systemd[1]: Created slice Root Slice.
[    3.699726] systemd[1]: Starting User and Session Slice.
[    3.710168] systemd[1]: Created slice User and Session Slice.
[    3.718224] systemd[1]: Starting /dev/initctl Compatibility Named Pipe.
[    3.730063] systemd[1]: Listening on /dev/initctl Compatibility Named Pipe.
[    3.739330] systemd[1]: Starting Delayed Shutdown Socket.
[    3.749556] systemd[1]: Listening on Delayed Shutdown Socket.
[    3.757494] systemd[1]: Starting Journal Socket (/dev/log).
[    3.767964] systemd[1]: Listening on Journal Socket (/dev/log).
[    3.776167] systemd[1]: Starting udev Control Socket.
[    3.786159] systemd[1]: Listening on udev Control Socket.
[    3.793864] systemd[1]: Starting udev Kernel Socket.
[    3.803610] systemd[1]: Listening on udev Kernel Socket.
[    3.811114] systemd[1]: Starting Journal Socket.
[    3.820696] systemd[1]: Listening on Journal Socket.
[    3.828070] systemd[1]: Starting System Slice.
[    3.837506] systemd[1]: Created slice System Slice.
[    3.844769] systemd[1]: Starting File System Check on Root Device...
[    3.860653] systemd[1]: Starting system-systemd\x2dfsck.slice.
[    3.882537] systemd[1]: Created slice system-systemd\x2dfsck.slice.
[    3.897474] systemd[1]: Starting system-autologin.slice.
[    3.915110] systemd[1]: Created slice system-autologin.slice.
[    3.924527] systemd[1]: Starting system-serial\x2dgetty.slice.
[    3.939584] systemd[1]: Created slice system-serial\x2dgetty.slice.
[    3.949190] systemd[1]: Starting Increase datagram queue length...
[    3.971123] systemd[1]: Starting Restore / save the current clock...
[    4.011520] systemd[1]: Mounting POSIX Message Queue File System...
[    4.045988] systemd[1]: Mounted Huge Pages File System.
[    4.114605] systemd[1]: Mounting Debug File System...
[    4.220232] systemd[1]: Started Set Up Additional Binary Formats.
[    4.279891] systemd[1]: Starting Load Kernel Modules...
[    4.312892] systemd[1]: Starting udev Coldplug all Devices...
[    4.358785] systemd[1]: Starting Create list of required static device nodes for the current kernel...
[    4.403001] fuse init (API version 7.23)
[    4.434562] systemd[1]: Starting Slices.
[    4.462789] systemd[1]: Reached target Slices.
[    4.523991] systemd[1]: Mounted Debug File System.
[    4.545602] systemd[1]: Mounted POSIX Message Queue File System.
[    4.569828] i2c /dev entries driver
[    4.588558] systemd[1]: Started File System Check on Root Device.
[    4.627558] systemd[1]: Started Increase datagram queue length.
[    4.641169] systemd[1]: Started Restore / save the current clock.
[    4.657331] systemd[1]: Started Load Kernel Modules.
[    4.685012] systemd[1]: Started Create list of required static device nodes for the current kernel.
[    4.723014] systemd[1]: Time has been changed
[    4.883663] systemd[1]: Started udev Coldplug all Devices.
[    5.095077] systemd[1]: Starting Create Static Device Nodes in /dev...
[    5.112404] systemd[1]: Starting Apply Kernel Variables...
[    5.146732] systemd[1]: Mounting Configuration File System...
[    5.199784] systemd[1]: Mounting FUSE Control File System...

Clairement, systemd a pris beaucoup d’ampleur dans cette version de la distribution Raspbian. Comme beaucoup de Linux « historiques », je ne suis pas très enthousiaste devant ce système, tout particulièrement dans le domaine de l’embarqué. Je le trouve plutôt complexe et difficile à maîtriser. Pour des systèmes restreints, que l’on doit ajuster précisément pour un besoin particulier, je trouve plus aisé de reposer sur quelques scripts shell, plutôt que sur un enchevêtrement de services imbriqués et peu configurables.

raspberrypi login: pi
Password: (raspberry)

Je suis connecté sur la console série du système (sur les broches 8 et 10 du Connecteur P1). Cet accès paraît certainement un peu aride à la plupart des utilisateurs, mais étant habitué à travailler sur des systèmes embarqués restreints, je suis plus à l’aise sur le Raspberry Pi en console texte que dans un terminal graphique qui « rame » un peu sur les modèles 1B et 1B+.

Utilisons quelques commandes d’administration simples pour examiner le système.

$ mount
/dev/mmcblk0p2 on / type ext4 (rw,noatime,data=ordered)
devtmpfs on /dev type devtmpfs (rw,relatime,size=218252k,nr_inodes=54563,mode=755)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct)
mqueue on /dev/mqueue type mqueue (rw,relatime)
debugfs on /sys/kernel/debug type debugfs (rw,relatime)
configfs on /sys/kernel/config type configfs (rw,relatime)
fusectl on /sys/fs/fuse/connections type fusectl (rw,relatime)
/dev/mmcblk0p1 on /boot type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=44508k,mode=700,uid=1000,gid=1000)
gvfsd-fuse on /run/user/1000/gvfs type fuse.gvfsd-fuse (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000)
$

Il y a un nombre important de montage de systèmes de fichiers virtuels, notamment dans la hiérarchie des Control Groups. Leur présence sur un système embarqué peut être discutable.

$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/root        4031552 3197560    609480  84% /
devtmpfs          218252       0    218252   0% /dev
tmpfs             222520       0    222520   0% /dev/shm
tmpfs             222520    4548    217972   3% /run
tmpfs               5120       4      5116   1% /run/lock
tmpfs             222520       0    222520   0% /sys/fs/cgroup
/dev/mmcblk0p1     57288   20232     37056  36% /boot
tmpfs              44508       0     44508   0% /run/user/1000

Il reste un peu de place dans le système de fichiers principal. Tant mieux car je ne compte pas l’agrandir pour le moment.

Désactivation du swap

$ free
             total       used       free     shared    buffers     cached
Mem:        445044     149644     295400       4960      17156      77196
-/+ buffers/cache:      55292     389752
Swap:       102396          0     102396

Horreur ! une zone de swap est réservée sur la carte SD. C’est une mauvaise idée, car dans le cas d’un système saturé on va réaliser un très grand nombre de lectures et d’écritures sur la mémoire Flash, ce qui peut avoir comme effet un vieillissement prématuré. Je conseille très fortement de désactiver ce mécanisme. Pour cela le plus simple, est de désinstaller le package dphys-swapfile utilisé pour le support du swap intégré dans un système de fichiers.

$ sudo apt-get remove dphys-swapfile
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages will be REMOVED:
  dphys-swapfile
0 upgraded, 0 newly installed, 1 to remove and 0 not upgraded.
After this operation, 85.0 kB disk space will be freed.
Do you want to continue? [Y/n] y
[...]

Bien que le package dphys-swapfile ait été supprimé, le fichier de 100 Mo qu’il utilisait pour proposer cette mémoire alternative subsiste encore… éliminons-le !

$ ls -l /var/swap
-rw------- 1 root root 104857600 Sep 24 15:33 /var/swap

$ sudo rm -f /var/swap

$ free
             total       used       free     shared    buffers     cached
Mem:        445044     220612     224432       4960      20384     142052
-/+ buffers/cache:      58176     386868
Swap:            0          0          0

Je préfère cette situation ! Nous voyons au passage qu’une bonne part de la mémoire est encore potentiellement libre (386868 ko).

Système de fichiers en lecture seule

Sur un système embarqué, je trouve important de laisser le système de fichiers principal, celui qui contient les exécutables binaires, les bibliothèques, etc. en lecture-seule. Ainsi en cas d’interruption brutale d’alimentation électrique, le système de fichiers restera intact, aucune écriture ne pouvant être en cours d’exécution au moment de la coupure. Cela réclame quelques efforts.

Tout d’abord, afin de simplifier les manipulations ultérieures, j’ai coutume de me créer deux petits scripts, que je nomme ro (read only) et rw (read/write), qui ont pour rôles respectifs de basculer le système de fichiers en lecture-seule ou lecture-écriture. Je les crée rapidement :

/usr/local/bin/ro:
#! /bin/sh

mount / -o ro,remount

et

/usr/local/bin/rw:
#! /bin/sh

mount / -o rw,remount

Il ne faut pas oublier de rendre ces scripts exécutables :

$ sudo chmod +x /usr/local/bin/ro /usr/local/bin/rw

Nous pouvons toujours essayer d’invoquer ro, mais il échouera car certains processus maintiennent déjà ouverts des fichiers de l’arborescence principale.

$ sudo ro
mount: / is busy

Lors du boot, le noyau monte lui-même la racine du système de fichier en s’appuyant sur le contenu du paramètre root= transmis par le bootloader. Sur le Raspberry Pi, les paramètres de démarrage du noyau se trouvent dans le fichier /boot/cmdline.txt. Le bootloader en ajoute d’autres également, mais cela ne nous concerne pas ici.

Par défaut, le noyau monte son arborescence en lecture-écriture, mais la présence du mot-clé ro dans ses paramètres de démarrage lui demande de réaliser le montage en lecture-seule. Je vais donc commencer par éditer le fichier /boot/cmdline.txt pour ajouter ce mot-clé.

/boot/cmdline.txt:
ro dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait

J’ai inséré ici le mot-clé en début de ligne, mais on peut le placer n’importe où sur celle-ci. Attention : il faut bien que toutes les options soient sur la même ligne.

Une fois le boot du noyau terminé, l’espace utilisateur prend le relais, par l’intermédiaire de init et ses scripts sur les distributions de type Système V ou de systemd pour la plupart des distributions récentes. Durant cette étape de démarrage, le système de fichiers principal peut être remonté en lecture-écriture s’il est configuré ainsi.

Pour m’assurer que la racine du système de fichiers reste en lecture-seule après le boot du noyau, j’édite le fichier /etc/fstab qui contient la configuration des partitions et je rajoute le mot clé ro sur la ligne décrivant le montage de « /« . Attention à ne pas ajouter d’espaces autour du mot-clé ou des virgules.

/etc/fstab:
proc            /proc           proc    defaults          0       0
/dev/mmcblk0p1  /boot           vfat    defaults          0       2
/dev/mmcblk0p2  /               ext4    defaults,ro,noatime  0       1

J’en ai profité pour effacer les deux dernières lignes de commentaire qui parlaient de dphys-swapfile (que nous avons supprimé précédemment).

Vérifions le fonctionnement :

# reboot
[...]
raspberrypi login: pi
Password: (raspberry)
pi@raspberrypi:~$ sudo -i
# echo hello > /test
-bash: /test: Read-only file system
# rw
# echo hello > /test
# cat /test
hello
# rm /test
# ro
mount: / is busy
#

Systèmes de fichiers temporaires non persistants

Notre système démarre bien en mode lecture-seule, et nous ne pouvons pas écrire sur le système de fichiers principal. Une fois basculé explicitement en mode lecture-écriture, il devient possible de modifier le contenu de ce système de fichiers.

Néanmoins, il est impossible de revenir en mode lecture-seule, car des processus ont profité du laps de temps où le système est monté en lecture-écriture pour y ouvrir des fichiers en écriture et les maintenir ouverts. Essayons de savoir quels fichiers sont concernés. Tout d’abord, je cherche la liste des processus qui tiennent un fichier ouvert dans cette arborescence.

# fuser -v -m /
                     USER        PID ACCESS COMMAND
/:                   root     kernel mount /
                     root          1 .rce. systemd
                     root          2 .rc.. kthreadd
                     [...]
                     root         60 .rc.. kworker/0:1H
                     root         91 .rce. systemd-journal
                     root         93 frce. systemd-udevd
                     root        180 .rc.. kworker/0:3
                     root        375 .rc.. cfg80211
                     root        450 .rce. sshd
                     root        451 .rce. cron
                     root        455 .rce. systemd-logind
                     avahi       471 .rce. avahi-daemon
                     messagebus    478 .rce. dbus-daemon
                     avahi       490 .rce. avahi-daemon
                     nobody      492 .rce. thd
                     ntp         501 .rce. ntpd
                     root        502 Frce. rsyslogd
                     root        521 .rce. login
                     root        522 .rce. login
                     pi          534 .rce. systemd
                     pi          545 .rce. (sd-pam
                     pi          571 .rce. bash
                     root        599 .rce. dhcpcd
                     pi          697 .rce. bash
                     root        707 .rce. sudo
                     root        714 .rce. bash
                     root        740 frce. lightdm
                     root        745 Frce. Xorg
                     root        758 .rce. lightdm
                     pi          771 Frce. lxsession
                     pi          795 Frce. ssh-agent
                     pi          798 .rce. dbus-launch
                     pi          799 .rce. dbus-daemon
                     pi          805 .rce. gvfsd
                     pi          809 .rce. gvfsd-fuse
                     pi          820 Frce. openbox
                     pi          821 Frce. lxpolkit
                     pi          823 Frce. lxpanel
                     pi          825 Frce. pcmanfm
                     pi          832 Frce. ssh-agent
                     root        834 .rce. polkitd
                     pi          847 .rce. gvfs-udisks2-vo
                     root        849 .rce. udisksd
                     pi          859 .rce. gvfs-afc-volume
                     pi          864 .rce. gvfs-mtp-volume
                     pi          868 .rce. gvfs-gphoto2-vo
                     pi          872 .rce. gvfs-goa-volume
                     pi          881 Frce. menu-cached
                     pi          888 .rce. gvfsd-trash

J’ai éliminé de la liste les threads du noyau sur lesquels je n’ai pas vraiment de contrôle. Les processus qui nous intéressent sont ceux maintenant un fichier ouvert en lecture-écriture. Ceci est caractérisé par un F majuscule en début de troisième champ. Je filtre ces processus et affiche la liste complète des descripteurs dont ils disposent. La liste est énorme car il y a également les terminaux, les sockets, les pipes, les fichiers exécutables, etc…

# fuser -v -m / 2>&1 | awk '($3 ~ /F.*/){ print "/proc/"$2"/fd"}' | xargs ls -l
[...]

À l’aide d’une longue séquence de grep successifs, je ne conserve que la liste des fichier qui m’intéressent.

# fuser -v -m / 2>&1 | awk '($3 ~ /F.*/){ print "/proc/"$2"/fd"}' | xargs ls -l| grep '^l.w' | grep -v socket: | grep -v /dev/ | grep -v "/proc" | grep -v anon_inode | grep -v pipe

l-wx------ 1 root root 64 Oct  4 15:52 10 -> /var/log/auth.log
l-wx------ 1 root root 64 Oct  4 15:52 11 -> /var/log/user.log
l-wx------ 1 root root 64 Oct  4 15:52 6 -> /var/log/syslog
l-wx------ 1 root root 64 Oct  4 15:52 7 -> /var/log/kern.log
l-wx------ 1 root root 64 Oct  4 15:52 8 -> /var/log/messages
l-wx------ 1 root root 64 Oct  4 15:52 9 -> /var/log/daemon.log
l-wx------ 1 root root 64 Oct  4 15:55 0 -> /var/log/Xorg.0.log
l-wx------ 1 pi pi 64 Oct  4 15:55 1 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 2 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 1 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 2 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 4 -> /home/pi/.cache/openbox/openbox.log
l-wx------ 1 pi pi 64 Oct  4 15:55 1 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 2 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 1 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 2 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 1 -> /home/pi/.cache/lxsession/LXDE-pi/run.log
l-wx------ 1 pi pi 64 Oct  4 15:55 2 -> /home/pi/.cache/lxsession/LXDE-pi/run.log

Nous voyons que les fichiers ouverts en écriture servent essentiellement à enregistrer des traces d’activité.

Sur un système embarqué les traces servent au moment de la mise au point, mais ne sont plus utiles après le basculement en production. Nous pouvons donc accepter de les placer sur un disque tmpfs (ramdisk automatiquement formaté et dimensionné par le noyau) où elles resteront accessibles pendant le fonctionnement du système mais ne survivront pas à un reboot du Raspberry Pi. Nous avons vu plus haut, dans le résultat de la commande mount, que le répertoire /run est un point de montage d’un tmpfs. Utilisons-le pour y stocker les fichiers de trace.

# rm -rf /var/log
# ln -s /run/log /var/log

Il existe également un cas particulier dans les fichiers de configuration du système : /etc/resolv.conf. Lorsque le système utilise le mécanisme DHCP pour demander à un serveur local (une box par exemple) les paramètres du réseau, il enregistre dans ce fichier l’adresse du DNS (le serveur de noms qui permet de résoudre un nom de machine par exemple www.kernel.org en une adresse IP comme 199.204.44.194). Le fichier doit donc être accessible en écriture pendant le fonctionnement normal du système. Pour cela le plus simple est de le rediriger via un lien symbolique vers un répertoire sur un système de fichier tmpfs.

# rm  -f  /etc/resolv.conf
# ln  -sf  /run/resolv.conf  /etc/
# reboot

Système de fichiers modifiables et persistants

Nous pouvons également être amenés à utiliser des fichiers qui devront être modifiables pendant le fonctionnement du système, mais qui devront aussi être persistants, c’est-à-dire survivre à un redémarrage de la machine. Nous allons les placer sur une partition spécifique en sachant que leur situation est néanmoins un peu plus précaire que le reste du système en cas de coupure d’alimentation électrique.

Créons une partition, que nous utiliserons par la suite pour le répertoire /home/pi. Ceci permettra ainsi de sauvegarder les fichiers de traces applicatives que nous avons observés plus haut. Je voudrais coller cette partition à la fin de l’espace disque libre, pour pouvoir ensuite agrandir la partition système (celle en lecture seule) afin de disposer de place supplémentaire si je veux installer de nouveaux packages. Je vais créer une partition de /home/pi de 2 Go.

# fdisk /dev/mmcblk0

Welcome to fdisk (util-linux 2.25.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): p
Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xba2edfb9

Device         Boot  Start     End Sectors Size Id Type
/dev/mmcblk0p1        8192  122879  114688  56M  c W95 FAT32 (LBA)
/dev/mmcblk0p2      122880 8447999 8325120   4G 83 Linux

Notre système dispose déjà de deux partitions : la première contient le bootloader et le noyau (nous y reviendrons plus bas), la seconde notre système de fichiers principal. Nous voyons dans le résultat de la commande ‘p‘ que la taille des secteurs est de 512 octets et que la carte SD contient 15523840 secteurs. Je vais donc démarrer ma partition au secteur 15523840 – 4*1024*1024 = 11329536.

Command (m for help): n
Partition type
   p   primary (2 primary, 0 extended, 2 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (3,4, default 3): 3
First sector (2048-15523839, default 2048): 11329536
Last sector, +sectors or +size{K,M,G,T,P} (11329536-15523839, default 15523839): (Entrée)

Created a new partition 3 of type 'Linux' and of size 2 GiB.

Command (m for help): p
Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xba2edfb9

Device         Boot    Start      End Sectors Size Id Type
/dev/mmcblk0p1          8192   122879  114688  56M  c W95 FAT32 (LBA)
/dev/mmcblk0p2        122880  8447999 8325120   4G 83 Linux
/dev/mmcblk0p3      11329536 15523839 4194304   2G 83 Linux


Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy

The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8).
#

Le message final m’indique que le noyau n’a pas honoré la demande de fdisk concernant la relecture de la nouvelle table de partition. Pour s’assurer qu’elle soit bien prise en compte, redémarrons.

# reboot
[...]
[    5.202270] systemd[1]: Mounting FUSE Control File System...

Raspbian GNU/Linux 8 raspberrypi ttyAMA0

raspberrypi login: pi
Password: (raspberry)
Last login: Sun Oct  4 16:13:30 UTC 2015 on tty1
Linux raspberrypi 4.1.7+ #817 PREEMPT Sat Sep 19 15:25:36 BST 2015 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
pi@raspberrypi:~$ sudo -i
# ls -l /dev/mmcblk0*
brw-rw---- 1 root disk 179, 0 Oct  4 16:13 /dev/mmcblk0
brw-rw---- 1 root disk 179, 1 Oct  4 16:13 /dev/mmcblk0p1
brw-rw---- 1 root disk 179, 2 Oct  4 16:13 /dev/mmcblk0p2
brw-rw---- 1 root disk 179, 3 Oct  4 16:13 /dev/mmcblk0p3

La partition 3 est bien présente. Formattons-la.

# mkfs.vfat -n HOME /dev/mmcblk0p3
mkfs.fat 3.0.27 (2014-11-12)

J’ai choisi un format vfat pour mon système de fichiers. Ce choix peut paraître a priori surprenant, il s’agit d’un format limité, ne supportant ni le choix d’appartenance des fichiers (ils appartiennent tous à root par défaut), ni la modifications des droits (rwxr-xr-x pour tous les fichiers), ni les fichiers spéciaux de périphériques, ni les sockets, ni les tubes nommés, ni même les liens symboliques. Autant dire que pour un système de type Unix, ce format est particulièrement restrictif. Mais justement, c’est ce qui lui donne une bonne robustesse en cas de coupure d’alimentation pendant une écriture. Naturellement le fichier en cours de modifications sera incomplet, mais le système de fichiers lui-même ne sera pas endommagé. Peut-être que quelques secteurs seront « perdus » car ils seront considérés comme utilisés alors qu’aucun fichier ne permettra d’y accéder, mais il n’y aura pas d’incohérence au niveau de la partition elle-même. C’est ce que notre expérience quotidienne des clés USB nous apprend : même si un utilisateur l’arrache de l’ordinateur sans l’avoir démontée proprement, la clé reste utilisable même si le fichier en cours d’écriture peut être éventuellement incomplet.

C’est pour cette propriété de vfat que je le préfère à d’autres systèmes comme ext2 qui peuvent nécessiter une étape de récupération (une vérification avec fsck) avant d’être remontés en cas d’arrêt intempestif. Bien sûr pour une partition système en lecture seule je préfère très largement ext2 qui offre tout le support des fichiers Unix, mais pour un répertoire de sauvegarde simple (comme /home/pi ici) le format fat32 conviendra parfaitement.

J’ai cité à dessein ext2 plutôt que ext3 ou ext4 car je refuse habituellement l’usage d’un journal lorsque le périphérique de support est une mémoire flash. Avec la journalisation proposée par ext3 et ext4 on multiplie par trois le nombre d’écritures en cas de modification d’un fichier. Ce qui divise donc par trois la durée de vie de la mémoire flash… Ceci n’a pas d’influence lorsque le système de fichiers est en lecture seule comme notre partition principale.

La partition est prête, il faudra la monter automatiquement au démarrage, je l’ajoute donc dans /etc/fstab avec des options uid et gid qui fixent l’appartenance par défaut des fichiers qu’elle contient :

# rw
# nano /etc/fstab

proc            /proc           proc    defaults                0    0
/dev/mmcblk0p1  /boot           vfat    defaults                0    2
/dev/mmcblk0p2  /               ext4    defaults,ro,noatime     0    1
/dev/mmcblk0p3  /home/pi        vfat    defaults,uid=pi,gid=pi  0    1

# reboot
[...]
Raspbian GNU/Linux 8 raspberrypi ttyAMA0

raspberrypi login: pi
Password: (raspberry)
[..]
pi@raspberrypi:~$ ls
pi@raspberrypi:~$ echo hello > test
pi@raspberrypi:~$ ls
test
pi@raspberrypi:~$ cat test
hello
pi@raspberrypi:~$

Nous avons bien réussi à créer un fichier sur la partition montée sur le répertoire /home/pi.

J’aimerais également augmenter la taille de la partition principale au maximum afin qu’elle remplisse tout l’espace restant de la carte SD pour pouvoir ajouter éventuellement d’autres applications. C’est ce que fait habituellement l’utilitaire raspi-config de la distribution Raspbian. Malheureusement il ne peut pas fonctionner ici car il n’accepte que de redimensionner la dernière partition de la liste, ce qui n’est pas notre cas.

Commençons donc par agrandir avec fdisk la partition sans toucher au système de fichiers. Pour cela, il faut la détruire et la reconstruire avec le même secteur de départ et une longueur plus grande.

pi@raspberrypi:~$ sudo fdisk /dev/mmcblk0 
[...]
Command (m for help): p
Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xba2edfb9

Device         Boot    Start      End  Sectors  Size Id Type
/dev/mmcblk0p1          8192   122879   114688   56M  c W95 FAT32 (LBA)
/dev/mmcblk0p2        122880  8447999  8325120   4G 83 Linux
/dev/mmcblk0p3      11329536 15523839  4194304    2G 83 Linux

Command (m for help): d
Partition number (1-3, default 3): 2

Partition 2 has been deleted.

Command (m for help): n
Partition type
   p   primary (2 primary, 0 extended, 2 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (2,4, default 2): 2
First sector (2048-15523839, default 2048): 122880
Last sector, +sectors or +size{K,M,G,T,P} (122880-11329535, default 11329535): (Entrée)

Created a new partition 2 of type 'Linux' and of size 5.4 GiB.

Command (m for help): p
Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xba2edfb9

Device         Boot    Start      End  Sectors  Size Id Type
/dev/mmcblk0p1          8192   122879   114688   56M  c W95 FAT32 (LBA)
/dev/mmcblk0p2        122880 11329535 11206656  5.4G 83 Linux
/dev/mmcblk0p3      11329536 15523839  4194304    2G 83 Linux

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy

The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8).
# reboot

Après redémarrage nous pouvons redimensionner le système de fichiers sans perdre son contenu

pi@raspberrypi:~$ sudo rw
pi@raspberrypi:~$ sudo resize2fs /dev/mmcblk0p2
pi@raspberrypi:~$ sudo reboot
[...]
pi@raspberrypi:~$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/root        5449836 3121868   2047096  61% /
devtmpfs          218240       0    218240   0% /dev
tmpfs             222512       0    222512   0% /dev/shm
tmpfs             222512    4784    217728   3% /run
tmpfs               5120       4      5116   1% /run/lock
tmpfs             222512       0    222512   0% /sys/fs/cgroup
/dev/mmcblk0p1     57288   24256     33032  43% /boot
/dev/mmcblk0p3   2093048 1010248   1082800  49% /home/pi

Recompilation du noyau

Une dernière phase de personnalisation consiste à recompiler notre noyau. Pourquoi cela ? Tout d’abord pour le plaisir de voir notre système fonctionner sur un kernel que nous pouvons configurer et ajuster à notre guise. Ensuite pour disposer de tout l’environnement nécessaire (fichiers d’en-tête, Makefiles, et fichiers de configuration) nécessaires à la compilation de modules supplémentaires personnels.

Cette compilation est facile à réaliser, elle nécessite surtout un temps de traitement assez long (deux bonnes heures avec un Raspberry Pi 2, et huit environ avec le modèle 1).

Installons les packages nécessaires non présents dans la distribution de base :

pi@raspberrypi:~$ sudo rw
pi@raspberrypi:~$ sudo apt-get install -y bc libncurses5-dev

Ensuite récupérons les sources du noyau adaptées pour le Raspberry Pi :

pi@raspberrypi:~$ git clone https://github.com/raspberrypi/linux rpi-linux
remote: Counting objects: 4521622, done.
remote: Compressing objects: 100% (4139/4139), done.
remote: Total 4521622 (delta 3292), reused 400 (delta 400), pack-reused 4517078
Réception d'objets: 100% (4521622/4521622), 1.26 GiB | 1.80 MiB/s, done.
Résolution des deltas: 100% (3735624/3735624), done.
Vérification de la connectivité... fait.
Checking out files: 100% (49945/49945), done.

Nous allons compiler un noyau 4.1 :

pi@raspberrypi:~$ cd rpi-linux
pi@raspberrypi:~/rpi-linux$ git checkout rpi-4.1.y

Voyons les configurations existantes pour notre système :

pi@raspberrypi:~/rpi-linux$ make help
  [..]
  bcm2709_defconfig        - Build for bcm2709
  bcm2835_defconfig        - Build for bcm2835
  bcm_defconfig            - Build for bcm
  bcmrpi_defconfig         - Build for bcmrpi
  bockw_defconfig          - Build for bockw
  cerfcube_defconfig       - Build for cerfcube
  clps711x_defconfig       - Build for clps711x
  [..]

Celle qui nous intéresse pour le Raspberry Pi modèle 1 (B, A B+) est bcmrpi (Broadcom Raspberry Pi).

pi@raspberrypi:~/rpi-linux$ make bcmrpi_defconfig

Pour un Raspberry Pi modèle 2, on fera :

pi@raspberrypi:~/rpi-linux$ make bcm2709_defconfig

Nous partons de cette configuration mais pouvons la modifier quelque peu :

pi@raspberrypi:~/rpi-linux$ make menuconfig

Je lance la compilation avec deux jobs en parallèle sur Raspberry Pi modèle 1 B, on peut en lancer environ 8 en parallèle sur un modèle 2.

pi@raspberrypi:~/rpi-linux$ make -j 2

Huit heures plus tard environ sur un modèle 1 (ou 1h30 sur un modèle 2) j’installe le nouveau noyau et ses modules :

pi@raspberrypi:~/rpi-linux$ sudo make modules_install
pi@raspberrypi:~/rpi-linux$ sudo  cp  /boot/kernel.img   /boot/kernel.img.prev
pi@raspberrypi:~/rpi-linux$ sudo  cp  /boot/kernel7.img  /boot/kernel7.img.prev
pi@raspberrypi:~/rpi-linux$ sudo  cp  arch/arm/boot/zImage  /boot/kernel.img
pi@raspberrypi:~/rpi-linux$ sudo  cp  arch/arm/boot/zImage  /boot/kernel7.img
pi@raspberrypi:~/rpi-linux$ sudo  reboot

Notez que le bootloader charge l’image kernel.img sur les Raspberry Pi 1 et kernel7.img sur les Raspberry Pi 2. Nous avons sauvegardé les images précédentes au cas où le nouveau noyau ne fonctionne pas… Après le redémarrage nous pouvons vérifier notre version :

pi@raspberrypi:~$ uname -a
Linux raspberrypi 4.1.10-cpb #1 PREEMPT Mon Oct 5 10:58:11 UTC 2015 armv6l GNU/Linux

Conclusion

Nous avons pris ainsi possession d’une distribution Raspbian standard et à travers quelques modifications (qui ne changent pas la compatibilité pour les mises à jour) amélioré sa résilience en cas de coupure d’alimentation, protégé son système de fichiers principal contre les modifications involontaires, évité l’usure prématurée due au swap et conservé une partition spécifique pour l’utilisateur.

Je pense que ces modifications sont particulièrement utiles si l’on souhaite utiliser le Raspberry Pi dans un contexte de système embarqué plutôt qu’en desktop classique.

Toutes les remarques, corrections, critiques sont les bienvenues !

Création d’un système complet avec Buildroot 2015.11

$
0
0

Création d'un système complet avec Buildroot 2015-11

Il y a quelques jours, la livraison trimestrielle de Buildroot a rendu disponible une version 2015.11. J’ai voulu l’essayer en construisant un petit système pour Raspberry Pi 2. et partager cette expérience qui viendra ainsi en complément de mon article précédent. Buildroot permet de construire un système embarqué plus traditionnel qu’en utilisant une distribution pré-compilée, et d’ajuster plus finement son contenu.

Nous allons successivement préparer une chaîne de compilation croisée, construire un système minimal pour vérifier le bon fonctionnement de celle-ci puis générer un système configuré de façon plus personnalisée.

Environnement de travail

Commençons par créer une arborescence de travail qui contiendra tous nos fichiers personnalisés, les répertoires de construction, la chaîne de compilation, etc.

[~]$ mkdir br-tree
[~]$ cd br-tree
[br-tree]$

Au sein de cet environnement, nous allons essayer de respecter l’organisation des fichiers proposées par le projet Buildroot : une arborescence board/ contenant un sous-répertoire pour chaque carte que nous supporterons (ici le Raspberry Pi 2 uniquement). Tous nos fichiers personnalisés se trouveront dans cette sous-arborescence.

[br-tree]$ mkdir -p board/raspberrypi2/

Nous téléchargeons les sources de la dernière version de Buildroot, les décompressons et entrons dans ce répertoire :

[br-tree]$ wget http://www.buildroot.org/downloads/buildroot-2015.11.tar.bz2
[br-tree]$ tar xjf buildroot-2015.11.tar.bz2
[br-tree]$ cd buildroot-2015.11/

Toolchain

J’ai l’habitude de commencer mes projets embarqués par la construction d’une toolchain de compilation croisée. Il s’agit d’obtenir un compilateur – et tous les outils associés – fonctionnant sur la machine de développement (un PC par exemple) et produisant du code pour la plate-forme cible (ici le Raspberry Pi 2).

Il est tout à fait possible de se procurer une toolchain pré-compilée, mais je trouve qu’il est dommage de se priver de cette étape de construction, d’autant que cela nous permet de maîtriser exactement les versions de la bibliothèque C, du noyau, des outils, etc. que nous souhaitons.

Si j’isole cette étape de la production du système complet, c’est qu’elle prend un temps significatif (environ vingt minutes sur un PC portable moyen avec une connexion Internet ADSL correcte). La toolchain est donc compilée et installée une fois pour toutes, et ne sera plus modifiée même si nous réitérons autant de génération du système que nous le désirons.

J’ai l’habitude de placer la toolchain dans le répertoire board/<target>/cross.

Pour produire la toolchain, nous demandons une configuration de Buildroot par défaut pour la cible choisie, et l’élaguons pour ne laisser que la production du compilateur et de ses outils :

[buildroot-2015.11]$ make raspberrypi2_defconfig
[buildroot-2015.11]$ make menuconfig

Voici la liste des modifications apportées :

  • Menu Target options : pas de changement, mais on peut en profiter pour observer et vérifier le support du processeur cible.
  • Menu Build options :
    • Download dir : nous extrayons de l’arborescence de Buildroot ce répertoire dans lequel il stocke les fichiers téléchargés. Ainsi les compilations successives ne nécessiterons pas de nouveaux téléchargements. Nouvelle valeur : $(TOPDIR)/../dl
    • Host dir : l’emplacement où se trouvera la toolchain compilée. Comme indiqué plus haut, j’ai pour habitude de la placer dans le répertoire board/<target>/cross de notre arborescence de travail. Nouveau chemin : $(TOPDIR)/../board/raspberrypi2/cross
  • Menu Toolchain :
    • C library : c’est un choix qui dépend beaucoup du code métier. La bibliothèque C est un point-clé du système ; c’est elle qui permet d’entrer dans le noyau pour bénéficier de ses services (appels-système). Pour être le plus générique possible, nous choisissons la Gnu C library, un peu plus volumineuse que les autres, mais plus riche également. Nouvelle valeur : glibc
  • Menu System configuration :
    • Init system : cette option contient au préalable BusyBox mais nous la désactivons pour pouvoir éliminer ce package. Nouvelle valeur : None
  • Menu Kernel :
    • Linux Kernel : nous ne voulons, dans un premier temps, produire que la toolchain et rien d’autre. Nous désactivons cette option. Nouvelle valeur : [ ]
  • Menu Target packages :
    • BusyBox : c’est le seul package initialement présent. Nous le désactivons. Nouvelle valeur : [ ]
  • Menu Filesystem images :
    • tar the root filesystem : inutile, nous ne voulons pas de filesystem pour le moment. Nouvelle valeur : [ ]

Sauvegardons la configuration et lançons la compilation :

[buildroot-2015.11]$ cp .config ../board/raspberrypi2/buildroot-01.cfg
[buildroot-2015.11]$ make

Après quelques minutes, la compilation se termine avec ces lignes :

( \
		echo "NAME=Buildroot"; \
		echo "VERSION=2015.11"; \
		echo "ID=buildroot"; \
		echo "VERSION_ID=2015.11"; \
		echo "PRETTY_NAME=\"Buildroot 2015.11\"" \
	) >  /home/cpb/br-tree/buildroot-2015.11/output/target/etc/os-release

Attention, des messages alarmants apparaissent auparavant (des fichiers inexistants, un octet magique incorrect, etc.), c’est parfaitement normal puisque nous n’avons pas créé de système de fichiers.

Vérifions la toolchain produite :

[buildroot-2015.11]$ cd ..
[br-tree]$ ls board/raspberrypi2/cross/usr/bin/
arm-buildroot-linux-gnueabihf-addr2line          arm-linux-c++
arm-buildroot-linux-gnueabihf-ar                 arm-linux-c++.br_real
arm-buildroot-linux-gnueabihf-as                 arm-linux-cc
arm-buildroot-linux-gnueabihf-c++                arm-linux-cc.br_real
arm-buildroot-linux-gnueabihf-c++.br_real        arm-linux-c++filt
arm-buildroot-linux-gnueabihf-cc                 arm-linux-cpp
arm-buildroot-linux-gnueabihf-cc.br_real         arm-linux-cpp.br_real
arm-buildroot-linux-gnueabihf-c++filt            arm-linux-elfedit
arm-buildroot-linux-gnueabihf-cpp                arm-linux-g++
arm-buildroot-linux-gnueabihf-cpp.br_real        arm-linux-g++.br_real
arm-buildroot-linux-gnueabihf-elfedit            arm-linux-gcc
arm-buildroot-linux-gnueabihf-g++                arm-linux-gcc-4.9.3
arm-buildroot-linux-gnueabihf-g++.br_real        arm-linux-gcc-4.9.3.br_real
arm-buildroot-linux-gnueabihf-gcc                arm-linux-gcc-ar
arm-buildroot-linux-gnueabihf-gcc-4.9.3          arm-linux-gcc.br_real
arm-buildroot-linux-gnueabihf-gcc-4.9.3.br_real  arm-linux-gcc-nm
arm-buildroot-linux-gnueabihf-gcc-ar             arm-linux-gcc-ranlib
arm-buildroot-linux-gnueabihf-gcc.br_real        arm-linux-gcov
arm-buildroot-linux-gnueabihf-gcc-nm             arm-linux-gprof
arm-buildroot-linux-gnueabihf-gcc-ranlib         arm-linux-ld
arm-buildroot-linux-gnueabihf-gcov               arm-linux-ld.bfd
arm-buildroot-linux-gnueabihf-gprof              arm-linux-nm
arm-buildroot-linux-gnueabihf-ld                 arm-linux-objcopy
arm-buildroot-linux-gnueabihf-ld.bfd             arm-linux-objdump
arm-buildroot-linux-gnueabihf-nm                 arm-linux-ranlib
arm-buildroot-linux-gnueabihf-objcopy            arm-linux-readelf
arm-buildroot-linux-gnueabihf-objdump            arm-linux-size
arm-buildroot-linux-gnueabihf-ranlib             arm-linux-strings
arm-buildroot-linux-gnueabihf-readelf            arm-linux-strip
arm-buildroot-linux-gnueabihf-size               gawk
arm-buildroot-linux-gnueabihf-strings            igawk
arm-buildroot-linux-gnueabihf-strip              m4
arm-linux-addr2line                              mkknlimg
arm-linux-ar                                     toolchain-wrapper
arm-linux-as
[br-tree]$

La toolchain de cross-compilation regroupe tous les outils dont les noms sont préfixés par l’architecture (arm), l’outil de production (buildroot), le système d’exploitation de la cible (linux) et les conventions d’interfaçage binaire entre applications et système (gnueabi). Pour simplifier l’appel des outils, des liens symboliques existent raccourcissant le préfixe à l’architecture et le système d’exploitation. On invoquera donc arm-linux-gcc ou arm-linux-g++ par exemple.

[br-tree]$ board/raspberrypi2/cross/usr/bin/arm-linux-gcc -v
Utilisation des specs internes.
COLLECT_GCC=/home/cpb/br-tree/board/raspberrypi2/cross/usr/bin/arm-linux-gcc.br_real
COLLECT_LTO_WRAPPER=/home/cpb/br-tree/board/raspberrypi2/cross/usr/bin/../libexec/gcc/arm-buildroot-linux-gnueabihf/4.9.3/lto-wrapper
Target: arm-buildroot-linux-gnueabihf
Configuré avec: ./configure --prefix=/home/cpb/br-tree/buildroot-2015.11/../board/raspberrypi2/cross//usr --sysconfdir=/home/cpb/br-
tree/buildroot-2015.11/../board/raspberrypi2/cross//etc --enable-static --target=arm-buildroot-linux-gnueabihf --with-sysroot=/home/
cpb/br-tree/buildroot-2015.11/../board/raspberrypi2/cross//usr/arm-buildroot-linux-gnueabihf/sysroot --disable-__cxa_atexit --with-g
nu-ld --disable-libssp --disable-multilib --with-gmp=/home/cpb/br-tree/buildroot-2015.11/../board/raspberrypi2/cross//usr --with-mpf
r=/home/cpb/br-tree/buildroot-2015.11/../board/raspberrypi2/cross//usr --with-pkgversion='Buildroot 2015.11' --with-bugurl=http://bu
gs.buildroot.net/ --disable-libquadmath --enable-tls --disable-libmudflap --enable-threads --with-mpc=/home/cpb/br-tree/buildroot-20
15.11/../board/raspberrypi2/cross//usr --without-isl --without-cloog --disable-decimal-float --with-abi=aapcs-linux --with-cpu=corte
x-a7 --with-fpu=neon-vfpv4 --with-float=hard --with-mode=arm --enable-languages=c,c++ --with-build-time-tools=/home/cpb/br-tree/buil
droot-2015.11/../board/raspberrypi2/cross//usr/arm-buildroot-linux-gnueabihf/bin --enable-shared --disable-libgomp
Modèle de thread: posix
gcc version 4.9.3 (Buildroot 2015.11)

Si l’on souhaite pouvoir invoquer directement le cross-compiler depuis la ligne de commande sans préciser tout le chemin (par exemple pendant une phase de développement de code métier hors Buildroot), on peut éditer le fichier ~/.bashrc afin d’y ajouter à la fin la ligne suivante :

PATH=$PATH:~/br-tree/board/raspberrypi2/cross/usr/bin/

Système complet

Nous allons construire à présent une image d’un système complet, y compris le noyau, en utilisant la toolchain obtenue précédemment. Il nous faut effacer les fichiers objets, fichiers temporaires, etc. produits auparavant et l’on serait tenté de faire un make clean. Abstenons-nous en néanmoins car cela aurait pour effet d’effacer la toolchain compilée. La solution la plus simple pour éviter les erreurs de manipulation est de supprimer le répertoire de compilation de Buildroot et de décompresser à nouveau l’archive téléchargée.

[buildroot-2015.11]$ cd ..
[br-tree]$ rm -rf buildroot-2015.11
[br-tree]$ tar xjf buildroot-2015.11.tar.bz2
[br-tree]$ cd buildroot-2015.11

Puis nous préparons une nouvelle configuration, toujours, en partant de celle par défaut.

[buildroot-2015.11]$ make raspberrypi2_defconfig
[buildroot-2015.11]$ make menuconfig

Passons en revue les menus pour observer ce qu’il faut modifier :

  • Target options : rien à changer
  • Build options :
    • Download dir : configurons le répertoire de téléchargement pour retrouver le précédent. Nouvelle valeur : $(TOPDIR)/../dl
  • Toolchain : plusieurs modifications sont nécessaires pour retrouver la toolchain précédente.
    • Toolchain type : nous souhaitons que Buildroot considère la toolchain comme préexistante, même si c’est lui qui l’a créée auparavant. Nouvelle valeur : External toolchain
    • Toolchain : elle a été compilée spécifiquement. Nouvelle valeur : Custom toolchain
    • Toolchain origin : il n’est pas nécessaire de la télécharger. Valeur conservée : Pre-installed toolchain
    • Toolchain path : le répertoire dans lequel se trouve le sous-répertoire bin de la chaîne de compilation. Nouvelle valeur : $(TOPDIR)/../board/rapsberrypi2/cross/usr
    • External toolchain gcc version : si vous n’avez pas noté ce numéro de version lors de la configuration de la toolchain, vous pouvez l’obtenir en appelant arm-linux-gcc -v comme ci-dessus. Nouvelle valeur : 4.9.x
    • External toolchain kernel headers series : on peut retrouver le numéro de version si on ne l’a pas noté, mais c’est plus compliqué. Il faut regarder le contenu du fichier ../board/raspberrypi2/cross/usr/arm-buildroot-linux-gnueabihf/sysroot/usr/include/linux/version.h. On y trouve une valeur LINUX_VERSION_CODE 262405. Il faut convertir ce nombre en hexadécimal, par exemple en saisissant sur la ligne de commande du shell printf '%x\n' 262405. Ceci nous affiche 40105 qui représente le numéro de noyau 4.1.5. Nouvelle valeur : 4.1.x
    • External toolchain C library : en tant que bibliothèque C, nous avons choisi de compiler une GlibC. Nouvelle valeur : glibc/eglibc
    • Toolchain has C++ support : cette option était activée par défaut lors de la compilation précédente. Nouvelle valeur : [*]
  • System Configuration : pour l’instant nous ne changeons pratiquement rien dans ce menu, mais nous l’ajusterons un peu plus tard.
    • [*] Run a getty (login prompt) after boot ---> : Je préfère me connecter au Raspberry Pi 2 en utilisant la console série (comme sur la plupart des systèmes embarqués que je configure) plutôt que l’écran HDMI et le clavier USB. Je modifie en conséquence le paramètre suivant :
      • TTY port : j’indique le port série du Raspberry Pi 2. Nouvelle valeur : ttyAMA0
  • Kernel : rien à changer
  • Target packages : rien à changer
  • Filesystem images : rien à changer
  • Bootloaders : rien à changer
  • Host utilities : rien à changer
  • Legacy config options : rien à changer

À nouveau, sauvegardons notre configuration pour pouvoir la réutiliser directement si besoin et lançons la compilation.

[buildroot-2015.11]$ cp .config ../board/raspberrypi2/buildroot-02.cfg
[buildroot-2015.11]$ make

La compilation se termine au bout de quelques minutes sur un message plutôt surprenant…

/br-tree/buildroot-2015.11/output/build/_fakeroot.fs
rootdir=/home/cpb/br-tree/buildroot-2015.11/output/target
table='/home/cpb/br-tree/buildroot-2015.11/output/build/_device_table.txt'
/usr/bin/install -m 0644 support/misc/target-dir-warning.txt /home/cpb/br-tree/buildroot-2015.11/output/ta
rget/THIS_IS_NOT_YOUR_ROOT_FILESYSTEM
[buildroot-2015.11]

Mais que signifie donc ce THIS_IS_NOT_YOUR_ROOT_FILESYSTEM ?

Il s’agit en fait d’un nom de fichier, très anodin. Lorsque Buildroot prépare l’arborescence de la cible, il construit une représentation de son système de fichiers, qui se trouve dans output/target. Ces fichiers sont créés en appartenant à l’utilisateur courant. Or les fichiers systèmes (ceux se trouvant dans les répertoires /bin, /etc, /usr… de la cible) doivent appartenir à root. Pour pouvoir produire une image (une archive tar par exemple) avec les bonnes appartenances, on fait appel à un utilitaire nommé fakeroot qui modifie les droits au moment de la création de l’archive. Pas d’inquiétude, il n’y a rien de malicieux là-dessous, aucun problème de sécurité.

Comme le répertoire output/target contient des fichiers n’ayant pas la bonne appartenance, il ne faut pas l’utiliser aveuglément, ne pas le copier directement sur une cible où l’exporter pour un montage NFS root. C’est ce que Buildroot nous rappelle en créant ce fameux fichier :

[buildroot-2015.11]$ ls output/target/
bin  lib      media  proc  sbin                              tmp
dev  lib32    mnt    root  sys                               usr
etc  linuxrc  opt    run   THIS_IS_NOT_YOUR_ROOT_FILESYSTEM  var
[buildroot-2015.11]$

Installation et boot

Insérons une carte micro-SD sur le poste de développement (par exemple avec un adaptateur USB). On recherche alors le nom du périphérique bloc qu’elle représente.

[buildroot-2015.11]$ dmesg | tail
[21924.805390] sd 5:0:0:0: [sdc] No Caching mode page found
[21924.805395] sd 5:0:0:0: [sdc] Assuming drive cache: write through
[21924.805400] sd 5:0:0:0: [sdc] Attached SCSI removable disk
[21926.017065] EXT4-fs (sdc2): recovery complete
[21926.020278] EXT4-fs (sdc2): mounted filesystem with ordered data mode. Opts: (null)

Dans cet exemple, il s’agit du périphérique sdc, c’est ce que nous utiliserons ci-dessous.

Attention à ne pas vous tromper d’identifiant de périphérique ! cela pourrait être dangereux pour votre système si vous confondez avec votre disque dur par exemple.

Je démonte les partitions auto-montées de la carte micro-SD et j’efface complètement ses premiers secteurs.

[buildroot-2015.11]$ umount /dev/sdc?
[buildroot-2015.11]$ sudo dd if=/dev/zero of=/dev/sdc bs=1M count=16
16+0 enregistrements lus
16+0 enregistrements écrits
16777216 octets (17 MB) copiés, 3,82191 s, 4,4 MB/s

Partitionnons la carte pour obtenir :

  • Une première partition bootable au format Dos Vfat : ici j’ai choisi une taille de 128 Mo pour pouvoir faire des expériences avec des noyaux supplémentaires, U-boot, etc. En réalité une partition de 16 Mo suffira largement.
  • Une seconde partition au format Linux s’étendant sur le reste de la carte micro-SD.
[buildroot-2015.11]$ sudo fdisk /dev/sdc
  [...]
Commande (m pour l'aide) : n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p):(Entrée)
Numéro de partition (1-4, 1 par défaut) : (Entrée)
Premier secteur (2048-7741439, 2048 par défaut) : (Entrée)
Dernier secteur, +secteurs ou +taille{K,M,G} (2048-7741439, 7741439 par défaut) : +128M
Commande (m pour l'aide) : t
Partition sélectionnée 1
Code Hexa (taper L pour lister les codes): c
Type système de partition modifié de 1 à c (W95 FAT32 (LBA))
Commande (m pour l'aide) : n
Partition type:
   p   primary (1 primary, 0 extended, 3 free)
   e   extended
Select (default p): (Entrée)
Numéro de partition (1-4, 2 par défaut) : (Entrée)
Premier secteur (264192-7741439, 264192 par défaut) : (Entrée)
Dernier secteur, +secteurs ou +taille{K,M,G} (264192-7741439, 7741439 par défaut) : (Entrée)
Commande (m pour l'aide) : a
Numéro de partition (1-4): 1
Commande (m pour l'aide) : p
[...]
Périphérique Amorçage  Début         Fin      Blocs    Id. Système
/dev/sdc1   *        2048      264191      131072    c  W95 FAT32 (LBA)
/dev/sdc2          264192     7741439     3738624   83  Linux
Commande (m pour l'aide) : w
  [...]
[buildroot-2015.11]$

Nous formatons les deux partitions, l’une au format vfat (le seul format connu par le firmware du Raspberry Pi 2 qui doit charger en mémoire le bootloader), la seconde au format classique Linux ext2. L’absence de journalisation des données dans ce format permet de limiter l’usure des cartes flash en évitant les écritures supplémentaires. Je nomme classiquement mes partitions BOOT et ROOT afin de les identifier aisément.

[buildroot-2015.11]$ sudo mkfs.vfat -n BOOT /dev/sdc1
[buildroot-2015.11]$ sudo mkfs.ext2 -L ROOT /dev/sdc2

Extrayons à présent la carte micro-SD et réinsérons-la afin que l’auto-monteur nous donne un accès immédiat aux partitions.

Tous les éléments produits par Buildroot se trouvent dans son arborescence output/. Je m’intéresse en particulier à output/images/ qui contient les éléments à installer sur le système cible.

Nous allons copier sur la première partition :

  • les fichiers du bootloader du Raspberry Pi 2, précompilés et propriétaires, que Buildroot à téléchargés et copiés dans output/images/rpi-firmware ;
  • le fichier dtb (Device Tree Blob) qui contient la description du matériel permettant l’utilisation d’un noyau générique.
  • le noyau zImage, que nous ne copions pas directement mais installons par l’intermédiaire de l’utilitaire mkknlimg qui lui ajoute un suffixe indiquant qu’il contient le support du Device Tree.
[buildroot-2015.11]$ ls output/images/
bcm2709-rpi-2-b.dtb  rootfs.tar  rpi-firmware  zImage
[buildroot-2015.11]$ sudo cp output/images/bcm2709-rpi-2-b.dtb  /media/cpb/BOOT/
[buildroot-2015.11]$ sudo cp output/images/rpi-firmware/*  /media/cpb/BOOT/
[buildroot-2015.11]$ sudo ./output/host/usr/bin/mkknlimg  output/images/zImage  /media/cpb/BOOT/zImage
Version: Linux version 4.1.5-v7 (cpb@Logilin) (gcc version 4.9.3 (Buildroot 2015.11) ) #1 SMP PREEMPT Wed Dec 2 13:38:15 CET 2015
DT: y
DDT: n
283x: n

Pour voir les traces de boot du noyau sur le port série du Raspberry Pi 2, nous éditons le fichier cmdline.txt se trouvant dans la partition BOOT, et ajoutons sur la première ligne (attention, il ne doit y avoir qu’une seule ligne dans ce fichier) la chaîne de caractères suivante console=ttyAMA0,115200.

Sur la seconde partition, nous décompressons toute l’archive tar produite par Buildroot. Pour que les appartenances des fichiers soient correctement respectées, il est nécessaire de le faire avec les droits root. Ensuite nous démontons proprement les deux partitions.

[buildroot-2015.11]$ sudo tar xf output/images/rootfs.tar  -C /media/cpb/ROOT/
[buildroot-2015.11]$ umount /media/cpb/*

J’insère la carte micro-SD sur un Raspberry Pi 2 auquel je suis relié par une liaison série, et j’observe les messages suivants à la mise sous tension.

Uncompressing Linux... done, booting the kernel.                                                                                                         
[    0.832142] Switched to clocksource arch_sys_counter                                                                                                   
[    0.888108] FS-Cache: Loaded                                                                                                                           
[    0.891365] CacheFiles: Loaded
[    0.906112] NET: Registered protocol family 2
[    0.911787] TCP established hash table entries: 8192 (order: 3, 32768 bytes)
[    0.919008] TCP bind hash table entries: 8192 (order: 4, 65536 bytes)
[    0.925674] TCP: Hash tables configured (established 8192 bind 8192)
[    0.932165] UDP hash table entries: 512 (order: 2, 16384 bytes)
[    0.938130] UDP-Lite hash table entries: 512 (order: 2, 16384 bytes)
[    0.944853] NET: Registered protocol family 1
[    0.949645] RPC: Registered named UNIX socket transport module.
[    0.955646] RPC: Registered udp transport module.
[    0.960346] RPC: Registered tcp transport module.
[    0.965063] RPC: Registered tcp NFSv4.1 backchannel transport module.
[    0.972612] hw perfevents: enabled with armv7_cortex_a7 PMU driver, 5 counters available
[    0.982104] futex hash table entries: 1024 (order: 4, 65536 bytes)
[    1.004416] VFS: Disk quotas dquot_6.6.0
[    1.008720] VFS: Dquot-cache hash table entries: 1024 (order 0, 4096 bytes)
[    1.018425] FS-Cache: Netfs 'nfs' registered for caching
[    1.024905] NFS: Registering the id_resolver key type
[    1.030011] Key type id_resolver registered
[    1.034264] Key type id_legacy registered
[    1.041110] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 252)
[    1.048794] io scheduler noop registered
[    1.052810] io scheduler deadline registered
[    1.057414] io scheduler cfq registered (default)
[    1.064669] BCM2708FB: allocated DMA memory f8800000
[    1.069667] BCM2708FB: allocated DMA channel 0 @ f3007000
[    1.081887] Console: switching to colour frame buffer device 90x30
[    1.092405] Serial: 8250/16550 driver, 0 ports, IRQ sharing disabled
[    1.099985] vc-cma: Videocore CMA driver
[    1.103939] vc-cma: vc_cma_base      = 0x00000000
[    1.108636] vc-cma: vc_cma_size      = 0x00000000 (0 MiB)
[    1.114048] vc-cma: vc_cma_initial   = 0x00000000 (0 MiB)
[    1.119688] vc-mem: phys_addr:0x00000000 mem_base=0x3dc00000 mem_size:0x3f000000(1008 MiB)
[    1.143850] brd: module loaded
[    1.156001] loop: module loaded
[    1.160180] vchiq: vchiq_init_state: slot_zero = 0xb8880000, is_master = 0
[    1.168931] Loading iSCSI transport class v2.0-870.
[    1.174819] usbcore: registered new interface driver smsc95xx
[    1.180646] dwc_otg: version 3.00a 10-AUG-2012 (platform bus)
[    1.386775] Core Release: 2.80a
[    1.389922] Setting default values for core params
[    1.394786] Finished setting default values for core params
[    1.600754] Using Buffer DMA mode
[    1.604091] Periodic Transfer Interrupt Enhancement - disabled
[    1.609915] Multiprocessor Interrupt Enhancement - disabled
[    1.615500] OTG VER PARAM: 0, OTG VER FLAG: 0
[    1.619856] Dedicated Tx FIFOs mode
[    1.623734] WARN::dwc_otg_hcd_init:1047: FIQ DMA bounce buffers: virt = 0xb8814000 dma = 0xf8814000 len=9024
[    1.633612] FIQ FSM acceleration enabled for :
[    1.633612] Non-periodic Split Transactions
[    1.633612] Periodic Split Transactions
[    1.633612] High-Speed Isochronous Endpoints
[    1.650386] WARN::hcd_init_fiq:412: FIQ on core 1 at 0x80413218
[    1.656305] WARN::hcd_init_fiq:413: FIQ ASM at 0x80413588 length 36
[    1.662569] WARN::hcd_init_fiq:438: MPHI regs_base at 0xb909a000
[    1.668603] dwc_otg 3f980000.usb: DWC OTG Controller
[    1.673680] dwc_otg 3f980000.usb: new USB bus registered, assigned bus number 1
[    1.681016] dwc_otg 3f980000.usb: irq 32, io mem 0x00000000
[    1.686653] Init: Port Power? op_state=1
[    1.690569] Init: Power Port (0)
[    1.694101] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002
[    1.700890] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    1.708137] usb usb1: Product: DWC OTG Controller
[    1.712863] usb usb1: Manufacturer: Linux 4.1.5-v7 dwc_otg_hcd
[    1.718692] usb usb1: SerialNumber: 3f980000.usb
[    1.724204] hub 1-0:1.0: USB hub found
[    1.728005] hub 1-0:1.0: 1 port detected
[    1.732954] usbcore: registered new interface driver usb-storage
[    1.739227] mousedev: PS/2 mouse device common for all mice
[    1.745700] bcm2835-cpufreq: min=600000 max=900000
[    1.750865] sdhci: Secure Digital Host Controller Interface driver
[    1.757068] sdhci: Copyright(c) Pierre Ossman
[    1.761869] mmc-bcm2835 3f300000.mmc: mmc_debug:0 mmc_debug2:0
[    1.767728] mmc-bcm2835 3f300000.mmc: DMA channels allocated
[    1.812493] sdhci-pltfm: SDHCI platform and OF driver helper
[    1.824209] ledtrig-cpu: registered to indicate activity on CPUs
[    1.830499] hidraw: raw HID events driver (C) Jiri Kosina
[    1.837286] usbcore: registered new interface driver usbhid
[    1.843977] usbhid: USB HID core driver
[    1.849291] Initializing XFRM netlink socket
[    1.853703] NET: Registered protocol family 17
[    1.861673] Key type dns_resolver registered
[    1.866448] Registering SWP/SWPB emulation handler
[    1.872248] registered taskstats version 1
[    1.876594] vc-sm: Videocore shared memory driver
[    1.881301] [vc_sm_connected_init]: start
[    1.886168] [vc_sm_connected_init]: end - returning 0
[    1.892592] uart-pl011 3f201000.uart: no DMA platform data
[    1.898409] Waiting for root device /dev/mmcblk0p2...
[    1.903640] mmc0: host does not support reading read-only switch, assuming write-enable
[    1.913753] mmc0: new high speed SDHC card at address b368
[    1.919832] mmcblk0: mmc0:b368 SMI   3.69 GiB 
[    1.924456] Indeed it is in host mode hprt0 = 00021501
[    1.942943]  mmcblk0: p1 p2
[    2.013559] EXT4-fs (mmcblk0p2): couldn't mount as ext3 due to feature incompatibilities
[    2.022462] EXT4-fs (mmcblk0p2): mounting ext2 file system using the ext4 subsystem
[    2.042841] EXT4-fs (mmcblk0p2): mounted filesystem without journal. Opts: (null)
[    2.050390] VFS: Mounted root (ext2 filesystem) readonly on device 179:2.
[    2.065487] devtmpfs: mounted
[    2.069355] Freeing unused kernel memory: 424K (80788000 - 807f2000)
[    2.102224] usb 1-1: new high-speed USB device number 2 using dwc_otg
[    2.108947] Indeed it is in host mode hprt0 = 00001101
[    2.297336] EXT4-fs (mmcblk0p2): warning: mounting unchecked fs, running e2fsck is recommended
[    2.312601] usb 1-1: New USB device found, idVendor=0424, idProduct=9514
[    2.319306] usb 1-1: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[    2.327381] hub 1-1:1.0: USB hub found
[    2.331252] hub 1-1:1.0: 5 ports detected
[    2.368084] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null)
Starting logging: OK
Initializing random number generator... [    2.505165] random: dd urandom read with 59 bits of entropy available
done.
Starting network...
[    2.612233] usb 1-1.1: new high-speed USB device number 3 using dwc_otg
Welcome to Buildroot
buildroot login: [    2.732562] usb 1-1.1: New USB device found, idVendor=0424, idProduct=ec00
[    2.739446] usb 1-1.1: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[    2.750075] smsc95xx v1.0.4
[    2.816413] smsc95xx 1-1.1:1.0 eth0: register 'smsc95xx' at usb-3f980000.usb-1.1, smsc95xx USB 2.0 Ethernet, b8:27:eb:ff:aa:a8

En fait, le boot du noyau est bien terminé. Il a déjà affiché un message de bienvenue et une invite de connexion. Néanmoins le contrôleur USB a mis du temps à démarrer et de nouvelles traces ont été affichées, masquant la réussite du démarrage. Il faut appuyer sur Entrée pour que le système nous ré-affiche son prompt de connexion.

Welcome to Buildroot
buildroot login: root
# uname -a
Linux buildroot 4.1.5-v7 #1 SMP PREEMPT Wed Dec 2 13:38:15 CET 2015 armv7l GNU/Linux

Le noyau correspondant bien à la version indiquée plus haut. Vérifions le processeur du Raspberry Pi2.

# cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 38.40
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

processor       : 1
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 38.40
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

processor       : 2
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 38.40
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

processor       : 3
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 38.40
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5

Hardware        : BCM2709
Revision        : a01041
Serial          : 00000000f0ff000
#

Nous avons bien observé les quatre cœurs de processeur du Raspberry Pi 2. Examinons l’état de la mémoire :

# free
             total       used       free     shared    buffers     cached
Mem:        911512      18156     893356         40       1948       2128
-/+ buffers/cache:      14080     897432
Swap:            0          0          0

Le système compte au total un Go (dont une partie est réservée pour le contrôleur graphique) et dispose de 897 Mo de mémoire libre. En pressant deux fois la touche tabulation, nous pouvons voir la liste des commandes disponibles :

# (tab) (tab)
[                  flock              lzcat              sha256sum
[[                 fold               lzma               sha3sum
addgroup           free               makedevs           sha512sum
adduser            freeramdisk        md5sum             sleep
ar                 fsck               mdev               sort
arp                fstrim             mesg               start-stop-daemon
arping             fuser              microcom           strings
ash                getopt             mkdir              stty
awk                getty              mkfifo             su
basename           grep               mknod              sulogin
blkid              gunzip             mkswap             swapoff
bunzip2            gzip               mktemp             swapon
busybox            halt               modprobe           switch_root
bzcat              hdparm             more               sync
cat                head               mount              sysctl
catv               hexdump            mountpoint         syslogd
chattr             hostid             mt                 tail
chgrp              hostname           mv                 tar
chmod              hwclock            nameif             tee
chown              i2cdetect          netstat            telnet
chroot             i2cdump            nice               test
chrt               i2cget             nohup              tftp
chvt               i2cset             nslookup           time
cksum              id                 od                 top
clear              ifconfig           openvt             touch
cmp                ifdown             passwd             tr
cp                 ifup               patch              traceroute
cpio               inetd              pidof              true
crond              init               ping               truncate
crontab            insmod             pipe_progress      tty
cut                install            pivot_root         udhcpc
date               ip                 poweroff           uevent
dc                 ipaddr             printenv           umount
dd                 ipcrm              printf             uname
deallocvt          ipcs               ps                 uniq
delgroup           iplink             pwd                unix2dos
deluser            iproute            rdate              unlink
devmem             iprule             readlink           unlzma
df                 iptunnel           readprofile        unxz
diff               kill               realpath           unzip
dirname            killall            reboot             uptime
dmesg              killall5           renice             usleep
dnsd               klogd              reset              uudecode
dnsdomainname      last               resize             uuencode
dos2unix           less               rm                 vconfig
du                 linux32            rmdir              vi
dumpkmap           linux64            rmmod              vlock
echo               ln                 route              watch
egrep              loadfont           run-parts          watchdog
eject              loadkmap           runlevel           wc
env                logger             sed                wget
ether-wake         login              seq                which
expr               logname            setarch            who
false              losetup            setconsole         whoami
fbset              ls                 setkeycodes        xargs
fdflush            lsattr             setlogcons         xz
fdformat           lsmod              setserial          xzcat
fdisk              lsof               setsid             yes
fgrep              lspci              sh                 zcat
find               lsusb              sha1sum
#

La commande ps nous affiche la liste des processus présents :

# ps
PID   USER     COMMAND
    1 root     init
    2 root     [kthreadd]
    3 root     [ksoftirqd/0]
    4 root     [kworker/0:0]
    5 root     [kworker/0:0H]
    6 root     [kworker/u8:0]
    7 root     [rcu_preempt]
    8 root     [rcu_sched]
    9 root     [rcu_bh]
   10 root     [migration/0]
   11 root     [migration/1]
   12 root     [ksoftirqd/1]
   14 root     [kworker/1:0H]
   15 root     [migration/2]
   16 root     [ksoftirqd/2]
   17 root     [kworker/2:0]
   18 root     [kworker/2:0H]
   19 root     [migration/3]
   20 root     [ksoftirqd/3]
   21 root     [kworker/3:0]
   22 root     [kworker/3:0H]
   23 root     [khelper]
   24 root     [kdevtmpfs]
   25 root     [netns]
   26 root     [perf]
   27 root     [khungtaskd]
   28 root     [writeback]
   29 root     [crypto]
   30 root     [bioset]
   31 root     [kblockd]
   32 root     [kworker/1:1]
   33 root     [rpciod]
   34 root     [kswapd0]
   35 root     [fsnotify_mark]
   36 root     [nfsiod]
   42 root     [kthrotld]
   43 root     [kworker/0:1]
   44 root     [VCHIQ-0]
   45 root     [VCHIQr-0]
   46 root     [VCHIQs-0]
   47 root     [iscsi_eh]
   48 root     [dwc_otg]
   49 root     [DWC Notificatio]
   51 root     [VCHIQka-0]
   52 root     [SMIO]
   53 root     [deferwq]
   54 root     [kworker/u8:2]
   55 root     [mmcqd/0]
   56 root     [kworker/0:1H]
   57 root     [ext4-rsv-conver]
   58 root     [kworker/1:1H]
   59 root     [kworker/1:2]
   60 root     [kworker/2:1]
   69 root     [kworker/2:1H]
   71 root     /sbin/syslogd -n
   74 root     /sbin/klogd -n
   93 root     [kworker/3:1]
  145 root     [kworker/0:2]
  149 root     -sh
  153 root     ps
#

Hormis les threads du noyau (toutes les tâches avec des noms entre crochets), nous observons la présence de seulement cinq processus :

  • init : le premier processus qui est chargé d’abord de l’initialisation du système depuis l’espace utilisateur et par la suite de « l’adoption » des processus dont les parents se terminent ;
  • syslogd et klogd : démons chargés de l’enregistrement des messages du système ;
  • sh le shell sur lequel nous sommes connectés et la commande ps elle-même.

 

Voilà un système dont le contenu est bien sous contrôle !

 

Affinement de la configuration

Nous pouvons faire une première série d’améliorations, afin d’obtenir un système un peu plus convivial, accueillant un autre utilisateur que root par exemple ou renforçant la partition principale contre les risques de coupures d’alimentation intempestives.

Configuration de Buildroot

[buildroot-2015.11]$ make menuconfig
  • Menu System configuration :
    • System hostname: Choisissons un nom plus représentatif pour notre carte. Il apparaîtra par exemple dans l’invite de connexion, et nous l’afficherons également dans le prompt du shell. Nouvelle valeur : R-Pi.
    • System banner : Cette petite phrase s’affichera au démarrage avant la proposition de connexion ; on peut la personnaliser à volonté. Nouvelle valeur : Welcome on board!
    • Enable root login with password : Si le système a la moindre chance de se retrouver connecté à Internet, il est préférable de désactiver cette option. En effet le compte root sera le premier visé par les attaques automatiques par force brute. Si cette option est désactivée, il faudra intégrer la commande sudo afin de pouvoir réaliser les opérations d’administration. Sur un système expérimental, nous laissons la valeur originale : [*].
    • Root password : De même, il est conseillé de choisir pour tous les comptes des mots de passe solides (longs, assez faciles à retenir mais difficiles à deviner). Pour cette démonstration prenons un mot de passe ridiculement simple. Nouvelle valeur root.
    • remount root filesystem read-write during boot : Sur un système embarqué où l’alimentation peut être coupée à tout moment, il est conseillé de conserver le système de fichiers principal en lecture-seule. On le basculera en lecture-écriture temporairement pour des modifications de configuration par exemple. Nouvelle valeur : [ ]
    • Network interface to configure through DHCP : Suivant la configuration, on utilisera ou non une configuration réseau par DHCP. Si tel est le cas, on indique ici le nom de l’interface Ethernet. Nouvelle valeur : eth0.
    • Path to the users tables : On indique ici le chemin d’accès pour un fichier contenant la liste des utilisateurs (voir plus loin). Nouvelle valeur : $(TOPDIR)/../board/raspberrypi2/users.cfg.
    • Root filesystem overlay directories : Le répertoire indiqué ici est l’origine d’une arborescence qui sera appliquée « par-dessus » le système de fichiers obtenu à l’issue des compilations et installations, avant de préparer l’image de sortie. Autrement dit notre arborescence va venir se superposer (remplaçant éventuellement des fichiers) à celle se trouvant dans output/target avant de la stocker dans output/image/rootfs.tar. Nous détaillerons plus loin ce qu’il faut ajouter dans cet overlay. Nouvelle valeur : $(TOPDIR)/../board/raspberrypi2/ovl.

Table des utilisateurs

Nous avons rempli l’option Path to the users tables avec le nom d’un fichier qui contient la liste des utilisateurs. Il doit y avoir un compte par ligne. Les champs, séparés par un espace, sont les suivants :

  • login : identifiant de connexion du compte (sauf root).
  • uid : numéro d’utilisateur. -1 pour que l’attribution soit automatique.
  • group : groupe principal de l’utilisateur. Généralement le même nom que le login, ou alors un groupe global pour tous les comptes, comme users.
  • gid : numéro du groupe. -1 pour une attribution automatique.
  • password : le mot de passe, en clair si précédé d’un ‘=‘, crypté sinon. Si le mot de passe est ‘!‘, pas de connexion possible (compte utilisé pour un démon système par exemple).
  • home : répertoire personnel (aucun si ‘-‘).
  • shell : le shell de connexion. Sur notre système minimal, /bin/sh est un shell ash inclus dans Busybox.
  • groups : ce champ contient la liste des groupes supplémentaires auxquels appartient l’utilisateur (-1 si aucun).
  • gecos : des informations sur le compte, comme le nom en clair de l’utilisateur. Ce dernier champ peut contenir éventuellement des espaces.

 

Voici notre fichier ../board/raspberrypi2/users.cfg :

rpi -1 rpi -1 =rpi /home/rpi /bin/sh - Raspberry Pi 2 user

Overlay

Comme indiqué plus haut nous nous créons une arborescence spécifique, contenant des fichiers qui viendront s’ajouter à ceux produits par Buildroot.

[buildroot-2015.11]$ mkdir -p ../board/raspberrypi2/ovl/usr/local/bin/

Scripts supplémentaires

Le système de fichiers principal est monté en lecture seule. Mais il est parfois nécessaire de repasser temporairement en lecture-écriture. Pour cela j’ai l’habitude de créer deux petits scripts rw et ro qui remontent la racine du système de fichiers respectivement en lecture-écriture ou lecture seule. Pour que Buildroot puisse les intégrer automatiquement dans l’image qu’il produit, nous les plaçons dans l’arborescence overlay :

[buildroot-2015.11]$ nano ../board/raspberrypi2/ovl/usr/local/bin/rw

Contenu du script rw

#! /bin/sh

mount / -o rw,remount
[buildroot-2015.11]$ cp ../board/raspberrypi2/ovl/usr/local/bin/rw ../board/raspberrypi2/ovl/usr/local/bin/ro
[buildroot-2015.11]$ nano ../board/raspberrypi2/ovl/usr/local/bin/ro

Contenu du script ro

#! /bin/sh

mount / -o ro,remount
[buildroot-2015.11]$ chmod +x ../board/raspberrypi2/ovl/usr/local/bin/*

Lançons la nouvelle compilation

[buildroot-2015.11]$ make

Nous ré-insérons la carte micro-SD dans le PC de développement.Seule la partition ROOT doit être modifiée.

[buildroot-2015.11]$ sudo rm -rf /media/cpb/ROOT/*
[buildroot-2015.11]$ sudo tar xf output/images/rootfs.tar -C /media/cpb/ROOT/
[buildroot-2015.11]$ sudo umount /media/cpb/*

Après redémarrage du Raspberry Pi 2, vérifions la connexion avec l’identité root :

Welcome on board!
R-Pi login: root
Password: (root)
#

Vérifions tout de suite si le système de fichiers est bien monté en lecture seulement.

# ls /
bin      etc      lib      linuxrc  mnt      proc     run      sys      usr
dev      home     lib32    media    opt      root     sbin     tmp      var
# echo hello > /test-file
-sh: can't create /test-file: Read-only file system

Très bien. Vérifions qu’il soit possible de passer temporairement en lecture-écriture.

# rw
-sh: rw: not found
# /usr/local/bin/rw
[  287.714995] EXT4-fs (mmcblk0p2): warning: mounting unchecked fs, running e2fsck is recommended
[  287.725877] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null)
# echo hello > /test-file
# ls /
bin        home       linuxrc    opt        run        test-file  var
dev        lib        media      proc       sbin       tmp
etc        lib32      mnt        root       sys        usr
# rm /test-file
# /usr/local/bin/ro
[  302.623095] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null)
# echo hello > /test-file
-sh: can't create /test-file: Read-only file system
# exit

Malgré un petit souci de PATH qui ne permet pas d’invoquer directement rw ou ro, la protection du système est donc correcte. Vérifions l’accès en tant qu’utilisateur normal.

Welcome on board!
R-Pi login: rpi
Password: (rpi)
$ pwd
/home/rpi
$ ls
$ echo hello > my-file
-sh: can't create my-file: Read-only file system
$

Ici, un petit problème se pose. La partition « système » de notre Raspberry Pi est bien protégée contre les écritures, mais nous aimerions peut-être disposer de possibilités de stockage de données utilisateur. Pour cela il va falloir envisager l’ajout d’une partition supplémentaire montée en lecture-écriture

Nouvelles améliorations

Nous allons donc ajouter une nouvelle partition, formatée en vfat, afin de contenir les données utilisateur. Comme j’en ai parlé dans cet article, le format vfat est beaucoup plus simple que les traditionnels ext4 et consorts, mais il résiste bien à des coupures d’alimentations pendant une écriture.

Il faut indiquer la présence de cette partition dans le fichier /etc/fstab. Nous allons copier le fichier original produit par Buildroot dans notre overlay et lui ajouter une dernière ligne.

[buildroot-2015.11]$ mkdir  -p  ../board/raspberrypi2/ovl/etc
[buildroot-2015.11]$ cp output/target/etc/fstab ../board/raspberrypi2/ovl/etc/
[buildroot-2015.11]$ nano ../board/raspberrypi2/ovl/etc/fstab

Le fichier est modifié ainsi (dernière ligne ajoutée)

#                 
/dev/root       /               ext2    rw,noauto       0       1
proc            /proc           proc    defaults        0       0
devpts          /dev/pts        devpts  defaults,gid=5,mode=620 0       0
tmpfs           /dev/shm        tmpfs   mode=0777       0       0
tmpfs           /tmp            tmpfs   mode=1777       0       0
tmpfs           /run            tmpfs   mode=0755,nosuid,nodev  0       0
sysfs           /sys            sysfs   defaults        0       0
/dev/mmcblk0p3  /home/rpi       vfat    defaults,uid=1000,gid=1000  0 0

Nous pouvons également ajouter dans l’overlay un fichier ../board/raspberrypi2/etc/profile.d/custom.sh ajustant la configuration du PATH et du prompt du shell.

[buildroot-2015.11]$ mkdir  -p  ../board/raspberrypi2/ovl/etc/profile.d/
[buildroot-2015.11]$ nano ../board/raspberrypi2/ovl/etc/profile.d/custom.sh

Contenu du fichier custom.sh

# Acces aux scripts personnels et code metier.
PATH=$PATH:/usr/local/bin

# Prompt indiquant nom d'hote et repertoire courant.
PS1='\h[\W]\$ '

Recompilons notre système, et réinscrivons-le sur la carte SD en ajoutant une partition vfat de 1Go, destinée à recevoir les données utilisateur (applicatives).

[buildroot-2015.11]$ cp .config ../board/raspberrypi2/buildroot-03.cfg
[buildroot-2015.11]$ make
 [...]

Lors du partitionnement, nous pouvons placer la partition 3 (celle des données utilisateur) avant la partition 2 (celle du système). Ceci permet de donner une taille fixe à la partition 3, tout en laissant la partition 2 occuper tout l’espace disque disponible.

[buildroot-2015.11]$ sudo fdisk /dev/sdc
Commande (m pour l'aide) : n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Numéro de partition (1-4, 1 par défaut) : 1
Premier secteur (2048-7741439, 2048 par défaut) : (Entrée)
Dernier secteur, +secteurs ou +taille{K,M,G} (2048-7741439, 7741439 par défaut) : +128M

Commande (m pour l'aide) : t
Numéro de partition (1-4): 1
Code Hexa (taper L pour lister les codes): C

Commande (m pour l'aide) : n
Partition type:
   p   primary (1 primary, 0 extended, 3 free)
   e   extended
Select (default p): p
Numéro de partition (1-4, 2 par défaut) : 3
Premier secteur (264192-7741439, 264192 par défaut) : (Entrée)
Dernier secteur, +secteurs ou +taille{K,M,G} (264192-7741439, 7741439 par défaut) : +1G

Commande (m pour l'aide) : t
Numéro de partition (1-4): 3
Code Hexa (taper L pour lister les codes): C

Commande (m pour l'aide) : n
Partition type:
   p   primary (2 primary, 0 extended, 2 free)
   e   extended
Select (default p): p
Numéro de partition (1-4, 2 par défaut) : 2
Premier secteur (2361344-7741439, 2361344 par défaut) : (Entrée)
Dernier secteur, +secteurs ou +taille{K,M,G} (2361344-7741439, 7741439 par défaut) : (Entrée)

Commande (m pour l'aide) : a
Numéro de partition (1-4): 1

Commande (m pour l'aide) : p
Périphérique Amorçage  Début         Fin      Blocs    Id. Système
/dev/sdc1   *        2048      264191      131072    c  W95 FAT32 (LBA)
/dev/sdc2         2361344     7741439     2690048   83  Linux
/dev/sdc3          264192     2361343     1048576    c  W95 FAT32 (LBA)

Les entrées de la table de partitions ne sont pas dans l'ordre du disque
Commande (m pour l'aide) : w

Formatage de toutes les partitions :

[buildroot-2015.11]$ sudo mkfs.vfat -n BOOT /dev/sdc1
[buildroot-2015.11]$ sudo mkfs.vfat -n DATA /dev/sdc3
[buildroot-2015.11]$ sudo mkfs.ext2 -L ROOT /dev/sdc2

Après retrait et réinsertion de la carte SD :

[buildroot-2015.11]$ sudo cp output/images/bcm2709-rpi-2-b.dtb  /media/cpb/BOOT/
[buildroot-2015.11]$ sudo cp output/images/rpi-firmware/*  /media/cpb/BOOT/
[buildroot-2015.11]$ sudo ./output/host/usr/bin/mkknlimg  output/images/zImage  /media/cpb/BOOT/zImage
[buildroot-2015.11]$ sudo tar xf output/images/rootfs.tar  -C /media/cpb/ROOT/

Nous devons toujours éditer le fichier cmdline.txt de la partition BOOT pour ajouter console=ttyAMA,115200 sur la première ligne. Nous verrons ultérieurement comment intégrer cela dans la configuration du noyau. J’ajoute également au passage un petit fichier sur la partition DATA afin de vérifier que tout soit bien monté au démarrage.

[buildroot-2015.11]$ sudo nano /media/cpb/BOOT/cmdline.txt
    [...]
[buildroot-2015.11]$ echo "Hello User !" > /media/cpb/DATA/file.txt
[buildroot-2015.11]$ sudo umount /media/cpb/*

Au démarrage du Raspberry Pi 2, nous observons :

Welcome on board!
R-Pi login: rpi
Password: (rpi)
R-Pi[~]$ ls
file.txt
R-Pi[~]$ cat file.txt
Hello User !
R-Pi[~]$ echo $PATH
/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
R-Pi[~]$ echo Modification > file.txt
R-Pi[~]$ cat file.txt
Modification
R-Pi[~]$ rm file.txt
R-Pi[~]$ ls
R-Pi[~]$ 

Nos modifications concernant le prompt du shell, le PATH et le répertoire utilisateur en lecture-écriture ont bien été prises en compte.

Conclusion

Nous disposons ainsi d’un système Linux embarqué minimal assez personnalisé. Bien sûr nous sommes très loin d’un environnement de travail complet comme on peut l’obtenir avec une distribution précompilée comme Raspbian. Mais cela nous permet justement de maîtriser parfaitement le contenu de notre système.

La configuration ainsi établie (dont on peut télécharger ici les fichiers utilisés) nous permettra, dans de prochains articles, d’ajouter des applications et services standards (SSH, HTTP, NTP, etc.), d’ajuster le contenu du noyau, d’installer le bootloader U-Boot pour programmer des scripts de démarrage (et mise à jour) robustes, d’ajouter une application personnalisée (code métier) et même des modules kernel « maison ».


Paris Embedded Meetup #8

$
0
0

Paris EmbeddedCe soir à 19h, retrouvez-nous chez Mozilla, 16 bis boulevard Montmartre pour le huitième Paris Embedded Meetup.

À l’affiche :

  • Créer un bootloader/bsp/crt0/base-system pour un design sparc bare-metal en utilisant cmake et llvm/clang par Patrick Boettcher
  • GameBoy Pi – Mathieu Maret
  • Le système de build Alchemy – Yves-Marie Morgan

Les trois conférences seront présentées en français, puis seront suivies vers 21h d’un moment convivial autour de pizzas et de boissons variées.

Participation aux frais : 10 €

Pour en savoir plus : http://www.parisembedded.fr

Xenomai 3 sur Raspberry Pi 2

$
0
0

Xenomai 3 on Raspberry Pi 2Depuis plusieurs années l’installation de Xenomai sur un Raspberry Pi 1 se fait assez facilement, et les résultats en sont plutôt satisfaisants. Malheureusement l’installation sur un Raspberry Pi 2 ne fonctionnait pas. Le problème a été résolu depuis quelques mois par un patch de Mathieu Rondonneau qui permet d’utiliser la toute dernière version de Xenomai (3.0.2).

Xenomai 3 propose deux modèles de fonctionnement :

  • Dans le mode Mercury, les bibliothèques de Xenomai permettent de faire fonctionner du code applicatif (utilisant éventuellement l’API d’un autre système comme VxWorks ou pSOS) sur un noyau Linux standard, de préférence une version PREEMPT_RT.
  • Dans le mode Cobalt, le système Adeos (ipipe) capture les interruptions avant le noyau Linux et est donc capable d’activer des tâches Xenomai dans un temps plus prévisible. L’intégration est un peu plus complexe puisqu’elle nécessite de modifier le code du noyau Linux.

C’est ce dernier mode (Cobalt) que nous allons installer.

Préparation

Commençons par télécharger et décompresser l’archive de Xenomai. Je prends la dernière version disponible :

$ wget  http://xenomai.org/downloads/xenomai/stable/xenomai-3.0.2.tar.bz2
 [...]
$ tar  xjf  xenomai-3.0.2.tar.bz2

Voyons pour quelles versions du kernel le patch ipipe est disponible :

$ ls  xenomai-3.0.2/kernel/cobalt/arch/arm/patches/
ipipe-core-3.10.32-arm-13.patch  ipipe-core-3.18.20-arm-9.patch  README
ipipe-core-3.14.44-arm-16.patch  ipipe-core-4.1.18-arm-4.patch

Le choix le plus adapté sera un noyau 4.1. Téléchargeons le noyau Linux, plus particulièrement sa version spécifique pour Raspberry Pi :

$ git  clone https://github.com/raspberrypi/linux
Clonage dans 'linux'...
remote: Counting objects: 4883927, done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 4883927 (delta 20), reused 12 (delta 12), pack-reused 4883889
Réception d'objets: 100% (4883927/4883927), 1.40 GiB | 1.63 MiB/s, done.
Résolution des deltas: 100% (4041858/4041858), done.
Vérification de la connectivité... fait.
Checking out files: 100% (52713/52713), done.

Nous extrayons la branche 4.1 et vérifions le numéro complet :

$ cd  linux/
$ git  checkout  rpi-4.1.y
Checking out files: 100% (22027/22027), done.
La branche rpi-4.1.y est paramétrée pour suivre la branche distante rpi-4.1.y depuis origin.
Basculement sur la nouvelle branche 'rpi-4.1.y'
$ head  -3  Makefile 
VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 21

Le noyau de la branche 4.1 pour Raspberry Pi est un 4.1.21. Le patch ipipe est prévu pour un noyau 4.1.18. Nous pourrions remonter l’historique git pour trouver une version 4.1.18 exacte. Néanmoins, je préfère éviter car nous aurons besoin d’appliquer un second patch, qui a été très probablement produit sur un noyau 4.1.21, et qui entre en conflit avec l’arborescence arch/arm/mach-bcm du 4.1.18.

Préparons une branche de travail afin d’isoler les modifications de Xenomai des sources d’origine :

$ git  checkout  -b  xenomai-3.0.2
Basculement sur la nouvelle branche 'xenomai-3.0.2'

Et vérifions si le patch ipipe s’applique bien :

$ patch  --dry-run  -p1  <  ../xenomai-3.0.2/kernel/cobalt/arch/arm/patches/ipipe-core-4.1.18-arm-4.patch
checking file arch/arm/Kconfig
Hunk #3 succeeded at 533 (offset 36 lines).
Hunk #4 succeeded at 720 (offset 36 lines).
[...]
checking file mm/mmu_context.c
checking file mm/mprotect.c
checking file mm/vmalloc.c

Nous voyons les messages Hunk succeeded indiquant que le patch a dû être décalé pour être appliqué correctement, mais aucune erreur ne se produit. L’argument --dry-run de l’exécution ci-dessus permettait de vérifier si le patch s’appliquait correctement sans faire de modifications.

Pour appliquer véritablement le patch ipipe, un script est livré qui permet en outre d’installer le domaine Xenomai proprement dit dans le noyau Linux. Utilisons-le :

$ cd  ..
$ xenomai-3.0.2/scripts/prepare-kernel.sh  --linux=linux/  --arch=arm  --ipipe=xenomai-3.0.2/kernel/cobalt/arch/arm/patches/ipipe-core-4.1.18-arm-4.patch

Enregistrons les modifications apportées :

$ cd  linux/
$ git  add  '*'
$ git  commit  -a  -m  "ipipe-core-4.1.18 patch"

Appliquons maintenant le patch de Mathieu Rondonneau que vous pouvez télécharger ici : patch-xenomai-3-on-bcm-2709.patch

$ cd  ..
$ wget  http://www.blaess.fr/christophe/files/article-2016-05-22/patch-xenomai-3-on-bcm-2709.patch
$ cd  linux/
$ patch  -p1  <  ../patch-xenomai-3-on-bcm-2709.patch  --dry-run
checking file arch/arm/Kconfig
checking file arch/arm/mach-bcm2709/armctrl.c
checking file arch/arm/mach-bcm2709/bcm2708_gpio.c
checking file arch/arm/mach-bcm2709/bcm2709.c
checking file arch/arm/mach-bcm2709/include/mach/entry-macro.S

Le patch passe bien sur cette version, appliquons-le en retirant l’argument --dry-run :

$ patch  -p1  <  ../patch-xenomai-3-on-bcm-2709.patch
patching file arch/arm/Kconfig
patching file arch/arm/mach-bcm2709/armctrl.c
patching file arch/arm/mach-bcm2709/bcm2708_gpio.c
patching file arch/arm/mach-bcm2709/bcm2709.c
patching file arch/arm/mach-bcm2709/include/mach/entry-macro.S
$ git  commit  -a  -m  "xenomai-3-on-bcm2709 patch"

Compilations

Kernel Linux et Xenomai

Nous pouvons configurer et compiler ce noyau :

$ make  ARCH=arm  bcm2709_defconfig
  HOSTCC  scripts/kconfig/zconf.tab.o
  HOSTLD  scripts/kconfig/conf
#
# configuration written to .config
#
$ make  ARCH=arm  menuconfig

Deux messages « WARNING » nous indiquent que certains éléments de la configuration actuelle entrent en conflit avec Xenomai. Nous désactivons les entrées suivantes :

CPU Power Management  --->
    CPU Frequency scaling  --->
        [ ] CPU Frequency scaling

et

Kernel Features  --->
    [ ] Contiguous Memory Allocator

puis

Kernel Features  --->
    [ ] Allow for memory compaction

et enfin

Kernel Hacking  --->
    [ ] KGDB: kernel debugger --->

Une dernière modification est nécessaire ; elle n’a rien de spécifique à Xenomai. Cela nous assure que le noyau disposera des bons arguments sur sa ligne de commande.
Dans le menu

Boot options  --->

passer l’option

    Kernel command line type (Use bootloader kernel arguments if available)  --->

à la valeur

        (X) Extend bootloader kernel arguments

Nous pouvons lancer la compilation avec un :

$ make  ARCH=arm  CROSS_COMPILE=arm-linux-

Bien entendu cela suppose que nous ayons configuré dans notre variable PATH l’accès à une toolchain de cross-compilation, par exemple générée avec Buildroot comme nous l’avions fait dans l’article « Création d’un système complet avec Buildroot 2015.11« .

Au bout de quelques minutes notre noyau est prêt. Je vais l’installer sur une carte micro-SD possédant deux partitions : BOOT (formatée en vfat) et ROOT (formatée en ext2). La partition BOOT comprend déjà le bootloader du Raspberry Pi obtenu lors de la compilation avec Buildroot 2016.02 (de manière identique à l’article indiqué ci-dessus).

$ cp  arch/arm/boot/zImage  /media/$USER/BOOT/
$ cp  arch/arm/boot/dts/bcm2709-rpi-2-b.dtb  /media/$USER/BOOT/

Xenomai en espace utilisateur

Nous allons maintenant compiler les bibliothèques de Xenomai, qui permettent de faire fonctionner le code dans l’espace utilisateur.

$ ./scripts/bootstrap 
  [...]
$ ./configure  --host=arm-linux  --enable-smp
  [...]
$ make
  [...]

Les bibliothèques et les utilitaires ainsi compilés vont être installés sur la partition ROOT, qui contient un système de fichiers minimal avec Busybox et quelques scripts générés par Buildroot, comme dans l’article mentionné ci-dessus.

L’accès à cette partition ROOT nécessitant les droits d’administrateur, je prépare d’abord une copie de l’ensemble des fichiers à installer que je transfère ensuite avec le préfixe sudo.

$ make  DESTDIR=$(pwd)/target  install
  [...]
$ sudo  cp  -a  target/*  /media/$USER/ROOT/
$ umount  /media/$USER/*

Tests

Le moment de vérité est arrivé, on insère la carte micro-SD, on branche une console sur le port série, et on démarre un émulateur de terminal comme Minicom. Voici le résultat :

Uncompressing Linux... done, booting the kernel.
[    0.000000] Booting Linux on physical CPU 0xf00
[    0.000000] Initializing cgroup subsys cpuset
[    0.000000] Initializing cgroup subsys cpu
[    0.000000] Initializing cgroup subsys cpuacct
[    0.000000] Linux version 4.1.21-logilin+ (cpb@Logilin) (gcc version 4.9.3 (Buildroot 2016.02) ) #2 SMP Sun May 22 08:40:39 CEST 2016
[    0.000000] CPU: ARMv7 Processor [410fc075] revision 5 (ARMv7), cr=10c5387d
[    0.000000] CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
[    0.000000] Machine: BCM2709
[    0.000000] Memory policy: Data cache writealloc
  [...]
[    0.000000] I-pipe, 19.200 MHz clocksource, wrap in 960767920505705 ms
[    0.000000] clocksource ipipe_tsc: mask: 0xffffffffffffffff max_cycles: 0x46d987e47, max_idle_ns: 440795202767 ns
[    0.000000] clocksource arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x46d987e47, max_idle_ns: 440795202767 ns
[    0.000000] sched_clock: 56 bits at 19MHz, resolution 52ns, wraps every 4398046511078ns
[    0.000001] Switching to timer-based delay loop, resolution 52ns
[    0.000917] Interrupt pipeline (release #4)

  [...]
[    0.948130] [Xenomai] scheduling class idle registered.
[    0.953408] [Xenomai] scheduling class rt registered.
[    0.958732] I-pipe: head domain Xenomai registered.
[    0.967286] [Xenomai] Cobalt v3.0.2 (Exact Zero) 
  [...]
Welcome to Buildroot
buildroot login:

Un oeil averti verra passer dans les traces du noyau quelques messages liés à Xenomai ou ipipe. Connectons-nous et examinons le système :

buildroot login: root
Password: 
# uname -a
Linux buildroot 4.1.21-logilin+ #2 SMP Sun May 22 08:40:39 CEST 2016 armv7l GNU/Linux
# cat /proc/xenomai/version
3.0.2
# cat /proc/ipipe/version
4

Notre système fonctionne bien avec Xenomai et ipipe. Le plus simple pour s’assure de son bon fonctionnement est d’utiliser l’un des outils de test livrés dans /usr/xenomai/bin.

# /usr/xenomai/bin/latency
== Sampling period: 1000 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
RTT|  00:00:01  (periodic user-mode task, 1000 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD|      2.551|      2.795|      7.290|       0|     0|      2.551|      7.290
RTD|      2.551|      2.809|      7.082|       0|     0|      2.551|      7.290
RTD|      2.498|      2.825|      8.748|       0|     0|      2.498|      8.748
RTD|      2.446|      2.787|      7.393|       0|     0|      2.446|      8.748
RTD|      2.549|      2.784|      6.768|       0|     0|      2.446|      8.748
RTD|      2.497|      2.784|      6.716|       0|     0|      2.446|      8.748
RTD|      2.549|      2.781|      6.611|       0|     0|      2.446|      8.748

Cet outil montre les fluctuations (en micro-secondes) d’un timer de fréquence 1kHz. Ceci est assez révélateur des variations du temps de latence des interruptions également. La colonne la plus intéressante est la dernière à droite, puisqu’elle montre le retard maximum atteint pendant l’exécution. Au bout de quelques minutes, cette valeur se stabilise autour de 9 micro-secondes. En laissant l’expérience pendant une heure et en récupérant un histogramme des latences observées, on obtient les valeurs et graphique suivants :

# ----lat min|----lat avg|----lat max|-overrun|---msw|
#       2.563|      3.363|     11.283|       0|     0|

Xenomai 3 on Raspberry Pi 2

Sur l’axe des abscisses, les latences en micro-secondes. En ordonnées le nombre de cas où chaque latence a été observée. Cet axe est logarithmique. Nous voyons donc qu’une énorme majorité des latences est de 3 ou 4 micro-secondes et que la pire latence est de 12 micro-secondes.

Néanmoins pour des vraies mesures réalistes, il faut stresser le système intensément. Pour ce faire, il existe un script nommé dohell, livré avec Xenomai qui assure une charge très forte en nombre de tâches et opérations d’entrées-sorties. J’ai fait tourner ce script pendant une heure, avec en outre une charge importante en interruptions grâce à un ping en mode flood qui se produisait régulièrement pendant une dizaine de secondes.

# ----lat min|----lat avg|----lat max|-overrun|---msw|
#       1.318|     23.331|     61.452|       0|     0|

1h. charge élevée

Enfin, j’ai réalisé une mesure pendant deux heures en alternant les périodes de faible charge et les périodes de charge très intense. Je pense en effet que les pires latences se mesurent plutôt en régimes transitoires qu’en régimes permanents. Il est donc important d’alterner les conditions d’exécution.

# ----lat min|----lat avg|----lat max|-overrun|---msw|
#      -0.346|     10.508|     62.491|       0|     0|

Xenomai 3 on Raspberry Pi 2
Nous pouvons remarquer que dans ce dernier cas, la latence minimale est de -0.346 micro-seconde. Ceci peut paraître surprenant mais est normal. Xenomai estime en effet la latence minimale et anticipe les déclenchements des timers de la valeur (en nanosecondes) indiquée dans /proc/xenomai/latency. Nous pourrions donc la corriger ainsi :

# cat /proc/xenomai/latency
10520
# echo $((10520 - 346 )) > /proc/xenomai/latency
# cat /proc/xenomai/latency
10156
#

Conclusion

Nous voyons ainsi que le support de Xenomai sur Raspberry Pi 2 est assez simple à configurer et installer. Bien sûr il faudrait faire des tests plus longs et plus complets pour avoir une bonne certitude de la latence maximale mais je pense que la valeur de 63 microsecondes que nous avons obtenue doit en être une assez bonne approche.

N’hésitez pas à me faire part de vos essais et résultats.

Je tiens à remercier Nicolas Schurando qui m’a aidé lors d’une séance de formation à tester cette nouvelle version de Xenomai.

Ajustements pour le Raspberry Pi 3

$
0
0

J’ai reçu récemment plusieurs demandes de lecteurs souhaitant appliquer les opérations proposées dans les articles Renforcer une distribution Raspbian Jessie et Création d’un système complet avec Buildroot pour la carte Raspberry Pi 3.

Voici donc un aperçu rapide des opérations à réaliser. Pour en comprendre le détail on se reportera aux articles concernés.

Booter une Raspbian avec une console série

Je prends la dernière version de cette distribution lors de la rédaction de cette note.

$ wget https://downloads.raspberrypi.org/raspbian/images/raspbian-2016-09-28/2016-09-23-raspbian-jessie.zip
$ unzip 2016-09-23-raspbian-jessie.zip

J’insère une carte SD, reconnue sur ma machine comme périphérique bloc /dev/sdc (visible dans le résultat des commandes « dmesg | tail » ou « mount » ). Ajustez ceci suivant votre système.

$ sudo umount /dev/sdc*
$ sudo dd if=2016-09-23-raspbian-jessie.img of=/dev/sdc bs=4M

Par défaut, la distribution Raspbian ne propose pas de console série, uniquement un terminal graphique (avec la nouvelle interface Pixel). Comme j’ai une prédilection pour le terminal en console série, je vais activer ce port, au détriment du Bluetooth qui utilise le même contrôleur UART. Pour cela, il faut ajouter un overlay au device tree qui décrit le matériel présent. Cet overlay va désactiver le Bluetooth, le contrôleur série pouvant à nouveau être dédiée à notre console. Bien entendu, cette étape est inutile si vous souhaitez travailler sur une console graphique normale.
Après une première extraction, je réinsère ma carte SD sur mon PC, et je me rends dans le répertoire où la partition de boot du Raspberry Pi est montée. J’y édite alors le fichier config.txt pour ajouter la ligne « dtoverlay=pi3-disable-bt » . Après démarrage du Raspberry Pi, je peux me connecter sur le port série, comme sur une console graphique classique.

raspberrypi login: pi
Password: (raspberry)
[...]
pi@raspberrypi:~$

Recompiler le noyau de la distribution Raspbian

Après les premières étapes de configuration de la distribution, j’entame une re-compilation du noyau directement sur le Raspberry Pi 3.

$ git clone --depth 1 https://github.com/raspberrypi/linux
$ cd linux
$ make bcm2709_defconfig
$ sudo apt-get install -y bc libncurses-dev
$ make menuconfig

Dans le menu « General setup » modifiez la troisième option (Local version) pour inscrire un tiret, suivi de vos initiales par exemple.

$ make -j 4
$ sudo make modules_install
$ sudo cp arch/arm/boot/zImage /boot/kernel7.img
$ sudo cp arch/arm/boot/dts/*dtb /boot/
$ sudo cp arch/arm/boot/dts/overlays/*dtbo /boot/overlays/
$ sudo reboot

Et voilà ! Un noyau tout frais, dont vous pouvez vérifier la version avec « uname -a » et vous y retrouverez le numéro personnalisé avec vos initiales.

Buildroot et Raspberry Pi 3

Le support du Raspberry Pi 3 est présent dans Buildroot depuis plusieurs mois, néanmoins je vais en télécharger une version récente pour bénéficier des overlays du device tree et pouvoir continuer à utiliser ma console série. Les opérations suivantes ont lieu à nouveau sur un PC de développement.

$ git clone git://git.buildroot.net/buildroot
$ cd buildroot
$ git checkout 9cc1da8f
$ ls configs/*raspberry*
configs/raspberrypi0_defconfig configs/raspberrypi3_defconfig
configs/raspberrypi2_defconfig configs/raspberrypi_defconfig
$ make raspberrypi3_defconfig
$ make menuconfig
$ make

Une fois la compilation terminée, une image toute neuve nous attend, prête à être installée sur une carte SD (sans nécessiter de partitionnement ou formatage).

$ sudo dd if=output/images/sdcard.img of=/dev/sd

Au boot, un prompt nous permet une connexion :

Welcome to Buildroot
buildroot login: root
#

Conclusion

Le support du Raspberry Pi 3 est bien inclus dans le noyau Linux ainsi que dans le projet Buildroot depuis plusieurs mois, néanmoins quelques options de configuration ont changées et ces quelques lignes devraient permettre de compléter les articles indiqués plus haut.

Raspberry Pi Zero et USB-net (1)

$
0
0

Pi Zero et USB-netLe petit Raspberry Pi Zero est un nano-ordinateur bien sympathique. Il dispose des capacités équivalentes au Raspberry Pi modèle 1 B+, pour un encombrement à peine supérieur à celui d’un ticket de métro.

Victime de son succès le Pi Zero s’écoule au compte-gouttes, mais il devient possible d’en obtenir un assez facilement pour 4$ (plus environ 4$ de port !).

Il existe de nombreux tutoriels de prise en main, à commencer par ceux – excellents – d’Adafruit. L’article ci-dessous permet une prise de contrôle uniquement par l’interface USB-net, sans souder de connecteur série, sans brancher de clavier (ce qui nécessiterait un adaptateur USB-A femelle micro USB-B mâle) ni même d’écran (ce qui nécessiterait un adaptateur HDMI femelle micro-HDMI mâle). Nous allons utiliser le Raspberry Pi Zero nu, et un simple câble USB micro-USB comme on en emploie pour connecter un téléphone portable et un PC.

Préparation de la carte micro-SD

Récupérons une version récente de la distribution Raspbian.

$ wget https://downloads.raspberrypi.org/raspbian/images/raspbian-2017-01-10/2017-01-11-raspbian-jessie.zip
$ unzip 2017-01-11-raspbian-jessie.zip
$ ls -l 2017-01-11-raspbian-jessie.img
-rw-r--r-- 1 cpb cpb 4371513344 janv. 10 17:59 2017-01-11-raspbian-jessie.img

Insérons une carte SD de capacité suffisante (au moins 8Go) dans le PC. Sur mon PC elle est reconnue comme un périphérique bloc /dev/sdc1 et automatiquement montée.

$ umount /media/${USER}/*
$ sudo dd if=2017-01-11-raspbian-jessie.img of=/dev/sdc bs=1M

Une fois la copie terminée, j’extraie la carte et la réinsère dans le PC. Elle est alors automatiquement montée sur /media/$USER/boot et /media/$USER/<numéro de partition>.

Nous ajoutons une ligne à la fin du fichier

/media/$USER/boot/config.txt:
[...]
dtoverlay=dwc2

Et nous insérons une option modules-load sur la ligne d’arguments du kernel. Attention, à ne pas insérer de retour-chariot, le fichier doit contenir une unique ligne.

/media/$USER/boot/cmdline.txt:
modules-load=dwc2,g_ether dwc_otg.lpm_enable=0 console=[...]

Avec cette configuration, le Pi Zero présentera une interface USB-net, un réseau IP point-à-point avec le PC auquel il sera connecté. Pour initialiser l’adresse IP de cette interface, ajoutons le paragraphe suivant dans le fichier de configuration du réseau.

/media/$USER/<numero de partition>/etc/network/interfaces:
[...]
allow-hotplug usb0
iface usb0 inet static
    address 192.168.7.2
    netmask 255.255.255.0
    network 192.168.7.0
    broadcast 192.168.7.255
    gateway 192.168.7.1
    dns-nameservers 80.67.169.12  # French Data Network DNS.

Le sous-réseau point-à-point sera donc en 192.168.7.x. Le Pi Zero prendra l’adresse 192.168.7.2. On suppose que le PC prendra l’adresse 192.168.7.1 et servira de passerelle vers Internet.

J’ai utilisé ici l’adresse IP d’un DNS ouvert de l’association FDN. Vous pouvez bien entendu utiliser tout autre résolveur.

 

Une dernière modification va nous permettre d’activer automatiquement le démon SSH afin de pouvoir se connecter facilement au Pi Zero.
Nous ajoutons deux lignes avant la commande exit 0 dans le fichier rc.d.

/media/$USER/<numero de partition>/etc/rc.d:
[...]

update-rc.d  ssh  enable
invoke-rc.d  ssh  start

exit 0

Nous pouvons démonter, extraire la carte micro-SD, l’insérer dans le Raspberry Pi Zero, et brancher ce dernier au PC en employant sa prise “USB” (et non pas “PWR IN”).

$ umount /media/$USER/*

Pi Zero et USB-net

Boot du Raspberry Pi Zero

Avant de brancher le Pi Zero, nous examinons la liste des interfaces réseau proposées par le PC.

$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
    link/ether b0:48:7a:83:3f:86 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 00:1d:09:23:b5:8e brd ff:ff:ff:ff:ff:ff
[...]

Le premier boot est un peu plus long que les suivants (environ une minute) car il étend automatiquement la taille du système de fichiers pour utiliser toute la carte SD. Avec une ligne comme “watch -n 1 ip link show” sur votre PC, vous verrez apparaître l’interface USB-net.

$ watch -n 1 ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
    link/ether b0:48:7a:83:3f:86 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 00:1d:09:23:b5:8e brd ff:ff:ff:ff:ff:ff
[...]
9: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 8a:f4:12:06:cd:84 brd ff:ff:ff:ff:ff:ff

Si votre PC présente directement les noms d’interface réseau du kernel, vous verrez apparaître l’interface usb0. Si votre système affiche les noms “prédictibles” de systemd, vous verrez une interface du type enp0s29u1u3 dont les numéros dépendront du port USB sur lequel vous branchez votre Pi Zero.

Pour pouvoir communiquer avec le Raspberry Pi, nous devrons commencer par forcer une adresse IP côté PC. Pour cela nous pouvons utiliser le Network Manager :

Boîte "Connexion réseau"
Cliquez sur “Ajouter”.

Boîte "Sélectionnner un type de connexion"Cliquez sur “Créer”.

Boîte "Modification de Connexion Ethernet 1"Sélectionnez votre interface et passez à l’onglet “Paramètres IPv4”.

Saisissez les adresses réseau, et cliquez sur “Routes”.

Boîte "Modification des routes IPv4 pour Wired Connection 1"Cochez la case “Utiliser cette connexion uniquement pour les ressources de son réseau”, puis cliquez sur “Ok”.

Vous pouvez maintenant tenter un “ping” vers le Raspberry Pi Zero :

$ ping 192.168.7.2
PING 192.168.7.2 (192.168.7.2) 56(84) bytes of data.
64 bytes from 192.168.7.2: icmp_seq=1 ttl=64 time=0.445 ms
64 bytes from 192.168.7.2: icmp_seq=2 ttl=64 time=0.293 ms
64 bytes from 192.168.7.2: icmp_seq=3 ttl=64 time=0.380 ms
^C
--- 192.168.7.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.293/0.372/0.445/0.066 ms

Et à présent, nous allons nous connecter en utilisant SSH sur le Raspberry Pi Zero.

$ ssh pi@192.168.7.2
The authenticity of host '192.168.7.2 (192.168.7.2)' can't be established.
ECDSA key fingerprint is SHA256:Ua2GcT+NJsw4VRdqHEs95IrGCWkwhI+SFFB+yfT4TMU.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.7.2' (ECDSA) to the list of known hosts.
pi@192.168.7.2's password: raspberry

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Nov 25 23:28:21 2016

SSH is enabled and the default password for the 'pi' user has not been changed.
This is a security risk - please login as the 'pi' user and type 'passwd' to set a new password.

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 4.4.34+ #930 Wed Nov 23 15:12:30 GMT 2016 armv6l GNU/Linux
pi@raspberrypi:~ $ lscpu
Architecture:          armv6l
Byte Order:            Little Endian
CPU(s):                1
On-line CPU(s) list:   0
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             1
Model name:            ARMv6-compatible processor rev 7 (v6l)
CPU max MHz:           1000.0000
CPU min MHz:           700.0000

L’initialisation de la connexion par SSH est un peu longue, nous résoudrons cela ultérieurement.

Conclusion

Nous avons réussi à nous connecter sur le Raspberry Pi Zero depuis un PC sous Linux (l’équivalent depuis Windows serait tout à fait possible en utilisant PuTTY) sans autre accessoire qu’un câble USB de téléphone.

Pour le moment notre Pi Zero est un peu coupé du monde, car il ne dispose pas d’accès Internet. Nous allons configurer ceci dans le prochain article

Pi Zero et USB-net (2)

$
0
0

Pi Zero et USB-net

Dans l’article précédent, nous avons réussi à nous connecter depuis un PC hôte vers un Raspberry Pi Zero uniquement en employant un câble USB semblable à ceux pour téléphone portable.

Néanmoins, nous avions relevé plusieurs points restant à améliorer :

  • la connexion SSH est un peu longue à établir,
  • le Raspberry Pi Zero n’a pas accès à Internet,
  • il est nécessaire de fixer manuellement l’adresse IP de l’interface USB-net du côté PC.

Réglons ces problèmes un à un.

Accélérer la connexion SSH

Le serveur SSH (démon sshd) présent sur la distribution Raspbian du Pi Zero essaye d’identifier la machine qui vient se connecter en utilisant une résolution DNS. Puisque l’accès au DNS est impossible (pas d’accès Internet), et que même s’il était joignable il y a peu de chances qu’il connaisse le PC hôte, il faut attendre que le démon sshd échoue dans cette identification avant de poursuivre la connexion.

Nous pouvons désactiver aisément ce comportement en éditant le fichier de configuration de sshd (sur le Raspberry Pi Zero).

pi@raspberry:~ $ sudo vi /etc/ssh/sshd_config

Il suffit d’ajouter la ligne suivante à la fin du fichier :

/etc/ssh/sshd_config:
[...]
UsePAM yes

UseDNS no
pi@raspberry:~ $ sudo reboot

À partir de ce moment, la connexion sera plus aisée…

L’avantage de cette première solution est qu’elle est assez générique, je l’adopte sur la plupart des systèmes embarqués que je configure.

Dans notre cas spécifique, où une seule machine est susceptible de venir se connecter sur le serveur SSH, on peut adopter la solution proposée par Stéphane Peters en commentaire de l’article précédent. Il s’agit d’éditer le fichier /media/$USER/etc/hosts pour y ajouter une ligne identifiant le PC :

/media/$USER/etc/hosts:
[...]
192.168.7.1 host-pc

Accéder à Internet depuis le Raspberry Pi Zero

Lorsque nous somme sur le Pi Zero, la seule connexion vers l’extérieur est le lien USB-net point-à-point avec le PC hôte. Pour accéder à Internet il nous faudra donc passer par ce PC (à supposer bien sûr qu’il soit lui même relié au Net). Pour cela nous allons configurer les règles de routage et de filtration du noyau Linux du PC

Une fois la connexion établie, il suffit d’exécuter sur le PC quelques lignes de commande iptables pour activer le NAT (Network Address Translation) ainsi que la redirection (forwarding) au niveau IP.

Je pense que seules les deux dernières commandes sont indispensables, mais j’ai l’habitude d’exécuter le script ci-dessous complet afin de repartir d’une configuration vierge.

set-nat-rules.sh:
#! /bin/sh

# Inside network (without Internet access).
INSIDE=usb0

# Outside network (with Internet access).
OUTSIDE=eth0

# Flush the rules of the NAT table.
iptables -t nat -F  || { echo "$0: error while flushing NAT table rules." >&2; exit 1; }

# Delete the user-defined chains of the NAT table.
iptables -t nat -X  || { echo "$0: error while deleting NAT table chains." >&2; exit 1; }

# Reset the statistics of the NAT table.
iptables -t nat -Z  || { echo "$0: error while resetting NAT table counters." >&2; exit 1;
 }

# Flush the current forwarding rules.
iptables -F FORWARD || { echo "$0: error while flushing forwarding rules." >&2; exit 1; }

# Accept packets from INSIDE network to OUTSIDE network.
iptables -A FORWARD -i ${INSIDE} -o ${OUTSIDE} -j ACCEPT   || { echo "$0: error while conf
iguring ${INSIDE}->${OUTSIDE} forwarding." >&2; exit 1; }

# Apply NAT to OUTSIDE packets.
iptables -t nat -A POSTROUTING -o ${OUTSIDE} -j MASQUERADE || { echo "$0: error while conf
iguring NAT on ${OUTSIDE}." >&2; exit 1; }

# Activate the packet forwarding.
echo 1 > /proc/sys/net/ipv4/ip_forward || { echo "$0: error while enabling IP forwarding."
 >&2; exit 1; }

echo "Configuration OK." >&2

exit 0

J’exécute donc la commande suivante sur mon PC, après avoir ajusté les deux premières variables du script.

$ sudo ./set-nat-rules.sh

Et sur le Pi Zero je tente un accès vers Internet

pi@raspberrypi:~ $ ping www.kernel.org
PING pub.all.kernel.org (198.145.20.140) 56(84) bytes of data.
64 bytes from tiz-korg-pub.kernel.org (198.145.20.140): icmp_seq=1 ttl=46 time=166 ms
64 bytes from tiz-korg-pub.kernel.org (198.145.20.140): icmp_seq=2 ttl=46 time=188 ms
64 bytes from tiz-korg-pub.kernel.org (198.145.20.140): icmp_seq=3 ttl=46 time=174 ms
64 bytes from tiz-korg-pub.kernel.org (198.145.20.140): icmp_seq=4 ttl=46 time=167 ms
^C
--- pub.all.kernel.org ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 166.502/174.144/188.734/8.915 ms
pi@raspberrypi:~ $

Le temps de ping est un peu long (170 ms environ), à cause du réseau USB-net et de la redirection sur le PC, mais cela fera l’affaire pour notre usage.

On pourra noter qu’au bout de quelques dizaines de secondes, l’heure du Raspberry Pi Zero sera correctement mise à jour. Il inclut en effet un client NTP (Network Time Protocol) qui interroge régulièrement des serveurs distants.

Automatiser l’affectation d’adresse

Pour que le PC reçoive automatiquement une adresse IP lors de l’établissement de l’interface USB-net, il suffit d’installer un serveur DHCP sur le Raspberry Pi Zero.

Ceci est facile à faire, puisque nous disposons de tout le confort de la distribution Raspbian et de ses packages. Nous pouvons par exemple installer le petit serveur udhcpd (provenant de Busybox) facile à configurer.

pi@raspberrypi:~ $ sudo apt-get update
[...]
pi@raspberrypi:~ $ sudo apt-get install -y udhcpd
[...]

Il faut configurer le serveur DHCP pour lui indiquer les adresses qui nous intéressent.

pi@raspberrypi:~ $ sudo mv /etc/udhcpd.conf /etc/udhcpd.conf.backup
pi@raspberrypi:~ $ sudo nano /etc/udhcpd.conf

On peut utiliser une configuration très simple qui impose à notre correspondant (le PC) l’adresse 192.168.7.1 :

/etc/udhcpd.conf:
 start      192.168.7.1
 end        192.168.7.1
 interface  usb0
 max_leases 1
 option subnet 255.255.255.252

Nous devons également faire démarrer le serveur udhcpd automatiquement au boot. Pour cela nous devons modifier une option du fichier /etc/default/udhcpd.

/etc/default/udhcpd:
DHCPD_ENABLED="yes"
[...]
pi@raspberrypi:~ $ sudo reboot

Il devient possible, sur le PC, de modifier la configuration du Network Manager pour que l’interface USB-net soit initialisée automatiquement avec le protocole DHCP.

Boîte "Configuration de connexion filaire 1"

Attention à bien laisser cochée la case “Utiliser cette connexion uniquement pour les ressources de son réseau”. C’est ce qui nous garantit que nous pourrons continuer à accéder à Internet grâce à l’autre interface du PC.

Boîte "Modification des routes IPv4 pour Connexion filaire 1"

Conclusion

Nous avons obtenu une configuration assez confortable pour le Raspberry Pi Zero : simplement branché à un PC par un câble USB standard, il devient joignable via SSH sur une adresse IP connue, et peut accéder librement à Internet (pour peu que le PC fasse le relais).

Un bête accident

$
0
0

Un bête accident

Dans la longue liste des cartes et composants détruits par un mauvais branchement je viens d’ajouter un Raspberry Pi 2.

Bien qu’assez précautionneux avec les cartes électroniques, je reconnais volontiers ma responsabilité dans plusieurs cas (maladresse, agacement, mauvaise lecture des numéros de broches, etc.). À quelques reprises les erreurs furent commises par des participants à mes sessions de formation.

Cette fois une bonne partie du problème relève de la malchance : je vais vous raconter ma mésaventure en espérant que cela évitera ce genre de situation à l’avenir.

Je préparais, sur un Raspberry Pi 2, une petite manipulation que j’espère vous présenter ici dans quelques temps.

Raspberry Pi serial port

Comme bien souvent, je me connecte sur le Raspberry Pi par son port série en utilisant un adaptateur USB (côté PC) vers 4 connecteurs Dupont (côté Raspberry) branchés sur le port d’extension : fil blanc sur la broche 8 (TX), fil vert sur la broche 10 (RX) et masse sur la broche 6 (GND). Cet adaptateur me permet en outre d’alimenter directement le Raspberry Pi en +5V sur la broche 2.

Ce petit câble est très pratique, il me sert pour la plupart des cartes pour Linux embarqué sur lesquelles je travaille.

Outre la simplicité de connexion, l’économie d’un bloc transformateur, et l’indépendance vis-à-vis des prises secteur, il m’évite de devoir débrancher/rebrancher le câble d’alimentation de la prise micro-USB avec les risques mécaniques que cela suppose. En contre-partie le courant délivré est assez limité, et le Raspberry Pi ne peux pas alimenter d’écran par exemple de cette manière.

J’étais donc connecté ainsi :

Un bête accident - 1

Pour extraire la carte SD et la brancher sur mon PC afin de modifier le bootloader, j’ai arrêté le Raspberry Pi en débranchant le fil rouge (+5V) de la borne 2.

Un bête accident - 2

Comme bien souvent, j’avais laissé le connecteur USB à l’autre extrémité du câble branché sur mon PC. Ce câble passait juste à côté de mon tapis de souris, et lorsque j’ai bougé celle-ci, il s’est un peu déplacé.

Un bête accident - 3

Le connecteur rouge +5V est alors venu “tout seul” s’enficher partiellement sur la borne 1 du connecteur du Raspberry Pi. Partiellement, mais suffisamment pour établir le contact.

Un bête accident - 4

Malheur ! Cette broche est une sortie +3.3V qui ne supporte pas du tout la présence d’une tension d’entrée +5V.

Un bref éclair sur la LED Power, et le Raspberry Pi a rendu l’âme définitivement.

En soi, la perte d’un Raspberry Pi n’est pas catastrophique, et est même plutôt prévisible vu l’ensemble des manipulations plus ou moins expérimentales que je leur fait subir. Ce qui m’a néanmoins dérangé, c’est qu’il s’agissait de mon dernier Pi 2. Pour mes sessions de formation je dispose de plusieurs dizaines de Pi 1, de Pi 3, et même quelques Pi 0. Mais l’expérience que je tentais était spécifique pour le Pi 2, et se trouve donc repoussée de quelques jours en attendant de passer une nouvelle commande.

Une habitude qui me vient du monde aéronautique est de toujours “débrieffer” les erreurs, en cherchant les causes et surtout comment les éviter à l’avenir. C’est l’objectif de ce petit article. La solution est simple : lors de l’utilisation de ce type de câble, ne pas se contenter de débrancher le +5V, en comptant sur la protection qu’offre le connecteur isolé, mais déconnecter plutôt l’extrémité USB du câble, côté PC. Je ne le faisais pas à cause de l’incertitude du sens de branchement de la prise USB et de la double tentative que cela implique bien souvent. C’est un moindre mal face au risque de griller une carte électronique. J’essaierai désormais de me tenir à cette bonne résolution.

Un noyau 4.10 sur un Raspberry Pi 3 64 bits

$
0
0

Vanilla kernel 4.10 on 64 bits Raspberry Pi 3

Lorsque nous recompilons un noyau Linux pour le Raspberry Pi, nous avons généralement l’habitude d’utiliser un kernel spécifique, disponible sur https://github.com/raspberrypi/linux contenant des drivers absents du noyau standard. Il est néanmoins possible de faire fonctionner sur la plupart des modèles de Raspberry Pi un noyau Linux parfaitement standard (aussi dit Vanilla Kernel). Ceci au prix de quelques efforts de configuration que nous allons voir.

À titre d’exemple, nous allons installer un noyau Linux 4.10.1 (diffusé depuis deux semaines) sur un Raspberry Pi 3.

En outre, nous allons faire fonctionner le Raspberry Pi 3 en mode 64 bits et utiliser le bootloader industriel U-boot !

Un avertissement pour commencer. Les étapes que nous allons suivre sont très simples, un peu longues mais très simples. Le résultat néanmoins ne sera pas spectaculaire, loin s’en faut.

Je n’ai pour l’instant pas réussi à faire fonctionner le support pour la console (ni HDMI, ni LVDS), aussi est-on limité à une connexion par le port série. L’intérêt est donc purement technique de voir son Raspberry Pi 3 fonctionner en mode 64 bits avec un kernel standard. Dans quelques temps peut-être, l’ensemble du système pourra s’exécuter de la sorte.

Intérêt de cette expérimentation

32 bits vs 64 bits ?

Le system-on-chip autour duquel est construit le Raspberry Pi 3 est un Broadcom BCM2837 comportant un processeur quad-cœurs Cortex A53. Ce dernier peut fonctionner en utilisant un jeu d’instructions ARMv8 64 bits. Néanmoins les distributions actuelles pour Raspberry Pi, afin d’assurer la compatibilité avec le Raspberry Pi 2, continuent de le faire fonctionner avec le jeu d’instructions ARMv7 32 bits (pour les Raspberry Pi 2 et 3, le bootloader charge le noyau kernel7.img alors qu’il charge le noyau kernel.img au format ARMv6 pour les modèles 1 et 0).

Que signifie qu’un processeur fonctionne en 32 ou 64 bits ? Il s’agit de la taille des registres du CPU, c’est-à-dire la taille maximale des données qu’il peut manipuler en une seule opération. L’effet le plus immédiatement visible du passage de 32 en 64 bits, est la taille de l’espace d’adressage qui passe de 232 octets (4 Gio) à 264 octets. Ceci ne présente pas particulièrement d’intérêt pour le Raspberry Pi 3 dont la quantité de RAM reste fixée à 1 Gio non extensible.

En ce qui concerne le passage en 64 bits, aucun intérêt réel donc hormis le challenge technique.

Vanilla kernel ?

Le fait d’utiliser un noyau standard en revanche est beaucoup plus intéressant pour tous les intégrateurs qui sont amenés à appliquer des patches provenant de diverses sources (par exemple PREEMPT_RT) ou à incorporer des drivers spécifiques développés indépendamment du Raspberry Pi.

Ces patches, ces drivers sont quasiment toujours fournis en référence au noyau Linux standard et leur intégration dans un noyau spécifiquement modifié peut être complexe.

Même si mes résultats avec le Raspberry Pi 3 et le noyau standard sont encore limités (pas de console), le principe décrit ici est valable pour les autres modèles qui fonctionnent beaucoup mieux. Pour cet aspect, l’intérêt est plus évident.

Bootloader U-boot

Le fait d’employer un bootloader libre de qualité industrielle comme U-Boot est très intéressant pour pouvoir “scripter” des procédures spécifiques de démarrage.

On pourra l’employer, en modifiant le script de démarrage, par exemple pour  :

  • démarrer sur une carte micro-SD minimale puis aller chercher le noyau et le système de fichiers dans un répertoire sur le réseau,
  • démarrer un noyau spécifique sélectionné en pressant un bouton relié aux bornes GPIO,
  • construire un système de mise à jour solide avec retour en arrière en cas d’erreur,
  • etc.

L’intérêt de l’expérimentation ci-dessous est donc nettement plus important en ce qui concerne cet aspect, pour disposer d’une base d’essai des scripts pour U-boot.

Environnement de construction

Après des essais assez longs de diverses méthodes (Crosstool-NG, compilations manuelles du kernel et de Busybox, etc.), j’ai décidé de m’appuyer sur l’outil Buildroot pour nous fournir un environnement de construction du système assez complet.

J’ai regroupé toute la configuration spécifique dans une recette defconfig pour Buildroot, qu’il nous suffit d’intégrer dans le projet standard. Il faudra également réaliser quelques opérations pour l’installation après la compilation.

La première étape consiste à télécharger la dernière version de Buildroot sur un PC de travail sous Linux (on peut réaliser cette opération sur un Raspberry Pi 3, mais elle durera plus longtemps) :

[~]$ mkdir Vanilla-Pi-3
[~]$ cd Vanilla-Pi-3/
[Vanilla-Pi-3]$ wget https://buildroot.org/downloads/buildroot-2017.02.tar.bz2
[Vanilla-Pi-3]$ tar xjf buildroot-2017.02.tar.bz2

Nous allons ajouter un fichier de recette dans le répertoire config/ de Buildroot. Lorsque j’aurai trouvé le temps de l’affiner et de la compléter, je la ferai parvenir aux développeurs de ce projet.

[Vanilla-Pi-3]$ cd buildroot-2017.02/configs/
[configs]$ wget http://www.blaess.fr/christophe/files/article-2017-03-13/raspberrypi3_64_defconfig 
[configs]$ cd ..

Nous demandons à Buildroot d’utiliser cette configuration :

[buildroot-2017.02]$ make raspberrypi3_64_defconfig

Si vous le souhaitez, vous pouvez explorer la configuration avec :

[buildroot-2017.02]$ make menuconfig

On y voit que j’ai choisi :

  • une toolchain de compilation pour Arm 64 bits, je me suis inspiré de la configuration Raspberry Pi 64 bits de Crosstool-NG.
  • un noyau standard 4.10.1 avec la configuration par défaut pour l’architecture Arm64.
  • un bootloader U-boot avec une configuration Raspberry Pi 3 (par défaut : 64 bits).
  • Busybox et les scripts de démarrage pour un système minimal

Nous pouvons ensuite lancer la compilation avec

[buildroot-2017.02]$ make

Cette étape a une durée variable suivant la puissance de votre processeur et votre lien Internet. Elle s’exécute en général pendant quelques dizaines de minutes.

Si un message d’erreur indique l’impossibilité de produire la cible bcm2837-rpi-3-b.dtb, c’est qu’il vous manque le Device Tree Compiler dtc et que vous devez l’ajouter sur votre machine. Par exemple sur un PC Ubuntu, il s’installe ainsi : sudo apt install device-tree-compiler.

Installation

Une fois le système compilé, nous allons l’installer sur une carte micro-SD. J’insère cette carte dans un adaptateur USB que je branche sur mon PC. Elle est alors automatiquement montée et le gestionnaire de fichiers m’affiche son contenu. Je commence par démonter manuellement la carte (ne pas le faire en cliquant sur l’icône d’éjection du gestionnaire de fichiers, sinon elle sera inaccessible).

[buildroot-2017.02]$ umount /media/$USER/*

Il va falloir partitionner (par exemple avec gparted ou fdisk) cette carte en deux partitions : la première d’une centaine de mégaoctets environ, la seconde couvrant le reste de la carte SD. Voici le résultat attendu :

[buildroot-2017.02]$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
[...]
sdc      8:32   1  1,9G  0 disk 
├─sdc1   8:33   1  128M  0 part 
└─sdc2   8:34   1  1,8G  0 part 
[...]

Nous formatons ces deux partitions, et en profitons pour les nommer boot et root par convention. Attention, les noms sdc1 et sdc2 sont spécifiques à ce que je vois sur ma machine, issus de la commande lsblk ci-dessus. Adaptez les bien à votre situation.

[buildroot-2017.02]$ sudo mkfs.vfat -n boot /dev/sdc1
[buildroot-2017.02]$ sudo mkfs.ext4 -L root /dev/sdc2

Extrayez la carte micro-SD et réinserez-la pour qu’elle soit automatiquement montée sur votre système, et vérifiez ainsi :

[buildroot-2017.02]$ ls /media/$USER/
boot  root

Il ne nous reste plus qu’à y copier les fichiers produits par Buildroot :

  • Image est le noyau Linux au format de l’architecture Arm64 (celui qu’on a l’habitude de voir sous le nom zImage ou vmlinuz sur d’autres architectures).
  • u-boot.bin est le bootloader U-Boot : il s’agit d’un outil très puissant permettant de charger et démarrer le noyau Linux en étant très largement configurable par des scripts. Nous en écrirons un petit exemple ultérieurement.
  • bcm2837-rpi-3-b.dtb est le blob binaire représentant la plate-forme matérielle Raspberry Pi 3 sous forme d’un Device Tree. Le noyau Image est générique pour toute plateforme Arm64 et c’est ce fichier qui lui indique sur quel système il s’exécute.
  • rootfs.tar est une archive tar contenant toute l’arborescence du système de fichiers (les répertoires /bin/, /dev/, /etc/, /usr/…).
[buildroot-2017.02]$ cp output/images/Image /media/$USER/boot/
[buildroot-2017.02]$ cp output/images/u-boot.bin  /media/$USER/boot/
[buildroot-2017.02]$ cp output/images/bcm2837-rpi-3-b.dtb /media/$USER/boot/
[buildroot-2017.02]$ sudo tar xf output/images/rootfs.tar -C /media/$USER/root
[buildroot-2017.02]$ cd ..

Nous avons installé un bootloader générique U-boot (que l’on retrouve sur la plupart des systèmes embarqués industriels). Néanmoins cela n’est pas suffisant, car au démarrage le Raspberry Pi est conçu pour charger un premier bootloader spécifique (et propriétaire). Nous devons l’y installer également et c’est lui qui viendra exécuter u-boot.bin.

[Vanilla-Pi-3]$ git clone https://github.com/raspberrypi/firmware

Le téléchargement est un peu long, car il y a eu de nombreuses versions successives, et comme elles sont fournies sous formes binaires, le stockage n’est pas limité aux différences entre versions. L’installation est très simple :

[Vanilla-Pi-3]$ cp -R firmware/boot/* /media/$USER/boot

Il nous reste trois petits fichiers à créer manuellement (attention aux fautes de frappe, cette étape est très sensible).

  • config.txt est lu par le bootloader primaire du Raspberry Pi (le fichier bootcode.bin). Nous allons lui indiquer de charger le Device Tree produit par Buildroot et de démarrer le second bootloader U-boot.
  • cmdline.txt est également lu par bootcode.bin. Il contient une seule ligne de texte, c’est la ligne de paramètres qui sera passée au noyau Linux (insérée dans le Device Tree).
  • boot.scr est un petit script pour U-boot lui indiquant de démarrer le noyau Image. Ce script est lui-même obtenu par compilation d’un petit fichier source.

Voici les contenus des deux premiers fichiers qui doivent être copiés dans /media/$USER/boot :

config.txt:
 kernel=u-boot.bin
 arm_control=0x200
 enable_uart=1
 device_tree_address=0x100
 device_tree=bcm2837-rpi-3-b.dtb

 

cmdline.txt:
 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait

Attention, le contenu de ce deuxième fichier doit tenir sur une seule ligne.

Le contenu du fichier source à compiler pour U-boot est le suivant :

boot.source:
 fatload mmc 0:1 ${kernel_addr_r} Image
 booti ${kernel_addr_r} - ${fdt_addr_r}

Son rôle est de charger le noyau Image en mémoire depuis la première partition de la carte micro-SD, puis de le démarrer en lui passant l’adresse du Device Tree. Pour le compiler on exécute :

[Vanilla-Pi-3]$ mkimage -A arm -O linux -T script -C none -n boot.scr -d boot.source  boot.scr

Si un message d’erreur vous indique l’absence de l’outil mkimage, installez-le sur votre système, par exemple sur Ubuntu on fera sudo apt install u-boot-tools.

Finalement, nous pouvons terminer en copiant ce fichier et en démontant la carte micro-SD.

[Vanilla-Pi-3]$ cp boot.scr /media/$USER/boot/
[Vanilla-Pi-3]$ umount /media/$USER/*

Test du noyau Vanilla 4.10.1 en mode 64 bits

Comme je l’indiquais au début de cet article, je n’ai pas réussi à faire fonctionne la sortie HDMI ni LVDS de Linux avec cette configuration. Il faut donc utiliser une connexion sur le port série du Raspberry Pi 3. Voici les lignes que je vois apparaître dans la console série de mon PC lors du boot. Cela commence par des messages provenant de U-boot :

U-Boot 2017.01 (Mar 10 2017 - 20:13:02 +0100)

DRAM:  944 MiB
RPI 3 Model B (0xa02082)
MMC:   bcm2835_sdhci: 0
reading uboot.env

** Unable to read "uboot.env" from mmc0:1 **
Using default environment

Cet avertissement est normal, nous n’avons pas fourni de fichier contenant des variables d’environnement personnalisées pour U-boot.

In:    serial
Out:   lcd
Err:   lcd
Net:   Net Initialization Skipped
No ethernet found.
starting USB...
USB0:   Core Release: 2.80a
scanning bus 0 for devices... 3 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
       scanning usb for ethernet devices... 1 Ethernet Device(s) found
Hit any key to stop autoboot:  0 
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
reading /boot.scr
151 bytes read in 24 ms (5.9 KiB/s)
## Executing script at 02000000

U-boot a trouvé notre script et l’a chargé. Il l’exécute.

reading Image
14862848 bytes read in 956 ms (14.8 MiB/s)
## Flattened Device Tree blob at 00000100
   Booting using the fdt blob at 0x000100
   Loading Device Tree to 000000003ab28000, end 000000003ab2dcaf ... OK

Starting kernel ...

Après lecture du fichier Image et détection de la présence d’un Device Tree chargé en mémoire par le bootloader primaire du Raspberry Pi, U-boot démarre le noyau. Sur une console texte (HDMI, LVDS, A/V) c’est là que les messages s’arrêtent…

[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 4.10.1 (logilin@TR-B-02) (gcc version 5.4.0 (Buildroot 2017.02) ) #1 SMP PREEMPT Fri Mar 10 20:28:37 CET 2017
[    0.000000] Boot CPU: AArch64 Processor [410fd034]

Nous pouvons d’ores et déjà observer l’architecture Arm 64 bits (AArch64) et le numéro du dernier noyau vanilla (4.10.1).

[    0.000000] efi: Getting EFI parameters from FDT:
[    0.000000] efi: UEFI not found.
[    0.000000] cma: Reserved 16 MiB at 0x0000000039800000
[    0.000000] WARNING: x1-x3 nonzero in violation of boot protocol:
[    0.000000]  x1: 0000000000000000
[    0.000000]  x2: 0000000000000000
[    0.000000]  x3: 0000000000080000
[    0.000000] This indicates a broken bootloader or old kernel
[    0.000000] percpu: Embedded 21 pages/cpu @ffff80003af8c000 s48024 r8192 d29800 u86016
[    0.000000] Detected VIPT I-cache on CPU0
[    0.000000] CPU features: enabling workaround for ARM erratum 845719
[    0.000000] Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 237888
[    0.000000] Kernel command line: earlyprintk console=ttyAMA0 bcm2708_fb.fbwidth=656 bcm2708_fb.fbheight=416 bcm2708_fb.fbswap=1 dma.dmachans=0x7f35 bcm2709.boardrev=0xa02082 bcm2709.serial=0xf7bb84e8 bcm2709.uart_clock=48000000 vc_mem.mem_base=0x3dc00000 vc_mem.mem_size=0x3f000000  dwc_otg.lpm_enable=0 console=ttyS0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait

Le bootloader primaire du Raspberry Pi enrichit la ligne de paramètres du noyau de manière substantielle comme nous pouvons l’observer ici (par rapport à notre fichier cmdline.txt).

[    0.000000] PID hash table entries: 4096 (order: 3, 32768 bytes)
[    0.000000] Dentry cache hash table entries: 131072 (order: 8, 1048576 bytes)
[    0.000000] Inode-cache hash table entries: 65536 (order: 7, 524288 bytes)
[    0.000000] Memory: 916920K/966656K available (8636K kernel code, 946K rwdata, 3848K rodata, 1024K init, 392K bss, 33352K reserved, 16384K cma-reserved)
[    0.000000] Virtual kernel memory layout:
[    0.000000]     modules : 0xffff000000000000 - 0xffff000008000000   (   128 MB)
[    0.000000]     vmalloc : 0xffff000008000000 - 0xffff7dffbfff0000   (129022 GB)
[    0.000000]       .text : 0xffff000008080000 - 0xffff0000088f0000   (  8640 KB)
[    0.000000]     .rodata : 0xffff0000088f0000 - 0xffff000008cc0000   (  3904 KB)
[    0.000000]       .init : 0xffff000008cc0000 - 0xffff000008dc0000   (  1024 KB)
[    0.000000]       .data : 0xffff000008dc0000 - 0xffff000008eaca00   (   947 KB)
[    0.000000]        .bss : 0xffff000008eaca00 - 0xffff000008f0ea4c   (   393 KB)
[    0.000000]     fixed   : 0xffff7dfffe7fd000 - 0xffff7dfffec00000   (  4108 KB)
[    0.000000]     PCI I/O : 0xffff7dfffee00000 - 0xffff7dffffe00000   (    16 MB)
[    0.000000]     vmemmap : 0xffff7e0000000000 - 0xffff800000000000   (  2048 GB maximum)
[    0.000000]               0xffff7e0000000000 - 0xffff7e0000ec0000   (    14 MB actual)
[    0.000000]     memory  : 0xffff800000000000 - 0xffff80003b000000   (   944 MB)
[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1
[    0.000000] Preemptible hierarchical RCU implementation.
[    0.000000]  Build-time adjustment of leaf fanout to 64.
[    0.000000]  RCU restricting CPUs from NR_CPUS=64 to nr_cpu_ids=4.
[    0.000000] RCU: Adjusting geometry for rcu_fanout_leaf=64, nr_cpu_ids=4
[    0.000000] NR_IRQS:64 nr_irqs:64 0
[    0.000000] arm_arch_timer: WARNING: Invalid trigger for IRQ1, assuming level low
[    0.000000] arm_arch_timer: WARNING: Please fix your firmware
[    0.000000] arm_arch_timer: WARNING: Invalid trigger for IRQ2, assuming level low
[    0.000000] arm_arch_timer: WARNING: Please fix your firmware

Tiens ? Apparemment il y a une petite incompatibilité de gestion des timers. Ce message va se répèter à quelques reprises.

[    0.000000] arm_arch_timer: Architected cp15 timer(s) running at 19.20MHz (phys).
[    0.000000] clocksource: arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x46d987e47, max_idle_ns: 440795202767 ns
[    0.000006] sched_clock: 56 bits at 19MHz, resolution 52ns, wraps every 4398046511078ns
[    0.000175] Console: colour dummy device 80x25
[    0.001340] console [tty1] enabled
[    0.001385] Calibrating delay loop (skipped), value calculated using timer frequency.. 38.40 BogoMIPS (lpj=76800)
[    0.001437] pid_max: default: 32768 minimum: 301
[    0.001551] Security Framework initialized
[    0.001623] Mount-cache hash table entries: 2048 (order: 2, 16384 bytes)
[    0.001656] Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes)
[    0.011920] ASID allocator initialised with 65536 entries
[    0.032584] EFI services will not be available.
[    0.048013] smp: Bringing up secondary CPUs ...
[    0.080151] Detected VIPT I-cache on CPU1
[    0.080198] arm_arch_timer: WARNING: Invalid trigger for IRQ1, assuming level low
[    0.080200] arm_arch_timer: WARNING: Please fix your firmware
[    0.080206] arm_arch_timer: WARNING: Invalid trigger for IRQ2, assuming level low
[    0.080208] arm_arch_timer: WARNING: Please fix your firmware
[    0.080218] CPU1: Booted secondary processor [410fd034]
[    0.112234] Detected VIPT I-cache on CPU2
[    0.112261] arm_arch_timer: WARNING: Invalid trigger for IRQ1, assuming level low
[    0.112263] arm_arch_timer: WARNING: Please fix your firmware
[    0.112268] arm_arch_timer: WARNING: Invalid trigger for IRQ2, assuming level low
[    0.112270] arm_arch_timer: WARNING: Please fix your firmware
[    0.112279] CPU2: Booted secondary processor [410fd034]
[    0.144325] Detected VIPT I-cache on CPU3
[    0.144352] arm_arch_timer: WARNING: Invalid trigger for IRQ1, assuming level low
[    0.144354] arm_arch_timer: WARNING: Please fix your firmware
[    0.144358] arm_arch_timer: WARNING: Invalid trigger for IRQ2, assuming level low
[    0.144360] arm_arch_timer: WARNING: Please fix your firmware
[    0.144369] CPU3: Booted secondary processor [410fd034]
[    0.144437] smp: Brought up 1 node, 4 CPUs
[    0.144935] SMP: Total of 4 processors activated.
[    0.144965] CPU features: detected feature: 32-bit EL0 Support
[    0.145041] CPU: All CPU(s) started at EL2
[    0.145084] alternatives: patching kernel code
[    0.146078] devtmpfs: initialized
[    0.152034] DMI not present or invalid.
[    0.152357] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[    0.152417] futex hash table entries: 1024 (order: 5, 131072 bytes)
[    0.153256] pinctrl core: initialized pinctrl subsystem
[    0.155078] NET: Registered protocol family 16
[    0.180398] cpuidle: using governor menu
[    0.181271] vdso: 2 pages (1 code @ ffff0000088f7000, 1 data @ ffff000008dc5000)
[    0.181328] hw-breakpoint: found 6 breakpoint and 4 watchpoint registers.
[    0.182539] DMA: preallocated 256 KiB pool for atomic allocations
[    0.183046] Serial: AMBA PL011 UART driver
[    0.217241] HugeTLB registered 2 MB page size, pre-allocated 0 pages
[    0.218481] ACPI: Interpreter disabled.
[    0.219567] vgaarb: loaded
[    0.219937] SCSI subsystem initialized
[    0.220594] usbcore: registered new interface driver usbfs
[    0.220695] usbcore: registered new interface driver hub
[    0.220823] usbcore: registered new device driver usb
[    0.221437] pps_core: LinuxPPS API ver. 1 registered
[    0.221467] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
[    0.221535] PTP clock support registered
[    0.221764] dmi: Firmware registration failed.
[    0.222009] Advanced Linux Sound Architecture Driver Initialized.
[    0.223377] clocksource: Switched to clocksource arch_sys_counter
[    0.223575] VFS: Disk quotas dquot_6.6.0
[    0.223659] VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes)
[    0.223977] pnp: PnP ACPI: disabled
[    0.233713] NET: Registered protocol family 2
[    0.234435] TCP established hash table entries: 8192 (order: 4, 65536 bytes)
[    0.234601] TCP bind hash table entries: 8192 (order: 5, 131072 bytes)
[    0.234867] TCP: Hash tables configured (established 8192 bind 8192)
[    0.235052] UDP hash table entries: 512 (order: 2, 16384 bytes)
[    0.235118] UDP-Lite hash table entries: 512 (order: 2, 16384 bytes)
[    0.235337] NET: Registered protocol family 1
[    0.236017] RPC: Registered named UNIX socket transport module.
[    0.236047] RPC: Registered udp transport module.
[    0.236072] RPC: Registered tcp transport module.
[    0.236097] RPC: Registered tcp NFSv4.1 backchannel transport module.
[    0.237356] kvm [1]: 8-bit VMID
[    0.237383] kvm [1]: IDMAP page: 8d8000
[    0.237408] kvm [1]: HYP VA range: 800000000000:ffffffffffff
[    0.238363] kvm [1]: Hyp mode initialized successfully
[    0.238413] kvm [1]: Invalid trigger for IRQ4, assuming level low
[    0.238455] kvm [1]: virtual timer IRQ4
[    0.240265] audit: initializing netlink subsys (disabled)
[    0.240419] audit: type=2000 audit(0.235:1): initialized
[    0.240884] workingset: timestamp_bits=46 max_order=18 bucket_order=0
[    0.254104] squashfs: version 4.0 (2009/01/31) Phillip Lougher
[    0.255158] NFS: Registering the id_resolver key type
[    0.255224] Key type id_resolver registered
[    0.255250] Key type id_legacy registered
[    0.255284] nfs4filelayout_init: NFSv4 File Layout Driver Registering...
[    0.255570] 9p: Installing v9fs 9p2000 file system support
[    0.258907] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 247)
[    0.258957] io scheduler noop registered
[    0.259300] io scheduler cfq registered (default)
[    0.274984] xenfs: not registering filesystem on non-xen platform
[    0.282150] Serial: 8250/16550 driver, 4 ports, IRQ sharing enabled
[    0.284613] console [ttyS0] disabled
[    0.284769] 3f215040.serial: ttyS0 at MMIO 0x0 (irq = 61, base_baud = 31224999) is a 16550
[    1.133165] console [ttyS0] enabled
[    1.138195] SuperH (H)SCI(F) driver initialized
[    1.143417] msm_serial: driver initialized
[    1.148357] cacheinfo: Unable to detect cache hierarchy for CPU 0
[    1.162381] loop: module loaded
[    1.166975] hisi_sas: driver version v1.6
[    1.175268] libphy: Fixed MDIO Bus: probed
[    1.180610] tun: Universal TUN/TAP device driver, 1.6
[    1.185774] tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
[    1.193842] e1000e: Intel(R) PRO/1000 Network Driver - 3.2.6-k
[    1.199809] e1000e: Copyright(c) 1999 - 2015 Intel Corporation.
[    1.205966] igb: Intel(R) Gigabit Ethernet Network Driver - version 5.4.0-k
[    1.213070] igb: Copyright (c) 2007-2014 Intel Corporation.
[    1.218865] igbvf: Intel(R) Gigabit Virtual Function Network Driver - version 2.4.0-k
[    1.226860] igbvf: Copyright (c) 2009 - 2012 Intel Corporation.
[    1.233001] sky2: driver version 1.30
[    1.237646] VFIO - User Level meta-driver version: 0.3
[    1.245111] ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
[    1.251778] ehci-pci: EHCI PCI platform driver
[    1.256424] ehci-platform: EHCI generic platform driver
[    1.261939] ehci-exynos: EHCI EXYNOS driver
[    1.266372] ehci-msm: Qualcomm On-Chip EHCI Host Controller
[    1.272209] ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
[    1.278539] ohci-pci: OHCI PCI platform driver
[    1.283179] ohci-platform: OHCI generic platform driver
[    1.288671] ohci-exynos: OHCI EXYNOS driver
[    1.293724] usbcore: registered new interface driver usb-storage
[    1.301603] mousedev: PS/2 mouse device common for all mice
[    1.309231] i2c /dev entries driver
[    1.316613] bcm2835-wdt 3f100000.watchdog: Broadcom BCM2835 watchdog timer
[    1.324703] sdhci: Secure Digital Host Controller Interface driver
[    1.331017] sdhci: Copyright(c) Pierre Ossman
[    1.335983] Synopsys Designware Multimedia Card Interface Driver
[    1.343142] sdhci-pltfm: SDHCI platform and OF driver helper
[    1.395407] mmc0: SDHCI controller on 3f300000.sdhci [3f300000.sdhci] using PIO
[    1.407332] ledtrig-cpu: registered to indicate activity on CPUs
[    1.415117] usbcore: registered new interface driver usbhid
[    1.421991] usbhid: USB HID core driver
[    1.428538] bcm2835-mbox 3f00b880.mailbox: mailbox enabled
[    1.436268] NET: Registered protocol family 17
[    1.440935] 9pnet: Installing 9P2000 support
[    1.445379] Key type dns_resolver registered
[    1.450920] registered taskstats version 1
[    1.458971] 3f201000.serial: ttyAMA0 at MMIO 0x3f201000 (irq = 72, base_baud = 0) is a PL011 rev2
[    4.700990] console [ttyAMA0] enabled
[    4.718338] raspberrypi-firmware soc:firmware: Attached to firmware from 2017-03-02 15:32
[    4.752120] 3f980000.usb supply vusb_d not found, using dummy regulator
[    4.778759] 3f980000.usb supply vusb_a not found, using dummy regulator
[    4.855887] dwc2 3f980000.usb: DWC OTG Controller
[    4.874781] dwc2 3f980000.usb: new USB bus registered, assigned bus number 1
[    4.903082] dwc2 3f980000.usb: irq 41, io mem 0x00000000
[    4.925228] hub 1-0:1.0: USB hub found
[    4.940287] hub 1-0:1.0: 1 port detected
[    4.956698] hctosys: unable to open rtc device (rtc0)
[    4.977145] ALSA device list:
[    4.989034]   No soundcards found.
[    5.023382] random: fast init done
[    5.043524] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null)
[    5.076028] VFS: Mounted root (ext4 filesystem) readonly on device 179:2.
[    5.107081] devtmpfs: mounted
[    5.120098] Freeing unused kernel memory: 1024K
[    5.351442] usb 1-1: new high-speed USB device number 2 using dwc2
[    5.408029] EXT4-fs (mmcblk0p2): re-mounted. Opts: data=ordered
[    5.588311] hub 1-1:1.0: USB hub found
[    5.603442] hub 1-1:1.0: 5 ports detected
Welcome to Buildroot
buildroot login: [    5.907403] usb 1-1.1: new high-speed USB device number 3 using dwc2
[    6.917298] random: crng init done

Le boot est à présent terminé et login a affiché son prompt de connexion. Néanmoins le contrôleur USB s’initialise plus lentement et un message vient “recouvrir” le prompt. J’appuie alors sur la touche Entrée.

Welcome to Buildroot
buildroot login: root
Password: root

Le mot de passe de root (root) est codé en dur dans la configuration de Buildroot. Vous pouvez le modifier lors de l’étape make menuconfig dans le menu System configuration.

# uname -a
Linux buildroot 4.10.1 #1 SMP PREEMPT Fri Mar 10 20:28:37 CET 2017 aarch64 GNU/Linux

Nous avons donc bien confirmation de la réussite de notre installation d’un noyau standard 4.10.1. Comment vérifier que notre système fonctionne bien en mode 64 bits ? Simplement en regardant la taille d’un pointeur. Pour cela je prends le petit programme C suivant :

sizeof.c:
#include <stdio.h>

int main(void)
{
        fprintf(stdout, "sizeof(int) = %lu\n", sizeof(int));
        fprintf(stdout, "sizeof(long) = %lu\n", sizeof(long));
        fprintf(stdout, "sizeof(void*) = %lu\n", sizeof(void *));
        return 0;
}

Je l’ai écrit sur mon PC, dans le répertoire Vanilla-Pi-3 et je le compile en utilisant la toolchain Arm64 que Buildroot a produit au début de sa compilation :

[Vanilla-Pi-3]$ ./buildroot-2017.02/output/host/usr/bin/aarch64-linux-gcc sizeof.c -o sizeof

Je copie le fichier sizeof sur la carte micro-SD du Raspberry Pi préalablement arrêté :

[Vanilla-Pi-3]$ sudo cp sizeof /media/$USER/root/root/
[Vanilla-Pi-3]$ umount /media/$USER/*

Après reboot du Raspberry Pi 3, exécutons le script :

Welcome to Buildroot
buildroot login: root
Password: root
# ls
sizeof
# ./sizeof 
sizeof(int) = 4
sizeof(long) = 8
sizeof(void*) = 8
# 

La taille d’un pointeur est bien de 8 octets, 64 bits. Ceci permet en théorie d’adresser 264 octets, soient 16 Eio de mémoire. En pratique les processeurs 64 bits actuels ne dépassent pas 48 bits d’adresse utilisables, soient 256 Tio (ce qui est déjà gigantesque). Du moins pour l’architecture x86, je ne sais pas ce qu’il en est pour le system-on-chip du Raspberry Pi 3. Quoiqu’il en soit, ce dernier ne possède que 1 Gio de RAM non extensible ce qui limite l’intérêt de ce type d’adressage…

L’exécution du programme ci-dessus donne exactement le même résultat que sur un PC x86 64 bits. En revanche sur un Raspberry Pi 3 avec une distribution Raspbian standard (donc 32 bits), on obtient :

pi@raspberrypi:~$ ./sizeof
sizeof(int) = 4
sizeof(long) = 4
sizeof(void*) = 4

Conclusion

Nous l’avons déjà dit, l’intérêt de cette opération est assez limité en ce qui concerne le passage au mode 64 bits. Le fait de pouvoir démarrer sur un noyau standard est plus intéressant. Enfin la mise en place de U-boot nous ouvre des possibilités en ce qui concerne les tout premiers scripts de démarrage, je reviendrai sur ce sujet ultérieurement.

Pour accélérer la compilation de Buildroot, je n’ai incorporé aucun packages supplémentaire par rapport au Busybox minimal ; naturellement vous pouvez ajouter toutes les applications qui vous semblent bonnes en éditant le contenu du menu Target Packages lors du make menuconfig.

Si vous trouvez ce qui cloche dans ma configuration et qui empêche d’utiliser la console graphique du Raspberry Pi 3, j’en serai ravi. Il faut savoir que sur les autres modèles 1 ou 2, on peut démarrer un système complet (y compris l’environnement graphique Pixel) sur un noyau Linux standard.


Xenomai sur Raspberry Pi 3 : bon espoir mais bilan mitigé

$
0
0

Depuis plusieurs mois, on m’interroge régulièrement sur le support de Xenomai sur Raspberry Pi 3 tant durant mes sessions de formations sur Xenomai que dans des messages par mail. J’avais l’habitude de répondre que Xenomai ne fonctionnait pas encore sur ce modèle de Raspberry Pi.

Une remarque récente de Syrine dans les commentaires de l’article “Xenomai 3 sur Raspberry Pi 2” a attiré mon attention sur un site japonais qui utilise les mêmes commandes que celles présentées dans cet article et fait fonctionner l’ensemble sur un Raspberry Pi 3. J’ai voulu tenter également cette installation. Le résultat est un peu mitigé : Xenomai fonctionne parfaitement avec une bonne stabilité, sauf en ce qui concerne la gestion des interruptions des GPIO qui n’est pas supportée encore. Si vous êtes néanmoins tentés par l’expérience, voici comment procéder simplement.

Compilation et installation

Dans le cadre d’une mise en œuvre professionnelle pour un client je travaillerais en cross-compilation sur une machine de développement de type PC pour produire l’image qui serait installée ensuite sur la cible. Pour ce petit article, j’ai préféré simplifier la mise en œuvre et réaliser une compilation native directement sur le Raspberry Pi 3. Le temps de construction est plus long, mais il reste très raisonnable (2h tout compris pour cette expérience).

Tout d’abord, installons une image de la distribution Raspbian toute neuve. N’utilisez pas la distribution Noob, l’organisation des partitions est différente.

[~]$ sudo dd if=2017-02-16-raspbian-jessie-lite.img of=/dev/sdc bs=4M

Nous démarrons à présent le Raspberry pi 3 sur cette nouvelle distribution. Elle va rebooter automatiquement pour agrandir la partition et occuper tout la carte SD. Connectons-nous

raspberrypi login: pi
Password: raspberry

Il nous faudra quelques outils supplémentaires à ajouter à la distribution d’origine :

pi@raspberrypi:~$ sudo apt install -y git ncurses-dev bc autoconf libtool

Téléchargeons les sources de Xenomai. Je prends la dernière version disponible à ce jour :

pi@raspberrypi:~$ wget http://xenomai.org/downloads/xenomai/stable/xenomai-3.0.3.tar.bz2
pi@raspberrypi:~$ tar xjf xenomai-3.0.3.tar.bz2
pi@raspberrypi:~$ ls xenomai-3.0.3/kernel/cobalt/arch/arm/patches/
ipipe-core-3.10.32-arm-13.patch  ipipe-core-3.18.20-arm-9.patch  README
ipipe-core-3.14.44-arm-16.patch  ipipe-core-4.1.18-arm-4.patch

Le patch ipipe le plus récent proposé par Xenomai s’applique sur un noyau Linux 4.1. Nous avons de la chance, il existe une branche 4.1 pour le kernel spécifiquement modifié pour Raspberry Pi. Téléchargeons cette branche. L’option --depth 1 raccourcit le temps de téléchargement (2 à 3 minutes) en limitant l’historique au seul dernier commit :

pi@raspberrypi:~$ git clone https://github.com/raspberrypi/linux.git -b rpi-4.1.y --depth 1
pi@raspberrypi:~$ head -4 linux/Makefile
VERSION = 4
PATCHLEVEL = 1
SUBLEVEL = 21
EXTRAVERSION =
pi@raspberrypi:~$

Le noyau est un 4.1.21. Il n’y a pas beaucoup de différences avec le 4.1.18, Xenomai fonctionnera. Néanmoins une option de configuration a été modifiée, et pour que le patch ipipe puisse s’applique nous devons le modifier. Pour cela, j’ai préparé un petit patch (un patch de patch, cela explique qu’il contienne un signe '+' décalé) que nous téléchargeons :

pi@raspberrypi:~$ wget https://www.blaess.fr/christophe/files/article-2017-03-20/001-adapt-4.1.18-patch-to-rpi-4.1.21-kernel.patch

Appliquons ce mini-patch à Xenomai.

pi@raspberrypi:~$ ls
001-adapt-4.1.18-patch-to-rpi-4.1.21-kernel.patch  linux  xenomai-3.0.3  xenomai-3.0.3.tar.bz2
pi@raspberrypi:~$ cd xenomai-3.0.3/
pi@raspberrypi:~/xenomai-3.0.3$ patch -p1 < ../001*
patching file kernel/cobalt/arch/arm/patches/ipipe-core-4.1.18-arm-8.patch
pi@raspberrypi:~/xenomai-3.0.3$

Nous pouvons maintenant appliquer le patch ipipe modifié au noyau Linux que nous avons téléchargé :

pi@raspberrypi:~/xenomai-3.0.3$ ./scripts/prepare-kernel.sh --arch=arm --linux=../linux --ipipe=kernel/cobalt/arch/arm/patches/ipipe-core-4.1.18-arm-8.patch

Il y a quelques messages indiquant un décalage entre le noyau standard et le noyau pour Raspberry Pi (offset xx lines) mais la commande patch arrive à résoudre ces problèmes (Hunk #x succeeded).

Un second patch va être nécessaire. Comme je l’indiquais dans cet article, il s’agit d’une gestion des timers spécifique au Raspberry Pi.

pi@raspberrypi:~/xenomai-3.0.3$ cd ..
pi@raspberrypi:~$ wget https://www.blaess.fr/christophe/files/article-2017-03-20/002-xenomai-3-on-bcm-2709.patch
pi@raspberrypi:~$ cd linux
pi@raspberrypi:~/linux$ patch -p1 < ../002*

Nous allons compiler ce noyau. Pour cela, j’ai préparé un fichier de configuration qui regroupe les options nécessaires. Il s’agit d’une configuration un peu allégée pour réduire le temps de compilation ; s’il vous manque des fonctionnalités, n’hésitez pas à l’éditer (avec make menuconfig) et la modifier :

pi@raspberrypi:~/linux$ wget https://www.blaess.fr/christophe/files/article-2017-03-20/linux.config
pi@raspberrypi:~/linux$ mv linux.config .config

Pour la compilation proprement dite, le Raspberry Pi 3 va être très sollicité, d’autant que nous occupons ses quatre cœurs au maximum (option -j 4 pour quatre jobs en parallèle).
Vu la chaleur qu’il dégage, je vous conseille de le placer de façon à ce que le processeur soit bien ventilé (pas de boîtier fermé). L’idéal consiste à placer sur le processeur un petit radiateur de dissipation comme celui-ci.

 

Pour un Raspberry Pi 3 dans un boîtier fermé sans dissipateur, j’aurais tendance à supprimer l’option -j 4 de la ligne suivante, afin de soulager la charge du processeur, au prix d’un temps de compilation  plus long.

pi@raspberrypi:~/linux$ time make -j 4
[...]
  IHEX2FW firmware/keyspan_pda/keyspan_pda.fw
  IHEX2FW firmware/keyspan_pda/xircom_pgs.fw

real    103m36.178s
user    277m51.420s
sys     13m39.250s
pi@raspberrypi:~/linux$ 
pi@raspberrypi:~/linux$

Comme vous le voyez, il faut environ 1h30 de compilation sur un Raspberry Pi 3 pour obtenir notre noyau. Ne soyez pas surpris de voir passer pendant la compilation des warnings indiquant du code douteux. Ils se produisent tous dans des modules spécifiques au Raspberry Pi ; c’est leur niveau inégal de qualité qui a empêché leur intégration dans le noyau Linux standard.

Nous allons installer ce nouveau kernel à côté de celui de la distribution Raspbian afin de pouvoir revenir en arrière en cas de problème :

pi@raspberrypi:~/ipipe$ sudo cp arch/arm/boot/zImage /boot/kernel-xenomai.img
pi@raspberrypi:~/ipipe$ sudo make modules_install
pi@raspberrypi:~/ipipe$ sudo make dtbs_install
pi@raspberrypi:~/ipipe$ ls /boot/dtbs/4.1.21-xenomai+/
bcm2709-rpi-2-b.dtb  bcm2710-rpi-3-b.dtb  overlays

Le nouveau noyau, ses modules et son device tree étant installés, nous devons modifier le fichier de configuration du bootloader pour lui indiquer quel kernel charger :

pi@raspberrypi:~/ipipe$ sudo nano /boot/config.txt

Ajoutez les deux lignes suivantes à la fin du fichier :

kernel=kernel-xenomai.img
device_tree=dtbs/4.1.21-xenomai+/bcm2710-rpi-3-b.dtb

Testons notre noyau incluant ipipe :

pi@raspberrypi:~/ipipe$ sudo reboot

Si le système ne redémarre pas, montez la carte SD sur un PC, mettez en commentaire les deux lignes ci-dessus (en les précédant d’un '#') pour redémarrer sur la distribution Raspbian initiale. Vérifiez bien l’emplacement du noyau, du device tree, et le contenu du fichier config.txt.

Le Raspberry Pi 3 ayant bien redémarré sur le noyau patché avec ipipe, nous pouvons vérifier sa présence ainsi

raspberrypi login: pi
Password: raspberry
[...]
pi@raspberrypi:~$ uname -a
Linux raspberrypi 4.1.21-xenomai+ #1 SMP PREEMPT Sat Mar 18 10:12:21 UTC 2017 armv7l GNU/Linux
pi@raspberrypi:~$ cat /proc/ipipe/version
8
pi@raspberrypi:~$ cat /proc/xenomai/version
3.0.3
pi@raspberrypi:~$

Le travail n’est pas terminé pour autant, nous devons compiler les bibliothèques et les outils de tests de Xenomai.

pi@raspberrypi:~$ cd xenomai-3.0.3/
pi@raspberrypi:~/xenomai-3.0.3$ ./scripts/bootstrap

Cette étape reste silencieuse durant quelques minutes. Laissez le script travailler, il n’est pas bloqué.

pi@raspberrypi:~/xenomai-3.0.3$ ./configure --enable-smp
pi@raspberrypi:~/xenomai-3.0.3$ make
pi@raspberrypi:~/xenomai-3.0.3$ sudo make install

Deux heures après le début de notre expérience, Xenomai est prêt à être utilisé. Le test le plus simple – et l’un des plus représentatifs – consiste à mesure les fluctuations d’un timer. Pour cela un outil est directement fourni. Je le lance en lui demandant de s’exécuter pendant 60 secondes :

pi@raspberrypi:~/xenomai-3.0.3$ sudo /usr/xenomai/bin/latency -T 60
== Sampling period: 1000 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
RTT|  00:00:01  (periodic user-mode task, 1000 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD|     -3.594|     -2.786|      1.822|       0|     0|     -3.594|      1.822
RTD|     -3.647|     -2.923|      0.051|       0|     0|     -3.647|      1.822
RTD|     -3.595|     -2.920|      1.613|       0|     0|     -3.647|      1.822
RTD|     -3.648|     -2.946|     -0.106|       0|     0|     -3.648|      1.822
[...]
RTD|     -3.616|     -2.936|     -0.230|       0|     0|     -5.017|      1.863
RTD|     -3.616|     -2.871|      1.488|       0|     0|     -5.017|      1.863
RTD|     -3.669|     -2.848|      0.186|       0|     0|     -5.017|      1.863
---|-----------|-----------|-----------|--------|------|-------------------------
RTS|     -5.017|     -2.893|      1.863|       0|     0|    00:01:00/00:01:00
pi@raspberrypi:~/xenomai-3.0.3$

Ces premiers résultats sont intéressants, ce sont les deux dernières colonnes qui sont les plus significatives.

La dernière colonne indique la pire latence mesurée, c’est à dire le pire retard de déclenchement d’un timer périodique. La valeur de 1,863 microsecondes est excellente, mais le système est au repos et ce n’est pas représentatif.
L’avant-dernière valeur montre la meilleure latence observée. Elle est négative (-5,017 microsecondes), ce qui indique que le timer s’est déclenché plus tôt que prévu. En effet, Xenomai anticipe les retards inhérents au matériel en déclenchant les timers un peu en avance. Ceci est intéressant pour avoir une bonne précision, mais nécessite une calibration. Nous pouvons consulter l’avance par défaut :

pi@raspberrypi:~/xenomai-3.0.3$ cat /proc/xenomai/latency 
10052

La valeur est de 10052 nanosecondes soit 10,052 microsecondes. Tous les timers sont donc anticipés de 10 microsecondes environ. Ceci peut être intéressant en production, mais dans un cadre expérimental, ce sont les vraies valeurs de latences qui m’intéressent. Je désactive donc cette avance :

pi@raspberrypi:~/xenomai-3.0.3$ sudo -i
root@raspberrypi:~# echo 0 > /proc/xenomai/latency 
root@raspberrypi:~# cat /proc/xenomai/latency 
0
root@raspberrypi:~# exit

Je relance la mesure pendant une minute pour vérifier :

[...]
RTD|      6.385|      7.073|      9.979|       0|     0|      5.405|     35.980
RTD|      6.437|      7.077|     10.499|       0|     0|      5.405|     35.980
RTD|      6.436|      7.074|      9.561|       0|     0|      5.405|     35.980
RTD|      6.384|      7.083|      9.874|       0|     0|      5.405|     35.980
---|-----------|-----------|-----------|--------|------|-------------------------
RTS|      5.405|      7.142|     35.980|       0|     0|    00:01:00/00:01:00

Presque 36 microsecondes de latence, voilà qui est plus réaliste (et très bien, notons-le).

Je vais lancer une nouvelle mesure pendant une heure en conservant un histogramme des latences observées :

pi@raspberrypi:~$ sudo /usr/xenomai/bin/latency  -T 3600 -g histo-01.plot
[...]
RTD|      6.492|      7.145|      9.825|       0|     0|      4.229|     36.862
RTD|      6.439|      7.146|      9.877|       0|     0|      4.229|     36.862
RTD|      6.491|      7.166|      9.877|       0|     0|      4.229|     36.862
HSH|--param|--samples-|--average--|---stddev--
HSS|    min|      3600|      5.972|      0.205
HSS|    avg|   3600017|      6.586|      0.870
HSS|    max|      3600|      9.651|      1.783
---|-----------|-----------|-----------|--------|------|-------------------------
RTS|      4.229|      7.199|     36.862|       0|     0|    01:00:00/01:00:00
pi@raspberrypi:~/xenomai-3.0.3$

L’histogramme peut être dessiné par Gnuplot. En abscisse la latence observée en microsecondes et en ordonnée le nombre d’occurrences de chaque classe. Le résultat est le suivant :

Xenomai latency - Low system load

Il est intéressant de voir que très peu d’occurrences se produisent au-delà de 10 microsecondes. Pour avoir un aperçu plus précis sur ce qui se passe au-delà de 13 microsecondes, il est préférable de faire une représentation logarithmique en ordonnée qui va dilater et rendre plus visibles les faibles occurrences :

Xenomai latency - Low system load

La mesure a été faite alors que le système était au repos, ce qui ne reflète pas un comportement en production. Je relance la mesure en ajoutant une très forte charge du système avec le script dohell fourni avec Xenomai :

pi@raspberrypi:~$ sudo /usr/xenomai/bin/dohell 3600 & sudo /usr/xenomai/bin/latency  -T 3600 -g histo-02.plot
RTD|      7.690|     14.418|     49.044|       0|     0|      5.644|     62.846
RTD|      7.637|     14.028|     37.637|       0|     0|      5.644|     62.846
RTD|      7.585|     14.297|     44.356|       0|     0|      5.644|     62.846
RTD|      8.261|     14.237|     33.939|       0|     0|      5.644|     62.846
HSH|--param|--samples-|--average--|---stddev--
HSS|    min|      3600|      7.507|      0.799
HSS|    avg|   3600006|     14.871|      5.938
HSS|    max|      3600|     41.476|      4.462
---|-----------|-----------|-----------|--------|------|-------------------------
RTS|      5.644|     15.366|     62.846|       0|     0|    01:00:00/01:00:00

En outre, des rafales de ping en mode flood provenaient régulièrement d’un PC, produisant de nombreuses interruptions réseau.

Xenomai latency - High system load

Nous voyons donc qu’il y a une latence intrinsèque de l’ordre de 4.2 microsecondes au repos, et qu’en fonction de la charge du système elle peut aller jusqu’à une soixantaine de microsecondes. Ces résultats sont tout à fait cohérents avec ceux que l’on peut obtenir avec Xenomai sur d’autres cartes à processeur ARM et correspondent bien à ce que l’on attend d’un système d’exploitation complet comme Linux sous des contraintes temps réel fortes.

J’ai également fait l’essai d’ajouter sur la ligne de commande du kernel les options dwc_otg.fiq_enable=0 et dwc_otg.fiq_fsm_enable=0 afin d’éviter l’utilisation des Fast Interrupt Request qui perturbent le déterminisme du traitement des autres interruptions par Xenomai. Cela n’a pas changé les performances observées.

Conclusion

Xenomai 3.0.3 s’installe bien sur un Raspberry Pi 3. La gestion des timers est précise et présente peu de latences, même sous une forte charge système et interruptive.

Toutefois, il y a un point très décevant : je ne suis pas arrivé à gérer (avec l’API RTDM) les handlers d’interruptions des GPIO. Je suppose que le patch ipipe n’a pas encore été ajusté pour le Raspberry Pi 3. Il va nous falloir patienter encore un peu, je continuerai à utiliser des Raspberry Pi 2 pour les exemples de mes formations sur le temps réel.

MSP-omodoro

$
0
0

MSP-omodoro

Le modeste programme présenté dans cet article est extrait d’un exercice de travaux pratiques que je propose aux participants de la formation “Programmation en C sur microcontrôleurs” que j’anime régulièrement. Cet exercice permet de réfléchir en profondeur sur le développement d’un petit projet sur microcontrôleur.

Comme l’objet même de la réalisation peut être utile au quotidien (je l’emploie personnellement), j’ai trouvé intéressant de le partager ici.

Le projet MSP-omodoro est un timer conçu pour rythmer l’activité d’un développeur. Comme expliqué dans cet article de Wikipédia, la méthode de travail par Pomodoro consiste à enchaîner des périodes de 25 minutes de concentration séparées par des pauses de 5 minutes. Toutes les 2 heures, on allonge la pause à 15 minutes.

Cela permet de conserver une bonne efficacité de travail, en évitant les périodes trop longues au cours desquelles on s’épuise intellectuellement avec une productivité diminuée. Chacun est libre d’ajuster la durée des périodes de travail et de pause à sa guise ; personnellement les durées originales me conviennent bien.

Pour rester concentré sur le projet en cours, la pause de 5 minutes ne doit pas être l’occasion d’entamer un nouveau sujet de réflexion. C’est un moment pour se lever, s’étirer, faire quelques pas, ranger un peu son bureau, etc. La pause de 15 minutes, en revanche doit permettre d’évacuer la tension accumulée pendant les phases de concentration. On peut boire un café, discuter avec des collègues, consulter Twitter, etc.

Il existe de nombreuses applications d’organisation en Pomodoro, tant sur smartphone que sur poste de travail et même en ligne. L’idée de ce projet était de montrer aux participants de mes sessions de formation comment implémenter un outil de ce type en utilisant un simple microcontrôleur.

Architecture matérielle

Microcontrôleur

J’ai choisi d’utiliser un classique Launchpad MSP-430 de Texas Instruments, bon marché, robuste et simple à utiliser. Les outils de programmation sont directement disponibles dans les dépôts Debian/Ubuntu.

Launchpad MSP 430

Launchpad MSP 430

Ce kit contient déjà deux leds programmables (une rouge et une verte), un bouton-poussoir pour le reset du microcontrôleur et un bouton-poussoir accessible par GPIO. La led verte que l’on voit sur la photo est simplement l’indicateur d’alimentation, il y en a une seconde à côté de la led rouge, c’est celle qui nous intéressera ici. Deux leds, un bouton, c’est parfaitement adapté pour notre application.

Notons qu’une fois le programme flashé dans la mémoire du microcontrôleur MSP430G253, ce dernier peut-être extrait et monté sur une carte indépendante comme j’en parlerai plus bas.

Outils de programmation

Pour programmer un MSP 430 de la manière la plus simple possible, il suffit d’utiliser le compilateur msp430-gcc fourni librement sur la plupart des distributions Linux et d’employer l’outil mspdebug pour flasher le code sur le microcontrôleur.

Chacun peut utiliser l’éditeur de texte de son choix pour écrire son code (nano, vim, gedit, geany, etc.). La présence d’un fichier Makefile nous permettra de lancer la compilation et le flashage directement depuis la ligne de commande.

Pour ceux qui préfèrent travailler dans un IDE (Integrated Development Environment), sachez qu’il existe des plugins pour Eclipse (voir http://eclipse.xpg.dk) pour compiler et télécharger le code dans le microcontrôleur depuis le workbench Eclipse.

Enfin, il est toujours possible d’utiliser msp-gcc sur Windows avec l’environnement Mingw, mais je ne l’ai pas testé (je n’ai pas de machines Windows dans mon bureau).

Installation sous Ubuntu :

$ sudo apt install gcc-msp430 mspdebug

Logiciel

Fonctionnement

La première partie du projet consiste à décrire le fonctionnement attendu. Voici quelques idées de description :

  1. Notre projet n’utilisera que les deux leds (rouge et verte) et un bouton-poussoir.
  2. Pendant les phases de travail, la led rouge sera allumée, pendant les pauses c’est la led verte qui sera active.
  3. Le système doit prévenir l’utilisateur de la proximité d’une pause et d’une reprise environ deux minutes auparavant.
  4. Lorsque le moment de pause ou de travail arrive, une led clignote pour avertir l’utilisateur qui doit acquitter en pressant le bouton.
  5. Si l’utilisateur ne presse pas le bouton pour acquitter la pause ou la reprise, le système doit s’éteindre automatiquement au bout d’un moment.
  6. L’utilisateur peut choisir de faire une longue pause en pressant deux fois le bouton.
  7. Vu la simplicité du système, les durées seront fixées à la compilation du code et non pas modifiables par l’utilisateur.
  8. Le système final est prévu pour être alimenté par piles et doit limiter sa consommation électrique.

Naturellement, pour un projet plus ambitieux il faudrait rédiger un cahier des charges complet avec une description et des spécifications beaucoup plus détaillées.

Automate à nombre fini d’états

La programmation d’un système pour microcontrôleur s’appuie souvent sur le concept d’automate à nombre fini d’états, souvent abrégé FSM pour Finite State Machine. Ceci peut en effet s’organiser sous forme d’un graphe que l’on peut transposer assez facilement en code séquencé par une grande boucle événementielle typique de la programmation sur microcontrôleur.

Nous devons tout d’abord commencer par lister les états possibles pour le système :

  • Off : le microcontrôleur est éteint ou en sommeil très profond (CPU et horloges désactivés). La consommation est très réduite. On sort de ce mode en pressant Reset. On y entre après quelques minutes sans réponse de l’utilisateur lors d’une attente d’acquittement.
  • Time to work : la led rouge clignote avec insistance et attend que l’utilisateur presse le bouton pour démarrer une séquence de travail.
  • Work : la led rouge est allumée en permanence pendant une durée prédéterminée (par exemple 23 minutes).
  • Prepare to take a pause : pendant les deux dernières minutes de travail, la led rouge s’éteint régulièrement pour prévenir de l’approche de la pause. Le programmeur sait qu’il ne doit pas entamer de nouvelle tâche.
  • Time to take a break : la led verte clignote avec insistance en attente d’un acquittement par le bouton.
  • Short break : la led verte est allumée pendant trois minutes. En cas de seconde pression sur le bouton on passe à l’état suivant.
  • Long break : la led verte verte est allumée pendant 13 minutes.
  • Prepare to return to work : pendant les deux dernières minutes de pause, la led verte s’éteint régulièrement pour prévenir de la prochaine période de travail.

Une fois les états déterminés, on recherche les transitions, et on les représente sur un graphe comme celui-ci :

Diagramme des transitions

Diagramme des transitions

Implémentation

L’implémentation traditionnelle d’un automate à nombre fini d’états sur un microcontrôleur consiste à boucler indéfiniment en attendant de détecter l’occurrence d’un événement déclenchant une transition à partir de l’état en cours. Toutefois la règle 8 des descriptions ci-dessus nous empêche d’utiliser une boucle active infinie. Nous devons essayer d’endormir le CPU le plus souvent possible pour limiter sa consommation d’énergie.

Nous allons donc utiliser un mécanisme d’interruptions qui réveilleront ponctuellement le CPU et tout le travail réel sera exécuté dans les routines de gestion de ces interruptions (handlers). Nous avons deux types de transitions : celles liées au temps et celles liées à la pression sur le bouton. Nous nous reposerons donc tout naturellement sur les interruptions d’un timer matériel intégré dans le MSP 430 et celles du bouton-poussoir.

Il y a plusieurs designs possibles. Celui que j’ai choisi ici consiste à utiliser l’interruption timer pour faire le travail central. À chaque tick, nous vérifierons si le temps écoulé correspond à la limite pour l’état en cours ou si le bouton a été pressé. Le timer est configuré pour déclencher des ticks tous les dixièmes de seconde, et une variable globale enregistre la pression éventuelle sur le bouton.

L’implémentation est disponible sur Github. Il y a un fichier source en C et
un fichier Makefile permettant de compiler et flasher le code dans le MSP430.

$ git clone https://github.com/cpb-/MSP-omodoro
Clonage dans 'MSP-omodoro'...
[...]
$ cd MSP-omodoro/
$ make
msp430-gcc -Wall -mmcu=msp430g2553 msp-omodoro.c -o msp-omodoro.elf

Pour pouvoir programmer le MSP430, il faut que le launchpad soit connecté sur un port USB :

$ make flash
mspdebug rf2500 erase
[...]
Erasing...
Programming...
Writing  846 bytes at c000 [section: .text]...
Writing   32 bytes at c34e [section: .rodata]...
Writing   32 bytes at ffe0 [section: .vectors]...
Done, 910 bytes total
$

Et voilà, le système est dans l’étape “Time to Work” et attend une pression sur le bouton S1 (celui à côté des leds) pour entamer une période de travail.

Conclusion

Le programme ci-dessus n’est pas parfait. On pourrait dans certain cas rater un appui sur le bouton ou au contraire déclencher une pause trop longue si un rebond du poussoir se produit juste au moment du tick timer. Néanmoins, pour une utilisation simple il fonctionne très bien et remplit son rôle d’illustration pédagogique d’un développement à microcontrôleur.

Un aspect particulièrement intéressant du microcontrôleur MSP430 est sa simplicité de mise en œuvre sur un montage indépendant. Il suffit en effet de l’alimenter en +3.3 V et de relier sa broche Reset à la masse (via une résistance) pour qu’il fonctionne parfaitement avec son oscillateur interne en guise d’horloge (on peut lui ajouter un quartz si on souhaite une meilleure précision). Il suffit alors de relier les entrées-sorties désirées. Voici un petit schéma de mise en oeuvre :

Schéma Pomodoro

Schéma Pomodoro

La valeur précise des résistances n’est pas très importante. Dans mon petit montage en wrapping, j’ai choisi trois résistances identiques de 1,3 kOhm.

Comme on peut le voir sur la photo ci-dessous, j’ai remplacé la led verte par une bleue qui me semblait plus adaptée à l’idée de pause, de repos. Avec le bouton blanc au milieu ça donne involontairement un côté french touch à mon pomodoro !

Montage du pomodoro

Montage du pomodoro

L’alimentation est fournie par deux piles de 1,5 V (le boîtier ci-dessus peut en contenir trois, mais un emplacement est vide). Il serait intéressant de réaliser un petit boîtier en impression 3D, rappelant éventuellement la tomate du pomodoro original…

Dailyfile : un petit outil en ligne de commande

$
0
0

Dailyfile

J’ai récemment été confronté à un souci de taille de fichiers de traces pour le débogage d’une application. Pour résoudre mon problème j’ai écrit un petit programme en ligne de commande permettant de répartir la sortie d’un pipeline du shell dans des fichiers quotidiens.

J’ai appelé cet outil dailyfile et je vous le présente ici, en espérant qu’il puisse servir à d’autres.

Présentation

Problématique

J’ai développé il y a quelques temps pour un client un programme sous Linux qui s’occupe d’initialiser et de superviser des communications entre des applications distantes et des serveurs. La mise au point est un peu compliquée car les communications reposant sur des modems 2G des difficultés inattendues apparaissent dans la gestion des déconnexions intempestives.

J’ai prévu, comme d’habitude, dans mon programme de nombreux messages de débogage que l’on peut activer ou non en fonction d’un niveau paramétrable à la compilation.

Un script démarre l’application, qui écrira tous ses messages de débogage sur la sortie stderr. Comme je n’ai pas accès directement à ce serveur, les données sont redirigées dans un fichier que mon client me transmet par mail quand un problème survient. Le script est un peu plus complexe, mais on peut imaginer quelque chose comme :

$ ./my-application >logfile.txt 2>&1

La première redirection envoie la sortie standard stdout du programme dans le fichier de trace, la seconde redirection 2>&1 redirige la sortie stderr de la même manière. Le script réel est plus compliqué car on gère un redémarrage automatique en cas de crash et un fichier de trace dont le nom contient la date de démarrage.

Les erreurs se produisant somme toute assez rarement, le programme tourne parfois pendant plusieurs jours entre deux redémarrages, et le fichier de trace pèse facilement plusieurs centaines de mégaoctets. Son transfert et l’analyse de son contenu deviennent alors plus compliqués.

On ne peut pas facilement répartir le contenu dans différents fichiers sans arrêter l’application, ce qui est problématique dans notre cas. L’utilisation de logrotate est un peu complexe, d’autant que nous n’avons pas les droits d’administration sur le serveur.

Solution

Mon idée a été de remplacer la redirection “>” vers un fichier par une redirection “|” dans un pipeline. De l’autre côté du pipeline un programme lira les données, et les copiera dans un fichier, en changeant tous les jours.

La programmation proprement dite est simple, et le code source est disponible dans mon dépôt Github.

Installation

La compilation du programme ne présente en principe pas de difficulté :

$ git  clone  https://github.com/cpb-/Dailyfile
Clonage dans 'Dailyfile'...
remote: Counting objects: 33, done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 33 (delta 12), reused 31 (delta 10), pack-reused 0
Dépaquetage des objets: 100% (33/33), fait.
Vérification de la connectivité... fait.
$ cd  dailyfile/
$ make clean  &&  make
rm -f dailyfile dailyfile.o *~
gcc -Wall -W -DPROGRAM_VERSION="0.3" -c  dailyfile.c
gcc -o dailyfile dailyfile.o 
$ sudo  make  install
cp dailyfile /usr/local/bin/
$

Utilisation

L’utilisation de base est plutôt simple :

$  commande-qui-dure-longtemps  |  dailyfile

Toutes les données écrites par la première commande dans le pipeline seront enregistrées dans des fichiers nommés day-YYYY-MM-DD.logYYYY, MM, et DD représentent respectivement l’année, le mois et le jour de création du fichier.

En voici un exemple d’exécution réelle :

$ while true; do date; sleep 5; done | dailyfile

Cette commande affiche la date toutes les cinq secondes. On retrouve plusieurs jours plus tard les résultats dans les fichiers :

$ ls -l
total 2148
-rw-r--r-- 1 cpb cpb 583906 avril 29 01:59 day-2017-04-28.log
-rw-r--r-- 1 cpb cpb 727838 avril 30 01:59 day-2017-04-29.log
-rw-r--r-- 1 cpb cpb 750812 mai    1 01:59 day-2017-04-30.log
-rw-r--r-- 1 cpb cpb 113240 mai    1 06:08 day-2017-05-01.log

On peut remarquer que l’heure de la dernière modification des fichiers écoulés est à 1 heure 59. C’est normal, le basculement est effectué en se basant sur l’heure G.M.T, décalée de deux heures l’été par rapport à l’heure locale affichée par ls.

Options

Il existe quelques options pour modifier le comportement du programme.

  • -b size ou --buffer-size=size : modifier la taille du buffer interne de copie des données. Par défaut 16384 octets.
  • -c seconds ou --cycle=seconds : fixer la durée de commutation en secondes. Par défaut 86400 secondes (une journée).
  • -d dir ou --directory=dir : indiquer le répertoire où stocker les fichiers. Par défaut, ils sont enregistrés dans le répertoire courant.
  • -h ou --help : afficher un rappel des commandes.
  • -l ou --localtime : utiliser l’heure locale lors de la création des noms de fichiers. Faux par défaut, on utilise l’heure G.M.T.
  • -p string ou --prefix=string : préciser le préfixe à écrire avant la date dans le nom du fichier. Par défaut, c’est “day-“.
  • -s string ou --suffix=string : indiquer le préfixe à écrire après la date dans le nom du fichier. Par défaut, c’est “.log“.
  • -v ou --version : afficher la version de l’outil dailyfile.

Lorsque la période de commutation (option -c) est inférieure à une journée, le nom des fichiers inclut des champs heure, minute, et seconde de création.

Exemple

Voici un exemple d’exécution où je modifie le préfixe du fichier pour le remplacer par “date-“, son suffixe devient “.txt“, et sa période est changée pour avoir un basculement de fichier à chaque changement d’heure :

$ while true; do date; sleep 5; done | dailyfile -c 3600 -p 'date-' -s '.txt' -l

Après quelques heures on observe des fichiers dont le nom inclut l’heure de création. On peut observer des petites fluctuations dans le champ “seconde”, dûes aux périodes de réveil du shell.

$ ls -l
-rw-r--r--  1 cpb cpb  2255 avril 27 12:59 date-2017-04-27-12-55-24.txt
-rw-r--r--  1 cpb cpb 29520 avril 27 13:59 date-2017-04-27-13-00-00.txt
-rw-r--r--  1 cpb cpb 29520 avril 27 14:59 date-2017-04-27-14-00-02.txt
-rw-r--r--  1 cpb cpb 29479 avril 27 15:59 date-2017-04-27-15-00-04.txt
-rw-r--r--  1 cpb cpb 29520 avril 27 16:59 date-2017-04-27-16-00-02.txt
-rw-r--r--  1 cpb cpb 29479 avril 27 17:59 date-2017-04-27-17-00-04.txt
-rw-r--r--  1 cpb cpb 29479 avril 27 18:59 date-2017-04-27-18-00-02.txt
-rw-r--r--  1 cpb cpb 29520 avril 27 19:59 date-2017-04-27-19-00-00.txt
-rw-r--r--  1 cpb cpb 29479 avril 27 20:59 date-2017-04-27-20-00-03.txt
-rw-r--r--  1 cpb cpb 29479 avril 27 21:59 date-2017-04-27-21-00-02.txt
-rw-r--r--  1 cpb cpb 29520 avril 27 22:59 date-2017-04-27-22-00-00.txt
-rw-r--r--  1 cpb cpb 29479 avril 27 23:59 date-2017-04-27-23-00-04.txt
-rw-r--r--  1 cpb cpb 31636 avril 28 00:59 date-2017-04-28-00-00-02.txt
-rw-r--r--  1 cpb cpb 31680 avril 28 01:59 date-2017-04-28-01-00-01.txt
-rw-r--r--  1 cpb cpb 31636 avril 28 02:59 date-2017-04-28-02-00-04.txt
-rw-r--r--  1 cpb cpb 31636 avril 28 03:59 date-2017-04-28-03-00-03.txt
-rw-r--r--  1 cpb cpb 31636 avril 28 04:59 date-2017-04-28-04-00-01.txt
-rw-r--r--  1 cpb cpb 31680 avril 28 05:59 date-2017-04-28-05-00-00.txt
-rw-r--r--  1 cpb cpb 31636 avril 28 06:59 date-2017-04-28-06-00-04.txt
  [...]

 

Les suggestions, remarques et pull requests sur Github sont les bienvenues !

[Kernel] Interruptions et tasklets

$
0
0

[Kernel Interruptions et tasklets

Il existe plusieurs mécanismes proposés par le noyau Linux pour programmer un traitement à réaliser lorsqu’un périphérique externe déclenche une interruption pour nous notifier de l’occurrence d’un événement. Gestionnaire monolithique, tasklet, workqueue, threaded interrupt, chaque solution a des avantages et des inconvénients, qu’il est intéressant de connaître pour optimiser l’efficacité de nos traitements.

Gestion des interruptions

Récapitulons rapidement le principe des interruptions sous Linux. Il convient tout d’abord de préciser que du fait de la portabilité du noyau sur un grand nombre d’architectures, les mécanismes très bas-niveau sont totalement abstraits pour ce qui concerne la plupart du code kernel classique (dans un driver par exemple).

Lorsqu’un périphérique externe – disons un contrôleur d’entrées-sorties GPIO par exemple – désire notifier le processeur de l’occurrence d’une situation intéressante (par exemple le changement d’état d’une broche d’entrée), il envoie un signal sur une entrée du contrôleur d’interruption APIC (Advanced Programmable Interrupt Controler). De nos jours celui-ci est intégré directement dans le microprocesseur, mais dans les anciens PC par exemple, il existait sous forme de composant indépendant.

Le contrôleur d’interruption effectue une demande d’interruption IRQ (Interrupt Request) auprès du processeur principal. Ce dernier arrête le traitement en cours, sauvegarde son état (ses registres) dans la pile et interroge le contrôleur d’interruption pour connaître l’événement survenu. L’APIC lui indique la source du signal initial sous forme d’un numéro. Le processeur déroute alors son exécution sur une routine de traitement bas-niveau chargée de prendre en compte l’interruption et de réagir en conséquence.

Une fois cette routine de traitement terminée le processeur reprend le cours de ses opérations précédentes comme si de rien n’était.

fig-01 - Déclenchement d'une interruption

Fig-01 – Déclenchement d’une interruption

On peut voir les différentes interruptions gérées par le noyau Linux dans le pseudo-fichier /proc/interrupts. Par exemple sur un Raspberry Pi 3, on observe les données suivantes.

# cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
 16:          0          0          0          0  bcm2836-timer   0 Edge      arch_timer
 17:        974        466        348       1505  bcm2836-timer   1 Edge      arch_timer
 23:         18          0          0          0  ARMCTRL-level   1 Edge      3f00b880.mailbox
 24:          2          0          0          0  ARMCTRL-level   2 Edge      VCHIQ doorbell
 39:          1          0          0          0  ARMCTRL-level  41 Edge    
 46:          0          0          0          0  ARMCTRL-level  48 Edge      bcm2708_fb dma
 48:        267          0          0          0  ARMCTRL-level  50 Edge      DMA IRQ
 50:          0          0          0          0  ARMCTRL-level  52 Edge      DMA IRQ
 62:       8865          0          0          0  ARMCTRL-level  64 Edge      dwc_otg, dwc_otg_pcd, dwc_otg_hcd:usb1
 79:          0          0          0          0  ARMCTRL-level  81 Edge      3f200000.gpio:bank0
 80:          0          0          0          0  ARMCTRL-level  82 Edge      3f200000.gpio:bank1
 86:          4          0          0          0  ARMCTRL-level  88 Edge      mmc0
 87:        116          0          0          0  ARMCTRL-level  89 Edge      uart-pl011
 92:        456          0          0          0  ARMCTRL-level  94 Edge      mmc1
FIQ:              usb_fiq
IPI0:          0          0          0          0  CPU wakeup interrupts
IPI1:          0          0          0          0  Timer broadcast interrupts
IPI2:        323        610        369        468  Rescheduling interrupts
IPI3:          2          5          4          4  Function call interrupts
IPI4:          2          2          0          0  Single function call interrupts
IPI5:          0          0          0          0  CPU stop interrupts
IPI6:          1          0          0          0  IRQ work interrupts
IPI7:          0          0          0          0  completion interrupts
Err:          0
#

La première colonne représente le numéro de l’interruption (sauf pour celles du bas de la liste, FIQ et IPIx qui représentent des interruptions internes au processeur). Les quatre colonnes suivantes indiquent le nombre d’occurrence de chaque interruption depuis le boot du système sur chacun des quatre cœurs de processeur. Les valeurs sont faibles ici, le Raspberry Pi 3 vient de démarrer. Les colonnes suivantes affichent le type de contrôleur, un numéro interne à celui-ci, et le driver concerné.

Les fonctions de traitement bas-niveau sont déjà écrites dans le noyau Linux et nous n’avons pas à y toucher. Elles ont pour rôle d’appeler des fonctions de plus haut-niveau que l’on nomme routines de service (Interrupt Service Routine ou ISR), et ce sont ces routines de service que nous pouvons écrire dans nos drivers. Une fonction bas-niveau a également pour rôle de désactiver dans l’APIC l’interruption qui l’a déclenchée avant d’appeler la routine de service et de réactiver l’interruption ensuite. Imaginons le déclenchement d’une hypothétique interruption 100 :

Fig-02 - Handlers bas-niveau et ISR

Fig-02 – Handlers bas-niveau et ISR

Lorsque, dans le reste de cet article je parlerai – avec un léger abus de langage – de gestionnaire (ou de handler) d’interruption, il s’agira toujours d’une routine de service invoquée par une fonction de plus bas-niveau. Autrement dit, les schémas à venir ne représenteront plus les handlers bas-niveau, mais uniquement les routines ISR.

Fig-03 - Handler d'interruption

Fig-03 – Handler d’interruption

Précisons dès à présent qu’une fonction bas-niveau peut appeler successivement plusieurs routines de service en réponse à la même interruption. Par exemple dans le fichier /proc/interrupts ci-dessus, on voit que l’interruption 62 est traitée conjointement par trois routines de service appartenant aux drivers dwc_otg, dwc_otg_pcd, et dwc_otg_hcd appelées successivement. C’est ce qu’on nomme une interruption partagée.

Fig-04 - Interruption partagée

Fig-04 – Interruption partagée

Il est possible, lors de l’écriture d’un driver d’agir de différentes façons sur le traitement des interruptions.
On peut désactiver (on dit généralement “masquer“) ou activer (“démasquer“) le traitement d’une interruption. Si on masque une interruption, et qu’elle se produit effectivement, l’IRQ restera en attente au niveau de l’APIC, jusqu’à ce que le processeur démasque l’interruption. C’est à ce moment seulement qu’il recevra la demande en attente.

Fig-05 - Masquage d'interruption

Fig-05 – Masquage d’interruption

Il faut bien comprendre qu’une seule instance de l’interruption sera délivrée au moment du déblocage, même si elle est survenue à plusieurs reprises pendant la période de masquage.

Fig-06 - Occurrences d'interruption masquée

Fig-06 – Occurrences d’interruption masquée

Sur certains processeurs, il existe des interruptions non-masquables servant à la notification d’événements très urgents.
En outre certaines interruptions peuvent être plus prioritaires que d’autres. Du fait de la portabilité de Linux sur de nombreuses architectures, cette notion n’est pas prise en compte au niveau de l’API proposée aux drivers.

Un peu de pratique

Nous allons commencer quelques expériences avec les interruptions. J’ai choisi comme plate-forme d’illustration pour cet article le Raspberry Pi 3 en raison de sa grande disponibilité. Toutefois les exemples sont adaptables sur d’autres cartes. Durant des sessions de formation je les ai testés sur de nombreux autres systèmes (BeagleBoneBlack, Pandaboard, IGEPv2, i.MX6 Sabrelite, toute la gamme des Raspberry Pi, etc.) en adaptant les numéros de GPIO.

Dans notre premier exemple nous allons installer un petit gestionnaire d’interruption très simple qui inscrira lors de son déclenchement un message dans les traces du noyau. L’intérêt de travailler avec un Raspberry Pi est de pouvoir facilement gérer communications avec l’extérieur grâce aux GPIO, et de déclencher facilement des interruptions avec un simple morceau de fil électrique.

J’ai fait le choix, pour éviter de mettre en œuvre un environnement de cross-compilation qui compliquerait le propos de cet article, de faire toutes les compilations de modules du kernel directement sur le Raspberry Pi. La compilation d’un module prend deux à trois secondes sur un Raspberry Pi 3, ce qui est très raisonnable, même lorsque le nombre d’exemples est assez élevé. L’inconvénient, est que la compilation d’un module nécessite la présence des fichiers d’en-tête, du fichier de configuration et des Makefile du noyau cible. Or, les distributions Raspbian ne fournissent pas cet ensemble. Il existe bien un package linux-headers dans cette distribution mais il ne correspond pas du tout au noyau fourni.

Nous devons donc commencer par recompiler un noyau, opération très simple mais qui prend environ deux heures de compilation sur le Raspberry Pi 3. En partant d’une distribution Raspbian fraîchement installée, voici la suite de commandes à exécuter.

$ sudo apt update
$ sudo apt install -y ncurses-dev  bc
$ git clone https://github.com/raspberrypi/linux --depth 1
$ cd linux/
$ make bcm2709_defconfig
$ make -j 8     # C'est cette étape qui dure environ deux heures...
$ sudo make scripts
$ sudo make modules_install
$ sudo mkdir /boot/old-dtbs
$ sudo mv /boot/*dtb /boot/old-dtbs/
$ sudo make INSTALL_DTBS_PATH=/boot dtbs_install
$ sudo cp /boot/kernel7.img /boot/old-kernel7.img
$ sudo cp arch/arm/boot/zImage /boot/kernel7.img 
$ sudo reboot

Après redémarrage, on vérifie avec :

$ uname -a

que l’on se trouve bien sur notre noyau tout neuf.

Attention, le répertoire qui a servi pour la compilation sera référencé pendant la compilation des modules ultérieurs, et ne doit donc pas être déplacé, ni effacé. À la rigueur, on peut y faire un peu de ménage pour gagner de la place :

$ cd /lib/modules/$(uname -r)/build                                                                                  
$ rm -rf Documentation
$ find . -name '*.[coS]' | xargs rm -f

Ceci efface les fichiers sources C et assembleur ainsi que les fichiers objets. Il est important de conserver les fichiers d’en-tête .h et les fichiers Makefile. On gagne ainsi environ 700 Mo.

Handler d’interruption monolithique

Le cas le plus simple – et le plus courant – est celui du handler monolithique. Lorsque l’interruption se produit, le handler invoqué fait tout le travail attendu puis se termine et le processeur reprend son activité initiale.

Un handler d’interruption s’écrit en respectant un prototype bien défini (il existe d’ailleurs un typedef irq_handler_t pour représenter ce prototype) :

irqreturn_t irq_handler(int irq_num, void *irq_id);

Lorsque le handler est appelé, il recevra automatiquement deux arguments : le numéro de l’interruption qui l’a déclenché (utile lorsque le même handler gère plusieurs interruptions différentes), et un identifiant représenté par un pointeur générique. Nous fournirons cet identifiant lors de l’installation du handler. En général il s’agit d’un pointeur sur une structure de données personnalisées, contenant des informations propres à notre instance de driver.

Ce pointeur joue également un second rôle, notamment dans le cas d’une interruption partagée entre plusieurs périphériques : on fournit le même pointeur lors du retrait du driver afin d’indiquer quel handler doit être désinstallé.

Le handler doit également renvoyer une valeur de type irqreturn_t. Il s’agit d’un type énuméré pouvant prendre les valeurs IRQ_NONE ou IRQ_HANDLED (ainsi que la valeur IRQ_WAKE_THREAD que nous verrons dans le prochain article). En principe le handler doit interroger le matériel qu’il gère afin de savoir si l’interruption lui était bien destinée. Dans l’affirmative il renverra IRQ_HANDLED. Sinon, (il est probable que l’interruption soit partagée entre plusieurs drivers) il renverra IRQ_NONE.

Pour installer et désinstaller le handler, on utilise les routines suivantes :

int  request_irq(unsigned int irq_num, irq_handler_t irq_handler, unsigned long flags, const char *name, void *irq_dev);
void free_irq(unsigned int irq_num, void *irq_dev);

Il existe divers flags pour l’installation d’un handler, on les trouve dans le fichier <linux/interrupt.h>. En voici quelques-uns utilisés régulièrement :

  • IRQF_TRIGGER_RISING : déclenchement sur le front montant d’un signal logique.
  • IRQF_TRIGGER_FALLING : déclenchement sur le front descendant d’un signal logique.
  • IRQF_TRIGGER_HIGH : déclenchement tant qu’un signal logique est au niveau haut.
  • IRQF_TRIGGER_LOW : déclenchement tant qu’un signal logique est au niveau bas.
  • IRQF_SHARED : le handler accepte le partage de l’interruption avec d’autres handlers.
  • IRQF_NO_THREAD : l’interruption ne peut pas être threadée (nous en reparlerons ultérieurement) même avec le patch PREEMPT_RT.

Implémentation

Nous allons programmer un premier module simple, qui installera un handler pour une entrée GPIO. Lorsqu’un front montant se présentera sur cette broche, le handler enverra un message dans les traces du noyau. Le numéro de l’interruption associé à une borne GPIO donnée est obtenu avec la fonction gpio_to_irq().

J’ai choisi arbitrairement la broche 16 (GPIO 23, comme on peut le voir sur ce schéma du connecteur P1 du Raspberry Pi).

Téléchargeons et compilons les exemples de cet article :

$ git clone https://github.com/cpb-/Article-2017-06-06
$ cd Article-2017-06-06
$ make

Voici le listing du premier exemple :

/// \file test-irq-01.c
///
/// \brief Exemples de l'article "[KERNEL] Interruptions et tasklets" (https://www.blaess.fr/christophe/2017/06/05)
///
/// \author Christophe Blaess 2017 (https://www.blaess.fr/christophe)
///
/// \license GPL.

        #include <linux/gpio.h>
        #include <linux/interrupt.h>
        #include <linux/module.h>


        #define IRQ_TEST_GPIO_IN  23


static irqreturn_t irq_test_handler(int irq, void * ident)
{
        printk(KERN_INFO "%s: %s()\n", THIS_MODULE->name, __FUNCTION__);
        return IRQ_HANDLED;
}


static int __init irq_test_init (void)
{
        int err;

        if ((err = gpio_request(IRQ_TEST_GPIO_IN,THIS_MODULE->name)) != 0)
                return err;

        if ((err = gpio_direction_input(IRQ_TEST_GPIO_IN)) != 0) {
                gpio_free(IRQ_TEST_GPIO_IN);
                return err;
        }

        if ((err = request_irq(gpio_to_irq(IRQ_TEST_GPIO_IN), irq_test_handler,
                               IRQF_SHARED | IRQF_TRIGGER_RISING,
                               THIS_MODULE->name, THIS_MODULE->name)) != 0) {
                gpio_free(IRQ_TEST_GPIO_IN);
                return err;
        }

        return 0;
}


static void __exit irq_test_exit (void)
{
        free_irq(gpio_to_irq(IRQ_TEST_GPIO_IN), THIS_MODULE->name);
        gpio_free(IRQ_TEST_GPIO_IN);
}


module_init(irq_test_init);
module_exit(irq_test_exit);


MODULE_DESCRIPTION("Simple monolithic interrupt handler");
MODULE_AUTHOR("Christophe Blaess <Christophe.Blaess@Logilin.fr>");
MODULE_LICENSE("GPL");

Chargeons le module, et vérifions que le handler est bien installé :

$ sudo insmod test-irq-01.ko
$ cat /proc/interrupts 
           CPU0       CPU1       CPU2       CPU3       
   [...]
189:          0          0          0          0  pinctrl-bcm2835  23 Edge      test_irq_01
   [...]

On peut alors faire un contact entre la broche 16 (notre GPIO) et la broche 1 (le +3.3V) avec un petit fil par exemple :

$ cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       
   [...]
189:         54          0          0          0  pinctrl-bcm2835  23 Edge      test_irq_01
   [...]

Cinquante quatre interruptions ! Et oui, ce n’est pas surprenant, un petit contact sec entre deux fils produit de nombreux rebonds très brefs. Vérifions les traces de notre handler :

$ dmesg
   [...]
[  177.947441] test_irq_01: irq_test_handler()
[  177.947541] test_irq_01: irq_test_handler()
[  177.947615] test_irq_01: irq_test_handler()
[  177.947675] test_irq_01: irq_test_handler()
[  177.947686] test_irq_01: irq_test_handler()
[  177.947696] test_irq_01: irq_test_handler()
[  177.947706] test_irq_01: irq_test_handler()
[  177.947728] test_irq_01: irq_test_handler()
[  177.947738] test_irq_01: irq_test_handler()
[  177.947747] test_irq_01: irq_test_handler()
   [...]
[  178.107233] test_irq_01: irq_test_handler()
[  178.107290] test_irq_01: irq_test_handler()
[  178.107322] test_irq_01: irq_test_handler()
[  178.107357] test_irq_01: irq_test_handler()
[  178.107376] test_irq_01: irq_test_handler()
$ sudo rmmod test_irq_01

Grâce à l’horodatage du printk(), nous voyons que les petits rebonds sont séparés par quelques dizaines de microsecondes seulement.

Différer un traitement

Nous avons parfaitement réussi à installer un handler simple qui se déclenche à chaque occurrence de l’interruption et s’exécute immédiatement et entièrement. Il est important de se souvenir que pendant toute l’exécution d’un handler, l’interruption qui l’a déclenché est masquée (sur tous les cœurs dans le cas d’un processeur multicœur).

Supposons à présent, que nous ayons un travail un peu plus conséquent à réaliser dans le handler, et que nous souhaitions par ailleurs horodater précisément l’occurrence de l’interruption. On pourrait très bien imaginer un transfert de données à effectuer, une vérification de checksum, voire un déchiffrement d’information encodées.

Pour simuler ceci, j’ai simplement ajouté dans le handler une attente active d’une milliseconde avec udelay(), après le printk() qui nous sert d’horodatage :

/// \file test-irq-02.c

   [...]

        #include <linux/delay.h>


        #define IRQ_TEST_GPIO_IN  23


static irqreturn_t irq_test_handler(int irq, void * ident)
{
        printk(KERN_INFO "%s: %s()\n", THIS_MODULE->name, __FUNCTION__);
        udelay(1000);
        return IRQ_HANDLED;
}

   [...]

Après chargement du module je réitère la même expérience en faisant un bref contact entre l’entrée GPIO et le +3.3V. Voici les traces des messages du kernel :

$ cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       
   [...]
189:         57          0          0          0  pinctrl-bcm2835  23 Edge      test_irq_01
   [...]
$ dmesg
   [...]
[ 1297.485853] test_irq_02: irq_test_handler()
[ 1297.486866] test_irq_02: irq_test_handler()
[ 1297.487876] test_irq_02: irq_test_handler()

Cette fois nous n’avons que trois interruptions prises en compte, toutes séparées d’une milliseconde. Pour simplifier notre propos, je n’en ai représentées que deux sur le schéma suivant, mais le raisonnement est tout aussi valable. Nous savons que des rebonds se produisent et qu’une cinquantaine d’IRQ seront envoyées par l’APIC, toutes les vingt microsecondes environ. Après avoir horodaté son déclenchement, par un printk(), le handler monolithique effectue un travail consommant du temps CPU, une boucle active dans udelay(), pendant une milliseconde.

Fig-07 - Rafales d'interruptions monolithiques

Fig-07 – Rafales d’interruptions monolithiques

Sur le schéma ci-dessus, j’ai numéroté entre parenthèses les occurrences des interruptions. Nous voyons que la première est correctement traitée, avec un retard (on parle généralement de latence) de quelques microsecondes parfaitement justifié. Lorsque la deuxième arrive, l’interruption 189 étant masquée dans l’APIC, elle reste en attente et sera délivrée plusieurs centaines de microsecondes plus tard, une fois que le premier handler sera terminé. Nous avons donc un problème d’horodatage pour cette deuxième occurrence. La situation est pire pour la troisième interruption et les suivantes, puisque le masquage ne conservant qu’une seule occurrence d’IRQ, elles seront tout simplement perdues.

Il est clair que si une interruption périodique se produit toutes les vingt microsecondes dont le handler dure une milliseconde, le système n’est pas viable. Ce qui m’intéresse, c’est le cas où nous avons quelques déclenchements occasionnellement très rapprochés, mais que cela se produit suffisamment rarement pour permettre au système de fonctionner normalement le reste du temps. Et avec un handler monolithique, le résultat n’est pas satisfaisant : seule la première occurrence est correctement traitée, la seconde est mal horodatée, et les suivantes sont perdues !

Top-half et bottom-half

Alf

Une autre approche est possible, qui consiste à distinguer les opérations devant être exécutées immédiatement au déclenchement de l’IRQ, de celles qui peuvent être réalisées une fois que l’interruption aura été démasquée dans l’APIC. Les premières sont regroupées dans la partie supérieure (top half) du traitement, et les secondes dans la partie inférieure (bottom half).

Il existe plusieurs supports pour implémenter top half et bottom half. Dans cet article nous observerons les tasklets, dans le suivant nous verrons les workqueues et les threaded interrupts.

Tasklets

Contrairement à ce que leur nom – particulièrement mal choisi – laisse entendre, les tasklets ne sont pas des petites tâches. Il s’agit simplement d’un mécanisme permettant à un handler d’interruption de programmer l’exécution d’une fonction après avoir démasqué l’IRQ qui l’a déclenché. La fonction qui s’exécute doit être de type :

void tasklet_function(unsigned long arg);

On déclare une tasklet avec :

DECLARE_TASKLET(tasklet_name, tasklet_function, tasklet_arg)

La tasklet ainsi déclarée est implémentée par une structure tasklet_struct. Puis on peut programmer son exécution avec :

void tasklet_schedule(struct tasklet_struct *tasklet_name);

Lorsque le handler invoque tasklet_schedule(), nous avons plusieurs garanties :

  • La fonction de la tasklet sera exécutée (par abus de langage, on dit “la tasklet sera exécutée”) ultérieurement, le plus tôt possible après la fin du handler, sans passer par l’ordonnanceur (contrairement à ce que schedule dans tasklet_schedule() laisse entendre).
  • Si la tasklet est déjà programmée, mais n’a pas encore débuté, une seule instance sera exécutée.
  • Si la tasklet est déjà en cours d’exécution, une seconde instance sera exécutée après la fin de la première, sur le même cœur de CPU que celle-ci.
  • Si la tasklet n’est pas programmée ni en cours d’exécution, elle sera exécutée sur le même cœur de CPU que le handler qui invoque tasklet_schedule().

 

Attention, il est important au retrait du module de s’assurer qu’il n’y a pas de tasklet en cours ou en attente d’exécution (sur un autre cœur). Pour cela il faut appeler :

void tasklet_kill(struct tasklet_struct * tasklet_name);

Voyons comment se déroule le traitement d’une occurrence unique de l’interruption :

Fig-08 - Une interruption, une tasklet

Fig-08 – Une interruption, une tasklet

J’ai abrégé sur le schéma ci-dessus T.H. pour Top Half (le code qui est exécuté directement dans le handler d’interruption), et B.H. pour Bottom Half, le code ultérieurement exécuté dans la tasklet. Pas de surprise, le traitement est identique à celui d’un handler monolithique, avec un temps d’exécution très légèrement plus long (non représenté sur ce schéma) dû au mécanisme de programmation et d’invocation de la tasklet.

Supposons maintenant que deux interruptions très rapprochées se déclenchent. La Bottom Half de la première est interrompue par la Top Half de la seconde, et la deuxième Bottom Half s’exécutera à la suite.

Fig-09 - Deux interruptions, deux tasklets

Fig-09 – Deux interruptions, deux tasklets

L’avantage par rapport au handler monolithique, c’est que la seconde interruption est correctement horodatée. Intéressons-nous maintenant au cas de trois interruptions rapprochées :

Fig-10 - Trois interruptions, deux tasklets

Fig-10 – Trois interruptions, deux tasklets

À nouveau les trois interruptions sont correctement horodatées. Mais cette fois, le troisième tasklet_schedule() n’a pas d’effet car la seconde tasklet n’a pas encore démarré. C’est ce que confirme l’expérience suivante :

/// \file test-irq-03.c

  [...]
        static void irq_test_tasklet_function(unsigned long);

        static DECLARE_TASKLET(irq_test_tasklet, irq_test_tasklet_function, 0);


static irqreturn_t irq_test_handler(int irq, void * ident)
{
        printk(KERN_INFO "%s: %s()\n", THIS_MODULE->name, __FUNCTION__);
        tasklet_schedule(&irq_test_tasklet);
        return IRQ_HANDLED;
}


static void irq_test_tasklet_function(unsigned long unused)
{
        printk(KERN_INFO "%s: %s()\n", THIS_MODULE-&gt,name, __FUNCTION__);
        udelay(1000);   
}
  [...]

Lorsqu’on fait à nouveau un bref contact entre la broche 16 et la broche 1, on observe :

$ dmesg
  [...]
[130129.690714] test_irq_03: irq_test_handler()
[130129.690734] test_irq_03: irq_test_handler()
[130129.690744] test_irq_03: irq_test_handler()
[130129.690776] test_irq_03: irq_test_tasklet_function()
[130129.690826] test_irq_03: irq_test_handler()
[130129.690857] test_irq_03: irq_test_handler()
[130129.690868] test_irq_03: irq_test_handler()
[130129.690877] test_irq_03: irq_test_handler()
[130129.690895] test_irq_03: irq_test_handler()
[130129.691814] test_irq_03: irq_test_tasklet_function()
  [...]

Mais alors, la situation n’est pas meilleure qu’avec un driver monolithique ! Somme nous condamnés à perdre la troisième interruption et les suivantes jusqu’au déclenchement de la tasklet ? Non. La situation n’est pas tout à fait la même. Car dans la Top Half, nous avons brièvement le contrôle, et pouvons par exemple incrémenter un compteur d’interruptions. La Bottom Half bouclera autant de fois qu’il y a d’interruptions reçues, et décrémentera le compteur. Voici un exemple d’implémentation :

/// \file test-irq-04.c
  [...]
        #include <linux/atomic.h>
        #include <linux/delay.h>
        #include <linux/gpio.h>
        #include <linux/interrupt.h>
        #include <linux/module.h>

  [...]
        static atomic_t irq_test_counter = ATOMIC_INIT(0);


static irqreturn_t irq_test_handler(int irq, void * ident)
{
        printk(KERN_INFO "%s: %s()\n", THIS_MODULE->name, __FUNCTION__);
        atomic_inc(&irq_test_counter);
        tasklet_schedule(&irq_test_tasklet);
        return IRQ_HANDLED;
}


static void irq_test_tasklet_function(unsigned long unused)
{
        printk(KERN_INFO "%s: %s()\n", THIS_MODULE->name, __FUNCTION__);
        do {
                printk(KERN_INFO "%s:%s() loop\n", THIS_MODULE->name, __FUNCTION__);
                udelay(1000);
        } while (atomic_dec_return(&irq_test_counter) > 0);
}
  [...]

Heureuse surprise, dès mon premier test, j’ai eu trois interruptions rapprochées et le résultat est concluant :

  [...]
[171984.092518] test_irq_04: irq_test_handler()
[171984.092569] test_irq_04: irq_test_handler()
[171984.092581] test_irq_04: irq_test_handler()
[171984.092595] test_irq_04: irq_test_tasklet_function()
[171984.092606] test_irq_04:irq_test_tasklet_function() loop
[171984.093617] test_irq_04:irq_test_tasklet_function() loop
[171984.094627] test_irq_04:irq_test_tasklet_function() loop
  [...]

Tasklet et contexte d’appel-système

Nous avons vu que, programmée depuis un contexte d’interruption, une tasklet est exécutée immédiatement sans repasser par l’ordonnanceur. Ceci est facile à vérifier, en modifiant légèrement le code de la tasklet ainsi :

/// \file test-irq-05.c

  [..]
static irqreturn_t irq_test_handler(int irq, void * ident)
{
        tasklet_schedule(&irq_test_tasklet);
        return IRQ_HANDLED;
}


static void irq_test_tasklet_function(unsigned long unused)
{
        printk(KERN_INFO "%s: current pid=%d comm=%s\n", THIS_MODULE->name, current->pid, current->comm);
}

Cette tasklet affiche le PID et le nom du processus current (celui actuellement ordonnancé sur le cœur de CPU où elle s’exécute). Nous voyons bien qu’il n’y a pas de tâche spécifique. Suivant les itérations différents processus où threads kernel sont actifs :

[198874.401602] test_irq_05: current pid=0 comm=swapper/0
  [...]
[198874.402142] test_irq_05: current pid=463 comm=rs:main Q:Reg
  [...]
[198874.402669] test_irq_05: current pid=90 comm=jbd2/mmcblk0p2-
  [...]
[198874.415554] test_irq_05: current pid=0 comm=swapper/0
  [...]
[198874.421986] test_irq_05: current pid=3 comm=ksoftirqd/0
  [...]
[198874.422530] test_irq_05: current pid=462 comm=in:imklog
  [...]
[198874.423017] test_irq_05: current pid=463 comm=rs:main Q:Reg
  [...]
[198874.423882] test_irq_05: current pid=0 comm=swapper/0
  [...]

Rien ne nous empêche néanmoins de programmer une tasklet depuis un contexte d’appel-système. Voici par exemple un module qui implémente un mini-driver proposant un unique appel-système write() qui ne fait qu’appeler notre tasklet :

/// \file test-irq-06.c
  [...]
        #include <linux/delay.h>
        #include <linux/interrupt.h>
        #include <linux/miscdevice.h>
        #include <linux/module.h>
        #include <linux/sched.h>

        static void irq_test_tasklet_function(unsigned long);

        static DECLARE_TASKLET(irq_test_tasklet, irq_test_tasklet_function, 0);


static void irq_test_tasklet_function(unsigned long unused)
{
        printk(KERN_INFO "%s: current pid=%d comm=%s\n", THIS_MODULE->name, current->pid, current->comm);
}


static ssize_t irq_test_write(struct file *filp, const char *buffer, size_t length, loff_t *offset)
{
        tasklet_schedule(&irq_test_tasklet);
        return length;
}


static struct file_operations irq_test_fops = {
        .owner = THIS_MODULE,
        .write = irq_test_write,
};

static struct miscdevice irq_test_misc = {
        .minor = MISC_DYNAMIC_MINOR,
        .name  = THIS_MODULE->name,
        .fops  = &irq_test_fops,
};


static int __init irq_test_init (void)
{
        return misc_register(&irq_test_misc);
}


static void __exit irq_test_exit (void)
{
        misc_deregister(&irq_test_misc);
}
  [...]

Pour déclencher l’appel-système, il nous suffit de faire une écriture avec un echo redirigé depuis la ligne de commande du shell.

$ sudo insmod test-irq-06.ko
$ echo 1 > /dev/test_irq_06 
-bash: /dev/test_irq_06: Permission denied
pi@raspberrypi:~/article-2017-06-06$ sudo -s
root@raspberrypi:/home/pi/article-2017-06-06# echo 1 > /dev/test_irq_06
root@raspberrypi:/home/pi/article-2017-06-06# echo 1 > /dev/test_irq_06
root@raspberrypi:/home/pi/article-2017-06-06# echo 1 > /dev/test_irq_06
root@raspberrypi:/home/pi/article-2017-06-06# dmesg
  [...]
[202648.772668] test_irq_06: current pid=15 comm=ksoftirqd/1
[202666.062771] test_irq_06: current pid=3 comm=ksoftirqd/0
[202668.352760] test_irq_06: current pid=3 comm=ksoftirqd/0

Lors de la programmation d’une tasklet depuis un contexte d’appel système, c’est donc un thread du kernel qui l’exécutera. Ces threads, nommés ksoftirqd sont parfaitement visibles dans la liste des tâches du système. Notons par ailleurs que ce n’est pas toujours le même thread, le noyau dispose d’un ensemble de ksoftirqd, et en sélectionne dynamiquement un disponible.

# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.4  22780  3816 ?        Ss   Jun02   0:07 /sbin/init splash
root         2  0.0  0.0      0     0 ?        S    Jun02   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        S    Jun02   0:05 [ksoftirqd/0]
root         5  0.0  0.0      0     0 ?        S<   Jun02   0:00 [kworker/0:0H]
root         7  0.0  0.0      0     0 ?        S    Jun02   0:17 [rcu_preempt]
root         8  0.0  0.0      0     0 ?        S    Jun02   0:00 [rcu_sched]
root         9  0.0  0.0      0     0 ?        S    Jun02   0:00 [rcu_bh]
root        10  0.0  0.0      0     0 ?        S    Jun02   0:00 [migration/0]
root        11  0.0  0.0      0     0 ?        S<   Jun02   0:00 [lru-add-drain]
root        12  0.0  0.0      0     0 ?        S    Jun02   0:00 [cpuhp/0]
root        13  0.0  0.0      0     0 ?        S    Jun02   0:00 [cpuhp/1]
root        14  0.0  0.0      0     0 ?        S    Jun02   0:00 [migration/1]
root        15  0.0  0.0      0     0 ?        S    Jun02   0:00 [ksoftirqd/1]
root        17  0.0  0.0      0     0 ?        S<   Jun02   0:00 [kworker/1:0H]
root        18  0.0  0.0      0     0 ?        S    Jun02   0:00 [cpuhp/2]

Conclusion

Nous avons vu le principe des tasklets pour réaliser un traitement différé depuis un handler d'interruption. Il existe d'autres mécanismes que nous verrons dans le prochain article. L'intérêt de la tasklet est d'être exécutée immédiatement, plus prioritairement que toutes les tâches ordonnancées du système. Ceci est toutefois différent si elle est programmée depuis un contexte d'appel système, puisqu'alors c'est un thread du kernel qui l'exécute.

Nous reviendrons sur cette exécution portée par un thread dans le prochain article, car cela peut nous réserver des surprises dans un contexte d'application temps réel...

Petit ajout dans spi-tools

$
0
0

Tout d’abord, je vous souhaite à tous une excellente année 2018 !

Dans l’article Projet spi-tools, je présentais un petit outil permettant de configurer les paramètres d’une liaison SPI depuis la ligne de commande du shell, en s’appuyant sur l’interface spidev. Depuis quelques temps, l’une des options de cet outil ne fonctionnait plus correctement. En effet, plusieurs drivers SPI du kernel réinitialisent la vitesse de communication lorsque l’on referme le descripteur de fichier /dev/spidevX.Y.

Nous pouvons bien modifier la vitesse, mais à peine notre commande spi-config s’achève-t-elle que le driver reconfigure la vitesse par défaut. En voici un exemple sur Raspberry Pi 3.

$ spi-config -d /dev/spidev0.0 -q
/dev/spidev0.0: mode=1, lsb=0, bits=8, speed=500000, spiready=0
$ spi-config -d /dev/spidev0.0 -s 200000
$ spi-config -d /dev/spidev0.0 -q
/dev/spidev0.0: mode=1, lsb=0, bits=8, speed=500000, spiready=0
$

La vitesse configurée à 200000 bits/seconde par notre commande est revenue immédiatement à 500000 bits/seconde.

Une solution est d’utiliser une nouvelle option : -w ou --wait. Ainsi la commande spi-config reste bloquée avec le descripteur ouvert, jusqu’à réception d’un signal qui la tue.

Il suffit de la passer en arrière-plan (en la faisant suivre d’un &) et éventuellement de récupérer son PID, disponible dans la variable spéciale $! du shell, pour pouvoir la tuer une fois la communication terminée.

$ spi-config -d /dev/spidev0.0 -q
/dev/spidev0.0: mode=1, lsb=0, bits=8, speed=500000, spiready=0
$ spi-config -d /dev/spidev0.0 -s 200000 -w &
[1] 7324
$ PID=$!
$ 

Le port reste configuré avec la vitesse désirée. La preuve :

$ spi-config -d /dev/spidev0.0 -q
/dev/spidev0.0: mode=1, lsb=0, bits=8, speed=200000, spiready=0
$ 

Quand nous n’avons plus besoin du port SPI on peut le libérer ainsi :

$ kill $PID
[1]+  Terminated              spi-config -d /dev/spidev0.0 -s 200000 -w
$ 

La vitesse reprend alors sa valeur par défaut :

 $ spi-config -d /dev/spidev0.0 -q
/dev/spidev0.0: mode=1, lsb=0, bits=8, speed=500000, spiready=0
$ 

Le projet spi-tools qui regroupe spi-config et spi-pipe est accessible sur mon dépôt Github.

Viewing all 31 articles
Browse latest View live