Solución:
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 archivo adecuadas, así como cómo permitir que el espacio de usuario complete una estructura con ioctl ().
En realidad, es un tutorial excelente y funcional para cualquiera que necesite escribir controladores de dispositivos de caracteres triviales.
Analicé la interfaz ioctl de softdog cuando respondí mi propia pregunta, lo que puede ser útil para usted.
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 miramos 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 al espacio de usuario correspondiente a ioctl () devuelva -1 con un errno significativo establecido.
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 llevarlo 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 entero, lo que sea, al kernel esperando que el kernel escriba su respuesta en un objeto idéntico y copie los resultados a la dirección que se proporcionó.
Lo segundo que tendrá que hacer es asegurarse de que su dispositivo sepa qué hacer cuando alguien lo abre, lee, escribe en él o usa un gancho 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 de kernel desaprueban la adición de nuevas interfaces ioctl a menos que sean absolutamente necesarias. Es demasiado fácil perder de vista el 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 sueño 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 del kernel de Linux, específicamente los capítulos 4 (Archivos de dispositivo de caracteres) y 7 (Hablar 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.
Ejemplo mínimo ejecutable
Probado en un entorno QEMU + Buildroot completamente reproducible, por lo que podría ayudar a otros a obtener su ioctl
laboral. GitHub upstream: módulo de kernel | encabezado compartido | userland.
La parte más molesta fue comprender que se secuestraron algunos identificadores bajos: ioctl no se llama si cmd = 2, debe usar _IOx
macros.
Módulo de kernel:
#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 = %xn", 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 = %dn", 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 %dn", 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 área de 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
Userland:
#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 = %dn", arg_int);
printf("ret = %dn", ret);
printf("errno = %dn", 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 %dn", arg_struct.i, arg_struct.j);
printf("ret = %dn", ret);
printf("errno = %dn", errno);
}
close(fd);
return EXIT_SUCCESS;
}