Firmware : config.txt et cmdline.txt#

Pourquoi#

config.txt est lu par le firmware du Raspberry Pi avant même que Linux ne démarre : c’est là que se décident le kernel à charger, les interfaces matérielles actives, et le comportement du bus PPS. cmdline.txt est lu par le kernel au tout début de son démarrage : c’est là qu’on isole des cœurs CPU pour l’usage temps réel. Ces deux fichiers sont le socle sur lequel tout le reste du système repose.

Comment#

/boot/firmware/config.txt#

# Désactivation des interfaces inutiles (surface d'attaque, consommation)
dtparam=spi=off
dtparam=audio=off
camera_auto_detect=0
display_auto_detect=1
auto_initramfs=1

dtoverlay=vc4-kms-v3d
max_framebuffers=2
disable_fw_kms_setup=1

# 64 bits, pas de bandes noires, fréquence maximale
arm_64bit=1
disable_overscan=1
arm_boost=1

[pi5]
# Image kernel RT ciblée (cf. chapitre précédent)
kernel=kernel8_rt.img

[all]
# UART activé (console série + récepteur GNSS sur ttyAMA0)
dtparam=uart0=on
enable_uart=1

# RAM allouée au GPU : minimale (pas d'affichage lourd sur un serveur headless)
gpu_mem=16

# RTC embarquée du Pi 5 (pile de sauvegarde)
dtparam=rtc=on
# 3 000 000 µV = 3,0 V : tension de charge assortie à la pile bouton
# ML-2032 (nominal 3 V) qui sauvegarde la RTC hors tension, doit être
# physiquement connectée avant le premier boot pour bénéficier de cette
# charge automatique
dtparam=rtc_bbat_vchg=3000000

# PPS sur GPIO 4, la pièce maîtresse de tout le projet
dtoverlay=pps-gpio,gpiopin=4

# Radios non utilisées, désactivées (surface d'attaque, interférences)
dtoverlay=disable-wifi
dtoverlay=disable-bt

# Profil thermique : 4 paliers de ventilation PWM
# Maintien actif dès 40 °C pour stabiliser l'oscillateur (cf. chapitre
# Stabilisation thermique)
dtparam=fan_temp0=40000
dtparam=fan_temp0_hyst=20000
dtparam=fan_temp0_speed=128
dtparam=fan_temp1=60000
dtparam=fan_temp1_hyst=7000
dtparam=fan_temp1_speed=170
dtparam=fan_temp2=70000
dtparam=fan_temp2_hyst=5000
dtparam=fan_temp2_speed=212
dtparam=fan_temp3=75000
dtparam=fan_temp3_hyst=2000
dtparam=fan_temp3_speed=255

Ces valeurs sont en millidegrés (40000 = 40 °C) et en PWM sur une échelle 0-255 (128 ≈ 50 %, 255 = 100 %). Le quartz (XO) qui cadence l’horloge système dérive d’environ 0,1 ppm par degré Celsius (chapitre Stabilisation thermique) : fan_temp0=40000 ouvre la régulation dès 40 °C, une température volontairement basse par rapport au défaut Raspberry Pi (généralement 60 °C), pour figer le point de fonctionnement thermique avant même que la charge normale du système ne le fasse dériver. Les hystérésis (_hyst) se resserrent à mesure que les paliers montent (20000 → 7000 → 5000 → 2000, soit 20 → 7 → 5 → 2 °C) : large au premier palier pour éviter que le ventilateur ne s’arrête et ne redémarre sans cesse autour de 40 °C (le bruit du cycling serait pire que quelques degrés de plus), plus fine ensuite car les paliers suivants n’ont vocation qu’à absorber des pics de charge ponctuels. L’objectif recherché n’est pas la température la plus basse possible, mais un ventilateur qui ne s’arrête jamais et ne change de vitesse que rarement, ce qui fige la dérive thermique plutôt que de la laisser fluctuer avec le bruit acoustique d’un cycling marche/arrêt.

Ce guide n’active pas de bus I2C utilisateur (dtparam=i2c_arm=on) : ce n’est pas nécessaire pour un Stratum 1 fonctionnel, seulement pour des périphériques additionnels (écrans, capteurs, HAT). La RTC embarquée du Pi 5 (dtparam=rtc=on) utilise un bus I2C interne, indépendant, et reste donc active.

