Search code examples
clinuxlinux-kernellinux-device-driverlibusb

Linux Driver Programming: Problem with usb_control_msg


Short Version

I want to write a Linux Driver for a custom USB device. Before writing the driver I used libusb-1.0 to test the device. With the following function call, I could read out a uin16_t value from the device:

status = libusb_control_transfer(handle,        /* Device Handle */
                                0x80,                           /* bRequestType */
                                0x10,                           /* bRequest */
                                value,                          /* wValue */
                                0x0,                            /* wIndex */
                                ((uint8_t *) &value),           /* data */
                                2,                              /* wLength */
                                100);                           /* timeout */

After this call, I got a new value in the value variable.

Now I want to accomplish the same call in my Driver. I have tried the following in the probe function of my USB driver:

status = usb_control_msg(data->udev, usb_rcvctrlpipe(data->udev, 0), 0x10, USB_DIR_IN, 0, 0, (u8*) &my_data, 2, 100);

All I get is the return value -11 and on my device I don't see anything.

The only thing I am doing before this call, is calling data->udev = interface_to_usbdev(intf); to get the USB device from my interface.

Does anyone know, if I am missing something or if I am doing something wrong?

Long version

I want to learn how to write USB Drivers in Linux. As a DUT for which I can write a driver, I choose a Raspberry Pi Pico and the dev_lowlevel USB example. I adapt the code a little bit, so I can use a control transfer with bRequest 0x10 and bRequestType 0x0 (USB_DIR_OUT) to turn the Pico's onboard LED on or off and a control transfer with bRequest 0x10 and bRequestType 0x80 (USB_DIR_IN) to read back the current value of the LED.

With a user space program and the following code I can read out the value of the LED and turn it on or off:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <libusb-1.0/libusb.h>

#define VID 0x0000
#define DID 0x0001

int main(int argc, char **argv) {
        int status, len;
        libusb_device_handle *handle = NULL;

       /* Init Libusb */
        status = libusb_init(NULL);
        if(status < 0) {
                printf("Error init USB!\n");
                return status;
        }

        handle = libusb_open_device_with_vid_pid(NULL, VID, DID);

        if(!handle) {
                printf("No device found with %04x:%04x\n", VID, DID);
                libusb_exit(NULL);
                return -1;
        }
        if(argc > 1)
                value = atoi(argv[1]);
        else {
                /* Do control transfer */
                status = libusb_control_transfer(handle,        /* Device Handle */
                                0x80,                           /* bRequestType */
                                0x10,                           /* bRequest */
                                value,                          /* wValue */
                                0x0,                            /* wIndex */
                                ((uint8_t *) &value),           /* data */
                                2,                              /* wLength */
                                100);                           /* timeout */
                if(status < 0) {
                        printf("Error during control transfer!\n");
                        libusb_close(handle);
                        libusb_exit(NULL);
                        return -1;
                }
                printf("Got: %d\n", value);
                value = (value + 1) & 0x1;
        }

        /* Do control transfer */
        status = libusb_control_transfer(handle, 0x0, 0x10, value, 0x0, NULL, 0, 100);
        if(status < 0) {
                printf("Error during control transfer!\n");
                libusb_close(handle);
                libusb_exit(NULL);
                return -1;
        }
        libusb_close(handle);
        libusb_exit(NULL);

        return 0;
}

Now I want to control my device over a USB Driver. Here is what I got already:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb.h>
#include <linux/slab.h>

/* Meta Information */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Johannes 4 GNU/Linux");
MODULE_DESCRIPTION("Driver for my custom RPi Pico USB device");

struct pico_usb {
        struct usb_device *udev;
};

#define PICO_VID 0x0000
#define PICO_PID 0x0001

static struct usb_device_id pico_usb_table [] = {
        { USB_DEVICE(PICO_VID, PICO_PID) },
        {},
};
MODULE_DEVICE_TABLE(usb, pico_usb_table);

static int pico_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) {
        struct pico_usb *data;
        int status;
        int my_data;

        printk("pico_usb_drv - Now I am in the Probe function!\n");

        data = kzalloc(sizeof(struct pico_usb), GFP_KERNEL);
        if(!data) {
                printk("pico_usb_drv - Out of memory\n");
                return -ENOMEM;
        }
        data->udev = interface_to_usbdev(intf);
        usb_set_intfdata(intf, data);

        /* Turn the LED on */
        status = usb_control_msg(data->udev, usb_sndctrlpipe(data->udev, 0), 0x10, USB_DIR_OUT, 1, 0, 0, 0, 100);
        /* Read LED state */
        printk("pico_usb_drv - status USB_DIR_OUT: %d\n", status);
        status = usb_control_msg(data->udev, usb_rcvctrlpipe(data->udev, 0), 0x10, USB_DIR_IN, 0, 0, (u8*) &my_data, 2, 100);
        printk("pico_usb_drv - status USB_DIR_IN: %d\n", status);

        return 0;
}

static void pico_usb_disconnect(struct usb_interface *intf) {
        struct pico_usb *data;

        printk("pico_usb_drv - Now I am in the Disconnect function!\n");

        data = usb_get_intfdata(intf);
        kfree(data);

}

static struct usb_driver pico_usb_driver = {
        //.owner = THIS_MODULE,
        .name = "pico_usb",
        .id_table = pico_usb_table,
        .probe = pico_usb_probe,
        .disconnect = pico_usb_disconnect,
};

/**
 * @brief This function is called, when the module is loaded into the kernel
 */
static int __init pico_usb_init(void) {
        int result;
        printk("pico_usb_drv - Registering the PICO USB device\n");
        result = usb_register(&pico_usb_driver);
        if(result) {
                printk("pico_usb_drv - Error registering the PICO USB device\n");
                return -result;
        }
        return 0;
}

/**
 * @brief This function is called, when the module is removed from the kernel
 */
static void __exit pcio_usb_exit(void) {
        printk("pico_usb_drv - Unregistering the PICO USB device\n");
        usb_deregister(&pico_usb_driver);
}

module_init(pico_usb_init);
module_exit(pcio_usb_exit);

The first control message works and my LED is turned on. But the second control message doesn't do anything, but gives me the error code -11 back.

Does anyone know, if I am missing something or if I am doing something wrong?


Solution

  • Ok, I found the solution. Instead of usb_control_msg I use usb_control_msg_recv now and everything works just fine.

    usb_control_msg_recv takes one more argument:

    int usb_control_msg_recv(struct usb_device *dev, __u8 endpoint, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *driver_data, __u16 size, int timeout, gfp_t memflags)
    

    As I pass the pointer to a variable and don't want to allocate memory dynamically, I set the memflags argument to 0.