1
0
Chat-lu-mot/chatlumot.c

241 lines
9.6 KiB
C

#include <linux/init.h> // Macros used to mark up functions e.g., __init __exit
#include <linux/module.h> // Core header for loading LKMs into the kernel
#include <linux/kernel.h> // Contains types, macros, functions for the kernel
#include <linux/syscalls.h>
// 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);