Files
LinuxEmbarque/tp6.md

35 KiB
Raw Permalink Blame History

Travail pratique 6

Objectif

Le but du TP est de pouvoir commencer à utiliser les GPIO en jouant avec une carte MyLab1. Ensuite l'idée est de faire notre propre module driver pour notre kernel embarqué qui s'occupe de gèrer le joystick et pouvoir l'utiliser ensuite dans un jeu.

Connectique...

Bon c'est un peu le bazard cette histoire, la carte myLab a des connecteurs un peu fébriles ou du moins pas très compatibles avec les cables dupont que l'on a et on a très peu d'informations sur le pcb pour se retrouver ce qui est très frustrant.

Pinout sama5d3

Voici tous les ports que on peut utiliser sur notre carte:

PA16, PA17, PA21, PA23, PB15, PB25, PB26, PB28, PB29, PC9, PC16, PC17, PC22, PC23, PC24, PC28, PC29, PE31

Nous allons utiliser les pins PA16 PA17 PA21 PA23 et PB27 car ils sont plutôt proches sur la carte et ils groupés et pour GND et +3.3V on va aller les chercher sur un autre endroit de la carte car c'est plus simple.

Voici un tableau des ports

Device Mylab Sama5d3
Up 2.24 PA16
Down 2.26 PA17
Left 2.25 PA21
Right 1.27 PB27
Button 2.23 PA23
+3.3V 2.1 +3.3v
GND 1.1 GND

En suivant ce tableau tout le long du tableau on devrait eviter toute confusion de câbles.

C'est un peu la galère pour tout faire tenir mais bon normalement ca passe.

librairie libgpiod

On peut voir dans le cours que les gpios sont visibles sur notre board dans /sys/class/gpio

/sys/class/gpio # ls
export       gpiochip128  gpiochip64   unexport
gpiochip0    gpiochip32   gpiochip96

On a bien nos 5 chips GPIO qui exposent nos 160 pins.

Mais depuis le kernel 4.8 on doit utiliser l'API GPIO et le code du projet libgpio se trouve sous "https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/"

Si on regarde dans /dev on voit bien nos controlleurs gpio également :

/dev # ls -a | grep gpio
gpiochip0
gpiochip1
gpiochip2
gpiochip3
gpiochip4

Et on voit que ils n'ont pas le même nom. Si on se fie au tableau trouvé dans le cours on peut directement voir quel controlleur utilise quels pins parmis ceux que on utilise :

Ports Controlleur
PA16 gpiochip0
PA17 gpiochip0
PA21 gpiochip0
PA23 gpiochip0
PB27 gpiochip1

On peut récupèrer les sources de la librairie gpiod avec git clone https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git si on utilise l'HTTPS

Pour pouvoir build cette lib on va avoir besoin d'outils que je vais installer sur la vm de compilation croisée.

sudo apt install dh-autoreconf sudo apt install autoconf-archive

Je vais créer un repertoire de destination pour le build de la lib /home/moi/tp/lib/compiledLib

Ensuite on peut lancer les commandes :

git checkout v2.1.3

./autogen.sh --enable-tools=yes

Mais je me retrouve avec cette erreur

./autogen.sh --enable-tools=yes
autoreconf: export WARNINGS=
autoreconf: Entering directory '.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force -I m4
autoreconf: configure.ac: tracing
autoreconf: running: libtoolize --copy --force
libtoolize: putting auxiliary files in AC_CONFIG_AUX_DIR, 'autostuff'.
libtoolize: copying file 'autostuff/ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
libtoolize: copying file 'm4/libtool.m4'
libtoolize: copying file 'm4/ltoptions.m4'
libtoolize: copying file 'm4/ltsugar.m4'
libtoolize: copying file 'm4/ltversion.m4'
libtoolize: copying file 'm4/lt~obsolete.m4'
autoreconf: configure.ac: not using Intltool
autoreconf: configure.ac: not using Gtkdoc
autoreconf: running: aclocal --force -I m4
autoreconf: running: /usr/bin/autoconf --force
configure.ac:88: error: possibly undefined macro: AC_CHECK_HEADERS
      If this token and others are legitimate, please use m4_pattern_allow.
      See the Autoconf documentation.
configure.ac:213: error: possibly undefined macro: AC_LANG_PUSH
configure.ac:216: error: possibly undefined macro: AC_LANG_POP
autoreconf: error: /usr/bin/autoconf failed with exit status: 1

J'avais besoin de cette librairie pour faire fonctionner la commande

sudo apt install pkg-config

Et ensuite on peut faire la suite

./configure --prefix=/home/moi/tp/lib/compiledLib --host=arm-linux --enable-tools

Ensuite on peut comnpiler la lib

make

Après avoir compilé on peut installer la lib ce qui devrait installer les fichiers dans compiledLib

make install

