Contents


Developing applications using libusb in IBM AIX

Writing user space drivers

Comments

libusb is a C library that can be used by application developers to write user space drivers for accessing USB devices. Application developers need not depend on the operating system drivers to be developed for new USB devices. The driver can be developed at user level using APIs exposed by the libusb framework, which in turn calls OS-specific APIs. You can find more information about libusb at http://libusb.info/

Currently Linux®, OS X, Microsoft® Windows®, Android, OpenBSD, and so on are supported operating systems are all little-endian systems. libusb is ported and supported for IBM® AIX® which is a big-endian operating system. Application developers must change their big endian sensitive code in their existing libusb applications to run on AIX. This article clearly explains how to write libusb applications and instances of changing the code in existing applications to make it run on big endian systems, such as AIX.

Installation and configuration

libusb rpm for AIX is available for download at: https://public.dhe.ibm.com/aix/freeSoftware/aixtoolbox/RPMS/ppc/libusb/

The rpm can be installed on the AIX system using the following command:

 #rpm -ivh libusb-1.0.19-1.aix7.1.ppc.rpm

The minimum AIX versions that support libusb are 7.2TL1 and 7.1TL4SP3.

libusb support in AIX is disabled by default. It must be enabled by setting a ODM variable by running the following command:

                #chdev –a usblibconfig="available" –l usb0

usb0 should be in defined state for the above command to be successful. The above command followed by the cfgmgr command can discover the libusb devices.

Understanding libusb devices

libusb devices are created in the /dev file system. The parent for these devices is usb0.

When libusb is enabled by setting the usblibconfig attribute to available, libusb devices get discovered as children of usb0.

You can list all USB devices by running the following command:

                # lsdev -C | grep usb

For devices, such as flash drive, keyboard, and mouse, where there are standard operating system client drivers, each device is represented as two devices. One is an operating system device and another is a libusb device. For example, if you connect a flash drive to an AIX system, the device is enumerated as usbms0 by the operating system client driver and usblibdev0 by libusb. If there is no operating system client driver to claim a device, it is represented as only one device. For example, if you connect a USB camera to an AIX system, it will be represented as usblibdev1 only.

For each libusb device, which also has an AIX operating system built-in client driver's device, a new attribute called usbdevice is created in the ODM to identify the corresponding client driver's device for a libusb device. The following example shows how to associate a libusb device with an operating system client driver's device:

                # lsattr -El usblibdev0 
                speed highspeed USB Protocol Speed of
                Device False 
                usbdevice usbms0 Actual USB Device with Client 
                Driver False

In this example, the USB device is a flash drive, which has the AIX operating system built in Mass Storage Class client driver (/usr/lib/drivers/usb/usbcd). The usbdevice attribute indicates that the device of the client driver associated with the usblibdev0 device is usbms0.

You can find more information about the libusb devices and the associated driver at: https://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.ktechrf2/usblibdd_pass.htm

Writing libusb applications on AIX

In this section, three examples using libusb APIs are explained. The description of these examples is given below:

listdevs: This example prints all the devices connected to the AIX system and prints their vendor ID (VID), product ID (PID), bus number, device address and port numbers.

xusb: This example reads device descriptor, Binary Device Object Store(BOS) descriptor, and string descriptor from a USB device.

Every libusb application must start with libusb_init and end with libusb_exit.

A typical libusb application will look as follows:

int main() {
    libusb_init(); /* Initializes the AIX LIUBUSB library */
     ……………
    libusb_get_device_list(…) /* Get the USB devices list */
     ………………
    libusb_open(…);/*opens your device using VID, PID combination*/
     ……………
    libusb_claim_interface(…) /* claim your device interface */
     ……………
     ……………
     /* the application code */
     ……………
     ……………
     libusb_release_interface(…) /* release your device interface*/
     ……………
     libusb_close(…); /* close your device */
     ……………
     libusb_exit(); /* undo the initialization and do cleanup */
}

Note: libusb_open will automatically detach the kernel driver if it is active already. libusb_close will attach the kernel driver back.

