Home > other >  Linux Kernel Module: ioctl always return -1 without entering unlocked_ioctl function
Linux Kernel Module: ioctl always return -1 without entering unlocked_ioctl function

Time:09-18

Hi I am sure the issue can be solved by something super simple I overlooked. This is a snippet from my header file:

#ifndef COMBLOCKDEV_H 
#define COMBLOCKDEV_H 
#define BUFF_LEN 256 // depends on CB FIFO and RAM max depth but also available Linux RA

struct user_params{
    int len;
    int data[BUFF_LEN];
    int reg;
};

#define COMBLOCK_IOC_MAGIC      'a' 
#define COMBLOCK_IOC_RESET              _IO(COMBLOCK_IOC_MAGIC, 0)
#define COMBLOCK_IOC_REG_READ           _IOR(COMBLOCK_IOC_MAGIC, 1, struct params *)
#endif

My kernel code loads and probes well. Open and release work as intended:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/ioctl.h>

#include <linux/io.h>

#include <linux/errno.h>
#include <linux/types.h>

#include <linux/of_device.h>
#include <linux/of_platform.h>

#include <asm/uaccess.h>

#include "comblock.h"
#define SUCCESS 0

static int device_open(struct inode *inode, struct file *file) {
    struct comblock_local * lp;
    pr_info("ComBlock_open(%p)\n", file);

    lp = container_of(inode->i_cdev, struct comblock_local, cdev);
    file->private_data = lp;
    
    return SUCCESS; 
} 

/*  This is called whenever a process attempts to release the device file 
    We release the module and clear the queue entry.for asynchronous notifications.
*/
static int device_release(struct inode *inode, struct file *file) {
    struct comblock_local * lp = (struct comblock_local*) file->private_data;
    pr_info("ComBlock_release(%p,%p)\n", inode, file);
    if(!lp->async_queue)
        device_fasync(-1, file, 0);

    return SUCCESS;
}

static long int device_ioctl(struct file *file, unsigned int cmd, unsigned long ioctl_param) {
    int ret = SUCCESS;
    struct comblock_local * lp = file->private_data;
    struct user_params params;
    
    printk(KERN_INFO "Comblock: IOCTL command %d", cmd);
    printk(KERN_INFO "Comblock: IOCTL command magic %c", COMBLOCK_IOC_MAGIC);
    printk(KERN_INFO "Comblock: IOCTL command max %d", COMBLOCK_IOC_MAXNR);
    printk(KERN_INFO "Comblock: IOCTL command dir %d", _IOC_DIR(cmd));
    if (_IOC_TYPE(cmd) != COMBLOCK_IOC_MAGIC)
        return -ENOTTY;
    if (_IOC_NR(cmd) > COMBLOCK_IOC_MAXNR) 
        return -ENOTTY;
    
    if(_IOC_DIR(cmd) & _IOC_READ)
        ret = !access_ok((void __user*) ioctl_param, _IOC_SIZE(cmd));
    else if(_IOC_DIR(cmd) & _IOC_WRITE)
        ret = !access_ok((void __user*) ioctl_param, _IOC_SIZE(cmd));
    if(ret) 
        return -EFAULT;
    if(down_interruptible(&lp->sem)) 
        return -ERESTARTSYS;

    printk(KERN_INFO "Comblock: Command %d", cmd);
    /* Switch according to the ioctl called */ 
    switch (cmd) {
        case COMBLOCK_IOC_RESET:
    break;
    }
return ret;
}

The test application is as follows:

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

#include "comblock.h"

int main() {
        struct params test;
        int ret, i;
        int dev = open("/dev/comblock_dev0", O_RDWR);
        if(dev == -1) {
                printf("Opening was not possible! error: %d\n",dev);
                return -1;
        }

        printf("Trying to reset the CB: %d", ioctl(dev, COMBLOCK_IOC_RESET));
    return 0;
}

The test application and kernel module use the same header, so I assume the IOCTL commands are valid. The problem is that ioctl() in the test application always returns -1 and no printk from the kernel module gets printed in the kernel log. So after one test dmesg shows this:

[11936.248350] Start ComBlock. ver.0.01
[11936.248589] comblock_dev 80000000.comblock: Device Tree Probing
[11936.248632] comblock_dev 80000000.comblock: Registering cdev.
[11936.248681] comblock_dev 80000000.comblock: ComBlock allocate cdev 510 0
[11936.248828] comblock_dev 80000000.comblock: Comblock detected with the following parameters:
[11936.248835] comblock_dev 80000000.comblock: Enables: OReg 1, IReg 1, IFIFO 1, OFIFO 1, RAM 1
[11936.248840] comblock_dev 80000000.comblock: Length: OReg 4, IReg 4, IFIFO 1024, OFIFO 1024, RAM 100
[11938.005460] ComBlock_open(00000000abab92a2)
[11938.005741] ComBlock_release(000000001d0f0893,00000000abab92a2)

I can confirm that open and release work but IOCTL gets ignored. I suspect there is an issue in the middle part but I can't really understand how to debug it.

Edit: Added file_operations struct, platform_driver, of_device_id, and module init function

static struct file_operations comblock_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = device_ioctl,
    .open = device_open,
    .release = device_release,
    .fasync = device_fasync
};


static int comblock_init(void) {
    printk(KERN_INFO "Start ComBlock. ver.0.01");
    return platform_driver_register(&comblock_driver);
}

#ifdef CONFIG_OF
static struct of_device_id comblock_of_match[] __devinitdata = {
    { .compatible = "ictp,comblock-1.0" },
    { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, comblock_of_match);
#else
#define comblock_of_match
#endif

static struct platform_driver comblock_driver = {
    .driver = {
        .name = DEVICE_NAME,
        .owner = THIS_MODULE,
        .of_match_table = comblock_of_match,
    },
    .probe = device_probe,
    .remove = device_remove,
};

The module and device are loaded after boot up, here is the probe function:

/* Probe device tree to retrieve parameters */
static int device_probe(struct platform_device *pdev) {
    struct resource *axi_l, *axi_f; /* IO mem resources */
    struct device *dev = &pdev->dev;
    struct comblock_local *lp = NULL;
    
    unsigned int reg_in_enable;
    unsigned int reg_in_width;
    unsigned int reg_in_length;
    unsigned int reg_out_enable;
    unsigned int reg_out_width;
    unsigned int reg_out_length;
    unsigned int fifo_in_enable;
    unsigned int fifo_in_length;
    unsigned int fifo_in_width;
    unsigned int fifo_out_enable;
    unsigned int fifo_out_length;
    unsigned int fifo_out_width;
    unsigned int ram_enable;
    unsigned int ram_length;
    unsigned int ram_width;

    int ret = 0;
    int mem_index = 0;

    /////////////////////////// Probing Device Tree ///////////////////////////////////////
    dev_info(dev, "Device Tree Probing\n");

    // load parameters into the structure
    {
        ret = device_property_count_u32(dev, "reg-in-enable");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "reg-in-enable", &reg_in_enable);
        if (ret)
            return ret;
        ret = device_property_count_u32(dev, "reg-in-length");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "reg-in-length", &reg_in_length);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "reg-in-width");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "reg-in-width", &reg_in_width);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "reg-out-enable");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "reg-out-enable", &reg_out_enable);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "reg-out-length");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "reg-out-length", &reg_out_length);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "reg-out-width");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "reg-out-width", &reg_out_width);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "fifo-in-enable");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "fifo-in-enable", &fifo_in_enable);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "fifo-in-length");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "fifo-in-length", &fifo_in_length);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "fifo-in-width");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "fifo-in-width", &fifo_in_width);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "fifo-out-enable");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "fifo-out-enable", &fifo_out_enable);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "fifo-out-length");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "fifo-out-length", &fifo_out_length);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "fifo-out-width");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "fifo-out-width", &fifo_out_width);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "ram-enable");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "ram-enable", &ram_enable);
        if (ret)
            return ret;

        ret = device_property_count_u32(dev, "ram-length");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "ram-length", &ram_length);
        if(ret)
            return ret;

        ret = device_property_count_u32(dev, "ram-width");
        if (ret <= 0) {
            dev_err(dev, "init property missing or empty");
            return ret ? ret : -ENODATA;
        }
        ret = device_property_read_u32(dev, "ram-width", &ram_width);
        if (ret)
            return ret;

    }

    ////////////////////////// allocating local data structure /////////////////////////////
    lp = (struct comblock_local *) kzalloc(sizeof(struct comblock_local), GFP_KERNEL);
    if (!lp) {
        dev_err(dev, "Cound not allocate comblock device\n");
        return -ENOMEM;
    }

    lp->reg_in_enable = reg_in_enable;
    lp->reg_in_length = reg_in_length;
    lp->reg_in_width = reg_in_width;
    lp->reg_out_enable = reg_out_enable;
    lp->reg_out_length = reg_out_length;
    lp->reg_out_width = reg_out_width;
    lp->fifo_in_enable = fifo_in_enable;
    lp->fifo_in_length = fifo_in_length;
    lp->fifo_in_width = fifo_in_width;
    lp->fifo_out_enable = fifo_out_enable;
    lp->fifo_out_length = fifo_out_length;
    lp->fifo_out_width = fifo_out_width;
    lp->ram_enable = ram_enable;
    lp->ram_length = ram_length;
    lp->ram_width = ram_width;

    /* register the device memory */

    if(lp->reg_in_enable || lp->reg_out_enable || lp->fifo_in_enable || lp->fifo_out_enable) {
        axi_l = platform_get_resource(pdev, IORESOURCE_MEM, mem_index);
        mem_index  ;
        if(!axi_l) {
            ret= -ENOMEM;
            goto out_free_local;
        }else if(!request_mem_region(axi_l->start, axi_l->end - axi_l->start   1, DEVICE_NAME)) {
            dev_err(dev, "Couldn't lock memory region at %llu\n", axi_l->start);
            ret = -EBUSY;
            goto out_free_local;
        }
        lp->axi_l = axi_l;
        lp->axi_l_base_addr = ioremap(axi_l->start, axi_l->end - axi_l->start   1);
    }

    if(lp->ram_enable) {
        axi_f = platform_get_resource(pdev, IORESOURCE_MEM, mem_index);
        if(!axi_f) {
            ret= -ENOMEM;
            goto out_free_local;
        }else if(!request_mem_region(axi_f->start, axi_f->end - axi_f->start   1, DEVICE_NAME)) {
            dev_err(dev, "Couldn't lock memory region at %llu\n", axi_f->start);
            ret = -EBUSY;
            goto out_free_local;
        }
        lp->axi_f = axi_f;
        lp->axi_f_base_addr = ioremap(axi_f->start, axi_f->end - axi_f->start   1);
    }

    dev_set_drvdata(dev, lp);

    if(!lp->axi_l_base_addr && !lp->axi_f_base_addr) {
        dev_err(dev, "Could not allocate iomem\n");
        ret = -EIO;
        goto out_free_mem_region;
    }

/////////////////////////  initializing char device /////////////////////////////////
    ret = comblock_cdev_init(dev, lp);
    if(ret < 0) {
        dev_err(dev, "Could not create cdev \n");
        goto out_free_iomap;
    }

    sema_init(&lp->sem, 1);
    dev_info(dev, "Comblock detected with the following parameters: \n");
    dev_info(dev, "Enables: OReg %d, IReg %d, IFIFO %d, OFIFO %d, RAM %d \n", lp->reg_in_enable, lp->reg_out_enable, lp->fifo_in_enable, lp->fifo_out_enable, lp->ram_enable);
    dev_info(dev, "Length: OReg %d, IReg %d, IFIFO %d, OFIFO %d, RAM %d \n", lp->reg_in_length, lp->reg_out_length, lp->fifo_in_length, lp->fifo_out_length, lp->ram_length);
    return 0;

    out_free_iomap:
        if(lp->axi_l_base_addr)
            iounmap(lp->axi_l_base_addr);
        if(lp->axi_f_base_addr)
            iounmap(lp->axi_f_base_addr);
    out_free_mem_region:
        if(axi_l)
            release_mem_region(axi_l->start, axi_l->end - axi_l->start   1);
        if(axi_f)
            release_mem_region(axi_f->start, axi_f->end - axi_f->start   1);
    out_free_local:
        kfree(lp);
        dev_set_drvdata(dev, NULL);
        return ret;
}

Here is the initialization of the chardev:

/*Initialize the chardevice*/
static int comblock_cdev_init(struct device * dev, struct comblock_local * lp) {
    dev_t devno;
    int ret = 0;
    struct device *device = NULL;

    dev_info(dev, "Registering cdev.");

    if(lp->major) {
        devno = MKDEV(lp->major, lp->minor);
        ret = register_chrdev_region(devno, comblock_nr_devs, DEVICE_NAME);
    } else {
        ret = alloc_chrdev_region(&devno, lp->minor, comblock_nr_devs, DEVICE_NAME);
        lp->major = MAJOR(devno);
    }

    dev_info(dev, "ComBlock allocate cdev %d %d", lp->major, lp->minor);

    if(ret < 0) {
        dev_warn(dev, " can't get major %d\n", lp->major);
        return ret;
    }

    cdev_init(&lp->cdev, &comblock_fops);
    lp->cdev.owner = THIS_MODULE;
    ret = cdev_add(&lp->cdev, devno, 1);
    if(ret) {
        dev_notice(dev, "Error %d adding %s%d", ret, DEVICE_NAME, 0);
        goto out_creg;
    }

    comblock_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(comblock_class)) {
        ret = PTR_ERR(comblock_class);
        goto out_cadd;
    }

    device = device_create(comblock_class, NULL, /* no parent device */ 
        devno, NULL, /* no additional data */
        DEVICE_NAME "%d", lp->minor);

    if (IS_ERR(device)) {
        ret = PTR_ERR(device);
        printk(KERN_WARNING "[target] Error %d while trying to create %s%d", ret, DEVICE_NAME, lp->minor);
        goto out_class;
    }

    return 0;

    out_class:
        class_destroy(comblock_class);
    out_cadd:
        cdev_del(&lp->cdev);
    out_creg:
        unregister_chrdev_region(MKDEV(lp->major, lp->minor), comblock_nr_devs);
        return ret;
}

Edit2: Added error to decode the issue in the test app after ioctl call.

ioctl: Inappropriate ioctl for device

I guess this could mean that the magic number in my header could be duplicated? I am currently testing with the COMBLOCK_IOC_RESET parameter which does not takes arguments so it should rule out parameters wrong formatting right?

Edit3: thanks to @rachid-k it was determined that the problem was a 64 kernel with a 32 bit user space. Just check uname -m for kernel and getconf LONG_BIT for userspace? At the end it was solved by changing unlocked_ioctl for compat_ioctl.

Now I have successfully obtained a Segmentation fault. New error means progress. Thanks a lot :)

CodePudding user response:

Edit3: thanks to @rachid-k it was determined that the problem was a 64 bits kernel versus a 32 bits user space.

Just check uname -m for kernel and getconf LONG_BIT for user space.

The issue was solved by changing unlocked_ioctl for compat_ioctl.

Now I have successfully obtained a Segmentation fault. New error means progress. Thanks a lot :)

  • Related