Les spéléologues du Kernel 🥾



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. ❤️

Merci !