Si on retourne dans notre repertoire nouvellement créé "compiledLib" et dedans on peut voir une architecture que l'on connait déja un peu de librairies.

Dans le sous repertoire /lib on peut retrouver ces fichiers

libgpiod.a libgpiod.la libgpiod.so libgpiod.so.3 libgpiod.so.3.1.1 pkgconfig

On va prendre tous les fichiers en .so sauf ligpio.so et les envoyer sur notre nfsroot sous /lib exactement comme dans le tp4 et la libc.

A partir de la on devrait pouvoir créer des programmes c qui utilisent la librairie GPIO.

Quand on veut le compiler

arm-buildroot-linux-musleabi-gcc testGpio.c -o gpio Mais ca ne marche pas car notre machine de compilation croisée ne connait pas la lib gpio

Pour le developpement on a besoin du libgpiod.so, du gpiod.h et potentiellement le libgpio.a si on fait de la compilation statique.

-l = nom de la librairie "gpiod" -I = chemin vers le .h "/home/moi/tp/lib/compiledLib/include/gpiod.h" -L = chemin vers le .so "/home/moi/tp/lib/compiledLib/lib/libgpiod.so"

arm-buildroot-linux-musleabi-gcc testGpio.c -o gpio -I /home/moi/tp/lib/compiledLib/include/ -lgpiod -L /home/moi/tp/lib/compiledLib/lib/

### Lecture du joystick via les outils libgpiod

Voici le premier bout de code qui permet de simplement de lister tous les devices GPIO sur la board embarquée :

#include <gpiod.h>
#include <stdio.h>

int main(void) {
    struct gpiod_chip *chip;
    char chip_name[32];
    int i = 0;

    printf("GPIO Chips on the system:\n");

    //Ouais c'est moi qui ai fait tout ce code t'inquiète
    // Iterate over possible chip names (gpiochip0, gpiochip1, etc.)
    for (i = 0; i < 10; i++) {
        snprintf(chip_name, sizeof(chip_name), "/dev/gpiochip%d", i);
        chip = gpiod_chip_open(chip_name);
        if (!chip)
            continue; // Skip if chip doesn't exist

        printf("- %s\n", chip_name);
        gpiod_chip_close(chip);
    }

    return 0;
}

Après avoir tftp le fichier sur la machine hôte du nfsroot et avoir changé les permissions pour permettre l'execution "chmod +x executable" on peut lancer le programme depuis notre système embarqué et on obtient ce resultat :

GPIO Chips on the system:
- /dev/gpiochip0
- /dev/gpiochip1
- /dev/gpiochip2
- /dev/gpiochip3
- /dev/gpiochip4

On pourra utiliser ces strings quand on voudra parler des différents GPIO dans la suite de nos codes.

On a deux features à implementer dans un premier temps :

  1. afficher en boucle l'étât du joystick (ex "haut", "bas", "gauche", "droite")
  2. Quand le bouton du joystick est pressé ca ferme le programme.

Voici un exemple de code que j'ai trouvé sur le git de gpiod et que j'ai très légèrement modifié pour qu'il ecrive en boucle la valeur du joystick up

// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2023 Kent Gibson <warthog618@gmail.com>

/* Minimal example of reading a single line. */

#include <errno.h>
#include <gpiod.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>

/* Request a line as input. */
static struct gpiod_line_request *request_input_line(const char *chip_path,
						     unsigned int offset,
						     const char *consumer){
	struct gpiod_request_config *req_cfg = NULL;
	struct gpiod_line_request *request = NULL;
	struct gpiod_line_settings *settings;
	struct gpiod_line_config *line_cfg;
	struct gpiod_chip *chip;
	int ret;

	chip = gpiod_chip_open(chip_path);
	if (!chip)
		return NULL;

	settings = gpiod_line_settings_new();
	if (!settings)
		goto close_chip;

	gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);

	line_cfg = gpiod_line_config_new();
	if (!line_cfg)
		goto free_settings;

	ret = gpiod_line_config_add_line_settings(line_cfg, &offset, 1,
						  settings);
	if (ret)
		goto free_line_config;

	if (consumer) {
		req_cfg = gpiod_request_config_new();
		if (!req_cfg)
			goto free_line_config;

		gpiod_request_config_set_consumer(req_cfg, consumer);
	}

	request = gpiod_chip_request_lines(chip, req_cfg, line_cfg);

	gpiod_request_config_free(req_cfg);

free_line_config:
	gpiod_line_config_free(line_cfg);

free_settings:
	gpiod_line_settings_free(settings);

close_chip:
	gpiod_chip_close(chip);

	return request;
}
int main(void)
{
	static const char *const gpio_0 = "/dev/gpiochip0";
    static const char *const gpio_1 = "/dev/gpiochip1";
	static const unsigned int JOYSTICK_UP = 16;     //GPIO0
    static const unsigned int JOYSTICK_DOWN = 17;   //GPIO0
    static const unsigned int JOYSTICK_LEFT = 21;   //GPIO0
    static const unsigned int JOYSTICK_RIGHT = 27;  //GPIO1
    static const unsigned int JOYSTICK_BUTTON = 23; //GPIO0


    struct gpiod_line_request *up_request;
    struct gpiod_line_request *down_request;
    struct gpiod_line_request *left_request;
    struct gpiod_line_request *right_request;
    struct gpiod_line_request *button_request;

    enum gpiod_line_value value;

	up_request = request_input_line(gpio_0, JOYSTICK_UP, "get-line-value");
    down_request = request_input_line(gpio_0, JOYSTICK_DOWN, "get-line-value");
    left_request = request_input_line(gpio_0, JOYSTICK_LEFT, "get-line-value");
    right_request = request_input_line(gpio_1, JOYSTICK_RIGHT, "get-line-value");
    button_request = request_input_line(gpio_0, JOYSTICK_BUTTON, "get-line-value");

	if (!up_request) {
		printf("failed to request line: %s\n");
        return EXIT_FAILURE;
    }
    if (!down_request) {
		printf("failed to request line: %s\n");
        return EXIT_FAILURE;
    }
    if (!left_request) {
		printf("failed to request line: %s\n");
        return EXIT_FAILURE;
    }
    if (!right_request) {
		printf("failed to request line: %s\n");
        return EXIT_FAILURE;
    }
    if (!button_request) {
		printf("failed to request line: %s\n");
        return EXIT_FAILURE;
    }

    printf("Listening to joystick press\n");

	while(true){
        sleep(0.1);
	    value = gpiod_line_request_get_value(up_request, JOYSTICK_UP);
        if (value == GPIOD_LINE_VALUE_INACTIVE)
            printf("UP\n");
        value = gpiod_line_request_get_value(down_request, JOYSTICK_DOWN);
        if (value == GPIOD_LINE_VALUE_INACTIVE)
            printf("DOWN\n");
        value = gpiod_line_request_get_value(left_request, JOYSTICK_LEFT);
        if (value == GPIOD_LINE_VALUE_INACTIVE)
            printf("LEFT\n");
        value = gpiod_line_request_get_value(right_request, JOYSTICK_RIGHT);
        if (value == GPIOD_LINE_VALUE_INACTIVE)
            printf("RIGHT\n");
        value = gpiod_line_request_get_value(button_request, JOYSTICK_BUTTON);
        if (value == GPIOD_LINE_VALUE_INACTIVE)
            break;
	}
	/* not strictly required here, but if the app wasn't exiting... */
	gpiod_line_request_release(up_request);
    gpiod_line_request_release(down_request);
    gpiod_line_request_release(left_request);
    gpiod_line_request_release(right_request);
    gpiod_line_request_release(button_request);
	return EXIT_SUCCESS;
}
}
~ # echo $?
0

[Q2] Expliquez limplémentation de votre programme et indiquez où il se trouve sur votre git.

Le code ci dessus devrait faire en permanence du pulling et dès que le joystick est bougé on l'affiche.

J'ai ajouté le fait que quand on appuie sur le bouton du joystick ca arrête le programme et que le sleep(0.1) permette à la boucle de ne s'executer que toutes les 0.1s pour ne pas surcharger le processeur.

Le code a été adapté d'un code existant dispo sur le git de libgpiod

Lecture du joystick via lAPI C libgpiod

[Q1]

En fait j'avais pas compris que il fallait utiliser les outils dabord et ensuite l'API. J'ai commencé direct avec l'API.

Mais on peut facilement imaginer un petit script bash qui utiliserait juste les outils avec des commande simples genre gpioget et gpioset

Pour le makefile voici un exemple très simple:

CC = arm-buildroot-linux-musleabi-gcc
SRC = testJoystick.c
TARGET = joystick
INCLUDE_DIR = /home/moi/tp/lib/compiledLib/include/
LIB_DIR = /home/moi/tp/lib/compiledLib/lib/
LIBS = -lgpiod

all: $(TARGET)

$(TARGET): $(SRC)
	$(CC) $(SRC) -o $(TARGET) -I $(INCLUDE_DIR) $(LIBS) -L $(LIB_DIR)

clean:
	rm -f $(TARGET)

Développement dun module noyau

On crée un repertoire sur la VM de developpement sous /tp/module

On va prendre le squelette du module sur le git

On va y aller étape par étape :

1 initialisation du driver

Les étapes pour bien initialiser notre driver sont :

  1. Enregistrer notre module en obtenant un numéro majeur
  2. Créer la classe
  3. Créer le module dans /dev

Les trois étapes ci dessus ont pu ête implémentées avec l'aide des slides du cours.

  1. Demander les différents GPIOS
  2. Enregistrer les différentes interruptions pour chaque GPIO
