Nielsvh
Nielsvh

Reputation: 1219

Linux USB driver with multiple read operations, ioctl or fops?

I am writing a driver for a USB device that has three different read/write operations (flash, EEPROM, and I2C), each with a different implementation. I've been scratching my head about this, since I'm new to this whole world of linux kernel development. I've read that I should avoid ioctl at all costs, but I cannot tell how to implement this. Since everything in linux is a file, could I create multiple endpoints to write to for each location? How would I even go about doing this? Would I define multiple usb_class_driver structs?

On the flip side, should I include all the functionality in a single endpoint and use ioctl? Is it better to split the work up from the same driver or to consolidate all the functionality in one place?

I cannot use libusb due to its limitations on isochronous transfers and lack of direct control over dma transfers (both needed the product final product).

UPDATE: After trying to use multiple generic file operations for each endpoint and getting a response code of -98 (already registered), I think I am going to have to use the single endpoint with ioctl. The code that isn't working is as follows:

In adriver.h

static struct usb_class_driver adriver_eeprom_class = {
    .name = "usb/adriver_eeprom%d",
    .fops = &adriver_eeprom_fops,
    .minor_base = USB_SKEL_MINOR_BASE,
};
static struct usb_class_driver adriver_flash_class = {
    .name = "usb/adriver_flash%d",
    .fops = &adriver_flash_fops,
    .minor_base = USB_SKEL_MINOR_BASE,
};
static struct usb_class_driver adriver_i2c_class = {
    .name = "usb/adriver_i2c%d",
    .fops = &adriver_i2c_fops,
    .minor_base = USB_SKEL_MINOR_BASE,
};
static struct usb_class_driver driver_fifo_class = {
    .name = "usb/driver_fifo%d",
    .fops = &driver_fifo_fops,
    .minor_base = USB_SKEL_MINOR_BASE,
};
static struct usb_class_driver adriver_class = {
    .name = "usb/adriver%d",
    .fops = &adriver_fops,
    .minor_base = USB_SKEL_MINOR_BASE,
};

In adriver.c

static int adriver_probe(struct usb_interface *interface, const struct usb_device_id *id) {
    struct usb_device *udev = interface_to_usbdev(interface);
    struct usb_adriver *gdev;
    int retval = -ENOMEM;
    gdev = kmalloc(sizeof(struct usb_adriver), GFP_KERNEL);
    if(gdev == NULL)
    {
        dev_err(&interface->dev, "Out of memory\n");
        goto error;
    }
    memset(gdev, 0x00, sizeof(*gdev));

    kref_init(&gdev->kref);

    gdev->udev = usb_get_dev(udev);

    usb_set_intfdata(interface,gdev);

    retval = usb_register_dev(interface, &adriver_eeprom_class);
    if (retval) {
        /* something prevented us from registering this driver */
        pr_err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }
    retval = usb_register_dev(interface, &adriver_flash_class);
    if (retval) {
        /* something prevented us from registering this driver */
        pr_err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }
    retval = usb_register_dev(interface, &adriver_i2c_class);
    if (retval) {
        /* something prevented us from registering this driver */
        pr_err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }
    retval = usb_register_dev(interface, &adriver_fifo_class);
    if (retval) {
        /* something prevented us from registering this driver */
        pr_err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }
    retval = usb_register_dev(interface, &adriver_class);
    if (retval) {
        /* something prevented us from registering this driver */
        pr_err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
    }

    dev_info(&interface->dev, "USB adriver device now attached\n");
    return 0;

error:
    if (gdev)
        kref_put(&gdev->kref, adriver_delete);
    return retval;
}

Upvotes: 1

Views: 1843

Answers (2)

wallyk
wallyk

Reputation: 57804

The device driver can create 3 devices as you originally proposed. If there is a single IRQ on the device, this model is even more apropos.

With a bit of luck and skill (probably more of the latter), the driver's read and write routines need be implemented only as one function for read and one for write with an extra parameter passed, or the read/write routine infers which device it is by inspecting its struct file * parameter (if named f, then MINOR(f -> f_dentry -> d_inode -> i_rdev) gives the device's minor device I.D. Since you control minor device assignment in your probe() function with device_create(), you can leverage that to have useful type information associated.

In this way, it is easy to avoid ioctl which really should be avoided for simple read and write operations. That makes it easy to use the device from bash scripts, command line, etc. If an ioctl is involved, it means a programming language is required to use it.
The man page for ioctl() says

The [ioctl] call is used as a catch-all for operations that don't cleanly fit the UNIX stream I/O model.

Upvotes: 1

6EQUJ5
6EQUJ5

Reputation: 3302

Thinking outside the box:

Assuming it is feasible with your device, you could consider writing a user space driver using libusb (old link) (SourceForge) or another user space USB library. Then you can debug easier, develop and test easier, and you gain the added advantage of potential cross-platform compatibility and without needing to deal with writing a kernel driver.

Upvotes: 0

Related Questions