Pilotes de périphériques
Pilotes orientés mémoire
Exercice #1: Réaliser un pilote orienté mémoire permettant de mapper en espace utilisateur les registres du microprocesseur en utilisant le fichier virtuel /dev/mem. Ce pilote permettra de lire l’identification du microprocesseur (Chip-ID aux adresses 0x01c1'4200 à 0x01c1'420c) décrit dans l’exercice “Accès aux entrées/sorties” du cours sur la programmation de modules noyau.
Solution
EXE=app
SRCS=$(wildcard *.c)
ifeq ($(target),)
target=nano
endif
CFLAGS=-Wall -Wextra -g -c -O0 -MD -std=gnu11
TOOLCHAIN_PATH=/buildroot/output/host/usr/bin/
TOOLCHAIN=$(TOOLCHAIN_PATH)aarch64-linux-
CFLAGS+=-mcpu=cortex-a53 -funwind-tables
##CFLAGS+=-O2 -fno-omit-frame-pointer
OBJDIR=.obj/nano
EXEC=$(EXE)
CC=$(TOOLCHAIN)gcc
LD=$(TOOLCHAIN)gcc
AR=$(TOOLCHAIN)ar
STRIP=$(TOOLCHAIN)strip
OBJDIR=.obj/$(target)
OBJS= $(addprefix $(OBJDIR)/, $(SRCS:.c=.o))
$(OBJDIR)/%o: %c
$(CC) $(CFLAGS) $< -o $@
all: $(OBJDIR)/ $(EXEC)
$(EXEC): $(OBJS) $(LINKER_SCRIPT)
$(LD) $(OBJS) $(LDFLAGS) -o $@
$(OBJDIR)/:
mkdir -p $(OBJDIR)
clean:
rm -Rf $(OBJDIR) $(EXEC) $(EXEC)_s *~
clean_all: clean
rm -Rf .obj $(EXE) $(EXE)_s $(EXE)_a $(EXE)_a_s $(EXE)_h $(EXE)_h_s
-include $(OBJS:.o=.d)
.PHONY: all clean clean_all
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/mman.h>
#include <unistd.h>
int main()
{
/* open memory file descriptor */
int fd = open("/dev/mem", O_RDWR);
if (fd < 0) {
printf("Could not open /dev/mem: error=%i\n", fd);
return -1;
}
size_t psz = getpagesize();
off_t dev_addr = 0x01c14200;
off_t ofs = dev_addr % psz;
off_t offset = dev_addr - ofs;
printf(
"psz=%lx, addr=%lx, offset=%lx, ofs=%lx\n", psz, dev_addr, offset, ofs);
/* map to user space nanopi internal registers */
volatile uint32_t* regs =
mmap(0, psz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
if (regs == MAP_FAILED) // (void *)-1
{
printf("mmap failed, error: %i:%s \n", errno, strerror(errno));
return -1;
}
uint32_t chipid[4] = {
[0] = *(regs + (ofs + 0x00) / sizeof(uint32_t)),
[1] = *(regs + (ofs + 0x04) / sizeof(uint32_t)),
[2] = *(regs + (ofs + 0x08) / sizeof(uint32_t)),
[3] = *(regs + (ofs + 0x0c) / sizeof(uint32_t)),
};
printf("NanoPi NEO Plus2 chipid=%08x'%08x'%08x'%08x\n",
chipid[0],
chipid[1],
chipid[2],
chipid[3]);
munmap((void*)regs, psz);
close(fd);
return 0;
}
Pilotes orientés caractère
Exercice #2: Implémenter un pilote de périphérique orienté caractère. Ce pilote sera capable de stocker dans une variable globale au module les données reçues par l’opération write et de les restituer par l’opération read. Pour tester le module, on utilisera les commandes echo et cat.
Solution
# 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 ../../buildroot_path
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
export PATH := /buildroot/output/host/usr/sbin$\
:/buildroot/output/host/usr/bin/$\
:/buildroot/output/host/sbin$\
:/buildroot/output/host/bin/$\
:$(PATH)
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
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/module.h> /* needed by all modules */
#include <linux/moduleparam.h> /* needed for module parameters */
#include <linux/uaccess.h> /* needed to copy data to/from user */
#define BUFFER_SZ 10000
static char s_buffer[BUFFER_SZ];
static dev_t skeleton_dev;
static struct cdev skeleton_cdev;
static int skeleton_open(struct inode* i, struct file* f)
{
pr_info("skeleton : open operation... major:%d, minor:%d\n",
imajor(i),
iminor(i));
if ((f->f_flags & (O_APPEND)) != 0) {
pr_info("skeleton : opened for appending...\n");
}
if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) != 0) {
pr_info("skeleton : opened for reading & writing...\n");
} else if ((f->f_mode & FMODE_READ) != 0) {
pr_info("skeleton : opened for reading...\n");
} else if ((f->f_mode & FMODE_WRITE) != 0) {
pr_info("skeleton : opened for writing...\n");
}
return 0;
}
static int skeleton_release(struct inode* i, struct file* f)
{
pr_info("skeleton: release operation...\n");
return 0;
}
static ssize_t skeleton_read(struct file* f,
char __user* buf,
size_t count,
loff_t* off)
{
// compute remaining bytes to copy, update count and pointers
ssize_t remaining = BUFFER_SZ - (ssize_t)(*off);
char* ptr = s_buffer + *off;
if (count > remaining) count = remaining;
*off += count;
// copy required number of bytes
if (copy_to_user(buf, ptr, count) != 0) count = -EFAULT;
pr_info("skeleton: read operation... read=%ld\n", count);
return count;
}
static ssize_t skeleton_write(struct file* f,
const char __user* buf,
size_t count,
loff_t* off)
{
// compute remaining space in buffer and update pointers
ssize_t remaining = BUFFER_SZ - (ssize_t)(*off);
pr_info("skeleton: at%ld\n", (unsigned long)(*off));
// check if still remaining space to store additional bytes
if (count >= remaining) count = -EIO;
// store additional bytes into internal buffer
if (count > 0) {
char* ptr = s_buffer + *off;
*off += count;
ptr[count] = 0; // make sure string is null terminated
if (copy_from_user(ptr, buf, count)) count = -EFAULT;
}
pr_info("skeleton: write operation... written=%ld\n", count);
return count;
}
static struct file_operations skeleton_fops = {
.owner = THIS_MODULE,
.open = skeleton_open,
.read = skeleton_read,
.write = skeleton_write,
.release = skeleton_release,
};
static int __init skeleton_init(void)
{
int status = alloc_chrdev_region(&skeleton_dev, 0, 1, "mymodule");
if (status == 0) {
cdev_init(&skeleton_cdev, &skeleton_fops);
skeleton_cdev.owner = THIS_MODULE;
status = cdev_add(&skeleton_cdev, skeleton_dev, 1);
}
pr_info("Linux module skeleton loaded\n");
return 0;
}
static void __exit skeleton_exit(void)
{
cdev_del(&skeleton_cdev);
unregister_chrdev_region(skeleton_dev, 1);
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: Etendre la fonctionnalité du pilote de l’exercice précédent afin que l’on puisse à l’aide d’un paramètre module spécifier le nombre d’instances. Pour chaque instance, on créera une variable unique permettant de stocker les données échangées avec l’application en espace utilisateur.
Solution
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/module.h> /* needed by all modules */
#include <linux/moduleparam.h> /* needed for module parameters */
#include <linux/slab.h> /* needed for dynamic memory management */
#include <linux/uaccess.h> /* needed to copy data to/from user */
static int instances = 3;
module_param(instances, int, 0);
#define BUFFER_SZ 10000
static char** buffers = 0;
static int skeleton_open(struct inode* i, struct file* f)
{
pr_info("skeleton : open operation... major:%d, minor:%d\n",
imajor(i),
iminor(i));
if (iminor(i) >= instances) {
return -EFAULT;
}
if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) != 0) {
pr_info("skeleton : opened for reading & writing...\n");
} else if ((f->f_mode & FMODE_READ) != 0) {
pr_info("skeleton : opened for reading...\n");
} else if ((f->f_mode & FMODE_WRITE) != 0) {
pr_info("skeleton : opened for writing...\n");
}
f->private_data = buffers[iminor(i)];
pr_info("skeleton: private_data=%p\n", f->private_data);
return 0;
}
static int skeleton_release(struct inode* i, struct file* f)
{
pr_info("skeleton: release operation...\n");
return 0;
}
static ssize_t skeleton_read(struct file* f,
char __user* buf,
size_t count,
loff_t* off)
{
// compute remaining bytes to copy, update count and pointers
ssize_t remaining = BUFFER_SZ - (ssize_t)(*off);
char* ptr = (char*)f->private_data + *off;
if (count > remaining) count = remaining;
*off += count;
// copy required number of bytes
if (copy_to_user(buf, ptr, count) != 0) count = -EFAULT;
pr_info("skeleton: read operation... read=%ld\n", count);
return count;
}
static ssize_t skeleton_write(struct file* f,
const char __user* buf,
size_t count,
loff_t* off)
{
// compute remaining space in buffer and update pointers
ssize_t remaining = BUFFER_SZ - (ssize_t)(*off);
// check if still remaining space to store additional bytes
if (count >= remaining) count = -EIO;
// store additional bytes into internal buffer
if (count > 0) {
char* ptr = f->private_data + *off;
*off += count;
ptr[count] = 0; // make sure string is null terminated
if (copy_from_user(ptr, buf, count)) count = -EFAULT;
}
pr_info("skeleton: write operation... private_data=%p, written=%ld\n",
f->private_data,
count);
return count;
}
static struct file_operations skeleton_fops = {
.owner = THIS_MODULE,
.open = skeleton_open,
.read = skeleton_read,
.write = skeleton_write,
.release = skeleton_release,
};
static dev_t skeleton_dev;
static struct cdev skeleton_cdev;
static int __init skeleton_init(void)
{
int i;
int status = -EFAULT;
if (instances <= 0) return -EFAULT;
status = alloc_chrdev_region(&skeleton_dev, 0, instances, "mymodule");
if (status == 0) {
cdev_init(&skeleton_cdev, &skeleton_fops);
skeleton_cdev.owner = THIS_MODULE;
status = cdev_add(&skeleton_cdev, skeleton_dev, instances);
}
if (status == 0) {
buffers = kzalloc(sizeof(char*) * instances, GFP_KERNEL);
for (i = 0; i < instances; i++)
buffers[i] = kzalloc(BUFFER_SZ, GFP_KERNEL);
}
pr_info("Linux module skeleton loaded\n");
pr_info("The number of instances: %d\n", instances);
return status;
}
static void __exit skeleton_exit(void)
{
int i;
cdev_del(&skeleton_cdev);
unregister_chrdev_region(skeleton_dev, instances);
for (i = 0; i < instances; i++) kfree(buffers[i]);
kfree(buffers);
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 #4: Développer une petite application en espace utilisateur permettant d’accéder à ces pilotes orientés caractère. L’application devra écrire un texte dans le pilote et le relire.
Solution
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static const char* text =
"\n"
"bonjour le monde\n"
"ce mois d'octobre est plutot humide...\n"
"ce n'est qu'un petit texte de test...\n";
static const char* text2 =
"\n"
"et voici un complement au premier text..\n"
"ce n'est qu'un deuxieme petit texte de test...\n";
static const char* blabla =
"blabla blabla blabla blabla blabla blabla blabla\n";
int main(int argc, char* argv[])
{
if (argc <= 1) return 0;
int fdw = open(argv[1], O_RDWR);
write(fdw, argv[1], strlen(argv[1]));
write(fdw, text, strlen(text));
write(fdw, text2, strlen(text2));
int s;
do {
s = write(fdw, blabla, strlen(blabla));
} while (s >= 0);
close(fdw);
int fdr = open(argv[1], O_RDONLY);
while (1) {
char buff[100];
ssize_t sz = read(fdr, buff, sizeof(buff) - 1);
if (sz <= 0) break;
buff[sizeof(buff) - 1] = 0;
printf("%s", buff);
}
close(fdr);
return 0;
}
sysfs
Exercice #5: Développer un pilote de périphérique orienté caractère permettant de valider la fonctionnalité du sysfs. Le pilote offrira quelques attributs pouvant être lus et écrites avec les commandes echo et cat. Ces attributs seront disponibles sous l’arborescence /sys/class/....
Dans un premier temps, implémentez juste ce qu’il faut pour créer une nouvelle classe (par exemple : my_sysfs_class)
Solution
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/device.h> /* needed for sysfs handling */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/miscdevice.h>
#include <linux/module.h> /* needed by all modules */
#include <linux/platform_device.h> /* needed for sysfs handling */
#include <linux/uaccess.h> /* needed to copy data to/from user */
#define USING_CLASS
//#define USING_MISC
//#define USING_PLATFORM
struct skeleton_config {
int id;
long ref;
char name[30];
char descr[30];
};
static struct skeleton_config config;
static int val;
ssize_t sysfs_show_val(struct device* dev,
struct device_attribute* attr,
char* buf)
{
sprintf(buf, "%d\n", val);
return strlen(buf);
}
ssize_t sysfs_store_val(struct device* dev,
struct device_attribute* attr,
const char* buf,
size_t count)
{
val = simple_strtol(buf, 0, 10);
return count;
}
DEVICE_ATTR(val, 0664, sysfs_show_val, sysfs_store_val);
ssize_t sysfs_show_cfg(struct device* dev,
struct device_attribute* attr,
char* buf)
{
sprintf(buf,
"%d %ld %s %s\n",
config.id,
config.ref,
config.name,
config.descr);
return strlen(buf);
}
ssize_t sysfs_store_cfg(struct device* dev,
struct device_attribute* attr,
const char* buf,
size_t count)
{
sscanf(buf,
"%d %ld %s %s",
&config.id,
&config.ref,
config.name,
config.descr);
return count;
}
DEVICE_ATTR(cfg, 0664, sysfs_show_cfg, sysfs_store_cfg);
#ifdef USING_MISC
static struct miscdevice misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mymodule",
.mode = 0,
};
#endif
#ifdef USING_PLATFORM
static void sysfs_dev_release(struct device* dev)
{
pr_info("skeleton - sysfs dev release\n");
}
static struct platform_device platform_device = {
.name = "mymodule", .id = -1, .dev.release = sysfs_dev_release};
#endif
#ifdef USING_CLASS
static struct class* sysfs_class;
static struct device* sysfs_device;
#endif
static int __init skeleton_init(void)
{
int status = 0;
#ifdef USING_MISC
if (status == 0) status = misc_register(&misc_device);
if (status == 0)
status = device_create_file(misc_device.this_device, &dev_attr_val);
if (status == 0)
status = device_create_file(misc_device.this_device, &dev_attr_cfg);
#endif
#ifdef USING_PLATFORM
if (status == 0) status = platform_device_register(&platform_device);
if (status == 0)
status = device_create_file(&platform_device.dev, &dev_attr_val);
if (status == 0)
status = device_create_file(&platform_device.dev, &dev_attr_cfg);
#endif
#ifdef USING_CLASS
sysfs_class = class_create(THIS_MODULE, "my_sysfs_class");
sysfs_device = device_create(sysfs_class, NULL, 0, NULL, "my_sysfs_device");
if (status == 0) status = device_create_file(sysfs_device, &dev_attr_val);
if (status == 0) status = device_create_file(sysfs_device, &dev_attr_cfg);
#endif
pr_info("Linux module skeleton loaded\n");
return 0;
}
static void __exit skeleton_exit(void)
{
#ifdef USING_MISC
misc_deregister(&misc_device);
#endif
#ifdef USING_PLATFORM
device_remove_file(&platform_device.dev, &dev_attr_cfg);
device_remove_file(&platform_device.dev, &dev_attr_val);
platform_device_unregister(&platform_device);
#endif
#ifdef USING_CLASS
device_remove_file(sysfs_device, &dev_attr_val);
device_remove_file(sysfs_device, &dev_attr_cfg);
device_destroy(sysfs_class, 0);
class_destroy(sysfs_class);
#endif
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 #5.1 : Ajoutez maintenant les opérations sur les fichiers définies à l’exercice #3. Vous pouvez définir une classe
comme dans l’exercice précédent, ou vous pouvez utiliser un platform_device, ou encore un miscdevice.
Solution
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/device.h> /* needed for sysfs handling */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/miscdevice.h>
#include <linux/module.h> /* needed by all modules */
#include <linux/platform_device.h> /* needed for sysfs handling */
#include <linux/uaccess.h> /* needed to copy data to/from user */
#define USING_MISC
//#define USING_PLATFORM
//#define USING_CLASS
struct skeleton_config {
int id;
long ref;
char name[30];
char descr[30];
};
static struct skeleton_config config;
static int val;
ssize_t sysfs_show_val(struct device* dev,
struct device_attribute* attr,
char* buf)
{
sprintf(buf, "%d\n", val);
return strlen(buf);
}
ssize_t sysfs_store_val(struct device* dev,
struct device_attribute* attr,
const char* buf,
size_t count)
{
val = simple_strtol(buf, 0, 10);
return count;
}
DEVICE_ATTR(val, 0664, sysfs_show_val, sysfs_store_val);
ssize_t sysfs_show_cfg(struct device* dev,
struct device_attribute* attr,
char* buf)
{
sprintf(buf,
"%d %ld %s %s\n",
config.id,
config.ref,
config.name,
config.descr);
return strlen(buf);
}
ssize_t sysfs_store_cfg(struct device* dev,
struct device_attribute* attr,
const char* buf,
size_t count)
{
sscanf(buf,
"%d %ld %s %s",
&config.id,
&config.ref,
config.name,
config.descr);
return count;
}
DEVICE_ATTR(cfg, 0664, sysfs_show_cfg, sysfs_store_cfg);
#define BUFFER_SZ 10000
static char s_buffer[BUFFER_SZ];
#ifndef USING_MISC
static dev_t skeleton_dev;
static struct cdev skeleton_cdev;
#endif
static int skeleton_open(struct inode* i, struct file* f)
{
pr_info("skeleton : open operation... major:%d, minor:%d\n",
imajor(i),
iminor(i));
if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) != 0) {
pr_info("skeleton : opened for reading & writing...\n");
} else if ((f->f_mode & FMODE_READ) != 0) {
pr_info("skeleton : opened for reading...\n");
} else if ((f->f_mode & FMODE_WRITE) != 0) {
pr_info("skeleton : opened for writing...\n");
}
return 0;
}
static int skeleton_release(struct inode* i, struct file* f)
{
pr_info("skeleton: release operation...\n");
return 0;
}
static ssize_t skeleton_read(struct file* f,
char __user* buf,
size_t count,
loff_t* off)
{
// compute remaining bytes to copy, update count and pointers
ssize_t remaining = BUFFER_SZ - (ssize_t)(*off);
char* ptr = s_buffer + *off;
if (count > remaining) count = remaining;
*off += count;
// copy required number of bytes
if (copy_to_user(buf, ptr, count) != 0) count = -EFAULT;
pr_info("skeleton: read operation... read=%ld\n", count);
return count;
}
static ssize_t skeleton_write(struct file* f,
const char __user* buf,
size_t count,
loff_t* off)
{
// compute remaining space in buffer and update pointers
ssize_t remaining = BUFFER_SZ - (ssize_t)(*off);
// check if still remaining space to store additional bytes
if (count >= remaining) count = -EIO;
// store additional bytes into internal buffer
if (count > 0) {
char* ptr = s_buffer + *off;
*off += count;
ptr[count] = 0; // make sure string is null terminated
if (copy_from_user(ptr, buf, count)) count = -EFAULT;
}
pr_info("skeleton: write operation... written=%ld\n", count);
return count;
}
static struct file_operations skeleton_fops = {
.owner = THIS_MODULE,
.open = skeleton_open,
.read = skeleton_read,
.write = skeleton_write,
.release = skeleton_release,
};
#ifdef USING_MISC
static struct miscdevice misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &skeleton_fops,
.name = "my_misc_module",
.mode = 0,
};
#endif
#ifdef USING_PLATFORM
static void sysfs_dev_release(struct device* dev)
{
pr_info("skeleton - sysfs dev release\n");
}
static struct platform_device platform_device = {
.name = "my_platform_module", .id = -1, .dev.release = sysfs_dev_release};
#endif
#ifdef USING_CLASS
static struct class* sysfs_class;
static struct device* sysfs_device;
#endif
static int __init skeleton_init(void)
{
int status = 0;
#ifdef USING_MISC
if (status == 0) status = misc_register(&misc_device);
if (status == 0)
status = device_create_file(misc_device.this_device, &dev_attr_val);
if (status == 0)
status = device_create_file(misc_device.this_device, &dev_attr_cfg);
#else // ~USING_MISC
status = alloc_chrdev_region(&skeleton_dev, 0, 1, "mymodule");
if (status == 0) {
cdev_init(&skeleton_cdev, &skeleton_fops);
skeleton_cdev.owner = THIS_MODULE;
status = cdev_add(&skeleton_cdev, skeleton_dev, 1);
}
#endif
#ifdef USING_PLATFORM
platform_device.dev.devt = skeleton_dev;
if (status == 0) status = platform_device_register(&platform_device);
if (status == 0)
status = device_create_file(&platform_device.dev, &dev_attr_val);
if (status == 0)
status = device_create_file(&platform_device.dev, &dev_attr_cfg);
#endif
#ifdef USING_CLASS
sysfs_class = class_create(THIS_MODULE, "my_sysfs_class");
sysfs_device =
device_create(sysfs_class, NULL, skeleton_dev, NULL, "my_sysfs_device");
if (status == 0) status = device_create_file(sysfs_device, &dev_attr_val);
if (status == 0) status = device_create_file(sysfs_device, &dev_attr_cfg);
#endif
pr_info("Linux module skeleton loaded\n");
return 0;
}
static void __exit skeleton_exit(void)
{
#ifdef USING_MISC
misc_deregister(&misc_device);
#endif
#ifdef USING_PLATFORM
device_remove_file(&platform_device.dev, &dev_attr_cfg);
device_remove_file(&platform_device.dev, &dev_attr_val);
platform_device_unregister(&platform_device);
#endif
#ifdef USING_CLASS
device_remove_file(sysfs_device, &dev_attr_val);
device_remove_file(sysfs_device, &dev_attr_cfg);
device_destroy(sysfs_class, 0);
class_destroy(sysfs_class);
#endif
#ifndef USING_MISC
cdev_del(&skeleton_cdev);
unregister_chrdev_region(skeleton_dev, 1);
#endif
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");
Device Tree (optionel)
Exercice #6:
Adapter l’implémentation de l’exercice #3 ci-dessus afin que celui-ci utilise un device tree (DT) pour décrire le nombre de périphériques à mettre en œuvre. Le DT sera externe à l’arborescence des sources du noyaux Linux. La structure struct miscdevice peut être utilisée pour instancier les devices et les fichiers d’accès (/dev/...).
Solution
# 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 ../../buildroot_path
include ../../kernel_settings
PWD := $(shell pwd)
INCL+=-I. -I$(KDIR)/include -I$(KDIR)/arch/arm64/boot/dts
DTB = mydt.dtb
DTS = $(DTB:.dtb=.dts)
all: dtb boot
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(CPU) CROSS_COMPILE=$(TOOLS) modules
dtb: $(DTB)
$(DTB) : $(DTS)
ln -s $(KDIR)/arch/arm/boot/dts arm
-cpp $(INCL) -E -P -x assembler-with-cpp $(DTS) | dtc -I dts -O dtb -o $(DTB) -
rm arm
boot:
mkimage -T script -A arm -C none -d boot.cmd boot.ovl
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
rm -f *.dtb *.ovl
install:
$(MAKE) -C $(KDIR) M=$(PWD) INSTALL_MOD_PATH=$(MODPATH) modules_install
endif
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/device.h> /* needed for sysfs handling */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/list.h> /* needed for linked list processing */
#include <linux/miscdevice.h>
#include <linux/module.h> /* needed by all modules */
#include <linux/of.h>
#include <linux/platform_device.h> /* needed for sysfs handling */
#include <linux/uaccess.h> /* needed to copy data to/from user */
#define BUFFER_SZ 10000
struct mydata {
char buffer[BUFFER_SZ];
struct miscdevice misc;
struct list_head node;
};
static int skeleton_open(struct inode* i, struct file* f)
{
pr_info("skeleton : open operation... major:%d, minor:%d, p_data=%llx\n",
imajor(i),
iminor(i),
(long long)f->private_data);
return 0;
}
static int skeleton_release(struct inode* i, struct file* f)
{
pr_info("skeleton: release operation...p_data=%llx\n",
(long long)f->private_data);
return 0;
}
static ssize_t skeleton_read(struct file* f,
char __user* buf,
size_t count,
loff_t* off)
{
struct mydata* mydata = container_of(f->private_data, struct mydata, misc);
// compute remaining bytes to copy, update count and pointers
ssize_t remaining = strlen(mydata->buffer) - (ssize_t)(*off);
char* ptr = mydata->buffer + *off;
if (count > remaining) count = remaining;
*off += count;
// copy required number of bytes
if (copy_to_user(buf, ptr, count) != 0) count = -EFAULT;
pr_info("skeleton: read operation...p_data=%llx read=%ld\n",
(long long)f->private_data,
count);
return count;
}
static ssize_t skeleton_write(struct file* f,
const char __user* buf,
size_t count,
loff_t* off)
{
struct mydata* mydata = container_of(f->private_data, struct mydata, misc);
// compute remaining space in buffer and update pointers
ssize_t remaining = sizeof(mydata->buffer) - (ssize_t)(*off);
char* ptr = mydata->buffer + *off;
*off += count;
// check if still remaining space to store additional bytes
if (count >= remaining) count = -EIO;
// store additional bytes into internal buffer
if (count > 0) {
ptr[count] = 0; // make sure string is null terminated
if (copy_from_user(ptr, buf, count)) count = -EFAULT;
}
pr_info("skeleton: write operation... written=%ld\n", count);
return count;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = skeleton_open,
.read = skeleton_read,
.write = skeleton_write,
.release = skeleton_release,
};
int drv_probe(struct platform_device* pdev)
{
int ret = 0;
struct device_node* dt_node = pdev->dev.of_node;
struct mydata* mydata = 0;
const char* prop_str = 0;
pr_info("skeleton - driver probe %llx (%s)- M.m=%d.%d, dtnode=%p\n",
(unsigned long long)pdev,
pdev->name,
MAJOR(pdev->dev.devt),
MINOR(pdev->dev.devt),
dt_node);
ret = of_property_read_string(dt_node, "attribute", &prop_str);
if (prop_str && ret == 0) pr_info("attribute=%s (ret=%d)\n", prop_str, ret);
if (pdev->num_resources) {
pr_info("resources: name=%s, start=%lld, end=%lld\n",
pdev->resource[0].name,
pdev->resource[0].start,
pdev->resource[0].end);
}
if (dt_node) {
int ret = 0;
const char* prop_str = 0;
const unsigned int* prop_reg = 0;
struct device_node* child = 0;
struct list_head* list =
devm_kzalloc(&pdev->dev, sizeof(struct list_head), GFP_KERNEL);
if (list == 0) {
dev_err(&pdev->dev, "Failed to allocate resource for miscdev\n");
return -ENOMEM;
}
INIT_LIST_HEAD(list);
platform_set_drvdata(pdev, list);
for_each_available_child_of_node(dt_node, child)
{
mydata = devm_kzalloc(&pdev->dev, sizeof(*mydata), GFP_KERNEL);
pr_info("miscdev=%llx\n", (long long)mydata);
if (mydata == 0) {
dev_err(&pdev->dev,
"Failed to allocate resource for miscdev\n");
return -ENOMEM;
}
list_add_tail(&(mydata->node), list);
pr_info("child found: name=%s, fullname=%s\n",
child->name,
child->full_name);
prop_reg = of_get_property(child, "reg", NULL);
if (prop_reg != 0) {
unsigned long reg = of_read_ulong(prop_reg, 1);
pr_info("reg:%lu\n", reg);
}
ret = of_property_read_string(child, "attribute", &prop_str);
if (prop_str && ret == 0)
pr_info("attribute=%s (ret=%d)\n", prop_str, ret);
/* register misc device ... */
mydata->misc.minor = MISC_DYNAMIC_MINOR;
mydata->misc.fops = &fops;
mydata->misc.name = child->full_name;
mydata->misc.mode = 0664;
ret = misc_register(&(mydata->misc));
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register miscdev\n");
return ret;
}
}
}
return 0;
}
int drv_remove(struct platform_device* pdev)
{
struct mydata* mydata;
struct list_head* list = platform_get_drvdata(pdev);
pr_info("skeleton - sysfs driver remove %s\n", pdev->name);
list_for_each_entry(mydata, list, node)
{
misc_deregister(&(mydata->misc));
}
return 0;
}
void drv_shutdown(struct platform_device* pdev)
{
pr_info("skeleton - sysfs driver shutdown %s\n", pdev->name);
}
int drv_suspend(struct platform_device* pdev, pm_message_t state)
{
pr_info("skeleton - sysfs driver suspend %s (state=%d)\n",
pdev->name,
state.event);
return 0;
}
int drv_resume(struct platform_device* pdev)
{
pr_info("skeleton - sysfs driver resume %s\n", pdev->name);
return 0;
}
struct of_device_id of_drv[] = {
{
.compatible = "mydevice",
},
{},
};
MODULE_DEVICE_TABLE(of, of_drv);
static struct platform_driver sysfs_driver = {
.probe = drv_probe,
.remove = drv_remove,
.shutdown = drv_shutdown,
.suspend = drv_suspend,
.resume = drv_resume,
.driver =
{
.name = "mydriver",
.of_match_table = of_match_ptr(of_drv),
},
};
#if 1
static int __init sysfs_driver_init(void)
{
int status = 0;
pr_info("Linux module skeleton loading...\n");
/* install sysfs */
if (status == 0) status = platform_driver_register(&sysfs_driver);
pr_info("Linux module skeleton loaded\n");
return 0;
}
static void __exit sysfs_driver_exit(void)
{
pr_info("Linux module skeleton exiting...\n");
/* uninstall sysfs */
platform_driver_unregister(&sysfs_driver);
pr_info("Linux module skeleton unloaded\n");
}
module_init(sysfs_driver_init);
module_exit(sysfs_driver_exit);
#else
module_platform_driver(sysfs_driver);
#endif
MODULE_AUTHOR("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION("Module skeleton");
MODULE_LICENSE("GPL");
/dts-v1/;
#include "allwinner/sun50i-h5-nanopi-neo-plus2.dts"
/ {
/delete-node/ leds;
mydevice {
compatible = "mydevice";
#address-cells = <1>;
#size-cells = <0>;
attribute = "idle";
mydevice@0 {
reg = <0x0>;
attribute = "on";
};
mydevice@1 {
reg = <0x1>;
attribute = "off";
};
mydevice@2 {
reg = <0x2>;
attribute = "off";
};
mydevice@3 {
reg = <0x3>;
attribute = "off";
};
};
};
setenv bootargs console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootwait
fatload mmc 0 $kernel_addr_r Image
#fatload mmc 0 $fdt_addr_r sun50i-h5-nanopi-neo-plus2.dtb
fatload mmc 0 $fdt_addr_r mydt.dtb
fdt addr $fdt_addr_r
fdt resize
#fdt mknode / mymodule2
#fdt set /mymodule2 compatible mymodule2
booti $kernel_addr_r - $fdt_addr_r
Opérations bloquantes
Exercice #7: Développer un pilote et une application utilisant les entrées/sorties bloquantes pour signaler une interruption matérielle provenant de l’un des switches de la carte d’extension du NanoPI. L’application utilisera le service select pour compter le nombre d’interruptions.
Remarque
les switches n’ont pas d’anti-rebonds, par conséquent il est fort probable que vous comptiez un peu trop d’impulsions; effet à ignorer.
Solution
Device Driver :
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/gpio.h> /* needed for i/o handling */
#include <linux/init.h> /* needed for macros */
#include <linux/interrupt.h> /* needed for interrupt handling */
#include <linux/io.h> /* needed for mmio handling */
#include <linux/ioport.h> /* needed for memory region handling */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/miscdevice.h>
#include <linux/module.h> /* needed by all modules */
#include <linux/poll.h> /* needed for polling handling */
#include <linux/sched.h> /* needed for scheduling constants */
#include <linux/uaccess.h> /* needed to copy data to/from user */
#include <linux/wait.h> /* needed for wating */
#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";
static atomic_t nb_of_interrupts;
DECLARE_WAIT_QUEUE_HEAD(queue);
irqreturn_t gpio_isr(int irq, void* handle)
{
atomic_inc(&nb_of_interrupts);
wake_up_interruptible(&queue);
pr_info("interrupt %s raised...\n", (char*)handle);
return IRQ_HANDLED;
}
static ssize_t skeleton_read(struct file* f,
char __user* buf,
size_t sz,
loff_t* off)
{
return 0;
}
static unsigned int skeleton_poll(struct file* f, poll_table* wait)
{
unsigned mask = 0;
poll_wait(f, &queue, wait);
if (atomic_read(&nb_of_interrupts) != 0) {
mask |= POLLIN | POLLRDNORM; /* read operation */
/* mask |= POLLOUT | POLLWRNORM; write operation */
atomic_dec(&nb_of_interrupts);
pr_info("polling thread waked-up...\n");
}
return mask;
}
static struct file_operations skeleton_fops = {
.owner = THIS_MODULE,
.read = skeleton_read,
.poll = skeleton_poll,
};
struct miscdevice misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &skeleton_fops,
.name = "mymodule",
.mode = 0777,
};
static int __init skeleton_init(void)
{
int status = 0;
atomic_set(&nb_of_interrupts, 0);
status = misc_register(&misc_device);
// install k1
if (status == 0)
status = devm_request_irq(misc_device.this_device,
gpio_to_irq(K1),
gpio_isr,
IRQF_TRIGGER_FALLING | IRQF_SHARED,
k1,
k1);
// install k2
if (status == 0)
status = devm_request_irq(misc_device.this_device,
gpio_to_irq(K2),
gpio_isr,
IRQF_TRIGGER_RISING | IRQF_SHARED,
k2,
k2);
// install k3
if (status == 0)
status = devm_request_irq(
misc_device.this_device,
gpio_to_irq(K3),
gpio_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_SHARED,
k3,
k3);
pr_info("Linux module skeleton loaded(status=%d)\n", status);
return status;
}
static void __exit skeleton_exit(void)
{
misc_deregister(&misc_device);
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");
Application :
EXE=app
SRCS=$(wildcard *.c)
CFLAGS=-Wall -Wextra -g -c -O0 -MD -std=gnu11
TOOLCHAIN_PATH=/buildroot/output/host/usr/bin/
TOOLCHAIN=$(TOOLCHAIN_PATH)aarch64-linux-
CFLAGS+=-mcpu=cortex-a53 -funwind-tables
##CFLAGS+=-O2 -fno-omit-frame-pointer
OBJDIR=.obj/nano
EXEC=$(EXE)
ifeq ($(target),host)
EXEC=$(EXE)_h
endif
CC=$(TOOLCHAIN)gcc
LD=$(TOOLCHAIN)gcc
AR=$(TOOLCHAIN)ar
STRIP=$(TOOLCHAIN)strip
OBJDIR=.obj/$(target)
OBJS= $(addprefix $(OBJDIR)/, $(SRCS:.c=.o))
$(OBJDIR)/%o: %c
$(CC) $(CFLAGS) $< -o $@
all: $(OBJDIR)/ $(EXEC)
$(EXEC): $(OBJS) $(LINKER_SCRIPT)
$(LD) $(OBJS) $(LDFLAGS) -o $@
$(OBJDIR)/:
mkdir -p $(OBJDIR)
clean:
rm -Rf $(OBJDIR) $(EXEC) $(EXEC)_s *~
clean_all: clean
rm -Rf .obj $(EXE) $(EXE)_s $(EXE)_a $(EXE)_a_s $(EXE)_h $(EXE)_h_s
-include $(OBJS:.o=.d)
.PHONY: all clean clean_all
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
char* dev = "/dev/mymodule";
if (argc > 1) dev = argv[1];
int fd = open(dev, O_RDWR);
fd_set set;
FD_ZERO(&set);
int interrupts = 0;
while (1) {
FD_SET(fd, &set);
int status = select(fd + 1, &set, 0, 0, 0);
if (status == -1) printf("error while waiting on signal...\n");
if (FD_ISSET(fd, &set)) {
interrupts++;
printf("Event %d occurred\n", interrupts);
}
printf("waked-up(%d), status=%d...\n", fd, status);
}
close(fd);
return 0;
}
Pilotes orientés mémoire (optionel)
Exercice #8: Sur la base de l’exercice 1, développer un pilote orienté caractère permettant de mapper en espace utilisateur ces registres (implémentation de l’opération de fichier « mmap »). Le driver orienté mémoire sera ensuite adapté à cette nouvelle interface.
Remarque
à effectuer après les exercices des pilotes orientés caractère.
Solution
Device Driver :
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/device.h> /* needed for sysfs handling */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/mm.h> /* needed for mmap handling */
#include <linux/module.h> /* needed by all modules */
#include <linux/platform_device.h> /* needed for sysfs handling */
#include <linux/uaccess.h> /* needed to copy data to/from user */
static int skeleton_mmap(struct file* f, struct vm_area_struct* vma)
{
int status = 0;
unsigned long size = vma->vm_end - vma->vm_start;
if (size > PAGE_SIZE) status = -EINVAL;
vma->vm_pgoff = 0x01c14000 >> PAGE_SHIFT;
if (status == 0)
status = remap_pfn_range(
vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot);
if (status != 0) status = -EAGAIN;
pr_info("skeleton: mmap (size=%lu, shift=%d) status=%d\n",
size,
PAGE_SHIFT,
status);
return status;
}
static struct file_operations skeleton_fops = {
.owner = THIS_MODULE,
.mmap = skeleton_mmap,
};
static dev_t skeleton_dev;
static struct cdev skeleton_cdev;
static void sysfs_dev_release(struct device* dev)
{
pr_info("skeleton - sysfs dev release\n");
}
static struct platform_device sysfs_device;
static int __init skeleton_init(void)
{
int status;
status = alloc_chrdev_region(&skeleton_dev, 0, 1, "mymodule");
if (status == 0) {
cdev_init(&skeleton_cdev, &skeleton_fops);
skeleton_cdev.owner = THIS_MODULE;
status = cdev_add(&skeleton_cdev, skeleton_dev, 1);
}
sysfs_device.name = "mymodule";
sysfs_device.id = -1;
sysfs_device.dev.release = sysfs_dev_release;
sysfs_device.dev.devt = skeleton_dev;
if (status == 0) status = platform_device_register(&sysfs_device);
pr_info("Linux module skeleton loaded\n");
return 0;
}
static void __exit skeleton_exit(void)
{
platform_device_unregister(&sysfs_device);
cdev_del(&skeleton_cdev);
unregister_chrdev_region(skeleton_dev, 1);
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");
Application :
/**
* Copyright 2015 University of Applied Sciences Western Switzerland / Fribourg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Module: MA-CSEL1 - Building Embedded Systems under Linux
*
* Abstract: Introduction to the Embedded Linux Development Environment
*
* Purpose: Core dump demo program.
* Before to call this demo program don't forget set
* $ ulimit -c 10000
*
* Autĥor: Daniel Gachet
* Date: 28.08.2015
*/
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/mman.h>
#include <unistd.h>
int main()
{
/* open memory file descriptor */
int fd = open("/dev/mymodule", O_RDWR);
if (fd < 0) {
printf("Could not open /dev/mem: error=%i\n", fd);
return -1;
}
/* map to user space APF27 FPGA registers */
volatile uint32_t* regs =
mmap(0, 0x100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (regs == (void*)-1) {
printf("mmap failed, error: %i:%s \n", errno, strerror(errno));
return -1;
}
uint32_t chipid[4] = {
[0] = *(regs + 0x200 / sizeof(uint32_t)),
[1] = *(regs + 0x204 / sizeof(uint32_t)),
[2] = *(regs + 0x208 / sizeof(uint32_t)),
[3] = *(regs + 0x20c / sizeof(uint32_t)),
};
printf("NanoPi NEO Plus2 chipid=%08x'%08x'%08x'%08x\n",
chipid[0],
chipid[1],
chipid[2],
chipid[3]);
munmap((void*)chipid, 0x100);
close(fd);
return 0;
}
Ioctl (optionel)
Exercice #9: Implémenter à l’intérieur d’un pilote de périphérique l’opération ioctl afin de pouvoir:
- Envoyer une commande
- Ecrire et lire une valeur entière
- Ecrire et lire un bloc de configuration de plus de 50 octets
Afin de valider le pilote, développer une petite application permettant d’effectuer ces opérations et de les valider.
Solution
Device Driver :
#ifndef SKELETON_H
#define SKELETON_H
#include <linux/ioctl.h>
struct skeleton_config {
int id;
long ref;
char name[30];
char descr[30];
};
#define SKELETON_IOMAGIC 'g'
#define SKELETON_IO_RESET _IO(SKELETON_IOMAGIC, 0)
#define SKELETON_IO_WR_REF _IOW(SKELETON_IOMAGIC, 1, struct skeleton_config)
#define SKELETON_IO_RD_REF _IOR(SKELETON_IOMAGIC, 2, struct skeleton_config)
#define SKELETON_IO_WR_VAL _IOW(SKELETON_IOMAGIC, 3, int)
#define SKELETON_IO_RD_VAL _IOR(SKELETON_IOMAGIC, 4, int)
#endif
/* skeleton.c */
#include "skeleton.h"
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/ioctl.h> /* needed for ioctl handling */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/module.h> /* needed by all modules */
#include <linux/uaccess.h> /* needed to copy data to/from user */
static struct skeleton_config config;
static int val;
static long skeleton_ioctl(struct file* f, unsigned int cmd, unsigned long arg)
{
int status = 0;
switch (cmd) {
case SKELETON_IO_RESET:
val = 0;
memset(&config, 0, sizeof(config));
pr_info("skeleton-ioctl: reset command\n");
break;
case SKELETON_IO_WR_REF:
if (_IOC_SIZE(cmd) == sizeof(config))
status =
copy_from_user(&config, (char __user*)arg, _IOC_SIZE(cmd));
else
status = -EFAULT;
pr_info("skeleton-ioctl: write config\n");
break;
case SKELETON_IO_RD_REF:
if (_IOC_SIZE(cmd) == sizeof(config))
status =
copy_to_user((char __user*)arg, &config, _IOC_SIZE(cmd));
else
status = -EFAULT;
pr_info("skeleton-ioctl: read config\n");
break;
case SKELETON_IO_WR_VAL:
val = arg;
pr_info("skeleton-ioctl: write value=%d\n", val);
break;
case SKELETON_IO_RD_VAL:
status = val;
pr_info("skeleton-ioctl: read value=%d\n", status);
break;
default:
pr_info("skeleton-ioctl: unknown command (cmd=%d)\n", cmd);
status = -EFAULT;
break;
}
return status;
}
static struct file_operations skeleton_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = skeleton_ioctl,
};
static dev_t skeleton_dev;
static struct cdev skeleton_cdev;
static int __init skeleton_init(void)
{
int status;
status = alloc_chrdev_region(&skeleton_dev, 0, 1, "mymodule");
if (status == 0) {
cdev_init(&skeleton_cdev, &skeleton_fops);
skeleton_cdev.owner = THIS_MODULE;
status = cdev_add(&skeleton_cdev, skeleton_dev, 1);
}
pr_info("Linux module skeleton loaded\n");
return 0;
}
static void __exit skeleton_exit(void)
{
cdev_del(&skeleton_cdev);
unregister_chrdev_region(skeleton_dev, 1);
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");
Application :
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "../drv/skeleton.h"
int main(int argc, char* argv[])
{
if (argc <= 1) return 0;
char* f_name = argv[1];
/* open memory file descriptor */
int fd = open(f_name, O_RDWR);
if (fd < 0) {
printf("Could not open /dev/mymodule: error=%i\n", fd);
return -1;
}
int io;
struct skeleton_config w = {
.id = 10,
.ref = 20,
.name = "a name",
.descr = "a description",
};
struct skeleton_config r;
io = ioctl(fd, SKELETON_IO_RESET);
printf(" SKELETON_IO_RESET io=%d\n", io);
io = ioctl(fd, SKELETON_IO_RD_REF, &r);
printf(" SKELETON_IO_RD_REF io=%d, r=%s,%s,%d,%ld\n",
io,
r.name,
r.descr,
r.id,
r.ref);
io = ioctl(fd, SKELETON_IO_WR_REF, &w);
printf(" SKELETON_IO_WR_REF io=%d, r=%s,%s,%d,%ld\n",
io,
w.name,
w.descr,
w.id,
w.ref);
io = ioctl(fd, SKELETON_IO_RD_REF, &r);
printf(" SKELETON_IO_RD_REF io=%d, r=%s,%s,%d,%ld\n",
io,
r.name,
r.descr,
r.id,
r.ref);
io = ioctl(fd, SKELETON_IO_RD_VAL);
printf(" SKELETON_IO_RD_VAL io=%d, r=%d\n", io, io);
io = ioctl(fd, SKELETON_IO_WR_VAL, 10);
printf(" SKELETON_IO_WR_VAL io=%d, r=%d\n", io, 10);
io = ioctl(fd, SKELETON_IO_RD_VAL);
printf(" SKELETON_IO_RD_VAL io=%d, r=%d\n", io, io);
io = ioctl(fd, SKELETON_IO_RESET);
printf(" SKELETON_IO_RESET io=%d\n", io);
io = ioctl(fd, SKELETON_IO_RD_REF, &r);
printf(" SKELETON_IO_RD_REF io=%d, r=%s,%s,%d,%ld\n",
io,
r.name,
r.descr,
r.id,
r.ref);
io = ioctl(fd, SKELETON_IO_RD_VAL);
printf(" SKELETON_IO_RD_VAL io=%d, r=%d\n", io, io);
io = ioctl(fd, 10);
printf(" 10 io=%d\n", io);
close(fd);
return 0;
}
procfs (optionel)
Exercice #10: Implémenter à l’intérieur d’un pilote de périphérique les opérations nécessaires afin de pouvoir lire un bloc de configuration et de pouvoir modifier le contenu de la valeur entière par procfs. Seules les commandes echo et cat doivent être nécessaires pour manipuler ces attributs.
Solution
/* skeleton.c */
#include <linux/cdev.h> /* needed for char device driver */
#include <linux/fs.h> /* needed for device drivers */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/module.h> /* needed by all modules */
#include <linux/proc_fs.h> /* needed for procfs handling */
#include <linux/uaccess.h> /* needed to copy data to/from user */
struct skeleton_config {
int id;
long ref;
char name[50];
char descr[50];
};
static struct skeleton_config config = {.id = 11,
.ref = 33,
.name = "config structure",
.descr = "procfs test driver"};
static int val = 55;
static ssize_t skeleton_read_config(struct file* f,
char __user* buf,
size_t count,
loff_t* off)
{
char temp[200];
int len = snprintf(temp,
sizeof(temp),
"id=%d\nref=%ld\nname=%s\ndescr=%s\n",
config.id,
config.ref,
config.name,
config.descr);
len -= (ssize_t)(*off);
if (count > len) count = len;
*off += count;
if (copy_to_user(buf, temp, count) != 0) count = -EFAULT;
return count;
}
static const struct proc_ops fops_config = {
.proc_read = skeleton_read_config,
};
static ssize_t skeleton_read_val(struct file* f,
char __user* buf,
size_t count,
loff_t* off)
{
char temp[20];
int len = snprintf(temp, sizeof(temp), "%d\n", val);
len -= (ssize_t)(*off);
if (count > len) count = len;
*off += count;
if (copy_to_user(buf, temp, count) != 0) count = -EFAULT;
return count;
}
static ssize_t skeleton_write_val(struct file* f,
const char __user* buf,
size_t count,
loff_t* off)
{
char temp[20];
if (count > sizeof(temp)) return -EIO;
if (copy_from_user(temp, buf, count)) return -EFAULT;
val = simple_strtol(temp, 0, 10);
return count;
}
static const struct proc_ops fops_val = {
.proc_read = skeleton_read_val,
.proc_write = skeleton_write_val,
};
static struct proc_dir_entry* procfs_dir = 0;
static int __init skeleton_init(void)
{
int status = 0;
/* create procfs node and attributes */
procfs_dir = proc_mkdir("mymodule", NULL);
proc_create("config", 0, procfs_dir, &fops_config);
proc_create("val", 0, procfs_dir, &fops_val);
if (procfs_dir == 0) status = -EFAULT;
pr_info("Linux module skeleton loaded (status=%d)\n", status);
return status;
}
static void __exit skeleton_exit(void)
{
/* remove procfs attributes and node */
remove_proc_entry("val", procfs_dir);
remove_proc_entry("config", procfs_dir);
remove_proc_entry("mymodule", NULL);
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");