8

I am in the process of writing a Linux Kernel Module (LKM) serving as a pseudo-driver - I am unable to figure out how to make IOCTL calls between the LKM (wait.c) and the user-level program (user.c).

The magic number for the device driver is 0xBF - the LKM does not communicate with a physical block/char device, it is simply an exercise. From what I can tell, the IOCTL call to KERN_IOCTL_CREATE_EVENT is not formatted properly & the magic number is incorrect.

The IOCTL call that I am attempting to use is:

#include <sys/ioctl.h>
#define KERN_IOCTL_CREATE_EVENT   _IOWR(WAIT_DEVICE_MAGIC, 1, int)

int main(){
int ret;
int fd;
fd = open("/dev/wait", 0);
if(fd < 0){
    return -1;
}
ret = ioctl(fd, KERN_IOCTL_CREATE_EVENT, 0);

Error:

[fail]: KERN_IOCTL_CREATE_EVENT: Inappropriate ioctl for device

The user-mode application can open/close a file descriptor pointing to the device: /dev/wait but the case/switch statement isn't accepting the IOCTL call. Any suggestions?

Here is the output of # uname -a

Linux vagrant-ubuntu-trusty-64 3.13.11.11+ #1 SMP Mon Dec 1 20:50:23 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

wait.c

#include <linux/miscdevice.h>
#include <linux/moduleparam.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/fs.h>

#include "wait.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tyler Fisher <[email protected]>");
MODULE_DESCRIPTION("In-kernel wait queue");

static unsigned long event_table_size = 50;
module_param(event_table_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));
MODULE_PARM_DESC(event_table_size, "Size of event table (i.e. how many processes can be blocking)");

/* IOCTL function headers */
static int wait_open(struct inode *, struct file *);
static int wait_close(struct inode *, struct file *);
static long wait_ioctl(struct inode *, struct file *, unsigned int, unsigned long);

/* other function headers */
static long event_open(int event_id);

/* file operations */
static struct file_operations wait_fops = {
    .owner = THIS_MODULE,
    .open = wait_open,
    .release = wait_close,
    .llseek = noop_llseek,
    .unlocked_ioctl = wait_ioctl
};

/* device handler */
static struct miscdevice wait_misc_device = {
  .minor = MISC_DYNAMIC_MINOR,
  .name = WAIT_DEVICE_NAME,
  .fops = &wait_fops
};

/* open wait device */
static int wait_open(struct inode *inode, struct file *file){
    dev_t node = iminor(inode);
    if(MINOR(node) != WAIT_DEVICE_MINOR){
        return -ENODEV;
    }
    return 0;
}

static int wait_close(struct inode *inode, struct file *file){
    return 0;
}

static long wait_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long sub_cmd){
switch(cmd){
    case KERN_IOCTL_CREATE_EVENT:
        printk(KERN_INFO "[wait device]: create event %lu\n", sub_cmd);
        return event_open(sub_cmd);

    default:
        return -ENOENT;
    }
}

static long event_open(int id){
    return 0;
}

static long __init wait_init(void){
    if(misc_register(&wait_misc_device) < 0){
        printk(KERN_ERR "[wait device] failed to register device\n");
        return -1;
    }
    printk(KERN_INFO "[wait device] has been registered\n");
    return 0;
}

static void __exit wait_exit(void){
    misc_deregister(&wait_misc_device);
    printk(KERN_INFO "[wait device] has been unregistered\n");
}

module_init(wait_init);
module_exit(wait_exit);

wait.h

#include <linux/ioctl.h>

#define WAIT_DEVICE_NAME    "wait"
#define WAIT_DEVICE_MAGIC   0xBF
#define WAIT_DEVICE_MAJOR   200
#define WAIT_DEVICE_MINOR   0

#define KERN_IOCTL_CREATE_EVENT     _IOWR(WAIT_DEVICE_MAGIC, 0x01, int)

#define MAX_WAITING 5

The test program for the IOCTL calls:

user.c

#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>

#define WAIT_DEVICE_MAGIC   0xBF
#define KERN_IOCTL_CREATE_EVENT   _IOWR(WAIT_DEVICE_MAGIC, 0x01, int)
#define KERN_IOCTL_DESTROY_EVENT  _IOWR(WAIT_DEVICE_MAGIC, 0x02, int)
#define KERN_IOCTL_LOCK_EVENT     _IOWR(WAIT_DEVICE_MAGIC, 0x03, int)
#define KERN_IOCTL_UNLOCK_EVENT   _IOWR(WAIT_DEVICE_MAGIC, 0x04, int)

