Genèse d’un pilote Linux (Part3)

Nous voici dans l’écriture proprement dite du driver. Comme expliqué
auparavant, nous allons nous inspirer du driver du ds1374. La stratégie
consiste à copier/coller le code rtc-ds1374.c puis en modifier le code:

$ cp linux-2.6.38.8/drivers/rtc/rtc-ds1374.c
linux-2.6.38.8/drivers/rtc/rtc-mcp7940x.c 

Puis chercher/remplacer tous les ds1374 par mcp7940x dans
le fichier rtc-mcp7940x.c nouvellement créé. Ce qui se fait dans vim par la
commande :%s/ds1374/mcp7940x/g

Une fois cela fait on va pouvoir commencer à adapter notre driver. 

Notre chip mcp7940x est un composant qui se connecte sur le bus I²C pour
«exporter» une interface d’horloge nommé RTC. Le rôle du driver est donc de
faire le liens entre le bus i²c et la RTC. On peut résumer cela visuellement
avec l’image suivante

Dessin représentant le driver du mcp79400

On commence toujours la lecture d’un driver par la fin, passons sur le nom
de l’auteur et la licence. Et intéressons nous à la connexion au bus.

On charge le driver en le connectant au bus i²c, pour se faire, on utilise
la structure i2c_driver que l’on ajoute sur le bus au moment du
chargement du driver:


static int __init mcp7940x_init(void)
{
	return i2c_add_driver(&mcp7940x_driver);
}

Structure que l’on supprime au déchargement du driver bien évidemment:


static void __exit mcp7940x_exit(void)
{
	i2c_del_driver(&mcp7940x_driver);
}

Cette structure déclare un certain nombre de fonctions et de structures
propres au mcp7940x:


static struct i2c_driver mcp7940x_driver = {
	.driver = {
		.name = "rtc-mcp7940x",
		.owner = THIS_MODULE,
	},
	.probe = mcp7940x_probe,
	.suspend = mcp7940x_suspend,
	.resume = mcp7940x_resume,
	.remove = __devexit_p(mcp7940x_remove),
	.id_table = mcp7940x_id,
};

Ce qui nous intéresse particulièrement pour l’instant c’est le
probe et le driver.name. En effet c’est tout
simplement le point d’entrée de notre driver. Une fois le driver chargé dans
le kernel, Linux observe le bus i²c et guette le nom des périphériques (struct
devices) que l’on charge dessus. Dès qu’un périphériques avec le nom
« rtc-mcp7940x » se pointe, le kernel appel la fonction de probe
mcp7940x_probe.

La structure de description du périphérique présent physiquement sur le bus
est quelques chose qui doit être chargé dans le noyau. En règle générale les
périphériques du bus i²c ne sont pas plug&play et sont donc présent sur la
carte électronique dès le démarrage du kernel.

C’est pour cette raison qu’on a l’habitude de charger les devices dans le
fichier dit de «plate-forme». Dans le cas de l’apf51dev, ce fichier de
plate-forme se nomme apf51dev-baseboard.c et se trouve dans le
répertoire

buildroot/output/build/linux-2.6.38.8/arch/arm/mach-mx5/

Dans ce fichier, le device se déclare simplement avec sont adresse sur le
bus, et son nom bien sur:


static struct i2c_board_info apf51dev_i2c2_devices[] __initdata = {
	{
		I2C_BOARD_INFO("mcp79400", 0x6f),
	},
};

Revenons à notre probe

 static int mcp7940x_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct mcp7940x *mcp7940x;
	int ret;

	mcp7940x = kzalloc(sizeof(struct mcp7940x), GFP_KERNEL);
	if (!mcp7940x)
		return -ENOMEM;

C’est la fonction d’initialisation de notre driver. C’est dans cette
fonction que l’on va démarrer le mcp7940x et allouer la mémoire pour la
structure mcp7940x. C’est aussi dans cette fonction que l’on va rattacher
notre driver à l’interface RTC.

L’interface Linux pour la RTC est extrêmement simpliste dans notre cas. En
effet on cherche juste à lire l’heure et la date dans le composant et à
l’écrire (Le mcp7940x à plein d’autre fonctionnalités mais mon besoin n’est
que l’heure). Nous n’aurons donc que les fonctions read_time et
set_time à écrire dans la structure rtc_class_ops
du driver:


static const struct rtc_class_ops mcp7940x_rtc_ops = {
	.read_time = mcp7940x_read_time,
	.set_time = mcp7940x_set_time,
};

