248 lines
9.7 KiB
C
248 lines
9.7 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 max
|
|
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
|
|
{
|
|
// On reset notre tableau de char
|
|
int i;
|
|
for(i = 0; i < sizeof(message); i++)
|
|
{
|
|
message[i] = '\0';
|
|
}
|
|
|
|
// 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); |