/boot/firmware/cmdline.txt#

Avant de poser ce paramètre, voici la répartition finale des 4 cœurs du Raspberry Pi 5 une fois l’ensemble de ce guide appliqué (le détail de chaque affectation est expliqué dans les chapitres Gouverneur CPU, Affinité IRQ et PHC/phc2sys) :

┌───────────────────────────────────────────────┐   ┌───────────────────────────────────────────────┐
              planificateur général                          isolcpus=2,3 (réservé aux IRQ)
┌─────────────────────┐   ┌─────────────────────┐   ┌─────────────────────┐   ┌─────────────────────┐
│       Core 0        │   │       Core 1        │   │       Core 2        │   │       Core 3        │
│                     │   │                     │   │                     │   │                     │
│    OS / systemd     │   │  phc2sys (FIFO 90)  │   │  IRQ end0 (réseau)  │   │  IRQ PPS (GPIO 4)   │
│       + gpsd        │   │ + chronyd (FIFO 98) │   │    chrt FIFO 90     │   │    chrt FIFO 99     │
│     (Nice -10)      │   │                     │   │                     │   │                     │
└─────────────────────┘   └─────────────────────┘   └─────────────────────┘   └─────────────────────┘

Deux cœurs (2 et 3) sont retirés du planificateur général par isolcpus et réservés en exclusivité aux deux interruptions matérielles les plus sensibles au temps : le PPS (le top de seconde du récepteur GNSS) et la réception réseau, chacune avec une priorité temps réel (chrt) maximale pour ne jamais attendre derrière une autre tâche. Les deux cœurs restants (0 et 1) forment le pool général : phc2sys et chronyd y sont explicitement épinglés sur le cœur 1, en ordonnancement temps réel FIFO (respectivement priorité 90 et 98, chapitres PHC et Chrony), tandis que gpsd et le reste du système d’exploitation partagent le cœur 0 (chapitre Acquisition GNSS/PPS).

Ajouter en fin de la ligne unique existante :

isolcpus=2,3

Exemple complet :

console=tty1 root=PARTUUID=xxxxxxxx-02 rootfstype=ext4 fsck.repair=yes rootwait isolcpus=2,3

Ce paramètre retire les cœurs 2 et 3 du planificateur généraliste : ils seront réservés aux interruptions PPS et réseau (chapitre Affinité IRQ).

Ordonnancement : FIFO et nice, deux mondes différents#

Le schéma ci-dessus mélange deux vocabulaires (« FIFO 99 », « Nice -10 ») qui relèvent de deux politiques d’ordonnancement distinctes du kernel. L’ordonnanceur (scheduler) décide, à chaque instant, quelle tâche s’exécute sur quel cœur :

  • SCHED_OTHER, la politique par défaut, fondée sur le partage équitable : toutes les tâches ordinaires se partagent le temps CPU. La valeur nice (-20 à +19, plus elle est basse, plus la tâche est servie en priorité) ne fait que pondérer ce partage : une tâche à nice -10 reçoit une part plus grande, mais aucune ne peut monopoliser le cœur ni obtenir de garantie de délai.
  • SCHED_FIFO, la politique temps réel : une tâche FIFO de priorité N (1 à 99) s’exécute dès qu’elle est prête et garde le CPU aussi longtemps qu’elle en a besoin, sauf si une tâche FIFO de priorité strictement supérieure devient prête à son tour. Toute tâche SCHED_OTHER attend, quel que soit son nice : nice ne pèse rien face à FIFO.

Sur ce serveur, la hiérarchie complète se lit ainsi :