Structure que l’on enregistre à la fin de la fonction probe:


	mcp7940x->rtc = rtc_device_register(client->name, &client->dev,
	                                  &mcp7940x_rtc_ops, THIS_MODULE);
	if (IS_ERR(mcp7940x->rtc)) {
		ret = PTR_ERR(mcp7940x->rtc);
		dev_err(&client->dev, "unable to register the class device\n");
		goto out_free;
	}

L’écriture de la fonction read_time se résume ensuite à lire
les valeurs des registres du composant au moyen de la fonction
i2c_smbus_read_byte_data() et de renseigner la structure
rtc_time
passée en paramètre.

Et pour la fonction set_time on fait l’inverse. On récupère
les valeurs se trouvant dans la structure rtc_time
passée en paramètre et on écrit les valeurs dans le composant au moyen de la
fonction i2c_smbus_write_byte_data().

Et voila !

En réalité le driver n’est que partiellement écrit vu que le composant
dispose de bien plus de fonctionnalités que simplement lire/écrire l’heure.
Le driver pourrait notament être étendu pour gérer les deux alarmes, la
gestion du signal de clock de sortie et surtout la SRAM, qui permet de stocker
des variables quand l’appareil est éteint.

Et non ça n’est pas terminé ! Il reste encore à publier ce driver pour
armadeus, et plus (mainline kernel) si affinité !

Maintenant publions le tout

Maintenant que nous avons écrit notre driver, l’objectif va être de le publier sur le git Armadeus. Pour cela il faut que nous finalisions le patch que nous avions commencé dans la partie 2 de cet article. Si tout a bien été fait dans la partie 2 il n’y a plus qu’à rafraichir le patch quilt:

$ cd buildroot/output/build/linux-2.6.38.8/
$ quilt refresh
Refreshed patch 450-armadeus-add_mcp7940x_rtc_driver.patch

Puis modifier l'entête du patch pour y mettre son nom (pour qu'on connaisse le coupable ;):


$ vim patches/450-armadeus-add_mcp7940x_rtc_driver.patch

et


Add mcp79400 Linux driver

Signed-off-by: Fabien Marteau 

---

Index: linux-2.6.38.8/drivers/rtc/Kconfig
...

Notre patch est prêt, on peut maintenant le proposer à la communauté Armadeus en le postant sur la liste de diffusion
armadeus-forum@lists.sourceforge.net.

Bon j'avoue, vu que j'ai les droits sur le git je l'ai commité directement 😉 Mais, pour ceux qui n'ont pas les droits, c'est la procédure qu'il faudrait respecter. Du coup pour voir le code complet du driver c'est par

Ce contenu a été publié dans embarqué, informatique, kernel. Vous pouvez le mettre en favoris avec ce permalien.

2 réponses à Genèse d’un pilote Linux (Part3)

  1. samuel dit :

    Hi!

    Ceci commence à bien m’éclairer dans mon exploration d’une extension du driver rtc_pcf8563.

    Je suis cependant un peu surpris de voir dans votre écriture du driver pour les fonctions :

    static int mcp7940x_read_time(...);
    static int mcp7940x_set_time(...);

    l’usage des fonctions:

    i2c_smbus_read_byte_data()
    i2c_smbus_write_byte_data()

    au lieu de:

    i2c_smbus_read_i2c_block_data()
    i2c_smbus_write_i2c_block_data()

    En ayant fouillé dans les doc, la lecture/écriture de l’heure dans les RTC devrait se faire de façon protégé en en indiquant à la puce de « bloquer » son compteur de temps avant d’effectuer une opération de lecture/écriture, puis « relâcher » le compteur de temps à la fin de l’opération.
    Ceci à pour but d’éviter une mise à jour de l’heure par la puce entre deux instructions de lecture/écriture et éviter une corruption du type j’écris les secondes minutes à 23h59:59 et avec le décalage l’écriture du jour à lieu à 0h00:00 … ce qui produit un retour dans le passé de 24h.

    A moins que la puce mcp7940x soit très différente dans son comportement que la pcf8563 que j’étudie actuellement.

    merci,

    Samuel

  2. admin dit :

    Salut Samuel,

    Le problème du blocage de l’heure au moment de l’écriture est aussi présent dans le mcp79400. Mais j’avoue l’avoir laisser de coté pour le moment car dans mon application l’appareil n’est allumé qu’en journée. Donc il ne peut pas écrire à cette heure là 😉

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *