Minimální spustitelný příklad
Testováno v plně reprodukovatelném prostředí QEMU + Buildroot, takže může pomoci ostatním získat jejich ioctl
pracovní. GitHub upstream:modul jádra |sdílená hlavička |userland.
Nejotravnější na tom bylo pochopení, že některá nízká id jsou unesena:ioctl se nezavolá, pokud cmd =2 , musíte použít _IOx
makra.
Modul jádra:
#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)
Sdílená hlavička mezi modulem jádra a uživatelskou zemí:
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
Uživatelská země:
#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;
}
Příklad kódu, který potřebujete, najdete v drivers/watchdog/softdog.c
(z Linuxu 2.6.33 v době, kdy to bylo napsáno), což ilustruje správné operace se soubory a také to, jak povolit uživatelské zemi vyplnit strukturu pomocí ioctl().
Je to vlastně skvělý, fungující návod pro každého, kdo potřebuje napsat triviální ovladače znakových zařízení.
Rozhraní ioctl softdogu jsem rozebral, když jsem odpovídal na svou vlastní otázku, což by vám mohlo být užitečné.
Zde je jeho podstata (i když zdaleka ne vyčerpávající) ...
V softdog_ioctl()
vidíte jednoduchou inicializaci struct watchdog_info, která inzeruje funkčnost, verzi a informace o zařízení:
static const struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
Poté se podíváme na jednoduchý případ, kdy uživatel chce pouze získat tyto schopnosti:
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
... což samozřejmě vyplní odpovídající watchdog_info v uživatelském prostoru výše inicializovanými hodnotami. Pokud copy_to_user() selže, je vráceno -EFAULT, což způsobí, že odpovídající volání uživatelského prostoru ioctl() vrátí -1 a nastaví se smysluplné errno.
Všimněte si, že magické požadavky jsou ve skutečnosti definovány v linux/watchdog.h , takže je jádro a uživatelský prostor sdílí:
#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 zjevně znamená "Watchdog ioctl"
Můžete to snadno udělat o krok dále, nechat váš ovladač něco udělat a umístit výsledek toho něčeho do struktury a zkopírovat jej do uživatelského prostoru. Pokud má například struct watchdog_info také člena __u32 result_code
. Poznámka:__u32
je pouze jaderná verze uint32_t
.
Pomocí ioctl() uživatel předá adresu objektu, ať už je to struktura, celé číslo, cokoliv, jádru a očekává, že jádro zapíše svou odpověď do identického objektu a zkopíruje výsledky na poskytnutou adresu.
Druhá věc, kterou budete muset udělat, je ujistit se, že vaše zařízení ví, co má dělat, když jej někdo otevře, čte z něj, zapisuje do něj nebo používá háček jako ioctl(), což můžete snadno zjistit studiem softdog.
Zajímavostí je:
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,
};
Kde vidíte, že handler unlocked_ioctl jde do ... uhodli jste, softdog_ioctl().
Myslím, že při práci s ioctl() možná stavíte vedle sebe vrstvu složitosti, která ve skutečnosti neexistuje, je to opravdu tak jednoduché. Ze stejného důvodu se většina vývojářů jádra mračí na přidávání nových rozhraní ioctl, pokud to není nezbytně nutné. Je příliš snadné ztratit přehled o typu, který ioctl() naplní, v porovnání s kouzlem, které k tomu použijete, což je hlavní důvod, proč copy_to_user() často selhává, což má za následek hnijící jádro s hordami procesů v uživatelském prostoru uvízlých v režim spánku disku.
Pro časovač, souhlasím, je ioctl() nejkratší cestou k příčetnosti.
Chybí vám .open
ukazatel funkce ve vašem file_operations
struktura k určení funkce, která má být volána, když se proces pokusí otevřít soubor zařízení. Budete muset zadat .ioctl
ukazatel funkce také pro vaši funkci ioctl.
Zkuste si přečíst The Linux Kernel Module Programming Guide, konkrétně kapitoly 4 (Soubory znakových zařízení) a 7 (Talking to Device Files).
Kapitola 4 představuje file_operations
struktura, která obsahuje ukazatele na funkce definované modulem/ovladačem, které provádějí různé operace, jako je open
nebo ioctl
.
Kapitola 7 poskytuje informace o komunikaci s modulem/jednotkou přes ioctls.
Dalším dobrým zdrojem jsou ovladače zařízení Linux, třetí vydání.