int major;
struct class *j_class;
struct device *j_device;
//mapping des fonctions classiques d'un char device
static const struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = dev_open
};
static int __init mylab1_joystick_dev_init(void) {
	// TODO
	// 1) Register the device by dynamically obtaining a major number
    major = register_chrdev(0, "mylab1_joystick", fops);
    if(major < 0){
        pr_info("mylab1_joystick: could not get a major number, initialisation failed\n");
        return -1;
    }
	// 2) Create the class
    j_class = class_create("mylab1_joystick");
    if (IS_ERR(j_class)){
        pr_info("mylab1_joystick: could not create class, initialisation failed\n");
        return -1:
    }
    // 3) Create the device in /dev
    j_device = device_create(j_class, NULL, MKDEV(major, 0), NULL, "mylab1_joystick");
    if (IS_ERR(j_device)){
        pr_info("mylab1_joystick: could not create device, initialisation failed\n");
        return -1:
    }
	// 6) Request the necessary GPIOs
	// 7) Register an IRQ handler per GPIO
	pr_info("mylab1_joystick: driver initialized\n");
	return 0;
}

par la même occasion avec le code ci dessus on peut déja préparer la sortie du module :

static void __exit mylab1_joystick_dev_exit(void) {
	// TODO
	// 1) Destroy the device
    device_destroy(j_device,MKDEV(major, 0));
	// 2) Destroy the class
    class_destroy(j_class);
	// 4) Unregister the device
    unregister_chrdev(major,"mylab1_joystick");
	// 5) Free the IRQs
	// 6) Free the GPIOs
	pr_info("mylab1_joystick: driver destroyed\n");
}

Normalement avec le squelette modifié de la sorte on devrait avoir un driver minimal qui ne sert à rien mais qui se créée correctement dans le kernel linux.

Voici a quoi ressemble un makefile classique pour compiler notre module:

# kernel headers
KDIR := /home/moi/tp/kernel/linux-6.5
# module name mymodule.c
obj-m := mymodule.o

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

Le KDIR to kernel correspond à l'endroit ou on a les sources du kernel.

make mrproper

make sama5_defconfig

make -j 6 //obligé de mettre 6 car dans mon cas car sans specifier le nombre de cpu sur ma VM, le -j crééait des instances en boucle et arrivait à saturer la memoire

Je peux lancer la compilation, la config est pas parfaite mais la le but est pas de faire un kernel pour notre board mais juste d'avoir un kernel compilé pour ensuite pouvoir compiler notre module.

Le chemin complet : "/home/moi/tp/kernel/linux-6.5"

Quand le kernel a fini d'être compilé on peut faire un make pour compiler notre (pour le moment) inutile module et ca fonctionne ! Voici le contenu du repertoire après compilation. Le code du module minimal est trouvable sous skeletton.c dans le git

Makefile Module.symvers modules.order mymodule.c mymodule.ko mymodule.mod mymodule.mod.c mymodule.mod.o mymodule.o

On va avoir besoin du .ko pour tester notre module.

insmod mymodule.ko

Le resultat de la commande est :

mymodule: loading out-of-tree module taints kernel.
mylab1_joystick: driver initialized

Ce qui est plutôt bon signe.

On peut voir la liste des modules avec lsmod

~ # lsmod
mymodule 12288 0 - Live 0xbf052000 (O)
ehci_atmel 12288 0 - Live 0xbf04c000
ehci_hcd 45056 1 ehci_atmel, Live 0xbf03e000
usb_storage 49152 0 - Live 0xbf02f000
usbcore 180224 3 ehci_atmel,ehci_hcd,usb_storage, Live 0xbf000000

Pour voir si des messages d'erreurs ont été levés on peut aller regarder dans les logs avec dmesg | tail

Et les deux seuls messages que l'on peut voir qui concernent notre module sont :

mymodule: loading out-of-tree module taints kernel.
mylab1_joystick: driver initialized

On peut même le voir dans /sys/module ls /sys/module | grep mymodule

pour retirer le module on peut utiliser la commande rmmod mymodule rmmod mymodule

~ # rmmod mymodule
mylab1_joystick: driver destroyed

Maintenant que on a driver qui s'enregistre correctemement et se désenregistre correctement on peut passer à la suite : Qu'il fasse un truc du coup

Pour la partie gestion des GPIO voici le code qui permet de request les différents GPIO :

const int JOYSTICK_UP = 16;     //GPIO0
const int JOYSTICK_DOWN = 17;   //GPIO0
const int JOYSTICK_LEFT = 21;   //GPIO0
const int JOYSTICK_RIGHT = 27 + 32;  //GPIO1 offset of 32 on GPIO 1
const int JOYSTICK_BUTTON = 23; //GPIO0

int err = 0;
err = gpio_request_one(JOYSTICK_UP,GPIOF_DIR_IN,"UP");
if(err != 0)
    pr_info("mylab1_joystick: Could not request GPIO %d\n",JOYSTICK_UP);

