DebugFs

Vous avez déjà entendu parler de DebugFs? Eclaircissont un peu le paysage, avec un suffixe comme "Fs" on apprend déjà que c'est un système de fichier. Bon mais qu'est ce qu'un système de fichier?

Les systèmes de fichiers

Un système de fichier est un logiciel qui fournit, d'ordinaire, l'interface entre un matériel de stockage et l'organisation de ses données en fichiers et répertoires. Par exemple sous Linux, les partitions des disques se présentent sous la forme de fichiers uniques.
/dev/hda1 sera la première partition de votre premier disque dur, et ce fichier présentera cette partition de manière très brute: un long fichier binaire incompréhensible.

Il faut donc trouver une manière d'organiser ce long fichier sous forme de repertoires qui contiennent des fichiers. Et c'est là que les systèmes de fichiers interviennent.
Si votre partition a été formatée en ext3, alors ses données seront rangées à la manière de ext3, et il faudra alors "monter" cette partition en précisant qu'il faut utiliser ext3 pour pouvoir exploiter son contnu de manière organisée.

Et DebugFs dans tout ça?

Alors DebugFs est un cas spécifique dans les systèmes de fichiers. A l'instar de procfs et sysfs, il s'agit d'un pseudo-système de fichiers. C'est à dire qu'il ne traduit pas réellement une partition en fichiers organisés. Par exemple Procfs sert initialement à fournir des renseignements sur les processus. Chaque processus dans un système de fichier procfs possède son repertoire avec des détails dans plusieurs fichiers etc.. Sysfs, lui, sert à obtenir des renseignements sur les périphériques et leurs drivers associés.
Et tandis que des développeurs de drivers Linux semblaient en baver pour exporter facilement des données dans des fichiers utilisateurs pour débugger leurs drivers, il fallait un système de fichiers facile à utiliser et le moins possible encombré de fioritures.

C'est quoi le principe?

Le but est de pouvoir créer des fichiers dans lesquels le driver ou les programmes utilisateur pourront écrire et lire ce qu'il veulent. Ce qui sera écrit par le driver pourra être lu par les programmes utilisateurs et ce qui sera écrit par les programmes utilisateurs pourra être lu par le driver. Voilà comment ça se passe, il faut que les choses restent aussi simples que possible. Le tout est que le driver et les programmes utilisateurs qui communiqueront avec ce driver par le biais de fichiers s'entendent sur un certain protocole d'échange. Par exemple si le driver écrit une structure de type A dans un fichier de l'arborescence debugfs, il faut que le programme utilisateur sache que ce qu'il va lire sera une structure de type A.

Et comment on l'utilise?

Alors typiquement, lorsqu'on utilise DebugFs de manière basique, on ne manipule quasiment qu'une seule structure de données: les structures de type "dentry". Ces structures représentent un repertoire ou un fichier créé par le driver dans l'arborescence DebugFs. Pour ce qui est des fonctions vous aurez besoin premièrement de créer votre répertoire racine dans l'arborescence debugfs, vous aurez donc besoin de la fonction debugfs_create_dir:

struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);

L'argument "name" sera le nom de votre répertoire. Etant donné que c'est le repertoire racine, il n'a pas de parent, on mettra donc NULL pour le parent. Le retour de cette fonction est un pointeur vers un "dentry" qui, comme dit précédemment, est le type utilisé pour représenter vos fichiers et repertoires. Vous aurez ensuite besoin de créer au moins un fichier. Etant donné que pour l'instant on utilisera debugfs de manière basique, on voudra juste créer un fichier qui ne fera qu'exporter un nombre entier. Pour celà nous utiliserons debugfs_create_u32:

struct dentry *debugfs_create_u32(const char *name, mode_t mode, struct dentry *parent, u32 *value);

Décrivons un peu cette fonction:

Enfin, nous aurons besoin d'effacer nos fichiers et repertoires lorsque notre driver en aura fini, pour celà une seule fonction: debugfs_remove

void debugfs_remove(struct dentry *dentry);

La structure dentry représente le fichier ou repertoire à libérer. C'est bon? Allez on y va!

J'me lance! (exporter une donnée à l'utilisateur)

On va créer un petit module simple. Le but sera de créer un répertoire nommé "repertoire_test" dans l'arborescence debugfs, puis un fichier dans ce repertoire uniquement accessible en lecture et nommé fichier_test. Ce fichier contiendra uneiquement un entier ayant la valeur 2008.
Voici le code:

#include <linux/debugfs.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");

struct dentry *root;		//Notre repertoire qui contiendra le fichier
struct dentry *test_file;	//Notre fichier
u32 value = 2008;			//La valeur que l'on souhaite passer à l'utilisateur grâce au fichier


/**
 * La fonction qui sera lancée au chargement de notre module
 */
