Un DIR qui éteint l'écran : la traque d'un verrou partagé
Sur le Neo6502, en mode Hercules, la première commande DIR après un démarrage éteignait l'écran. Douze correctifs, onze réfutés par la mesure, quatre mesures mal calibrées — et au bout, un verrou du SDK partagé entre les deux cœurs, qui retardait l'interruption vidéo d'une seule ligne. Une ligne suffisait.
Le Neo6502 associe un 65C02 et un RP2040 : le second fait tout le reste, dont la sortie vidéo en DVI, via la bibliothèque PicoDVI de Luke Wren. Notre firmware Trinity y ajoute un mode 1 « Hercules » : 720 × 350 en monochrome, dans un signal 720 × 480 à 60 Hz — un mode natif, une ligne écran par ligne d'image, là où le mode 0 (320 × 240) double chaque ligne et dispose donc du double de temps.
Le défaut était reproductible et absurde : MODE 1 passait bien, puis le premier DIR éteignait l'écran. Le second passait sans broncher. Le clavier répondait pendant la panne. Seul un changement de mode ramenait l'image.
1. Ce que la mesure a démenti, une hypothèse après l'autre
La différence entre le premier et le second DIR était le cache de FatFs : à froid, la première commande lit vingt-deux secteurs sur la clé USB ; à chaud, presque plus rien. La charge disque était donc le déclencheur. Restait à savoir comment elle cassait l'affichage.
Nous avons d'abord cru à une famine mémoire, puis à un manque de tampons, puis à un arbitrage de bus, puis à un moniteur qui décroche. Chacune de ces explications a été démentie par la mesure suivante, et deux correctifs livrés sur cette base ont aggravé le défaut avant d'être annulés. Quelques exemples, parce qu'ils sont plus instructifs que la solution :
- Quatre tampons de ligne au lieu de deux : l'écran noir devenait un blocage complet des deux cœurs. Annulé. Un projet voisin, pico-pacPlus, documente exactement l'inverse de ce qu'on espérait : « deeper line pools widen the window ».
- Priorité du bus mémoire au cœur vidéo : l'écran noircissait dès l'entrée en mode 1, sans le moindre accès disque, et la sonde de débogage ne pouvait plus lire la mémoire.
- Baisser l'horloge de 270 à 250 MHz — un timing 720 × 540 à 50 Hz, emprunté au fork ikjordan/PicoDVI. Aucun effet sur le défaut. C'était pourtant le bon indice : si ralentir ne change rien, ce n'est pas un problème de débit.
Nous avons aussi changé le mode 1 pour un timing VGA 720 × 400 à 70 Hz — celui du mode texte des cartes MDA, exactement 720 pixels de large. Le moniteur a enfin annoncé un mode, ce qu'il n'avait jamais fait en 720 × 480 (un timing CEA, prévu pour les téléviseurs). Mais l'image sortait fausse, et la piste a été rangée en réserve.
2. L'instrument fausse la mesure, quatre fois
Le port de débogage série du firmware n'a jamais fonctionné : le fil n'a jamais été trouvé sur le connecteur d'extension. Toute l'instrumentation est donc passée par la sonde SWD, qui lit la mémoire du RP2040 pendant qu'il tourne. Nous avons écrit un outil qui tape au clavier de la machine en écrivant directement dans la file clavier du firmware, et relève les compteurs — utilisable écran noir.
Cet instrument nous a menti quatre fois, et chaque mensonge a coûté une hypothèse :
- Attacher le second cœur à la sonde fait tomber l'encodeur à 15 images par seconde et fabrique une panne qui n'existe pas. Une série entière a été jetée.
- Un compteur de « débordement » mal calibré comptait 96 000 événements par seconde en fonctionnement normal : il ignorait que chaque canal DMA est déclenché une fois par bloc de sa propre voie.
- Le compteur de latence mesurait le blanking vertical — 1 462 µs, toujours la même valeur, au repos comme sous charge. Il fallait filtrer sur la durée, pas sur l'index de ligne, parce que le firmware décale volontairement son compteur de deux lignes au démarrage.
- Un
resetpar la sonde laisse la carte dans un état où le mode 1 bloque les deux cœurs. Plusieurs relevés de la journée ont été pris ainsi, sans que ce facteur soit contrôlé.
Règle retenue : pour toute mesure sur le mode 1, repartir d'une coupure d'alimentation, jamais d'un reset logiciel. Et ne jamais comparer deux essais qui ne partent pas du même état.
3. Ce que la boîte noire a montré
Tous les relevés étaient pris après coup, une fois la panne installée. Nous avons donc posé une boîte noire : au moment précis où l'anomalie est détectée, et avant toute réparation, le firmware copie l'état complet des six canaux DMA et de la machine à états vidéo.
Le résultat était sans ambiguïté. PicoDVI utilise, pour chacune des trois voies TMDS, un canal de données et un canal de contrôle qui le reprogramme. Pendant la panne, deux canaux de données portaient la configuration d'une autre voie — mauvais signal de requête, mauvaise FIFO. Deux canaux écrivaient dans la même FIFO, une troisième n'était plus alimentée du tout. Les listes de blocs en mémoire, elles, étaient intactes : le défaut n'était pas dans les données, mais dans leur chargement.
Une photo de l'écran a complété le tableau mieux que dix relevés : après un DIR, le texte apparaissait dédoublé en deux copies, une bleue et une jaune, décalées d'une quarantaine de pixels. Jaune = rouge + vert : les deux autres canaux restaient alignés entre eux, seul le bleu glissait. Or le bleu est la voie de synchronisation, la seule à porter quatre blocs de contrôle là où les autres en ont deux. Un décalage d'un rang ne la déplace donc pas de la même quantité : les trois voies sortent de phase.
4. Rendre le défaut inoffensif, avant de le comprendre
De là est venu un correctif qui ne corrige pas la cause : donner le même nombre de blocs à toutes les voies, en découpant le blanking des deux voies de données comme celui de la voie de synchronisation. Un débordement les décale alors toutes de la même phase, et l'image reste d'un seul tenant.
Sur carte, sous quatre DIR enchaînés : le débordement se produisait toujours (quinze occurrences), mais les trois voies bougeaient ensemble — zéro déphasage — et le texte était net. Le défaut était devenu invisible.
C'est utile, ce n'est pas satisfaisant. Restait la vraie question : pourquoi les canaux de contrôle prenaient-ils un tour d'avance ?
5. La cause : un verrou que deux cœurs se partageaient à leur insu
La mesure de latence, enfin correctement filtrée, a donné un chiffre modeste et décisif. Entre deux lignes actives, l'écart nominal est de 32 µs. Pendant le DIR, le pire relevé était de 63 µs — soit une seule ligne de retard, jamais plus. Pas de blocage long, pas de section critique de plusieurs centaines de microsecondes : une ligne.
Et une ligne suffit. Pendant ce retard, les canaux de données finissent leur bloc et chaînent vers leurs canaux de contrôle, qui avancent alors tout seuls dans la liste — sans que personne le leur demande. Ils prennent un tour d'avance, les voies se décalent, et tout le reste suit.
Reste à savoir ce qui retenait l'interruption. La réponse était dans les numéros de verrous. PicoDVI protège ses quatre files avec des spinlocks obtenus par next_striped_spin_lock_num(). Cette fonction du SDK distribue les verrous 16 à 23 en tourniquet entre tous les appelants — files, mutex, réveils, et tout ce que les bibliothèques tierces utilisent. Lus sur la carte : PicoDVI tenait les numéros 18 et 19, que FatFs et la pile USB pouvaient prendre depuis l'autre cœur.
Le détail qui fait mal est dans la documentation du SDK : spin_lock_blocking commence par désactiver les interruptions du cœur qui attend. Quand le cœur applicatif tenait le verrou pendant une rafale disque, le cœur vidéo l'attendait, aveugle. Son interruption de ligne arrivait une ligne en retard.
Le SDK documente d'ailleurs le compromis sans détour : ces verrous sont partagés, et tirer les numéros dans une plage « réduit la probabilité » que deux usages tombent sur le même. Probabilité, pas garantie.
6. Le correctif tient en une ligne
// avant
dvi_init(&dvi0, next_striped_spin_lock_num(), next_striped_spin_lock_num());
// après
static int slTmds = -1, slColour = -1;
if (slTmds < 0) { slTmds = spin_lock_claim_unused(true); slColour = spin_lock_claim_unused(true); }
dvi_init(&dvi0, slTmds, slColour);
Des verrous réservés, que personne d'autre ne peut prendre (numéros 24 et 25, hors de la plage partagée). Mesuré sur carte, DIR à froid de vingt-deux secteurs, deux essais dont un depuis une coupure d'alimentation :
| avant | après | |
|---|---|---|
| débordements de canal | 9 à 15 | 0 |
| écart entre deux lignes | 63 µs | 33 µs (le nominal) |
| lignes en retard pendant le DIR | +10 à +16 | +2 |
| écran | noir, ou texte dédoublé | parfait |
Le défaut ne se produit plus. Ce n'est plus un contournement.
7. Pourquoi personne d'autre ne l'a vu
Nous avons regardé comment les autres projets PicoDVI initialisent la bibliothèque : l'amont, le fork ikjordan, le fork xep80, pico-pacPlus. Tous reprennent le motif de l'exemple d'origine, next_striped_spin_lock_num() — et il leur convient. Le compromis du SDK ne se révèle que dans une situation particulière, qui réunit trois conditions :
- de l'entrée-sortie soutenue sur le cœur applicatif — une rafale de secteurs, pas une lecture isolée ;
- un mode vidéo natif, sans doublement de ligne, donc sans marge de rattrapage ;
- la malchance du tirage : le même verrou attribué aux deux sous-systèmes.
Les émulateurs qui chargent des programmes depuis une carte SD réunissent les deux premières. Nous ne savons pas si d'autres ont rencontré ce cas : il demande une sonde et un compteur d'avance de canal DMA pour être vu, et nous ne l'avons trouvé décrit nulle part. C'est d'ailleurs la raison de cet article — si la signature parle à quelqu'un, elle lui fera gagner les deux jours qu'elle nous a coûtés.
8. Ce que cette traque laisse
Douze correctifs, onze réfutés. Quatre mesures fausses qu'il a fallu recalibrer. Et deux choses qui ont réellement débloqué l'affaire, aucune des deux venue d'un relevé : une photo de l'écran, qui montrait qu'une seule voie glissait, et la lecture des numéros de verrous, qui a donné la cause.
Un correctif jugé sur ses propres compteurs est un correctif non jugé. Deux fois dans la même soirée, tous les indicateurs étaient au vert pendant que l'écran n'affichait rien.
La signature de ce défaut mérite d'être retenue, parce qu'elle est générique au RP2040 : un retard d'exactement une unité de temps du système temps réel — une ligne, ici — insensible à la fréquence d'horloge. Ce n'est pas un problème de débit : c'est quelqu'un qui tient un verrou.
Sources & liens
- Wren6991/PicoDVI — la bibliothèque d'origine, DVI bit-bangé sur RP2040.
- ikjordan/PicoDVI — fork avec modes 50 Hz et largeurs de 720 pixels.
- pico-pacPlus — tampons abandonnés, blocage des deux cœurs, lignes rouges : une panne voisine, documentée.
- pico-sdk — hardware_sync : les verrous « striped » et leur compromis, dans les commentaires du SDK.
- neo6502.com — la carte d'Olimex, firmware de Paul Robson.