Et pour rendre les GPIO :

gpio_free(JOYSTICK_UP);

Ensuite pour demander les interruptions

#define IRQ_NAME "myjoystick_irq"
int irqNbr_UP = 0;
int irqNbr_DOWN = 0;
int irqNbr_LEFT = 0;
int irqNbr_RIGHT = 0;
int irqNbr_BUTTON = 0;

int irqres = 0;

irqNbr_UP = gpio_to_irq(JOYSTICK_UP);
if(irqNbr_UP < 0)
    pr_info("mylab1_joystick: Could not request IRQ number on GPIO : %d\n",JOYSTICK_UP);
irqres = request_irq(irqNbr_UP, mylab_gpio_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, IRQ_NAME, (void*)JOYSTICK_UP);
if(irqres < 0)
    pr_info("mylab1_joystick: Could not request IRQ on GPIO : %d\n",JOYSTICK_UP);

pour les free

free_irq(irqNbr_UP, (void*)JOYSTICK_UP);

ATTENTION, dans le free_irq le second paramètre doit être identique au request_irq. Si il est null il doit l'être également ici et sinon il doit contenir la même valeur sinon ca ne permet pas de free les IRQ correctement au déchargement du module. (Non j'ai pas mis 30min a comprendre pourquoi ca voulait pas marcher)

Pour lire des valeurs :

int res = 0;
res = gpio_get_value(JOYSTICK_UP);

Et voici le code de l'interruption

static irqreturn_t mylab_irq_handler(int irq, void *dev_id) {
    int gpio = (int)dev_id; // GPIO number passed as dev_id
    int value = gpio_get_value(gpio); // Get the current GPIO value
    if (value == 1) {
        pr_info("GPIO %d: Rising edge detected\n", gpio);
    } else {
        pr_info("GPIO %d: Falling edge detected\n", gpio);
    }
	return (irqreturn_t) IRQ_HANDLED;  // Announce that the IRQ has been handled correctly
}

Avec le code que on peut trouver sous skeletton_gpio.c dans le git, on a un code qui va detecter quand un bouton du joystick a été pressé ou lâché et l'ecrit dans les logs.

Dans le tp il est demandé à ce que l'interruption garde en memoire l'état des joysticks pour que quand le user vienne effectuer un read on lui renvoie l'étât (UP,DOWN,LEFT,RIGHT,BUTTON, ou NULL)

L'interruption n'aura alors comme travail que de mettre à jour les étâts.

static int UP,DOWN,LEFT,RIGHT,BUTTON = 0;
static irqreturn_t mylab_irq_handler(int irq, void *dev_id) {
    int gpio = (int)dev_id; // GPIO number passed as dev_id
    int value = gpio_get_value(gpio); // Get the current GPIO value
    switch(gpio){
        case JOYSTICK_UP:
            UP = value;
        break;
        case JOYSTICK_DOWN:
            DOWN = value;
        break;
        case JOYSTICK_LEFT:
            LEFT = value;
        break;
        case JOYSTICK_RIGHT:
            RIGHT = value;
        break;
        case JOYSTICK_BUTTON:
            BUTTON = value;
        break;
        default:
            //Wtf?
        break;
    }
	return (irqreturn_t) IRQ_HANDLED;
}

Ok on pourrait imaginer un code qui met juste une valeur codée genre 1 = UP et 2 = DOWN comme ca pas besoin de cinq variables, mais j'aime l'idée que notre joystick pourrait avoir plusieurs positions en même temps pour aller en diagonale par exemple. Je ne sais pas si c'est physiquement possible sur notre mylab1 mais ca m'est égal.

Maintenant le moment tant attendu, on implémente la methode read();

static ssize_t dev_read(struct file *filep, char __user *buffer, size_t len, loff_t *offset){
    int joystick_status[5] = {UP,DOWN,LEFT,RIGHT,BUTTON};
    int data_len = 5*sizeof(int);
    // Copy data to user-space buffer
    if (copy_to_user(buffer, joystick_status, data_len)) {
        pr_err("Failed to send data to user\n");
        return -EFAULT;
    }
    return 0;
}

Dans le code ci dessus on envoie simplement un tableau avec toutes les valeurs des GPIO pour plus de simplicité du côté user pour les recuperer.

Verifications du bon fonctionnement du module terminé:

~ # insmod mymodule.ko
mymodule: loading out-of-tree module taints kernel.
mylab1_joystick: driver initialized

/dev

/dev # ls /dev | grep mylab
mylab1_joystick

/proc/devices

/proc # cat /proc/devices | grep mylab
245 mylab1_joystick

/sys/class

/sys/class # ls /sys/class | grep mylab
mylab1_joystick

/proc/interrupts

/proc # cat /proc/interrupts | grep my
 58:         17      GPIO  16 Edge      myjoystick_irq
 59:          8      GPIO  17 Edge      myjoystick_irq
 60:         10      GPIO  21 Edge      myjoystick_irq
 61:         15      GPIO  27 Edge      myjoystick_irq
 62:          6      GPIO  23 Edge      myjoystick_irq

Et quand on le décharge :

~ # rmmod mymodule
mylab1_joystick: driver destroyed

Aucun message d'erreur rien.

Et si on retourne dans /proc/interrupts

~ # cat /proc/interrupts | grep my
~ #

On voit bien que elles ont toutes été free.

### [Q3] Expliquez le fonctionnement de votre module et les parties sensibles de son implémentation.

Sur le git les trois niveaux de modules se trouvent sous

  • skeletton.c qui est un module inutile qui ne sert qu'à s'enregistrer et se desenregistrer
  • skeletton_gpio.c qui est une version du module qui s'occupe de mettre en place les gpios et qui les affiche en message d'erreur ce qui est pratique pour debug
  • module.c qui est la version finale du module qui devrait être utilisée par un user et qui alloue et désaloue tout correctement en principe

Le fonctionnement est décrit plus haut.

Utilisation du module noyau depuis lespace utilisateur

La le but ca va être d'utiliser notre module avec un programme en C.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define PATH_MODULE "/dev/mylab1_joystick"

int main(){
    int fd;
    int buffer[5] = {1,1,1,1,1};
    int read_err;
    int offset = 0;
    fd = open(PATH_MODULE,O_RDONLY,S_IRUSR);
    if(fd < 0){
        printf("Could not retrieve module at PATH_MODULE\n");
        return fd;
    }
    while(1){

        read_err = read(fd,buffer,5 * sizeof(int));
        if(read_err < 0)
            printf("Could not read module\n");

        if(!buffer[0])
            printf("UP\n");
        if(!buffer[1])
            printf("DOWN\n");
        if(!buffer[2])
            printf("LEFT\n");
        if(!buffer[3])
            printf("RIGHT\n");
        if(!buffer[4])
            break;

        usleep(100); //1000 microseconds delay to not stress the cpu too much 
        
        //printf("Up %d,Down %d,Left %d,Right %d,Button %d\n",buffer[0],buffer[1],buffer[2],buffer[3],buffer[4]);
    }
    close(fd);
    printf("Exiting the app !\n");
}

arm-buildroot-linux-musleabi-gcc moduletester.c -o moduletester

chmod +x moduletester

Ensuite quand on essaie de le lancer on a l'erreur : could not retrieve module par ce que je viens de lancer mon kernel et que le module n'est pas directement chargé par défaut pour le moment.

Donc insmod mymodule.ko

./mymodule.ko

Va savoir pourquoi quand le gpio est pas pressé c'est 1 et pas 0, bon ca demande juste une simple inversion.

Pour le makefile on peut utiliser ca :

# Name of the target executable
TARGET = moduletester

# Compiler
CC = arm-buildroot-linux-musleabi-gcc

# Source file
SRC = moduletester.c

# Default target
all: $(TARGET)

$(TARGET): $(SRC)
	$(CC) $(SRC) -o $(TARGET)

# Clean up generated files
clean:
	rm -f $(TARGET)

[Q4] Expliquez le fonctionnement de votre programme, notamment comment vous obtenez les états du joystick. Indiquez où se trouvent les sources du programme ainsi que le Makefile correspondant sur votre git.

Le code du testeur n'est pas sur le git car il est assez petit pour tenir sur ce markdown. J'ai mis un exemple de makefile juste au dessus. Pour recupèrer les valeurs du driver on le lit comme un fichier et comme ca a été prévu lors de la conception du driver, les données sont envoyées sous forme de tableau avec cinq positions ou dans chaque position on a l'étât du GPIO correspondant.

Compilation croisée de la librairie ncurses

wget https://ftp.gnu.org/gnu/ncurses/ncurses-6.5.tar.gz

xz -d ncurses-6.5.tar.gz.xz

tar -xvf ncurses-6.5.tar.gz

ensuite dans le repertoire nouvellement créé :

./configure --host=arm-linux --prefix=/usr --without-progs --with-shared

On met à jour la variable d'environnement

echo $CC
arm-buildroot-linux-musleabi-gcc

Ensuite on peut lancer la compilation :

make -j 8

Et evidemment comme dhab ca prend 2h sur ma vm mais pas de soucis.

Je créée ensuite un repertoire de staging "/home/moi/tp/lib/ncurses/buildNcurse" et on peut faire l'installation:

make DESTDIR=/home/moi/tp/lib/ncurses/buildNcurse install

Dans le repertoire staging sous usr on peut voire cette arborescence :

bin include lib share

De ce que je comprends dans le tp, le contenu qui nous intéresse se trouve dans /lib

ls | grep .so
libformw.so
libformw.so.6
libformw.so.6.5
libmenuw.so
libmenuw.so.6
libmenuw.so.6.5
libncursesw.so
libncursesw.so.6
libncursesw.so.6.5
libpanelw.so
libpanelw.so.6
libpanelw.so.6.5

[Q5] Quels répertoires avez-vous copiés ?

J'ai commencé par mettre le libncursesw.so.6.5 dans le repertoire /lib de mon système embarqué, mais je me suis aussi dit que ca coutait pas bien cher de mettre les autres et que si ca se trouve Ctris aura besoin d'elles aussi.

Donc en tout j'ai mis

  • libncursesw.so.6.5
  • libpanelw.so.6.5
  • libmenuw.so.6.5
  • libformw.so.6.5

EDIT DECOUVERTES PENDANT L'INSTALLATION DE CTRIS: Finalement je n'ai pris que la lib libncursesw.so.6 et j'ai pris ce qui se trouvait sous /usr/share et je l'ai mis dans un repertoire /usr/share nouvellement créé dans le système embarqué.

Compilation croisée de CTRIS

git clone https://github.com/0xminik/ctris.git

Il faut modifier le makefile pour changer différents paramètres.

1: Il faut changer le compilateur CC avec notre gcc arm

old CC=gcc new CC=arm-buildroot-linux-musleabi-gcc

old CFLAGS=-Wall -fomit-frame-pointer -O3 new CFLAGS=-Wall -fomit-frame-pointer -O3 -I/home/moi/tp/lib/ncurses/buildNcurse/usr/include

old LIBS=-lm -lncurses new LIBS=-L/home/moi/tp/lib/ncurses/buildNcurse/usr/lib -lm -lncursesw

et ensuite on peut faire un make

Maintenant on a un exectuable ctris que on peut mettre sur le système embarqué

Et après un chmod +x ctris

Quand on essaie de ./ctris on a un message d'erreur qui nous indique que la librairie ncurses demandée n'est pas la 6.5 mais la 6, je remplace donc la lib et ensuite on peut re essayer de lancer ctris :

~ # ./ctris
Error opening terminal: vt102.

Après un peu de googling je comprends que le repertoire ncurses /usr/share peut avoir une utilité.

J'ai donc créé un repertoire share dans /usr sur la machine embarquée et j'y ai mis tout ce qui se trouvait dans /usr/share dans le repertoire staging.

Desormais quand on tente de lancer ctris on a l'erreur attendue :

~ # ./ctris
ERROR, your terminal can't display colors.

[Q6] Quelle est le type du terminal courant sur votre système embarqué ?

On peut voir quel terminal notre système embarqué utilise comme cela :

~ # echo $TERM
vt102

Le vt102 est un terminal très ancien (1978 de ce que j'ai pu voir sur internet) sur des machines de l'époque. Il est donc très barebones et ne peut pas afficher de couleurs ou autres choses plus avancées que du texte simple.

On peut changer ca avec export TERM=linux

Et avec ce nouveau terminal on peut voir des couleurs quand on fait des ls et quand on lance ctris ca fonctionne !

Mais quand on redémarre le système, le terminal revient au mode Vt102.

Pour ca on peut aller dans notre /etc/inittab et changer la ligne

::respawn:/sbin/getty 115200 ttyS0

en

::respawn:/sbin/getty 115200 ttyS0 linux

A defaut d'avoir un fichier .profile dans /etc

EDIT:

Cette methode avait l'air de marcher en serie mais avec Telnet plus tard il faut utiliser une autre methode :

Il faut ajouter export TERM=linux

dans /etc/init.d/rcS

#!/bin/sh
# sets the terminal
export TERM=linux

# Start all init scripts in /etc/init.d
# executing them in numeric order.

for i in /etc/init.d/S??*; do
    case "$i" in
        *.sh)
            # Source shell script for speed.
            . "$i"
            ;;
        *)
            # No .sh extension, so fork subprocess.
            "$i" start
            ;;
    esac
done

[Q7] Quelle en est la raison à votre avis ?

Bon honnêtement je n'ai pas spécialement vu de ralentissement et je trouvais pas le jeu peu fluide mais la raison pour laquelle ca n'est pas aussi fluide que sur un vrai écran natif c'est que tout est envoyé caractère par caractère en série et donc forcément ca pue un peu pour des applications qui utilisent ncurses qui demande beaucoup de caractères par seconde pour mettre à jour tout l'écran.

Du moins c'est mon avis.

Mise en place dun serveur telnet

Si on fait un telentd --help sur le système embarqué l'aide apparait donc j'ai la chance de ne pas avoir à recompiler un busybox.

On lance un telnetd en foreground -F

telnetd -F

Du côté de la machine hôte on peut faire

brew install telnet

et ensuite

telnet 192.168.144.100
Trying 192.168.144.100...
Connected to 192.168.144.100.
Escape character is '^]'.
Connection closed by foreign host.

Et du côté du système embarqué on a ce message :

telnetd: can't find free pty

Comme marqué dans le tp pour solutionner ce problème on peut monter le système de fichier virtuel pty dans /dev/pty.

mkdir /dev/pts

mount -t devpts devpts /dev/pts

Et la si je lance un telnet et que je tente de me connecter depuis ma machine hôte ca fonctionne !! Magnifique plus besoin d'utiliser ces fichus câbles.

honnêtement je ne vois pas forcément la différence de fluidité mais bon le fait de plus avoir besoin de se brancher avec le port série c'est vraiment un plus !

[Q8] Quels changements avez-vous effectués pour réaliser ceci ?

Pour faire en sorte que le serveur telnetd soit lancé au démarrage on peut créer un petit script dans /etc/init.d

vi Stelnetd.sh

#!/bin/sh
mkdir /dev/pts
mount -t devpts devpts /dev/pts
telnetd

chmod +x Stelnetd.sh

Et avec ca, après chaque reboot ce script est lancé et je peut accèder au système embarqué sans utiliser le port série !!

Modification de CTRIS pour utiliser le joystick de la carte MyLab1

Bon on est pas la pour faire du super beau code ou quoi que ce soit donc j'ai juste été dans le fichier game.c et j'ai ajouté cette methode :

#define PATH_MODULE "/dev/mylab1_joystick"
int fd;
int buffer[5] = {1,1,1,1,1};
int read_err;
int init_mylab(){
    fd = open(PATH_MODULE,O_RDONLY,S_IRUSR);
    if(fd < 0){
        printf("Could not retrieve mylab driver\n");
        return fd;
    }
}

Ensuite dans la fonction game_engine() j'ai ajouté cette ligne

init_mylab(); au début pour que le driver soit load au début du game engine.

Ensuite le switch case avant se trouvait comme cela :

switch(get_key(board_win))

Je l'ai courcircuité de cette facon

int keyValue = get_key(board_win);
switch(keyValue){}

Ce qui me permet ensuite de venir me plugger comme un petit parasite et interferer avec les touches detectées :

int keyValue = get_key(board_win);

read_err = read(fd,buffer,5 * sizeof(int));
if(read_err < 0)
    printf("Could not read module\n");

if(!buffer[0])
    keyValue = KEY_UP;
if(!buffer[1])
    keyValue = KEY_DOWN;
if(!buffer[2])
    keyValue = KEY_LEFT;
if(!buffer[3])
    keyValue = KEY_RIGHT;
if(!buffer[4])
    keyValue = 'p';

switch(keyValue){}

Seule différence avec l'énoncé c'est que j'ai mis le bouton du milieu comme pause je trouve que c'est mieux mais pour remettre sur espace c'est vraiment une ligne à changer.

De cette manière si le user utilise des touches sur son clavier, on ne change rien au comportement, mais si on utilise le joystick ca override les valeurs.

C'est un peu degeu mais bon.

Ensuite quand on compile et que on lance le jeu on peut voir que ca ne marche pas evidemment car j'ai redémarré la board et le driver n'est plus chargé.

Je crée donc un un fichier Smylab1DriverLoad.sh dans /etc/init.d/ qui ressemble à ca :

#!/bin/sh

insmod /lib/modules/6.6.56/kernel/drivers/gpio/mymodule.ko

Et la quand on redemarre le systeme embarqué, le module est chargé par défaut.

Bon avec tout ca j'ai essayé de jouer a CTRIS et en vrai ca passe. C'est pas le plus facile car le joystick est très sensible sans sleep mais c'est jouable juste pas facile.

Après désolé mais faire un joli petit truc modulaire avec des jolies fonctions faites exprès la GROSSE FLEMME. Ce tp est déja beaucoup trop long j'en ai d'autres sur lesquels ruiner le reste de mes vacances ainsi que mes revisions.

pour crééer un patch :

git add -A

git diff > ctris_changes.patch

[Q9] Expliquez comment vous avez modifié le code de CTRIS afin que celui-ci utilise le joystick de votre carte MyLab1 et que le jeu reste jouable. Créez un patch avec le code complet de vos changements (en incluant le Makefile) et indiquez où il se trouve sur votre git.

Les modifications sont expliquées plus haut

Le fichier game.c dans la racine du git contient le fichier game.c modifié avec mon code.

Ensuite le fichier ctris_changes.patch aussi à la racine est le fichier qui contient le patch de toutes mes modifications sur le repo du jeu ctris

Integration sécurisée

On va créer un user qui s'occupera de lancer notre module

adduser -D -H -G nogroup -s /bin/false gpio_mylab

On change ensuite le Stelnetd.sh en

#!/bin/sh
mkdir /dev/pts
mount -t devpts devpts /dev/pts
telnetd -u gpio_mylab