listdevs:

                int main(void)
                { 
                     libusb_device **devs; 
                     int r; 
                     ssize_t cnt; 
                     
                     r = libusb_init(NULL); 
                     if (r < 0) 
                           return r; 
                     cnt = libusb_get_device_list(NULL, &devs);
                     if (cnt < 0) 
                            return (int) cnt; 
                            
                     print_devs(devs);
                     libusb_free_device_list(devs, 1); 
                     
                     libusb_exit(NULL); 
                     return 0; 
                } 
                static void print_devs(
                     libusb_device **devs) 
                { 
                     libusb_device *dev; 
                     int i = 0, j = 0; 
                     uint8_t path[8];
                     
                     while ((dev = devs[i++]) != NULL)
                     { 
                        struct libusb_device_descriptor desc;
                        int r = libusb_get_device_descriptor(dev, &desc); 
                        if (r < 0) {
                           fprintf(stderr, "failed to getdescriptor"); 
                           return; 
                        }
                        printf("%04x:%04x (bus %d, device %d)", desc.idVendor, desc.idProduct,
                             libusb_get_bus_number(dev), libusb_get_device_address(dev)); 
                        r = libusb_get_port_numbers(dev, path, sizeof(path)); 
                        if (r > 0) { 
                           printf("path: %d", path[0]); 
                           for (j = 1; j < r; j++) 
                               printf(".%d",path[j]); 
                        } 
                        printf("\n"); 
                     } 
                }

To get the list of all the devices libusb_get_device_list must be called. Internally, this libusb function calls the AIX-specific API to get the list of devices connected to the AIX system. Along with the device list, the device information, such as bus number, port number, device address, and device descriptors are also retrieved. All this device information is stored in the libusb local data structure (struct libusb_device) for each device. Subsequent calls, such as libusb_get_device_descriptor, libusb_get_bus_number, and libusb_get_device_address will get the data associated with these calls from the local data structure and do not generate API calls to the AIX USB subsystem.

