Aller au contenu

Modules noyaux

Exercice #1: Générez un module noyau out of tree pour la cible NanoPi :

  1. Créez le squelette d’un module noyau et générez-le en dehors des sources du noyau à l’aide d’un Makefile. Le module devra afficher un message lors de son enregistrement et lors de sa désinstallation.
  2. Testez sur la machine hôte la commande modinfo1 sur votre squelette de module et comparez les informations retournées avec celles du code source.
  3. Installez le module (insmod) et contrôlez le log du noyau (dmesg)
  4. Comparez les résultats obtenus par la commande lsmod avec ceux obtenus avec la commande cat /proc/modules
  5. Désinstallez le module (rmmod).
  6. Adaptez le Makefile du module pour autoriser l’installation du module avec les autres modules du noyau permettant l’utilisation de la commande modprobe. Le module devra être installé dans le root filesystem utilisé en cifs par la cible.

Exercice #2: Adaptez le module de l’exercice précédent afin qu’il puisse recevoir deux ou trois paramètres de votre choix. Ces paramètres seront affichés dans la console. Adaptez également le rootfs afin de pouvoir utiliser la commande modprobe.

Solution

/workspace/src/kernel_settings
CVER     := aarch64-buildroot-linux-gnu-
KVER     := 5.15.148
CPU      := arm64

KDIR     := /buildroot/output/build/linux-$(KVER)/
TOOLS    := /buildroot/output/host/usr/bin/$(CVER)
MODPATH  := /rootfs
#MODPATH := /buildroot/output/target
/workspace/src/02_modules/exercice01/Makefile
export PATH := /buildroot/output/host/usr/sbin$\
    :/buildroot/output/host/usr/bin/$\
    :/buildroot/output/host/sbin$\
    :/buildroot/output/host/bin/$\
    :$(PATH)

# Part executed when called from kernel build system:
ifneq ($(KERNELRELEASE),)
obj-m += mymodule.o     ## name of the generated module

mymodule-objs := skeleton.o ## list of objects needed for that module
CFLAGS_skeleton.o := -DDEBUG

# Part executed when called from standard make in module source directory:
else
include ../../kernel_settings
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(CPU) CROSS_COMPILE=$(TOOLS) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

install:
    $(MAKE) -C $(KDIR) M=$(PWD) INSTALL_MOD_PATH=$(MODPATH) modules_install

endif
/workspace/src/02_modules/exercice01/skeleton.c
// skeleton.c
#include <linux/module.h>   // needed by all modules
#include <linux/init.h>     // needed for macros
#include <linux/kernel.h>   // needed for debugging

#include <linux/moduleparam.h>  // needed for module parameters

static char* text = "dummy text";
module_param(text, charp, 0664);
static int  elements = 1;
module_param(elements, int, 0);

static int __init skeleton_init(void)
{
    pr_info ("Linux module 01 skeleton loaded\n");
    pr_debug ("  text: %s\n  elements: %d\n", text, elements);
    return 0;
}

static void __exit skeleton_exit(void)
{
    pr_info ("Linux module skeleton unloaded\n");
}

module_init (skeleton_init);
module_exit (skeleton_exit);

MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");

Exercice #3: Trouvez la signification des 4 valeurs affichées lorsque l’on tape la commande cat /proc/sys/kernel/printk

Gestion de la mémoire, bibliothèque et fonction utile

Exercice #4: Créez dynamiquement des éléments dans le noyau. Adaptez un module noyau, afin que l’on puisse lors de son installation spécifier un nombre d’éléments à créer ainsi qu’un texte initial à stocker dans les éléments précédemment alloués. Chaque élément contiendra également un numéro unique. Les éléments seront créés lors de l’installation du module et chaînés dans l’une liste. Ces éléments seront détruits lors de la désinstallation du module. Des messages d’information seront émis afin de permettre le debugging du module.

Solution

/workspace/src/02_modules/exercice04/Makefile
export PATH := /buildroot/output/host/usr/sbin$\
    :/buildroot/output/host/usr/bin/$\
    :/buildroot/output/host/sbin$\
    :/buildroot/output/host/bin/$\
    :$(PATH)

# Part executed when called from kernel build system:
ifneq ($(KERNELRELEASE),)
obj-m += mymodule.o     ## name of the generated module

mymodule-objs := skeleton.o ## list of objects needed for that module

# Part executed when called from standard make in module source directory:
else
include ../../kernel_settings
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(CPU) CROSS_COMPILE=$(TOOLS) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

install:
    $(MAKE) -C $(KDIR) M=$(PWD) INSTALL_MOD_PATH=$(MODPATH) modules_install

endif
/workspace/src/02_modules/exercice04/skeleton.c
/* skeleton.c */
#include <linux/module.h>   /* needed by all modules */
#include <linux/init.h>     /* needed for macros */
#include <linux/kernel.h>   /* needed for debugging */

#include <linux/moduleparam.h>  /* needed for module parameters */

#include <linux/slab.h>     /* needed for dynamic memory allocation */
#include <linux/list.h>     /* needed for linked list processing */
#include <linux/string.h>   /* needed for string handling */

static char* text = "dummy text";
module_param(text, charp, 0);
static int  elements = 0;
module_param(elements, int, 0);

struct element {
    char text[100];
    int ele_nr;
    struct list_head node;
};

static LIST_HEAD (my_list);

static int __init skeleton_init(void)
{
    int i;
    pr_info ("Linux module 04 skeleton loaded\n");
    pr_info ("  text: %s\n  elements: %d\n", text, elements);
    for (i = 0; i < elements; i++) {
        struct element* ele = kzalloc (sizeof(*ele), GFP_KERNEL);
        if (ele != 0) {
            strncpy (ele->text, text, 99);
            ele->ele_nr = i;
            list_add_tail (&ele->node, &my_list);
        }
    }
    return 0;
}

static void __exit skeleton_exit(void)
{
    struct element* ele;
    int nb_eles = 0;
    list_for_each_entry (ele, &my_list, node) {
        pr_info ("ele [%d/%d/%d] = %s\n", nb_eles, ele->ele_nr, elements, ele->text); 
        nb_eles++;
    }
    while (!list_empty (&my_list)) {
        ele = list_entry (my_list.next, struct element, node);
        list_del (&ele->node);
        kfree (ele);
    }
    pr_info ("All elements (%d/%d) of the list have been removed and deleted!\n", nb_eles, elements);
    pr_info ("Linux module skeleton unloaded\n");
}

module_init (skeleton_init);
module_exit (skeleton_exit);

MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");

Accès aux entrées/sorties

Exercice #5: À l’aide d’un module noyau, afficher le Chip-ID du processeur, la température du CPU et la MAC adresse du contrôleur Ethernet.

  • Les 4 registres de 32 bits du Chip-ID sont aux adresses 0x01c1'4200 à 0x01c1'420c
  • Le registre de 32 bits du senseur de température du CPU est à l’adresse 0x01c2'5080
  • Les 2 registres de 32 bits de la MAC adresse sont aux adresses 0x01c3'0050 et 0x01c3'0054

Pour obtenir la température du CPU, il faut, après lecture du registre, appliquer la formule suivante :

\[ \mathsf{temperature} = -1'191 \cdot \frac{\mathsf{register\, value}}{10} + 223'000\]

Avant d’accéder aux registres du Chip-ID, veuillez réserver la zone mémoire correspondante aux registres du microprocesseur. Validez cette réservation à l’aide de la commande cat /proc/iomem. La commande cat /sys/class/thermal/thermal_zone0/temp permet de valider la bonne lecture de la température. La commande ifconfig permet de valider la bonne lecture de la MAC adresse.

Solution
/workspace/src/02_modules/exercice04/skeleton.c
/* skeleton.c */
#include <linux/module.h>   /* needed by all modules */
#include <linux/init.h>     /* needed for macros */
#include <linux/kernel.h>   /* needed for debugging */

#include <linux/moduleparam.h>  /* needed for module parameters */

#include <linux/slab.h>     /* needed for dynamic memory allocation */
#include <linux/list.h>     /* needed for linked list processing */
#include <linux/string.h>   /* needed for string handling */

#include <linux/ioport.h>   /* needed for memory region handling */
#include <linux/io.h>       /* needed for mmio handling */


static struct resource* res[3]={[0]=0,};

static int __init skeleton_init(void)
{
    unsigned char* regs[3]={[0]=0,};
    unsigned int chipid[4]={[0]=0,};
    long temp = 0;
    unsigned int addr[2] = {[0]=0,};

    pr_info ("Linux module 05 skeleton loaded\n");

    res[0] = request_mem_region (0x01c14000, 0x1000, "allwiner h5 sid");
    //res[1] = request_mem_region (0x01C25000, 0x1000, "allwiner h5 ths");
    //res[2] = request_mem_region (0x01C30000, 0x1000, "allwiner h5 emac");
    if ((res[0] == 0))// || (res[1] == 0) ||(res[2] == 0))
        pr_info ("Error while reserving memory region... [0]=%d, [1]=%d, [2]=%d\n", res[0]==0, res[1]==0, res[2]==0);

    regs[0] = ioremap (0x01c14000, 0x1000);
    regs[1] = ioremap (0x01C25000, 0x1000);
    regs[2] = ioremap (0x01C30000, 0x1000);

    if ((regs[0] == 0) || (regs[1] == 0) ||(regs[2] == 0)) {
        pr_info ("Error while trying to map processor register...\n");
        return -EFAULT;
    }
    chipid[0] = ioread32 (regs[0]+0x200);
    chipid[1] = ioread32 (regs[0]+0x204);
    chipid[2] = ioread32 (regs[0]+0x208);
    chipid[3] = ioread32 (regs[0]+0x20c);
    pr_info("chipid=%08x'%08x'%08x'%08x\n",
        chipid[0], chipid[1], chipid[2], chipid[3]);

    temp = -1191 * (int)ioread32(regs[1]+0x80) / 10 + 223000;
    pr_info ("temperature=%ld (%d)\n", temp, ioread32(regs[1]+0x80));

    addr[0]=ioread32(regs[2]+0x50);
    addr[1]=ioread32(regs[2]+0x54);
    pr_info("mac-addr=%02x:%02x:%02x:%02x:%02x:%02x\n",
            (addr[1]>> 0) & 0xff,
            (addr[1]>> 8) & 0xff,
            (addr[1]>>16) & 0xff,
            (addr[1]>>24) & 0xff,
            (addr[0]>> 0) & 0xff,
            (addr[0]>> 8) & 0xff
    );

    iounmap (regs[0]);
    iounmap (regs[1]);
    iounmap (regs[2]);

    return 0;
}

static void __exit skeleton_exit(void)
{
    pr_info ("Linux module skeleton unloaded\n");
    if (res[0] != 0) release_mem_region (0x01c14000, 0x1000);
    //release_mem_region (0x01C25000, 0x1000);
    //release_mem_region (0x01C30000, 0x1000);
}

module_init (skeleton_init);
module_exit (skeleton_exit);

MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");

Threads du noyau

Exercice #6: Développez un petit module permettant d’instancier un thread dans le noyau. Ce thread affichera un message toutes les 5 secondes. Il pourra être mis en sommeil durant ces 5 secondes à l’aide de la fonction ssleep(5) provenant de l’interface <linux/delay.h>.

Solution
/workspace/src/exercice04/skeleton.c
/* skeleton.c */
#include <linux/module.h>   /* needed by all modules */
#include <linux/init.h>     /* needed for macros */
#include <linux/kernel.h>   /* needed for debugging */

#include <linux/moduleparam.h>  /* needed for module parameters */

#include <linux/slab.h>     /* needed for dynamic memory allocation */
#include <linux/list.h>     /* needed for linked list processing */
#include <linux/string.h>   /* needed for string handling */

#include <linux/ioport.h>   /* needed for memory region handling */
#include <linux/io.h>       /* needed for mmio handling */

#include <linux/kthread.h>  /* needed for kernel thread management */
#include <linux/delay.h>    /* needed for delay fonctions */


static struct task_struct* my_thread;


static int skeleton_thread (void* data)
{
    pr_info ("skeleton thread is now active...\n");
    while(!kthread_should_stop()) {
        ssleep (5);
        pr_info ("skeleton thread is kick every 5 seconds...\n");
    }
    return 0;
}


static int __init skeleton_init(void)
{
    pr_info ("Linux module 06 skeleton loaded\n");

    my_thread = kthread_run (skeleton_thread, 0, "s/thread");

    return 0;
}

static void __exit skeleton_exit(void)
{
    kthread_stop (my_thread);

    pr_info ("Linux module skeleton unloaded\n");
}

module_init (skeleton_init);
module_exit (skeleton_exit);

MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");

Mise en sommeil

Exercice #7: Développez un petit module permettant d’instancier deux threads dans le noyau. Le premier thread attendra une notification de réveil du deuxième thread et se remettra en sommeil. Le 2ème thread enverra cette notification toutes les 5 secondes et se rendormira. On utilisera les waitqueues pour les mises en sommeil. Afin de permettre le debugging du module, chaque thread affichera un petit message à chaque réveil.

Solution
/workspace/src/exercice04/skeleton.c
/* skeleton.c */
#include <linux/module.h>   /* needed by all modules */
#include <linux/init.h>     /* needed for macros */
#include <linux/kernel.h>   /* needed for debugging */

#include <linux/kthread.h>  /* needed for kernel thread management */
#include <linux/wait.h>     /* needed for waitqueues handling */
#include <linux/delay.h>    /* needed for delay fonctions */


static struct task_struct* my_thread[2];

DECLARE_WAIT_QUEUE_HEAD (queue_1);
static atomic_t is_kicked;

static int skeleton_thread_1 (void* data)
{
    pr_info ("skeleton thread_1 is now active...\n");
    while(!kthread_should_stop()) {
        int status = wait_event_interruptible 
            (queue_1, (atomic_read(&is_kicked) != 0) 
                    || kthread_should_stop());
        if (status == -ERESTARTSYS) {
            pr_info ("skeleton thread_1 has been interrupted\n");
            break;
        }
        atomic_dec (&is_kicked);
        pr_info ("skeleton thread_1 has been kicked\n");
    }
    return 0;
}

static int skeleton_thread_2 (void* data)
{
    wait_queue_head_t queue;
    pr_info ("skeleton thread_2 is now active...\n");
    init_waitqueue_head (&queue);
    while(!kthread_should_stop()) {
        ssleep(5);
/*
        int status = wait_event_interruptible_timeout 
            (queue, kthread_should_stop(), 5*HZ);
        if (status == -ERESTARTSYS) {
            pr_info ("skeleton thread_2 has been interrupted\n");
            break;
        }
*/
        pr_info ("skeleton thread_2 timout elapsed...\n");
        atomic_set (&is_kicked, 1);
        wake_up_interruptible (&queue_1);
    }
    return 0;
}


static int __init skeleton_init(void)
{
    pr_info ("Linux module 07 skeleton loaded\n");

    atomic_set (&is_kicked, 0);
    my_thread[0] = kthread_run (skeleton_thread_1, 0, "s/thread/%d", 1);
    my_thread[1] = kthread_run (skeleton_thread_2, 0, "s/thread/2");

    return 0;
}

static void __exit skeleton_exit(void)
{
    kthread_stop (my_thread[1]);
    kthread_stop (my_thread[0]);

    pr_info ("Linux module skeleton unloaded\n");
}

module_init (skeleton_init);
module_exit (skeleton_exit);

MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");

Interruptions

Exercice #8: Développez un petit module permettant de capturer les pressions exercées sur les switches de la carte d’extension par interruption. Afin de permettre le debugging du module, chaque capture affichera un petit message.

Quelques informations pour la réalisation du module :

  1. Acquérir la porte GPIO avec le service gpio_request (<io_nr>, <label>);
  2. Obtenir le vecteur d’interruption avec le service gpio_to_irq (<io_nr>);
  3. Informations sur les switches de la carte d’extension
    • k1 - gpio: A, pin_nr=0, io_nr=0
    • k2 - gpio: A, pin_nr=2, io_nr=2
    • k3 - gpio: A, pin_nr=3, io_nr=3
Solution
/workspace/src/exercice08/skeleton.c
/* skeleton.c */
#include <linux/module.h>   /* needed by all modules */
#include <linux/init.h>     /* needed for macros */
#include <linux/kernel.h>   /* needed for debugging */

#include <linux/interrupt.h>    /* needed for interrupt handling */
#include <linux/gpio.h>         /* needed for i/o handling */


#define K1  0
#define K2  2
#define K3  3

static char* k1="gpio_a.0-k1";
static char* k2="gpio_a.2-k2";
static char* k3="gpio_a.3-k3";


irqreturn_t gpio_isr(int irq, void* handle)
{
    pr_info ("interrupt %s raised...\n", (char*)handle);

    return IRQ_HANDLED;
}


static int __init skeleton_init(void)
{
    int status = 0;

    // install k1
    if (status == 0) status = gpio_request (K1, "k1");
    if (status == 0)
        status = request_irq(gpio_to_irq(K1), gpio_isr,
            IRQF_TRIGGER_FALLING | IRQF_SHARED, k1, k1);

    // install k2
    if (status == 0) status = gpio_request (K2, "k2");
    if (status == 0)
        status = request_irq(gpio_to_irq(K2), gpio_isr,
            IRQF_TRIGGER_FALLING | IRQF_SHARED, k2, k2);

    // install k3
    if (status == 0) status = gpio_request (K3, "k3");
    if (status == 0)
        status = request_irq(gpio_to_irq(K3), gpio_isr,
            IRQF_TRIGGER_FALLING | IRQF_SHARED, k3, k3);

    pr_info ("Linux module 08 skeleton loaded\n");

    return status;
}

static void __exit skeleton_exit(void)
{
    gpio_free(K1);
    free_irq(gpio_to_irq(K1), k1);

    gpio_free(K2);
    free_irq(gpio_to_irq(K2), k2);

    gpio_free(K3);
    free_irq(gpio_to_irq(K3), k3);

    pr_info ("Linux module skeleton unloaded\n");
}

module_init (skeleton_init);
module_exit (skeleton_exit);

MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");

Archives 2021/2022


  1. Pour installer modinfo, ajoutez le package “kmod utilities” : Target PackagesSystem Toolskmod et kmod utilities. Vous pouvez ensuite mettre à jour le root file system avec la commande extract-rootfs.sh, mais attention, vous allez remplacer des fichiers tels que /etc/fstab. Sauvegardez vos fichiers importants!