FIFO 99   IRQ PPS (top de seconde, cœur 3)      ne cède jamais le passage
FIFO 98   chronyd (discipline d'horloge, cœur 1)
FIFO 90   IRQ réseau (cœur 2), phc2sys (cœur 1)
········· frontière temps réel ·········
nice -10  gpsd (parsing NMEA, cœur 0)           premier parmi les tâches ordinaires
nice 0    tout le reste du système (cœur 0)

La priorité FIFO décroît à mesure qu’on s’éloigne de l’instant physique à mesurer. Le flanc PPS d’abord (99), la boucle de discipline qui l’exploite ensuite (98), puis ce qui aligne la PHC et horodate le réseau (90). Sous la frontière temps réel, il ne reste que des tâches dont le retard éventuel n’affecte pas la précision.

Pourquoi gpsd reste sur le cœur 0 (et pas sur le cœur 1)#

gpsd fait partie de la chaîne de temps, l’intuition pousserait donc à le loger sur le cœur 1 avec chronyd et phc2sys. Trois raisons s’y opposent :

  • gpsd n’a besoin d’aucune précision temporelle. Son rôle se limite à étiqueter la seconde : dire quelle seconde vient de sonner (chapitre Vue d’ensemble). L’instant précis, lui, est capturé par le kernel au flanc PPS, sur le cœur 3, avant même que gpsd ne soit consulté. Une trame NMEA peut arriver avec des centaines de millisecondes de retard sans aucun effet sur la précision finale : la marge utile est de l’ordre de la demi-seconde, très loin de la microseconde visée.
  • Le cœur 1 doit rester prévisible. chronyd et phc2sys y tournent en FIFO : une tâche ordinaire ajoutée sur ce cœur ne s’exécuterait que dans leurs interstices (nice ne pèse rien face à FIFO) et apporterait en échange ses réveils fréquents et sa pollution de cache au moment précis où chronyd fait ses calculs les plus sensibles.
  • Promouvoir gpsd en temps réel serait pire. gpsd est un démon généraliste (parsing série, clients JSON, sockets) : lui donner du FIFO élargirait le cercle des tâches capables, en cas d’emballement, d’affamer tout ce qui se trouve en dessous. La règle va dans l’autre sens : le club des tâches temps réel doit rester le plus petit et le plus auditable possible.

Le Nice -10 sur le cœur 0 suffit : il fait passer gpsd devant les tâches d’entretien du système pour lire l’UART sans traîner, ce qui est tout ce qu’on lui demande.

Erreurs à éviter#

Toute directive config.txt invalide est silencieusement ignorée, aucune erreur, aucun message. C’est le piège le plus fréquent de ce chapitre : écrire dtoverlay=i2c_arm=on au lieu de dtparam=i2c_arm=on (aucun overlay nommé i2c_arm n’existe, mais rien ne le signale), ou une typo dans un nom de paramètre. Toujours vérifier le résultat après reboot, jamais supposer qu’une absence d’erreur au boot signifie que la directive a été prise en compte.

  • Confondre paramètre firmware et paramètre kernel. config.txt est lu par le firmware (avant Linux) ; cmdline.txt est lu par le kernel. Un paramètre kernel comme nohz=off placé dans config.txt serait inerte, et il ne faut de toute façon pas l’ajouter dans cmdline.txt non plus : le comportement par défaut (tick désactivé sur cœur isolé inactif) est celui recherché.
  • Section [pi5] mal placée. Les directives sous [pi5] ne s’appliquent qu’au Pi 5, mais la section doit être correctement fermée (implicitement, par la section suivante) ou les directives qui suivent hériteront de la restriction par erreur.

Vérifier#

mount -o remount,rw /boot/firmware
# (édition des fichiers)
sync
reboot
# config.txt bien pris en compte par le firmware
vcgencmd get_config int | grep -iE "gpu_mem|arm_boost"

# cmdline.txt : isolation effective
cat /proc/cmdline
# Doit contenir "isolcpus=2,3"

cat /sys/devices/system/cpu/isolated
# Doit retourner : 2-3

# Aucun warning kernel sur des paramètres inconnus
dmesg | grep -i "Unknown kernel command line"
# Doit être VIDE

# PPS actif : le device doit exister
ls -l /dev/pps0

Pour les paramètres qui ne sont pas des entiers simples (comme i2c_arm, absents ici), vcgencmd get_config int ne les affichera pas : c’est normal, ce n’est pas un signe d’échec : les dtparam= sont consommés par l’overlay device-tree du kernel, pas par le magasin de configuration du firmware VideoCore. Le vrai test est l’existence du device correspondant (/dev/pps0, /dev/i2c-1, etc.).