I'm starting to learn Linux driver development. I am trying to create a simple driver that polls a function which in the end will read a hardware register at a constant rate (i.e. 10 times a second) and adds the hardware output to a queue which can then be accessed by procfs.
First things first. I need to be able to poll at a consistent rate. I have been reading this online a lot and it seems very simple (my code below). However, when I insmod my module, it doesn't seem to poll at all!!
Can someone please help me understand this and help me figure out what I need to do to make it poll?
I really appreciate you guys' help!
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/version.h>
#include <linux/sched.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/poll.h>
MODULE_LICENSE("Dual BSD/GPL");
int silly_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "open\n");
return 0;
}
int silly_release(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "release\n");
return 0;
}
ssize_t silly_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "read\n");
return 0;
}
ssize_t silly_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
printk(KERN_ALERT "write\n");
return 0;
}
unsigned int silly_poll(struct file *filp, poll_table *wait)
{
printk(KERN_ALERT "poll\n");
return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}
struct file_operations silly_fops = {
.read = silly_read,
.write = silly_write,
.poll = silly_poll,
.open = silly_open,
.release = silly_release,
.owner = THIS_MODULE
};
int silly_init(void)
{ printk(KERN_ALERT "init\n");
return 0;
}
static int hello_init(void)
{
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
In hello_init(), you don't connect silly_fops to it. It cannot work automatically if there is no any connection between them.
In order to connect, you probably need to initialize a device with silly_fops(), refer to ch3.4 of ldd3 at http://www.makelinux.net/ldd3/, hope this works.
Related
I'm learning about Linux kernel module with Ubuntu 20.04 (Linux kernel 5.4.0-37 generic). The following is the code that actual code.
I have expected to following module to that pass single byte random number between 0-255 (get_random_bytes(&c, 1)) to user space buffer when it handle ->read() syscall then print a message to dmesg.
But Unfortunately, for now, it does not work. It seems like does not add handle of ->read() and ->open() syscalls.
Why it does not handle ->read() and ->open() syscall?
User space applicattion code (open and read device file) app.c:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
int main() {
char c[8];
memset(c, '\0', 8);
int fd = open("/dev/devsaikoro0", O_RDONLY);
read(fd, &c, 1);
//printf("Hello\n");
printf("%s\n", c);
}
Kernel module code:
#include <linux/init.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/random.h>
MODULE_LICENSE("Dual BSD/GPL");
static int devsaikoro_num = 3;
static int devsaikoro_major = 0;
static int devsaikoro_minor = 0;
static struct cdev devsaikoro_cdev;
static struct class *devsaikoro_class = NULL;
static dev_t saikoro_dev;
ssize_t read_num(struct file * filp, char __user *buf, size_t count, loff_t *f_pos)
{
int retval;
char c;
get_random_bytes(&c, 1);
if (copy_to_user(buf, &c, 1)) {
printk("devsaikoro read failed\n");
retval = -EFAULT;
return retval;
} else {
printk("devsaikoro read succeeded\n");
return 1;
}
}
struct file_operations fops = {
.read = read_num,
};
int saikoro_open(struct inode *inode, struct file *file) {
printk("devsaikoro open\n");
file->f_op = &fops;
return 0;
}
struct file_operations fops2 = {
.open = saikoro_open,
};
static int devsaikoro_init(void)
{
dev_t dev = MKDEV(devsaikoro_major, 0);
int alloc_ret = 0;
int major;
int cdev_err = 0;
struct device *class_dev = NULL;
alloc_ret = alloc_chrdev_region(&dev, 0, devsaikoro_num, "devsaikoro");
if (alloc_ret) {
goto error;
}
devsaikoro_major = major = MAJOR(dev);
cdev_init(&devsaikoro_cdev, &fops2);
devsaikoro_cdev.owner = THIS_MODULE;
cdev_err = cdev_add(&devsaikoro_cdev, MKDEV(devsaikoro_major, 0), devsaikoro_num);
if (cdev_err)
goto error;
devsaikoro_class = class_create(THIS_MODULE, "devsaikoro");
if (IS_ERR(devsaikoro_class))
goto error;
saikoro_dev = MKDEV(devsaikoro_major, devsaikoro_minor);
class_dev = device_create(devsaikoro_class, NULL, saikoro_dev, NULL, "devsaikoro%d", devsaikoro_minor);
printk(KERN_ALERT "devsaikoro_driver (major %d) installed\n", major);
return 0;
error:
if (cdev_err == 0) {
cdev_del(&devsaikoro_cdev);
}
if (alloc_ret == 0) {
unregister_chrdev_region(dev, devsaikoro_num);
}
return -1;
}
static void devsaikoro_exit(void)
{
dev_t dev = MKDEV(devsaikoro_major, 0);
device_destroy(devsaikoro_class, saikoro_dev);
class_destroy(devsaikoro_class);
cdev_del(&devsaikoro_cdev);
unregister_chrdev_region(dev, devsaikoro_num);
printk(KERN_ALERT "devsaikoro driver removed\n");
}
module_init(devsaikoro_init);
module_exit(devsaikoro_exit);
I am trying to implement a system call interception for sys_open() call via kernel module and for that I have defined a miscellaneous device driver MyDevice which can be inserted as kernel module. Below is the code for my kernel module:
#include <linux/version.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/highmem.h>
#include <asm/unistd.h>
MODULE_LICENSE("GPL");
// IOCTL commands
#define IOCTL_PATCH_TABLE 0x00000001
#define IOCTL_FIX_TABLE 0x00000004
//Global variables
int in_use = 0; //set to 1 in open handler and reset to zero in release handler
int is_set = 0; // flag to detect system call interception
unsigned long *sys_call_table = (unsigned long*)0xffffffff81801400; //hard coded address of sys_call_table from /boot/System.map
//function pointer to original sys_open
asmlinkage int (*real_open)(const char* __user, int, int);
//Replacement of original call with modified system call
asmlinkage int custom_open(const char* __user file_name, int flags, int mode)
{
printk("interceptor: open(\"%s\", %X, %X)\n", file_name,flags,mode);
return real_open(file_name,flags,mode);
}
/*
Make the memory page writable
This is little risky as directly arch level protection bit is changed
*/
int make_rw(unsigned long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);
if(pte->pte &~ _PAGE_RW)
pte->pte |= _PAGE_RW;
return 0;
}
/* Make the page write protected */
int make_ro(unsigned long address)
{
unsigned int level;
pte_t *pte = lookup_address(address, &level);
pte->pte = pte->pte &~ _PAGE_RW;
return 0;
}
/* This function will be invoked each time a user process attempts
to open my device. You should keep in mind that the prototype
of this function may change along different kernel versions. */
static int my_open(struct inode *inode, struct file *file)
{
/*Do not allow multiple processes to open this device*/
if(in_use)
return -EBUSY;
in_use++;
printk("MyDevice opened\n");
return 0;
}
/* This function, in turn, will be called when a process closes our device */
static int my_release(struct inode *inode, struct file *file)
{
in_use--;
printk("MyDevice closed\n");
return 0;
}
/*This static function handles ioctl calls performed on MyDevice*/
static int my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int retval = 0;
switch(cmd)
{
case IOCTL_PATCH_TABLE:
make_rw((unsigned long)sys_call_table);
real_open = (void*)*(sys_call_table + __NR_open);
*(sys_call_table + __NR_open) = (unsigned long)custom_open;
make_ro((unsigned long)sys_call_table);
is_set=1;
break;
case IOCTL_FIX_TABLE:
make_rw((unsigned long)sys_call_table);
*(sys_call_table + __NR_open) = (unsigned long)real_open;
make_ro((unsigned long)sys_call_table);
is_set=0;
break;
default:
printk("sys_open not executed\n");
break;
}
return retval;
}
//populate data struct for file operations
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = &my_open,
.release = &my_release,
.unlocked_ioctl = (void*)&my_ioctl,
.compat_ioctl = (void*)&my_ioctl
};
//populate miscdevice data structure
static struct miscdevice my_device = {
MISC_DYNAMIC_MINOR,
"MyDevice",
&my_fops
};
static int __init init_my_module(void)
{
int retval;
printk(KERN_INFO "Inside kernel space\n");
retval = misc_register(&my_device);
return retval;
}
static void __exit cleanup_my_module(void)
{
if (is_set)
{
make_rw((unsigned long)sys_call_table);
*(sys_call_table + __NR_open) = (unsigned long)real_open;
make_ro((unsigned long)sys_call_table);
}
misc_deregister(&my_device);
printk(KERN_INFO "Exiting kernel space\n");
return;
}
module_init(init_my_module);
module_exit(cleanup_my_module);
The code for my test file is as follows:
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* Define ioctl commands */
#define IOCTL_PATCH_TABLE 0x00000001
#define IOCTL_FIX_TABLE 0x00000004
int main(void)
{
int device = open("/dev/MyDevice", O_RDWR);
printf("%d\n",device);
ioctl(device, IOCTL_PATCH_TABLE);
sleep(2);
ioctl(device, IOCTL_FIX_TABLE);
close(device);
return 0;
}
The problem is that in my test file open("/dev/MyDevice", O_RDWR); is always returning -1, why is it so ? Where am I going wrong ? I checked with ls -l /dev/MyDevice, MyDevice has been successfully registered with following details: crw------- 1 root root 10, 56 Dec 9 19:33 /dev/MyDevice
Sorry, seriously stupid mistake, that's what happens when rookies do things. I just needed to grant the read and write permissions for my miscellaneous char device driver.
sudo chmod a+r+w /dev/MyDevice
I try to implement a write function to a debugfs file. I hope I can use echo "hello" > /sys/kernel/debugfs/mydir/myfile to write a string to the file. And use echo "world" >> /sys/kernel/debugfs/mydir/myfile to append world after hello. I found two problem in my implementation. One is the echo command would stuck if the length of input string is over the buffer size. The other is the echo "world" >> /sys/kernel/debugfs/mydir/myfile never append the string. Instead, it new a string. Below is my implementation.
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h> /* Needed for the macros */
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/sched.h>
MODULE_LICENSE("GPL");
#define BUF_SIZE 10
static char foo_buf[BUF_SIZE];
static struct dentry *debug_dir;
static struct dentry *debug_foo;
static ssize_t foo_read(struct file *file, char __user *buf, size_t count,
loff_t *f_pos)
{
return simple_read_from_buffer(buf, count, f_pos, foo_buf, sizeof(foo_buf));
}
static ssize_t foo_write(struct file *file, const char __user *buf, size_t count,
loff_t *f_pos)
{
size_t ret;
if (*f_pos > BUF_SIZE)
return -EINVAL;
ret = simple_write_to_buffer(foo_buf, sizeof(foo_buf), f_pos, buf, count);
if (ret < 0)
return ret;
foo_buf[ret] = '\0';
return ret;
}
static const struct file_operations foo_fops = {
.owner = THIS_MODULE,
.read = foo_read,
.write = foo_write,
};
static int __init debugfs_start(void)
{
pr_err("init debugfs");
debug_dir = debugfs_create_dir("mydir", NULL);
if (debug_dir == NULL) {
pr_err("debugfs create my dir failed");
return -ENOMEM;
}
debug_foo = debugfs_create_file("foo", 0744, debug_dir,
NULL, &foo_fops);
if (!debug_foo) {
debugfs_remove(debug_dir);
return -ENOMEM;
}
return 0;
}
static void __exit debugfs_end(void)
{
pr_err("exit debugfs");
debugfs_remove_recursive(debug_dir);
}
module_init(debugfs_start);
module_exit(debugfs_end);
One is the echo command would stuck if the length of input string is
over the buffer size.
This is because it keeps retrying to write to the file while each attempt would fail.
The other is the echo "world" >>
/sys/kernel/debugfs/mydir/myfile never append the string. Instead, it
new a string.
this is expected with your implementation. you would need to cat the new one to the existing string if you want to have it appended. That is, you need to keep a record of the string length. But this is
different than the f_pos which is specific for a open file of a process.
How do I identify what commands(echo > or echo >>) users will use?
so you mean whether or not the user 'truncates' the file after opening it?
debugfs doesn't seem to support seek but i suppose you can provide your .open function and also the .llseek function to implement that. You need to see to the end of the file when opening the file if it is for APPEND.
Sorry I could not provide the complete code but just some pointers.
I wrote a small device driver for a "coin" device. I create an entry in /drivers/char/Kconfig
and corresponding Makefile, then selected built-in option in menuconfig. The kernel compiled fine (built-in.o file was created). But I still can't access the device (/dev/coin was not created) and there was no entry under /proc/devices.
Please help!!
I am cross-compiling for powerpc
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/random.h>
#include <linux/debugfs.h>
#include <linux/init.h>
#define DEVNAME "coin"
#define LEN 20
enum values {HEAD, TAIL};
struct dentry *dir, *file;
int file_value;
int stats[2] = {0, 0};
char *msg[2] = {"head\n", "tail\n"};
static int major;
static struct class *class_coin;
static struct device *dev_coin;
static ssize_t r_coin(struct file *f, char __user *b,
size_t cnt, loff_t *lf)
{
char *ret;
u32 value = random32() % 2;
ret = msg[value];
stats[value]++;
return simple_read_from_buffer(b, cnt,
lf, ret,
strlen(ret));
}
static struct file_operations fops = { .read = r_coin };
#ifdef CONFIG_COIN_STAT
static ssize_t r_stat(struct file *f, char __user *b,
size_t cnt, loff_t *lf)
{
char buf[LEN];
snprintf(buf, LEN, "head=%d tail=%d\n",
stats[HEAD], stats[TAIL]);
return simple_read_from_buffer(b, cnt,
lf, buf,
strlen(buf));
}
static struct file_operations fstat = { .read = r_stat };
#endif
static int __init coin_init(void)
{
void *ptr_err;
major = register_chrdev(0, DEVNAME, &fops);
if (major < 0)
return major;
class_coin = class_create(THIS_MODULE,
DEVNAME);
if (IS_ERR(class_coin)) {
ptr_err = class_coin;
goto err_class;
}
dev_coin = device_create(class_coin, NULL,
MKDEV(major, 0),
NULL, DEVNAME);
if (IS_ERR(dev_coin))
goto err_dev;
#ifdef CONFIG_COIN_STAT
dir = debugfs_create_dir("coin", NULL);
file = debugfs_create_file("stats", 0644,
dir, &file_value,
&fstat);
#endif
return 0;
err_dev:
ptr_err = class_coin;
class_destroy(class_coin);
err_class:
unregister_chrdev(major, DEVNAME);
return PTR_ERR(ptr_err);
}
static void __exit coin_exit(void)
{
#ifdef CONFIG_COIN_STAT
debugfs_remove(file);
debugfs_remove(dir);
#endif
device_destroy(class_coin, MKDEV(major, 0));
class_destroy(class_coin);
return unregister_chrdev(major, DEVNAME);
}
module_init(coin_init);
module_exit(coin_exit);
What if you manually insert module into kernel using insmod? Does it work? Any messages in dmesg?
As I remember entries in /dev (/dev/coin) should be created manually using mknod, but you need major number of registered device. Just printk it after register_chrdev().
So I'm trying to write a kernel module that uses the linux/timer.h file. I got it to work inside just the module, and now I am trying to get it to work from a user program.
Here is my kernel module:
//Necessary Includes For Device Drivers.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <linux/ioctl.h>
#define DEVICE_NAME "mytimer"
#define DEVICE_FILE_NAME "mytimer"
#define MAJOR_NUM 61
#define MINOR_NUM 0
MODULE_LICENSE("Dual BSD/GPL");
static struct timer_list my_timer;
struct file_operations FileOps =
{
//No File Operations for this timer.
};
//Function to perform when timer expires.
void TimerExpire(int data)
{
printk("Timer Data: %d\n", data);
}
//Function to set up timers.
void TimerSetup(void)
{
setup_timer(&my_timer, TimerExpire, 5678);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));
}
//Module Init and Exit Functions.
int init_module(void)
{
int initResult = register_chrdev(MAJOR_NUM, "mytimer", &FileOps);
if (initResult < 0)
{
printk("Cannot obtain major number %d\n", MAJOR_NUM);
return initResult;
}
printk("Loading MyTimer Kernel Module...\n");
return 0;
}
void cleanup_module(void)
{
unregister_chrdev(MAJOR_NUM, "mytimer");
printk("Unloading MyTimer Kernel Module...\n");
}
More specifically, I want my user program to call the TimerSetup() function. I know that I'll need to use ioctl() but I'm not sure how to specify in my MODULE FILE that TimerSetup() should be callable via ioctl().
Also, my second question: I was able to insmod my module and also mknod into /dev/mytimer with the correct major number. But when I tried to open() it so that I can get the file descriptor from it, it kept returning -1, which I'm assuming is wrong. I made sure the permissions were fine (in fact, I made it 777 just to be sure)... It still doesn't work... Is there something I'm missing?
Here is the user program just in case:
#include <stdio.h>
int main(int argc, char* argv[])
{
int fd = open("/dev/mytimer", "r");
printf("fd: %d\n", fd);
return 0;
}
The example code you need can be found in drivers/watchdog/softdog.c (from Linux 2.6.33 at the time this was written), which illustrates proper file operations as well as how to permit userland to fill a structure with ioctl().
It's actually a great, working tutorial for anyone who needs to write trivial character device drivers.
I dissected softdog's ioctl interface when answering my own question, which may be helpful to you.
Here's the gist of it (though far from exhaustive) ...
In softdog_ioctl() you see a simple initialization of struct watchdog_info that advertises functionality, version and device information:
static const struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
We then look at a simple case where the user just wants to obtain these capabilities:
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
... which of course, will fill the corresponding userspace watchdog_info with the initialized values above. If copy_to_user() fails, -EFAULT is returned which causes the corresponding userspace ioctl() call to return -1 with a meaningful errno being set.
Note, the magic requests are actually defined in linux/watchdog.h , so that the kernel and userspace share them:
#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 obviously signifying "Watchdog ioctl"
You can easily take that a step further, having your driver do something and place the result of that something in the structure and copy it to userspace. For instance, if struct watchdog_info also had a member __u32 result_code. Note, __u32 is just the kernel's version of uint32_t.
With ioctl(), the user passes the address of an object, be it a structure, integer, whatever to the kernel expecting the kernel to write its reply in an identical object and copy the results to the address that was provided.
The second thing you are going to need to do is make sure your device knows what to do when someone opens, reads from it, writes to it, or uses a hook like ioctl(), which you can easily see by studying softdog.
Of interest is:
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,
};
Where you see the unlocked_ioctl handler going to ... you guessed it, softdog_ioctl().
I think you might be juxtaposing a layer of complexity that really doesn't exist when dealing with ioctl(), it really is that simple. For that same reason, most kernel developers frown on new ioctl interfaces being added unless they are absolutely necessary. Its just too easy to lose track of the type that ioctl() is going to fill vs the magic you use to do it, which is the primary reason that copy_to_user() fails often resulting in the kernel rotting with hordes of userspace processes stuck in disk sleep.
For a timer, I agree, ioctl() is the shortest path to sanity.
You are missing a .open function pointer in your file_operations structure to specify the function to be called when a process attempts to open the device file. You will need to specify a .ioctl function pointer for your ioctl function as well.
Try reading through The Linux Kernel Module Programming Guide, specifically chapters 4 (Character Device Files) and 7 (Talking to Device Files).
Chapter 4 introduces the file_operations structure, which holds pointers to functions defined by the module/driver that perform various operations such as open or ioctl.
Chapter 7 provides information on communicating with a module/drive via ioctls.
Linux Device Drivers, Third Edition is another good resource.
Minimal runnable example
Tested in a fully reproducible QEMU + Buildroot environment, so might help others get their ioctl working. GitHub upstream:
kernel module |
shared header |
userland.
The most annoying part was understanding that some low ids are hijacked: ioctl is not called if cmd = 2 , you have to use _IOx macros.
Kernel module:
#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)
Shared header between the kernel module and userland:
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 = %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;
}