Les spéléologues du Kernel 🥾
Reconstitution historique (à peu près...)
On regarde le BIOS...
... Et on voit les cartes réseau ! 🎉
"Tranquille, c'est juste un driver" !
On installe une Debian 12
- Assez logiquement, ça marche pas
- On voit bien les cartes dans
lspci, mais pas avec ip link
- On récupère les erreurs dans
dmesg
[ 9.526144] QLogic FastLinQ 4xxxx Core Module qed
[ 9.531195] qede init: QLogic FastLinQ 4xxxx Ethernet Driver qede
[ 9.583755] [qed_mcp_nvm_info_populate:3374()]Failed getting number of images
[ 9.583758] [qed_hw_prepare_single:4722()]Failed to populate nvm info shadow
[ 9.583761] [qed_probe:513()]hw prepare failed
Et on apprend des choses
- Nos cartes sont bien reconnues par le kernel ! (mais ça coupe après)
- Visiblement,
qed et qede sont les drivers responsables de ces cartes ;
- Chaque carte devrait apparaître comme 4 cartes de 25Gbps qui forment au total 100GBps ;
- Et le driver plante à l'initialisation de la carte.
Il doit exister un système driver
- On se met à chercher un driver pour nos cartes sur Internet ;
- On trouve des drivers chez HPE...
- ... Qui sont juste des repackaging de
qed/qede...
- ... Sur de vieilles versions du Kernel (5.x)...
- ... Et uniquement pour RHEL et SUSE.
Bon, ça va pas nous aider.
Ça reste intéressant :
- Les drivers sont pour RHEL 7 et SUSE 12 (donc "vieux")
- => Les drivers du kernel doivent marcher en natif, sinon il y aurait eu du support !
- Serait-ce un bug de la distribution Debian ?
Du coup, on installe plein de trucs !
- Ubuntu (24.04) car "y'a tous les drivers de la terre dessus" => Nope.
- CentOS (Stream) car "C'est juste RHEL rebadgé et instable" => Nope.
- Alpine Linux (3.18) car "On sait jamais, sur un malentendu, ça peut passer..." => Nope.
Et là, l'envie d'abandonner.
Rien ne marche, et il n'existe aucun driver disponible.
On retourne à nos sujets habituels
- On conserve un serveur sur un coin de bureau, au cas où une idée surgisse.
Et soudain, l'idée à la con 💡
(et c'est moi qui l'ai eu, eheh)
"Hey, si on installe cette vieille Ubuntu 18, ça donne quoi ?"
"Mais Florian c'est totalement idiot, on casse jamais* la compatibilité ascendante dans Linux tu le sais tr... Oh,
ça marche"
Ubuntu 18 => Tout droit !
root@ubuntu-18-lts:~# ip a
[...]
6: ens2f0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
7: ens2f1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
8: ens2f2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
9: ens2f3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN [...]
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
[...]
Et là, on hésite entre la joie et l'envie de pleurer
- Ça marche !
- Mais sur un OS qui date d'il y à des années.
Faire n'importe quoi fonctionne, continuons !
- On récupère la version du kernel de notre Ubuntu 18 (4.15) ;
- Et on le relance notre Debian 12 sur le 4.15.
- Et... ça marche. 👀
- Aller, juste pour être sûr, on recompile un 4.15 depuis les sources... Ça marche aussi !
Jazz music stops
- Le bug est dans le kernel, quelque part.
- Mais comment le trouver ?
- Ubuntu 18 : Kernel 4.15
- Debian 12 : Kernel 6.1
- Il y a 4 ans de commits sur le projet le plus gros du monde !
La question à 1 million :
En quelle version le bug apparait ?
C'est quand que ça a foiré ?
- Name & Shame
- Essayer de trouver l'endroit exact pour identifier la source du bug
- => Et ainsi faciliter le fix !
C'est quand que ça a foiré ?
- Entre la 4.15 et la 6.1, il y a 28 versions (hors version "fix")
- Idée : On monte de LTS en LTS, et on voit où ça casse.
- Une LTS => Plusieurs années de support. Non-LTS : 6 mois.
- => La LTS après la 4.15, c'est la 4.19
4.19 sur Debian 12 -> Fail !
- Notre bug est donc entre la 4.15 et la 4.19 !
4.17 sur Debian 12 -> Fail !
- C'est quelque part entre la 4.15 et la 4.17
4.16 sur Debian 12 -> It works !
- On est donc quelque part sur les commits entre la 4.16 et la 4.17 ! C'est parti pour analyser les commits.
Et on trouve assez vite !
- Le commit 43645ce rajoute la
fonction
qed_mcp_nvm_info_populate
- Et c'est cette méthode qui semble poser soucis d'après les logs :
[ 9.526144] QLogic FastLinQ 4xxxx Core Module qed
[ 9.531195] qede init: QLogic FastLinQ 4xxxx Ethernet Driver qede
[ 9.583755] [qed_mcp_nvm_info_populate:3374()]Failed getting number of images <== ICI !
[ 9.583758] [qed_hw_prepare_single:4722()]Failed to populate nvm info shadow
[ 9.583761] [qed_probe:513()]hw prepare failed
Pour être sûr, on recompile
- Avec le commit 43645ce => Ça
plante ;
- Avec le commit 50bc60c
(juste avant) => Ça fonctionne !
Et c'est parti pour le fix
- On regarde la fonction qui échoue :
qed_mcp_nvm_info_populate
- Grâce au log, on identifie sa localisation
/* Acquire from MFW the amount of available images */
nvm_info.num_images = 0;
rc = qed_mcp_bist_nvm_get_num_images(p_hwfn, p_ptt, &nvm_info.num_images);
if (rc == -EOPNOTSUPP) {
DP_INFO(p_hwfn, "DRV_MSG_CODE_BIST_TEST is not supported\n");
goto out;
} else if (rc || !nvm_info.num_images) {
DP_ERR(p_hwfn, "Failed getting number of images\n");
goto err0;
}
Et c'est parti pour le fix
int qed_mcp_bist_nvm_get_num_images(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt, u32 *num_images) {
u32 drv_mb_param = 0, rsp;
int rc = 0;
drv_mb_param = (DRV_MB_PARAM_BIST_NVM_TEST_NUM_IMAGES << DRV_MB_PARAM_BIST_TEST_INDEX_SHIFT);
rc = qed_mcp_cmd(p_hwfn, p_ptt, DRV_MSG_CODE_BIST_TEST, drv_mb_param, &rsp, num_images);
if (rc)
return rc;
if (((rsp & FW_MSG_CODE_MASK) != FW_MSG_CODE_OK))
rc = -EINVAL;
return rc;
}
Et c'est parti pour le fix
- Comme on a deux possibilités, on rajoute des logs sur les deux
if et pour afficher les valeurs de rc et rsp
- Visiblement, c'est
rsp qui n'a pas la valeur attendue...
- Et sa valeur, c'est
0x00000000
- Grâce au fichier
qed_mfw_hsi.h on découvre que le code...
- ... Correspond à une opération non supportée !
Et c'est parti pour le fix
- Pour rappel, dans
qed_mcp_nvm_info_populate, nous avions ceci :
if (rc == -EOPNOTSUPP) {
DP_INFO(p_hwfn, "DRV_MSG_CODE_BIST_TEST is not supported\n");
goto out;
}
... Sauf que si personne ne renvoie EOPNOTSUPP, ça sert pas à grand chose !
Et c'est parti pour le fix
- On rajoute donc un simple check dans
qed_mcp_bist_nvm_get_num_images :
int qed_mcp_bist_nvm_get_num_images(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt, u32 *num_images) {
u32 drv_mb_param = 0, rsp;
[...]
rc = qed_mcp_cmd(p_hwfn, p_ptt, DRV_MSG_CODE_BIST_TEST, drv_mb_param, &rsp, num_images);
if (rc)
return rc;
+ if (((rsp & FW_MSG_CODE_MASK) == FW_MSG_CODE_UNSUPPORTED)) // <= Là :-)
+ rc = -EOPNOTSUPP;
~ else if (((rsp & FW_MSG_CODE_MASK) != FW_MSG_CODE_OK))
rc = -EINVAL;
return rc;
}
On recompile...
- Version 6.12 (latest), avec notre fix...
Et ça marche !
On a sauvé nos serveurs ! 🎉
- On soumet alors une modification dans le kernel pour faire profiter à tout le monde
- Et envoie notre modification dans le kernel lore
Et au bout de quelques semaines...
- Notre soumission est approuvée, et nous arrivons dans le code ! (en 6.13)
- Pour anecdote, ce fix va être backporté jusqu'en LTS 5.4 (2019) !
Pour conclure...
- On a eu de la chance dans notre debug.
- Linux, c'est super cool ; et le fait qu'il soit open-source encore plus !
- Aucun système n'est infaillible, même le meilleur de tous.
- Si trois gobelins y sont arrivés, vous pouvez vous aussi ! Contribuez à l'OSS. ❤️