int main(){
    int fd;
    if(fd = open("/dev/wait", O_RDWR) < 0){
        perror("failed to open /dev/wait");
        return -1;
    }

    /* test IOCTL: event creation */
    if(ioctl(fd, KERN_IOCTL_CREATE_EVENT, 0) < 0){
        perror("[fail]: KERN_IOCTL_CREATE_EVENT");
        return -1;
    }
    return 0;
}

Makefile

obj-m += wait.o
CFLAGS_wait.o += -DDEBUG

all:
  make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
  make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

In order to test out the LKM - clear dmesg, compile & execute user.c w/GCC:

# dmesg -c > /dev/null 2>&1
# make
# rmmod wait.ko
# insmod wait.ko
# gcc user.c -o user && ./user

The amount of debugging errors is embarassing. I feel bad for sharing this - and realize that this may cause the issue to be closed/downvoted into oblivion.

# sh test.sh
[+] cleared dmesg
make -C /lib/modules/3.13.11.11+/build M=/home/vagrant/PROG40000-kernel-synchronization modules
make[1]: Entering directory `/home/vagrant/ubuntu-trusty'
  CC [M]  /home/vagrant/PROG40000-kernel-synchronization/wait.o
/home/vagrant/PROG40000-kernel-synchronization/wait.c:61:1: warning: initialization from incompatible pointer type [enabled by default]
 };
 ^
/home/vagrant/PROG40000-kernel-synchronization/wait.c:61:1: warning: (near initialization for ‘wait_fops.unlocked_ioctl’) [enabled by default]
In file included from include/linux/moduleparam.h:4:0,
                 from /home/vagrant/PROG40000-kernel-synchronization/wait.c:11:
/home/vagrant/PROG40000-kernel-synchronization/wait.c: In function ‘__inittest’:
include/linux/init.h:297:4: warning: return from incompatible pointer type [enabled by default]
  { return initfn; }     \
    ^
/home/vagrant/PROG40000-kernel-synchronization/wait.c:167:1: note: in expansion of macro ‘module_init’
 module_init(wait_init);
 ^
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/vagrant/PROG40000-kernel-synchronization/wait.mod.o
  LD [M]  /home/vagrant/PROG40000-kernel-synchronization/wait.ko
make[1]: Leaving directory `/home/vagrant/ubuntu-trusty'
[--dmesg--]
[13112.810008] [wait device] has been unregistered
[13112.819049] [wait device] has been registered
[-/dmesg--]
[+] compiled user-mode program
-----
[fail]: KERN_IOCTL_CREATE_EVENT: Inappropriate ioctl for device
[fail]: KERN_IOCTL_CREATE_EVENT: Inappropriate ioctl for device
[+] executed user-mode program
-----
[--dmesg--]
[13112.810008] [wait device] has been unregistered
[13112.819049] [wait device] has been registered
[13112.893049] SOMEONE DARE READ FROM ME!?
[13112.893057] [wait device] invalid magic number: 0:0:191
[13112.893535] [wait device] invalid magic number: 0:0:191
[-/dmesg--]
2
  • 1
    You may need to install your updated kernel headers. Without this the user space program is unable to locate the correct ioctl, for some reason I had a similar issue on Ubuntu and the only thing that worked was installing the updated kernel headers. Commented Dec 5, 2014 at 13:30
  • I updated the kernel using sudo make modules_install install -- and have the linux-headers-generic package installed -- current kernel version is 3.13.11.11+ Commented Dec 5, 2014 at 15:06

2 Answers 2

3

Okay. So. Here's the solution.

In Linux kernel 2.6.x the declaration for _ioctl calls changed from

static long wait_ioctl(struct inode *, struct file *, unsigned int, unsigned long);

To:

static long wait_ioctl(struct file *, unsigned int, unsigned long);

The fix is thus:

...
static long wait_ioctl(struct file *, unsigned int, unsigned long);
...
static long wait_ioctl(struct file *file, unsigned int cmd, unsigned long sub_cmd){
    if(_IOC_TYPE(cmd) != WAIT_DEVICE_MAGIC){
       printk(KERN_INFO "[wait device] invalid magic number: %u:%u:%u", _IOC_TYPE(cmd), cmd, WAIT_DEVICE_MAGIC);
       return -ENOTTY;
    }
 ....
Sign up to request clarification or add additional context in comments.

Comments

1

.compat_ioctl

Also make sure to implement this file_operation if you are making 32-bit calls to a 64-bit kernel.

The symptom is that your ioctl handler is never run.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.