Ejemplo ejecutable mínimo
Probado en un entorno QEMU + Buildroot totalmente reproducible, por lo que podría ayudar a otros a obtener su ioctl
laboral. Upstream de GitHub:módulo kernel | encabezado compartido | área de usuario.
La parte más molesta fue entender que algunas identificaciones bajas están secuestradas:no se llama a ioctl si cmd =2, debe usar _IOx
macros.
Módulo del núcleo:
#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include "ioctl.h"
MODULE_LICENSE("GPL");
static struct dentry *dir;
static long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long argp)
{
void __user *arg_user;
union {
int i;
lkmc_ioctl_struct s;
} arg_kernel;
arg_user = (void __user *)argp;
pr_info("cmd = %x\n", cmd);
switch (cmd) {
case LKMC_IOCTL_INC:
if (copy_from_user(&arg_kernel.i, arg_user, sizeof(arg_kernel.i))) {
return -EFAULT;
}
pr_info("0 arg = %d\n", arg_kernel.i);
arg_kernel.i += 1;
if (copy_to_user(arg_user, &arg_kernel.i, sizeof(arg_kernel.i))) {
return -EFAULT;
}
break;
case LKMC_IOCTL_INC_DEC:
if (copy_from_user(&arg_kernel.s, arg_user, sizeof(arg_kernel.s))) {
return -EFAULT;
}
pr_info("1 arg = %d %d\n", arg_kernel.s.i, arg_kernel.s.j);
arg_kernel.s.i += 1;
arg_kernel.s.j -= 1;
if (copy_to_user(arg_user, &arg_kernel.s, sizeof(arg_kernel.s))) {
return -EFAULT;
}
break;
default:
return -EINVAL;
break;
}
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = unlocked_ioctl
};
static int myinit(void)
{
dir = debugfs_create_dir("lkmc_ioctl", 0);
/* ioctl permissions are not automatically restricted by rwx as for read / write,
* but we could of course implement that ourselves:
* https://stackoverflow.com/questions/29891803/user-permission-check-on-ioctl-command */
debugfs_create_file("f", 0, dir, NULL, &fops);
return 0;
}
static void myexit(void)
{
debugfs_remove_recursive(dir);
}
module_init(myinit)
module_exit(myexit)
Encabezado compartido entre el módulo del kernel y el espacio del usuario:
ioctl.h
#ifndef IOCTL_H
#define IOCTL_H
#include <linux/ioctl.h>
typedef struct {
int i;
int j;
} lkmc_ioctl_struct;
#define LKMC_IOCTL_MAGIC 0x33
#define LKMC_IOCTL_INC _IOWR(LKMC_IOCTL_MAGIC, 0, int)
#define LKMC_IOCTL_INC_DEC _IOWR(LKMC_IOCTL_MAGIC, 1, lkmc_ioctl_struct)
#endif
Zona de usuario:
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "../ioctl.h"
int main(int argc, char **argv)
{
int fd, arg_int, ret;
lkmc_ioctl_struct arg_struct;
if (argc < 2) {
puts("Usage: ./prog <ioctl-file>");
return EXIT_FAILURE;
}
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
/* 0 */
{
arg_int = 1;
ret = ioctl(fd, LKMC_IOCTL_INC, &arg_int);
if (ret == -1) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("arg = %d\n", arg_int);
printf("ret = %d\n", ret);
printf("errno = %d\n", errno);
}
puts("");
/* 1 */
{
arg_struct.i = 1;
arg_struct.j = 1;
ret = ioctl(fd, LKMC_IOCTL_INC_DEC, &arg_struct);
if (ret == -1) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("arg = %d %d\n", arg_struct.i, arg_struct.j);
printf("ret = %d\n", ret);
printf("errno = %d\n", errno);
}
close(fd);
return EXIT_SUCCESS;
}
El código de ejemplo que necesita se puede encontrar en drivers/watchdog/softdog.c
(de Linux 2.6.33 en el momento en que se escribió esto), que ilustra las operaciones de archivos adecuadas y cómo permitir que el espacio de usuario llene una estructura con ioctl().
En realidad, es un gran tutorial funcional para cualquier persona que necesite escribir controladores de dispositivos de caracteres triviales.
Analicé la interfaz ioctl de softdog al responder mi propia pregunta, lo que puede resultarle útil.
Aquí está la esencia (aunque lejos de ser exhaustiva)...
En softdog_ioctl()
verá una inicialización simple de struct watchdog_info que anuncia la funcionalidad, la versión y la información del dispositivo:
static const struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
Luego observamos un caso simple en el que el usuario solo desea obtener estas capacidades:
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
... que, por supuesto, llenará el espacio de usuario correspondiente watchdog_info con los valores inicializados arriba. Si copy_to_user() falla, se devuelve -EFAULT, lo que hace que la llamada ioctl() del espacio de usuario correspondiente devuelva -1 con un errno significativo configurado.
Tenga en cuenta que las solicitudes mágicas están realmente definidas en linux/watchdog.h, de modo que el kernel y el espacio de usuario las comparten:
#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
WDIOC obviamente significa "Watchdog ioctl"
Puede llevar eso un paso más allá fácilmente, haciendo que su controlador haga algo y coloque el resultado de ese algo en la estructura y lo copie en el espacio de usuario. Por ejemplo, si struct watchdog_info también tuviera un miembro __u32 result_code
. Nota, __u32
es solo la versión del kernel de uint32_t
.
Con ioctl(), el usuario pasa la dirección de un objeto, ya sea una estructura, un número entero, lo que sea, al núcleo esperando que el núcleo escriba su respuesta en un objeto idéntico y copie los resultados en la dirección proporcionada.
Lo segundo que deberá hacer es asegurarse de que su dispositivo sepa qué hacer cuando alguien lo abra, lea, escriba o use un enlace como ioctl(), que puede ver fácilmente al estudiar softdog.
De interés es:
static const struct file_operations softdog_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = softdog_write,
.unlocked_ioctl = softdog_ioctl,
.open = softdog_open,
.release = softdog_release,
};
Donde ves que el controlador unlocked_ioctl va a... lo has adivinado, softdog_ioctl().
Creo que podría estar yuxtaponiendo una capa de complejidad que realmente no existe cuando se trata de ioctl(), realmente es así de simple. Por esa misma razón, la mayoría de los desarrolladores del kernel fruncen el ceño ante la adición de nuevas interfaces ioctl a menos que sean absolutamente necesarias. Es demasiado fácil perder la noción del tipo que ioctl() va a llenar frente a la magia que usa para hacerlo, que es la razón principal por la que copy_to_user() falla a menudo, lo que hace que el kernel se pudra con hordas de procesos de espacio de usuario atascados suspensión del disco.
Para un temporizador, estoy de acuerdo, ioctl() es el camino más corto hacia la cordura.
Te falta un .open
puntero de función en su file_operations
estructura para especificar la función que se llamará cuando un proceso intente abrir el archivo del dispositivo. Deberá especificar un .ioctl
puntero de función para su función ioctl también.
Intente leer la Guía de programación del módulo kernel de Linux, específicamente los capítulos 4 (Archivos de dispositivo de caracteres) y 7 (Hablando con archivos de dispositivo).
El capítulo 4 presenta el file_operations
estructura, que contiene punteros a funciones definidas por el módulo/controlador que realizan varias operaciones como open
o ioctl
.
El Capítulo 7 proporciona información sobre la comunicación con un módulo/unidad a través de ioctls.
Controladores de dispositivos Linux, tercera edición es otro buen recurso.