xusb:

                int main(int argc, char **argv) 
                { 
                     int i; 
                     unsigned tmp_vid, tmp_pid; 
                     static uint16_t VID, PID; 
                     libusb_device **devs; 
                     int r; 
                     ssize_t cnt;
                     libusb_device_handle *handle; 
                     
                     for (i=0; i<arglen; i++) {
                          if (argv[j][i] == ':') 
                               break; 
                     if (i != arglen) { 
                          if (sscanf(argv[j], "%x:%x" ,&tmp_vid, &tmp_pid) != 2) 
                          {
                             printf(" Please specify VID & PID as \"vid:pid\" in hexadecimal format\n"); 
                             return 1; 
                          } 
                     VID = (uint16_t)tmp_vid; 
                     PID = (uint16_t)tmp_pid; 
                          
                     r = libusb_init(NULL); 
                     if (r < 0)
                          return r; 
                     handle = libusb_open_device_with_vid_pid(NULL, vid, pid); 
                     if (handle == NULL) {
                          perr(" Failed.\n"); 
                          return -1; 
                     } 
                     get_descriptors(handle, VID, PID);
                     printf("Closing device...\n"); 
                     libusb_close(handle);
                     libusb_exit(NULL); 
                     return 0;
                } 
                int get_descriptors( libusb_device_handle *handle, int vid, int pid)
                { 
                     libusb_device *dev; 
                     uint8_t bus, port_path[8]; 
                     struct libusb_bos_descriptor *bos_desc; 
                     struct libusb_config_descriptor *conf_desc; 
                     const struct libusb_endpoint_descriptor *endpoint; 
                     int i, j, k, r; 
                     int iface, nb_ifaces, first_iface = -1; 
                     struct libusb_device_descriptor dev_desc; 
                     char string[128];
                     uint8_t string_index[3]; 
                     uint8_t endpoint_in = 0, endpoint_out = 0
                     printf("Opening device %04X:%04X...\n", vid, pid); 
                     dev = libusb_get_device(handle); 
                     printf("\nReading device descriptor:\n");
                     libusb_get_device_descriptor(dev, &dev_desc)); 
                     printf("length: %d\n", dev_desc.bLength); 
                     printf("device class: %d\n", dev_desc.bDeviceClass);
                     printf("S/N: %d\n", dev_desc.iSerialNumber);
                     printf("VID:PID: %04X:%04X\n", dev_desc.idVendor, dev_desc.idProduct);
                     printf("nb confs:%d\n", dev_desc.bNumConfigurations); 
                     // Copy the string descriptors for easier parsing 
                     string_index[0] = dev_desc.iManufacturer; 
                     string_index[1] = dev_desc.iProduct; 
                     string_index[2] = dev_desc.iSerialNumber; 
                     printf("\nReading string descriptors:\n"); 
                     for (i=0; i<3; i++) 
                     { 
                        if (string_index[i] == 0){
                            continue; 
                        } 
                        if (libusb_get_string_descriptor_ascii(handle, string_index[i],
                                      (unsigned char*)string, 128) >= 0)
                        { 
                            printf(" String (0x%02X): \"%s\"\n", string_index[i], string);
                         } 
                     } 
                }

A device should be opened before doing any read or write operations on it. A handle is returned after a device open event which needs to be used in subsequent libusb API calls to reference a device. libusb has an API, libusb_open_device_with_vid_pid, which takes VID and PID as input, opens the device, and returns the device handle. The alternative to this function is to call libusb_get_device_list and obtain the device that matches the VID and PID, and then call libusb_open to obtain a handle for the device. At the end of the application, libusb_close must be called to destroy the handle created by libusb_open.

Writing libusb applications that perform I/O operations

The following steps are involved while writing the libusb application to perform a read or write operation to the USB flash device.

  1. Populate all the required fields in the Command Block Wrapper (CBW) structure and send the CBW command on the bulk-out endpoint.
  2. Send the buffer along with the size of it to bulk-in or bulk-out endpoint based on the read or write operation. If it is read operation, the buffer must be sent to the bulk-in endpoint and if it is write operation it must be sent to the bulk-out endpoint.
  3. Read the status on bulk-in endpoint.

A typical application code that can perform I/O operations with an USB device is given below:

                read_io(){
                          ………… 
                          …………. 
                          /* Send CBW */ 
                          libusb_bulk_transfer (handle, endpoint, (unsigned char*) &cbw, 31, &size, 1000); 
                          ………….. 
                          /* Send Buffer */
                          libusb_bulk_transfer (handle, endpoint_in, data, block_size, &size, 5000);
                          ………….. 
                          ………….. 
                          /* Get Status */ 
                          libusb_bulk_transfer (handle, endpoint, (unsigned char*)&csw, 13, &size, 1000); 
                          …………. 
                          if (csw.bCSWStatus) 
                          { 
                              if (csw.bCSWStatus == 1) 
                                   return -2;       // request Get Sense 
                              else 
                                   return -1; 
                         } 
                }

Sending CBW

The typical size of CBW is 31 bytes if the USB device follows SCSI standard. The fields are populated as shown below:

                cbw.dCBWSignature[0] = 'U'; 
                cbw.dCBWSignature[1] = 'S'; 
                cbw.dCBWSignature[2] = 'B'; 
                cbw.dCBWSignature[3] = 'C'; 
                cbw.dCBWTag = tag++; 
                cbw.dCBWDataTransferLength = data_length;
                cbw.dCBWDataTransferLength = REV32(data_length); 
                cbw.bmCBWFlags = direction;
                cbw.bCBWLUN = lun; 
                cbw.bCBWCBLength = cdb_len; 
                memcpy(cbw.CBWCB, cdb, cdb_len);

The field dCBWDataTransferLength should be populated differently on AIX because of the big endianness. It must be populated after byte swapping. REV32 is a macro which converts 32 bit big endian to little endian. The macro is given below:

                #define REV32 ((num>>24)&0xff) |      // move byte 3 to byte 0 
                              ((num<<8)&0xff0000)|    // move byte 1 to byte 2 
                              ((num>>8)&0xff00) |     // move byte 2 to byte 1 
                              ((num<<24)&0xff000000); // byte 0 to byte 3

After the fields are filled, the command must be sent to the USB device in the following way. libusb_bulk_transfer is a libusb API to submit bulk I/O transactions.

                r = libusb_bulk_transfer (handle, endpoint, (unsigned char*)&cbw, 31, &size, 1000); 
                if (r == LIBUSB_ERROR_PIPE) {
                     libusb_clear_halt(handle, endpoint); 
                }

Where:

  • handle: Is the device handle for the libusb device.
  • Endpoint: Is the endpoint number for the device based on the I/O operation. Because CBW is always sent on out endpoint, the out endpoint number should be given here.
  • cbw: Is the CBW structure which is filled as shown above.
  • 31: Is the size of the CBW structure.
  • size: Is the number of bytes successfully transferred in the I/O transfer.
  • 1000: Is the time out for the command in milliseconds.

Some devices stall the pipe and cause pipe error. Hence this error must be checked whenever libusb_bulk_transfer is called and the libusb_clear_halt function should be called to clear the stall condition on the pipe.

Sending read/write operations to the device

After CBW is sent, the data buffer needs to be sent. In the following example, a read operation for 512 bytes is sent to the device.

                r = libusb_bulk_transfer(handle, endpoint_in, data, 512, &size, 5000); 
                if (r == LIBUSB_ERROR_PIPE) { 
                     libusb_clear_halt(handle, endpoint_in); 
                }

The timeout used for the read operation is 5 seconds.

Reading status

After sending data buffer, the status of the I/O operation is retrieved from the device by sending CSW. The typical size of CSW is 31 bytes if the USB device follows SCSI standard.

                r = libusb_bulk_transfer (handle, endpoint, (unsigned char*)&csw, 13, &size, 1000); 
                if (r == LIBUSB_ERROR_PIPE) {
                     libusb_clear_halt (handle, endpoint); 
                }

The status of the command is stored in csw.bCSWStatus.

The error conditions are not explained in this article. If the status is -2, then a request sense command must be sent to the device.

libusb APIs supported in AIX

Each operating system must implement the interfaces provided by the usbi_os_backend structure given in libusbi.h. AIX currently implements the following interfaces:

                Init,
                exit, 
                get_device_list,
                open, 
                close, 
                get_device_descriptor,
                get_active_config_descriptor, 
                get_config_descriptor, 
                get_config_descriptor_by_value,
                get_configuration, 
                set_configuration, 
                set_interface_altsetting, 
                kernel_driver_active
                clear_halt, 
                reset_device, 
                destroy_device, 
                submit_transfer, 
                cancel_transfer,
                clear_transfer_priv,
                handle_events, 
                clock_gettime

Note: The submit_transfer API in AIX currently implements bulk, interrupt and isochronous transfers but supports only the bulk transfers. Also, it does not implement USB 3.0 bulk stream libusb interfaces.

Compiling your libusb application

To compile your libusb applications on AIX, libusb-1.0.19-1.aix7.1.ppc.rpm must be installed. For installation of rpm, device configuration and supported AIX versions, refer to the Installation and configuration section in this article.

The following command confirms the successful installation of the libusb rpm.

                    # rpm -qa libusb                
                    libusb-1.0.19-1.ppc

The header file libusb.h is installed in /usr/include/libusb-1.0 and the libusb static library (libusb-1.0) to which application should link is in /usr/lib/libusb-1.0.

The libusb application (say listdevs.c) can be compiled using the following command:

                    # xlc -I /usr/include/libusb-1.0 listdevs.c -lusb-1.0 -lpthread -lcfg -lodm -o listdevs

The lisdevs executable provides the following output:

                # ./listdevs
                found /dev/usbhc0 
                found 1 device 
                0951:1656 (bus 0, device 2) path: 1

Debugging your application on AIX

This section explains how to collect application and kernel logs when you are running a libusb application on AIX.

Application logs

The applications logs can be collected in a file by setting an environment variable.

Run the following command on the shell where you are running your application

                #export LIBUSB_DEBUG=4

After running your application, the applications logs will be captured in /var/adm/ras/libusb.log

Kernel logs

The kernel can also be debugged while the libusb application is running. The kernel logs can be collected in a file using the following approach:

Create a directory where you need to keep the logs.

                #mkdir -p /var/adm/ras/kernellogs

Before you start your application, run the following command:

                #trace -a -L 16777216 -T 1048576 -j 1075,7380 -o
                /var/adm/ras/kernellogs/usbsystrace 
                #ctctrl -c usblibdd -r memtracedetail

After your application quits, dump the collected traces.

                #trcstop
                #ctctrl -D -c usblibdd -r -d /var/adm/ras/kernellogs

The log files will be in the /var/adm/ras/kernellogs directory.

References


Downloadable resources


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=AIX and UNIX
ArticleID=1043779
ArticleTitle=Developing applications using libusb in IBM AIX
publish-date=03142017