static int __init debug_init(void)
{	
	root = debugfs_create_dir("repertoire_test", NULL); //Création du repertoire
	test_file = debugfs_create_u32("fichier_test", S_IRUSR | S_IRGRP | S_IROTH, root, &value); //Création du fichier
	return 0;
}

/**
 * La fonction qui sera lancée au déchargement de notre module
 */
static void __exit debug_exit(void)
{
	debugfs_remove(test_file);	//Destruction du fichier
	debugfs_remove(root);		//Destruction du repertoire		
}

module_init(debug_init);
module_exit(debug_exit);

module_init et module_exit définissent les procédures à lancer lors du chargement ou déchargement du module. Dans notre procédure debug_init, on crée notre repertoire et notre fichier. Le mode est une combinaison de 3 valeurs:

Lecture autorisée pour tout le monde donc (pas d'écriture).
Puis notre procédure debug_exit efface nos fichiers/repertoires créés. Avant de charger notre driver, nous allons créer un point de montage debug. Pour celà tapez ces commandes dans votre shell:


sudo mkdir /debug sudo mount -t debugfs debug /debug

Voilà votre point de montage est situé dans /debug. Maintenant nous allons créer un programme utilisateur pour lire la valeur exportée dans le fichier:

#include <stdio.h>

int main()
{
	unsigned int entier;
	char test[100];
	FILE *fp;
	fp = fopen("/debug/repertoire_test/fichier_test","r");
	fscanf(fp, "%u", &entier);
	fclose(fp);
	printf("%u\n", entier);
	return 0;
}

Il est hors de question que vous deviez recopier tout ce code, je vous ai préparé une archive avec la source du driver ainsi que le programme utilisateur. Vous pouvez la télécharger en cliquant ici. Vous avez donc monté votre repertoire debugfs, il vous reste à décompresser l'archive, compiler les fichier et lancer le tout:

Et dans l'autre sens?(importer une donnée de l'utilisateur)

Bien, on a vu comment il était possible d'exporter un fichier contenant juste un nombre entier que l'utilisatur pouvait récupérer. Maintenant pourquoi on n'essaierais pas de faire les choses dans l'autres sens? Cette fois-ci nous allons créer notre fichier avec des droits en écriture uniquement. On va faire en sorte d'attendre que l'utilisateur écrive dans le fichier pour ensuite afficher la nouvelle valeur entrée. Pour ça nous allons créer une boucle après la création de notre fichier: tant que la valeur de notre variable entière associée au fichier n'aura pas changé: on continuera d'attendre.
Le code:

#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
#define YEAR 2008

struct dentry *root;
struct dentry *test_file;
u32 value = YEAR;

static int __init debug_init(void)
{	
	root = debugfs_create_dir("repertoire_test", NULL);
	test_file = debugfs_create_u32("fichier_test", S_IWUSR | S_IWGRP | S_IWOTH, root, &value);

	while(value == YEAR) //La valeur initiale n'a pas été changé par l'utilisateur?
	{
		ssleep(1); //Alors on attend: une seconde.
	}
	printk("L utilisateur a entre %u\n", value); //Affichage de la nouvelle valeur (utiliser la commande dmesg)
	return 0;
}

static void __exit debug_exit(void)
{
	debugfs_remove(test_file);
	debugfs_remove(root);
}

module_init(debug_init);
module_exit(debug_exit);

Les droit utilisés ici (S_IWUSR | S_IWGRP | S_IWOTH) signifient le droit à l'écriture pour tous uniquement. Vous remarquerez que j'utilise la fonction ssleep danss la boucle. ssleep est une fonction qui permet à un driver d'attendre n secondes.
Voici maintenant le programme utilisateur qui va écrire la nouvelle donnée dans le fichier:

#include <stdio.h>

int main()
{
	unsigned int entier = 2009;
	FILE *fp;
	fp = fopen("/debug/repertoire_test/fichier_test","w");
	fprintf(fp, "%u", entier);
	fclose(fp);
	return 0;
}

Bien évidemment je vous ai encore concocté une petite archive pour tester ça. Vous pouvez la télécharger ici.
On y va (n'oubliez de monter un repertoire debugfs dans /debug si vous avez perdu celui d'avant):

C'est fini!

Et voilà, nous avons vu comment utiliser basiquement debugfs. Néanmoins, vous l'aurez peut être remarqué, nous n'avons pu exporter qu'un simple entier à l'utilisateur. Comment faire pour lui exporter des données plus complexes?
Et puis c'est pas très élégant cette solution pour que l'utilisateur puisse m'envoyer une donnée: il faut que je boucle en attendant un événement, en plus ici pour l'exemple je boucle dans la fonction d'initiation => horrible. Non vraiment il y a mieux, la suite au prochain épisode avec l'utilisation avancée de debugfs.

Sources