#include // Macros used to mark up functions e.g., __init __exit #include // Core header for loading LKMs into the kernel #include // Contains types, macros, functions for the kernel #include // Prototypes static int deranger_chatlumot(struct inode *, struct file *); static int laisser_chatlumot_tranquille(struct inode *, struct file *); ssize_t lire_chatlumot(struct file *, char *, size_t, loff_t *); ssize_t ecrire_chatlumot(struct file *, const char *, size_t, loff_t *); // Variables globales int majeur = 0; // Numéro de majeur du périphérique int chatlumot_libre = 1; // Protection contre les ouvertures multiples du périphérique struct file* fichier_serie; // Structure contenant l'ouverture du fichier /dev/xxx du chatlumot char message[] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r', ' ', 'l', 'e', ' ', 'm', 'o', 'n', 'd', 'e' }; // Message du chatlumot, 16 caractères mm_segment_t fs; // Contient le fs, permettant le passage du kernel space memory au user space memory // Informations sur le module MODULE_LICENSE("GPL"); // La licence influence le "runtime behaviour" MODULE_AUTHOR("Quentin BOUTEILLER"); MODULE_DESCRIPTION("Module contrôleur du chatlumot."); // Affiché dans modinfo MODULE_VERSION("1.0"); // Version du module // Définissons un premier paramètre : le nom du chatlumot static char *name = "chalut"; // Valeur par défaut module_param(name, charp, S_IRUGO); // Description du paramètre : charp = char ptr, S_IRUGO = peut être lu, mais pas changé (read-only) MODULE_PARM_DESC(name, "Nom du chatlumot"); // Descripteur du paramètre, tel qu'écrit dans /var/log/kern.log // Définissons un second paramètre : le chemin /dev/xxx vers le chatlumot static char *devsource = "/dev/ttyACM0"; // Valeur par défaut module_param(devsource, charp, S_IRUGO); // Description du paramètre : charp = char ptr, S_IRUGO = peut être lu, mais pas changé (read-only) MODULE_PARM_DESC(devsource, "Périphérique sériel à utiliser"); // Descripteur du paramètre, tel qu'écrit dans /var/log/kern.log // Fonction appelée à l'initialisation du module // Le mot-clé static réduit la visibilité à ce seul fichier C // __init est une macro : elle indique, lorsque le module est intégré au noyau (et pas compilé comme module dé-chargeable à la volée), // que sa mémoire pourra être libérée juste après l'exécution de la fonction // Retournera 0 si tout s'est bien passé static int __init naissanceDuChatlumot(void) { // On logue la naissance dans les logs du noyau printk(KERN_INFO "Chatlumot (%s): initialisation du chatlumot", name); // On créé un périphérique type caractère pour contrôler l'affichage // Création d'une structure pour déclarer les opérations possibles sur le périphérique static struct file_operations fops_chatlumot = { .owner = THIS_MODULE, // Macro permettant de créer un pointeur vers ce module en mémoire .open = deranger_chatlumot, // Pointeur vers la fonction appelée à l'ouverture du fichier périphérique type bloc .release = laisser_chatlumot_tranquille, // Pointeur vers la fonction appelée à la fermeture du fichier périphérique type bloc .read = lire_chatlumot, // Pointeur vers la fonction appelée à la lecture du périphérique .write = ecrire_chatlumot, // Pointeur vers la fonction appellée à l'écriture du périphérique }; // Enregistrement du device // 0 = allocation automatique d'un numéro de majeur (classe du périphérique : lecteur de disquette, disque dur sata, autre, etc...) // chatlumot = nom du périphérique, interne au noyau (n'influe pas sur son chemin /dev), apparaîtra dans /proc/devices int resultat = register_chrdev(0, "chatlumot", &fops_chatlumot); // Un enregistrement réussi renverra son numéro de majeur (ici zéro) // S'il est négatif, c'est un échec if(resultat >= 0) { // On sauvegarde notre majeur majeur = resultat; // Et on affiche notre message de succès printk(KERN_INFO "Chatlumot (%s): périphérique bien enregistré", name); printk(KERN_INFO "Chatlumot (%s): numéro de majeur : %d", name, majeur); printk(KERN_INFO "Chatlumot (%s): pour créer le périphérique, merci d'exécuter 'sudo mknod /dev/chatlumot c %d 0'", name, majeur); } else { printk(KERN_WARNING "Chatlumot (%s): meh, impossible d'enregistrer le périphérique", name); return resultat; } // On indique le succès return 0; } // Fonction de nettoyage (cleanup) // la macro __exit a le même rôle que pour l'init // Ici pas besoin de valeur de retour static void __exit mortDuChatlumot(void) { // On logue la mort dans les logs du noyau printk(KERN_INFO "Chatlumot (%s): déchargement du chatlumot", name); // On décharge le périphérique précédemment enregistré unregister_chrdev(majeur, "chatlumot"); } // Fonction appelée à l'ouverture du fichier périphérique static int deranger_chatlumot(struct inode *inode, struct file *fichier) { // On empêche chatlumot d'être ouvert plusieurs fois if (chatlumot_libre == 0) { printk(KERN_WARNING "Chatlumot (%s): on ne peut pas déranger chatlumot plusieurs fois !", name); return -EBUSY; } // Chalumot n'est plus libre chatlumot_libre = 0; printk(KERN_INFO "Chatlumot (%s): on dérange chatlumot !", name); // On ouvre le port série du chatlutmot fichier_serie = filp_open(devsource, O_RDWR | O_NOCTTY | O_NDELAY, 0644); // Si c'est raté if(IS_ERR(fichier_serie)) { printk(KERN_ALERT "Chatlumot (%s): impossible de communiquer avec le chatlutmot sur %s !", name, devsource); chatlumot_libre = 1; return -1; } // Succès ! return 0; } // Fonction appelée à la fermeture du fichier périphérique static int laisser_chatlumot_tranquille(struct inode *inode, struct file *fichier) { // Chalumot est à nouveau libre chatlumot_libre = 1; printk(KERN_INFO "Chatlumot (%s): je suis libre à nouveau !", name); // On relache le fichier, dans le contexte de l'user space filp_close(fichier_serie, NULL); // Succès return 0; } // Fonction appelée à la lecture du périphérique, après ouverture ssize_t lire_chatlumot(struct file *fichier, char *buffer, size_t taille_buffer, loff_t *offset_fichier) { // Nombre d'octets de notre buffer ssize_t taille_lue = 0; // Si nous n'avons rien à écrire if (sizeof(message) <= *offset_fichier) { taille_lue = 0; printk(KERN_WARNING "Chatlumot (%s): lecture impossible (rien à lire) !", name); } else { // On tente d'inscrire le texte dans if (copy_to_user(buffer, message + *offset_fichier, sizeof(message))) { // On retourne une erreur ici aussi taille_lue = -EFAULT; printk(KERN_WARNING "Chatlumot (%s): lecture impossible (problème avec copy_to_user) !", name); } else { // Sinon on retourne la taille écrite taille_lue = taille_buffer; printk(KERN_INFO "Chatlumot (%s): lecture réussie !", name); // On décale l'offset *offset_fichier += taille_lue; } } // Par convention, on retourne le nombre d'octets lus return taille_lue; } // Fonction appelée à l'écriture du périphérique, après ouverture ssize_t ecrire_chatlumot(struct file *fichier, const char *buffer, size_t taille_buffer, loff_t *offset_fichier) { // Taille écrite ssize_t taille_ecrite; printk(KERN_INFO "Chatlumot (%s): demande d'écriture d'une taille %zu et d'un offset %lld !", name, taille_buffer, (long long)*offset_fichier); // Si nous n'avons rien à écrire if (sizeof(message) <= *offset_fichier) { taille_ecrite = 0; printk(KERN_WARNING "Chatlumot (%s): écriture impossible (rien à écrire) !", name); } else { // Si nous risquons le buffer overflow, on retourne ENOSPC if (sizeof(message) - (size_t)*offset_fichier < taille_buffer) { taille_ecrite = -ENOSPC; printk(KERN_WARNING "Chatlumot (%s): écriture impossible (risque de buffer overflow) !", name); } else { // On tente d'inscrire le texte dans if (copy_from_user(message + *offset_fichier, buffer, taille_buffer)) { // On retourne une erreur ici aussi taille_ecrite = -EFAULT; printk(KERN_WARNING "Chatlumot (%s): écriture impossible (problème avec copy_from_user) !", name); } else { // Sinon on retourne la taille écrite taille_ecrite = taille_buffer; printk(KERN_INFO "Chatlumot (%s): écriture réussie : %.*s", name, (int)taille_buffer, message + *offset_fichier); // On décale l'offset *offset_fichier += taille_ecrite; // Puis on écrit notre message dans le port série du chatlumot, dans un contexte user space fs = get_fs(); set_fs(KERNEL_DS); fichier_serie->f_op->write(fichier_serie, message, sizeof(message), &fichier_serie->f_pos); set_fs(fs); } } } // On retourne le nombre d'octets écrits return taille_ecrite; } // Identification des fonctions d'initialisation et de déchargement module_init(naissanceDuChatlumot); module_exit(mortDuChatlumot);