Connecting to the USB stack

As you're communicating with a separate process when querying the USB stack on QNX, the driver-side setup is quite different than the kernel module setup that a Linux driver would have. Here are some similarities and differences:

  1. When a driver identifies itself to the USB stack on Linux, it registers itself with that part of the kernel. On QNX, a driver establishes a connection with the USB stack process. This connection is handled by a separate thread of your process, which is responsible for handling IPC and triggering callbacks when needed (whereas the kernel is simply calling another part of itself).

    1. This means more information must be provided when connecting, as both registration and IPC messaging information is needed.

    2. Any data accesses both in a callback and elsewhere in the code must be thread-safe (i.e., protected by a mutex or similar).

  2. Device interest is handled differently across both platforms. Linux provides these as a list of VIDs and PIDs, which your driver may be interested in. QNX provides a filter device profile (including VID, PID, class, subclass, and protocol) and checks against it for matching devices. You can find more details in the following section.

  3. The QNX implementation provides less callbacks. Only insertion and removal, which correlate to Linux's probe and disconnect, are available. More details in the following section.

Data structures

There are three structures associated with connecting to the USB Stack on QNX. They are as follows:

USBD device ID filter

/* In startup, acts as a filter for devices that this driver will connect to */
/* Later, this structure is used to store device information from the USB Stack */
typedef struct usbd_device_ident {
    uint32_t vendor;
    uint32_t device;
    uint32_t dclass;
    uint32_t subclass;
    uint32_t protocol;
} usbd_device_ident_t;

The usbd_device_ident_t structure stores identifying information about a USB device. When connecting to the stack, an instance is passed in to act as a filter specifying which devices the driver would want to connect to. Unlike Linux, you can only provide one filter (although the identifiers it checks against are more broad).

You must set all of the above to either USBD_CONNECT_WILDCARD or an explicit value. The USB stack checks all existing and incoming connections against this filter for exact matches in explicit values and accepting any value against USBD_CONNECT_WILDCARD.

Examples: 

  • Set vendor to 0x046d and the rest to USBD_CONNECT_WILDCARD to match any Logitech devices.

  • Set dclass to any defined class code to filter by class, e.g., 0x03 for HID Devices or 0xFF for vendor-specific.

USBD function callbacks

/* Contains pointers to USBD Callbacks */

typedef struct usbd_funcs {
    uint32_t nentries;
    void (*insertion)(struct usbd_connection *, usbd_device_instance_t *instance);
    void (*removal)(struct usbd_connection *, usbd_device_instance_t *instance);
    void (*event)(struct usbd_connection *, usbd_device_instance_t *instance, uint16_t type);
} usbd_funcs_t;

The usbd_funcs_t structure passes references to user-defined callback functions, which it uses in the management of device connections.

  • The nentries parameter represents the number of callback functions in total, and should always be set to _USBDI_NFUNCS

  • The insertion and removal parameters are pointers to functions of the form void example (struct usbd_connection *, usbd_device_instance_t *), and are called on insertion or removal of USB devices that match the device filter set in the usbd_device_ident structure seen above. The insertion parameter is roughly equivalent to the probe() callback in Linux, and removal is roughly equivalent to the disconnect callback in Linux.

  • The event parameter is a reserved function and should be set as a placeholder (or NULL) for now.

The insertion and removal parameters also control the mode in which you connect to devices.

  • If they're set, you attach to devices with exclusive access and can perform I/O.

  • If they aren't set, you attach with shared access and cannot perform I/O but can read USB configuration values.

The following section provides examples of insertion and removal callbacks.

USBD connection parameters

/* Specifies general settings for the connection, and contains
references to the usbd_device_ident_t and usbd_funcs_t structures */

typedef struct usbd_connect_parm {
    const char             *path;
    uint16_t                vusb;
    uint16_t                vusbd;
    uint32_t                flags;
    int                     argc;
    char                  **argv;
    uint32_t                evtbufsz;
    usbd_device_ident_t    *ident;
    usbd_funcs_t           *funcs;
    uint16_t                connect_wait;
    uint8_t                 evtprio;
    uint8_t                 _reserved[13];
} usbd_connect_parm_t;

The usbd_connect_parm_t structure is the actual structure passed in when connecting to the USB stack and contains both various parameters for starting the IPC connection and references to the callback and identity filter structures above.

path

The path to the USB stack. You can pass the path as NULL, in which case it defaults to /dev/io-usb/io-usb. (This is similar to the dev folder in Linux)

vusb, vusbd

Versions of the USB stack and USBD interface, which should be set to USB_VERSION and USBD_VERSION. This is required as if the USB stack gets updated, it needs to be able to refuse a connection prior to parsing structures; otherwise it may access unallocated memory.

flags

A placeholder for connection flags. Set this to 0, as none are currently available.

argc, argv

Command line arguments that you want to be available for query at device insertion or attach time.

evtbufsz

Controls the size of the buffer used by the USB stack to store insertion, removal, and other events queued to be sent to the driver. Can be set at 0 for default.

ident,funcs

References to the structures listed above, used for filtering and callback storage. The funcs parameter can be set to NULL to indicate that this utility is not interested in asynchonous I/O and will not receive insert, removal, or event callbacks.

connect_wait

A number in seconds to wait for a connection to be established. Can be set to USBD_CONNECT_WAIT to wait for a connection indefinitely. If you are unsure that io-usb is running when your driver is, make sure to set this parameter!

evtprio

Allows class drivers to set the priority of the event handling thread.

reserved

Reserved for future functionality.

Registration settings in Linux

 * this includes a name, ID table, and a number of callback
 * functions on specific events. */

/* NOTE: This code is in a Kernel Module (.ko) */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>

/* Interest is declared via an interest table */
/* Note the available macros for setting up the table */
/* Note the terminating entry */
static struct usb_device_id interest_table[] =
{
    { USB_DEVICE(0x046d,0xc21d) }, /* Logitech F310 */
    {} /* Terminating Entry */
};
MODULE_DEVICE_TABLE(usb, interest_table);

/* All callbacks and the id_table are in the struct */
static struct usb_driver example_driver = {
        .name        = "example",
        .probe       = callback_probe,  /* Called for all potential devices */
        .disconnect  = callback_disconnect, /* Called for removed devices */
        .suspend     = callback_suspend,
        .resume      = callback_resume,
        .pre_reset   = callback_pre_reset,
        .post_reset  = callback_post_reset,
        .id_table    = interest_table,
        .supports_autosuspend = 1,
};

Connection settings in QNX

/* QNX uses a set of three structs to pass in connection settings.
 * these are: a usbd_device_ident_t which is used as a filter for
 * potential matching devices, a usbd_funcs_t which contains a list
 * of callbacks, and a usbd_connect_parm_t which holds references
 * to the above and IPC related parameters */
/* NOTE: This code is in its own process (binary file) */
#include <errno.h>
#include <sys/usbdi.h>


/* Interest is declared via a filter struct */
/* Note USBD_CONNECT_WILDCARD is available as a catch-all */
usbd_device_ident_t filter = {
    .vendor   = 0x046d,                /* Logitech's VID */
    .device   = 0xc21d,                /* F310 Gamepad */
    .dclass   = 0xFF,                  /* 0xFF is Vendor-Specific */
    .subclass = USBD_CONNECT_WILDCARD, /* Any Subclass */
    .protocol = USBD_CONNECT_WILDCARD  /* Any Protocol */
    };

/* Callbacks are declared. Correlations are listed: */
usbd_funcs_t funcs = {
    .nentries  = _USBDI_N_FUNCS, /* MUST be set to this */
    .insertion = on_usbd_insert, /* Works like probe in Linux */
    .removal   = on_usbd_remove, /* Works like disconnect in Linux */
    .event     = NULL            /* Reserved */
    };

/* The final connection settings struct is populated */
usbd_connect_parms_t connect_parms = {
    .path     = NULL,       /* Indicate that the default io-usb path is correct */
    .vusb     = USB_VERSION,
    .vusbd    = USBD_VERSION,
    .flags    = 0,          /* No flags to be passed */
    .argc     = 0,          /* Set as argc in your main function */
    .argv     = NULL,       /* Set as argv in main */
    .evtbufsz = 0,          /* Default event buffer size */
    .ident    = &interest
    .funcs    = &funcs
    .connect_wait = USBD_CONNECT_WAIT /* No Timeout */
    };

Initializing a connection

Once you've populated these structures, we can pass the usbd_connect_parm struct into the usbd_connect() function to request a connection. You should also allocate space for an opaque handle to the USBD connection, represented by struct usbd_connection*. This handle will later be used by usbd_disconnect() to disconnect from the USB Stack.

You can think of usbd_connect() and usbd_disconnect() as analogues to the usb_register() and usb_deregister() functions, which would be used in a Linux kernel module. Except, rather than being place in your module's __init and __exit functions, usbd_connect() is typically placed in the main() function of the program, with usbd_disconnect() also placed there or in signal handling functions.

Note:

If your connection needs to perform I/O, the insertion and removal callbacks MUST be set in your usbd_funcs_t.

/* Available via #include <sys/usbdi.h> on QNX */

struct usbd_connection; /* opaque */

int usbd_connect( usbd_connect_parm_t *parm,
                  struct usbd_connection **connection );
int usbd_disconnect( struct usbd_connection *connection );

As mentioned previously,usbd_connect() creates a separate thread to monitor insertion, removal, or other events via IPC. Make sure any resources shared between the main thread and any callback functions or other threads are properly protected.

The following example extends the settings example from above to actually connect or register:

Registering a USB driver in Linux

/* Continued from above settings */

/* Initialization */
static int __init driver_init(void){
    int result;

    /* Register the driver */
    if((result = usb_register(&example_driver))){
        printk(KERN_ERR "usbd_register failed with errno %d", result);
    }

    return result;
}

/* Exiting */
static void __exit driver_exit(void){
    /* Deregister */
    usb_deregister(&example_driver);
}

/* Module init/exit info */
module_init(driver_init);
module_exit(driver_exit);

Connecting to the USB stack in QNX

/* Continued from above settings */
struct usbd_connection *usb_conn;

/* Exiting when Signals received */
void signal_handler(int signo){
    usbd_disconnect(usb_conn);
    _exit(0);
}

/* Initialization and setting up signal handling */
void main(int argc, char* argv[]){
    /* Set up argc and argv in connect parms*/
    connect_parms.argc = argc;
    connect_parms.argv = argv;

    /* Set up signal handling to be the exit condition */
    signal (SIGHUP, SIG_IGN);
    signal (SIGPWR, SIG_IGN);
    signal (SIGTERM, signal_handler);
    signal (SIGKILL, signal_handler);

    /* Connect */
    int result;
    if((result = usbd_connect(&usb_parms ,&usb_conn))){
        printf("Failed to connect with errno %d\n", result);
        return 0;
    }

    /* Loop until disconnect */
    for( ; ; ) sleep(60);

    /* Unreachable, but here in case we edit the for loop */
    usbd_disconnect(usb_conn);
    return 0;
}
Page updated: