Contents
USB HID PDC
Simplified UPS Application
Binding Ugen Driver to Your Device
Obtaining USB Device Status
Reading the USB Device Descriptor
Descriptors Galore: the Configuration, Interface, HID, and Report Descriptors
Reading the Report Descriptor
Reading the HID Input Report
Putting It All Together
Nice to Know
References
Appendix A: usbups.h
Appendix B: usbups.c
Appendix C: Ugen Man Page
The Solaris USB Driver Development Kit includes the USB Generic Driver (Ugen), which presents USB devices to applications through a standard read/write UNIX interface. Ugen allows for easy access to USB peripherals connected to your SPARC processor-based Solaris system. Currently, Ugen supports USB control, bulk, and interrupt-IN transfers, but not isochronous transfers. We provide an example that uses Ugen to access status information about a USB uninterruptible power supply (UPS). This paper targets application programmers with limited knowledge of USB devices.
Please download the latest Solaris 10 Express releases for the new and
ongoing enhancements in USB. Information about the Solaris Express release can be found at
http://www.sun.com/software/solaris/solaris-express/sol_index.html.
USB HID PDC
Having been invented by engineers, USB comes with a slew of acronyms. An uninterruptible power supply is classified as a USB HID PDC, for Universal Serial Bus Human Interface Devices Power Device Class. The USB Implementers Forum has defined a number of device class specifications, among them the mass storage class specification, the audio class specification, and the human interface device (HID) class. For a given set of devices, a class specification defines the methods of transferring data, the commands the devices should support, and how they should respond to these commands. The HID class deals with keyboards, mice, and joysticks, but also with devices such as a UPS. The HID specifications for these devices are listed on the USB-IF HID web page. The following paragraphs refer to several sections of these specifications.
Simplified UPS Application
The simple UPS application first will obtain manufacturer and device information about UPS. It will also query additional USB information. The application's goal is to show how to obtain the battery status of the UPS. Is the AC wall power available? Is the battery charging? Does the device need to be replaced? Other information can easily be obtained in a similar fashion. To keep the application simple, a HID parser or GUI has been omitted.
Appendix A provides the demonstration source code for usbups.h, and Appendix B shows the source code for usbups.c.
Binding Ugen Driver to Your Device
The Solaris Ugen driver will interface with the USBA1.0 Framework to issue low-level USB transfers. Before being able to talk to the device, the Ugen driver must be bound to the device. This is accomplished using device-specific information. Upon reboot of the system or hot-plugging of a USB device, the Solaris USBA1.0 Framework reads out the USB device's device-specific information (device descriptor).
Upon plugging the UPS into our system, the prtconf(1M) command shows that the device is initially being recognized as an HID device, using the Solaris hid(7D) driver. Because the current Solaris USB HID implementation does not support UPS devices, the hid binding will be removed and the device will be bound to the Ugen driver.
# prtconf -v
input (driver not attached)
Hardware properties:
name='low-speed' type=boolean
...
name='compatible' type=string items=8
value='usb463,ffff.1' + 'usb463,ffff'
+ 'usbif463,class3.0.0' + 'usbif463,class3.0'
+ 'usbif463,class3' + 'usbif,class3.0.0'
+ 'usbif,class3.0' + 'usbif,class3'
# rem_drv ugen
# add_drv -i '"usb463,ffff.1"' -m '* 0666 root sys' ugen
# grep ugen /etc/driver_aliases
ugen "usb463,ffff.1"
# reboot -- -r // Reconfiguration reboot
# prtconf -D
...
usb, instance #0 (driver name: ohci)
mouse, instance #0 (driver name: hid)
keyboard, instance #1 (driver name: hid)
input, instance #0 (driver name: ugen)
...
# cd /dev/usb/463.ffff/0
# cd 0
# ls -l
cntrl0 // Default Control Pipe
cntrl0stat // Default Control status
devstat // Device Status
if0in1 // Interrupt IN Pipe
if0in1stat // Interrupt IN status
Details on ugen are available as part of the Solaris man pages. You can also access
details at http://developers.sun.com/solaris/developer/support/driver/docs/ugen7d.txt.
Obtaining USB Device Status
Before accessing specific device information, the application intends to find out the overall status of the device. Ugen will report four device status levels:
- Device is available (
USB_DEV_STAT_ONLINE)
- Device has been disconnected (
USB_DEV_STAT_DISCONNECTED)
- Device has been resumed (
USB_DEV_STAT_RESUMED)
- Device has been reconnected (
USB_DEV_STAT_UNAVAILABLE)
This information can be used throughout the application; for example, it could be used to ask the user to (re)insert the device. To obtain the device status, you need to consult the devstat device node.
/*
* Obtain Device status
*/
int
get_dev_stat(int fd) {
int error;
int status;
error = read(fd, &status, sizeof(status));
if (error != sizeof(status)) {
fprintf(stderr, "usbups: Could not read device
status: %sn", strerror(error));
status = -1;
}
else {
switch(status) {
case USB_DEV_STAT_ONLINE:
DPRINT("usbups: Device Status - Device
is availablen");
break;
case USB_DEV_STAT_DISCONNECTED:
DPRINT("usbupsS: Device Status - Device
has been disconnectedn");
break;
case USB_DEV_STAT_RESUMED:
DPRINT("usbups: Device Status - Device
has been resumedn");
break;
case USB_DEV_STAT_UNAVAILABLE:
DPRINT("usbups: Device Status - Device
powered downn");
break;
}
}
return status;
} // end get_dev_stat
...
./* Obtain Device Status
* The default device status node is /devstat
* i.e. /dev/usb/463.ffff/0/devstat
*/
strcpy(name_buffer, path);
strcat(name_buffer, "/");
strcat(name_buffer, "devstat");
devstat_fd = open(name_buffer, O_RDONLY | O_EXCL );
if (devstat_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
if (get_dev_stat(devstat_fd) != USB_DEV_STAT_ONLINE) {
fprintf(stderr, "usbups: Device is not availablen");
exit(1);
}
Reading the USB Device Descriptor
USB devices report their attributes using descriptors. A descriptor is simply a data structure with a defined format. The device descriptor describes general information about a USB device. It includes the manufacturer ID, product ID, class code, and so on (see USB 2.0/ 9.6.1 in References section). The device descriptor information is also requested by the USBA1.0 framework in order to build the device tree; its contents were used when binding Ugen to our device (see the preceding code sample).
USB devices present their device information in response to a GET_DESCRIPTOR SETUP request in a form that is little-endian and, for multibyte integers, unaligned. This SETUP request is sent to the device using a control pipe. In the following code snippet, the SETUP request (setup_data) is prepared in the init_cntrl_req function. (init_cntrl_req fills up an 8-byte data structure, setup_data, and performs a byte swap for SPARC platforms.) Details about init_cntrl_req can be found in the source code listing (see Appendixes A and B).
/* Open default control endpoint, in order to send GET_DESCRIPTOR
* requests. The default control pipe name is /cntrl0 */
strcpy(name_buffer, path);
strcat(name_buffer, "/");
strcat(name_buffer, "cntrl0");
ctrl_fd = open(name_buffer, O_RDWR|O_EXCL);
if (ctrl_fd < 0) {
// Error Handling
}
...
/*********************************************************
* Obtain device descriptor
* Device Descriptor Setup Packet
* bmRequestType = USB_DEV_REQ_DEV_TO_HOST (0x80)
* bRequest = USB_REQ_GET_DESCR (0x06)
* wValue = USB_DESCR_TYPE_DEV (0x0001)
* wIndex = 0
* wLength = 12
*********************************************************/
init_cntrl_req(&setup_data, USB_DEV_REQ_DEV_TO_HOST,
USB_REQ_GET_DESCR, USB_DESCR_TYPE_DEV, 0, 0x0012);
count = write(ctrl_fd, &setup_data, sizeof(setup_data));
if (count != sizeof(setup_data)) {
error = get_ctrl_stat(ctrlstat_fd);
fprintf(stderr, "usbups: Error issuing
GET_DESCRIPTOR(device): %sn", error);
}
else { /* Read DEVICE_DESCRIPTOR */
count = read(ctrl_fd, &dev_descr, sizeof(dev_descr));
if (count != sizeof(dev_descr)) {
error = get_ctrl_stat(ctrlstat_fd);
fprintf(stderr, "usbups: Error reading device
descriptor: %sn", error);
}
}
print_dev_descr(dev_descr);
Descriptors Galore: the Configuration, Interface, HID, and Report Descriptors
A single USB device might have several configurations; that is, a high-power device might support a low-power mode, which might have different functionality. The configuration descriptors include general information about each configuration. The configuration descriptor is obtained in a similar manner as the device descriptor, by sending a GET_DESCRIPTOR request. In this example the configuration descriptor is skipped and the default configuration is assumed. (A HID class device typically has only one configuration descriptor).
Each device (or configuration) may support one or more interfaces. An example of a multiple interface device could be an audio device with a streaming interface and a control interface (for volume, for example). Another example is a CDROM, which can be used as a mass storage device and an audio device, controlled by its respective drivers. The interface descriptor details the different interfaces. Note, however, that devices with multiple interfaces in the same configuration can be used by multiple drivers simultaneously. Drivers with multiple configurations, which each have a single interface, can operate only one interface at a time (as only one configuration can be active at a time).
Reading and processing all the descriptors in our example leads to one goal: parsing HID reports. For a HID device, information is sent to the driver in the form of reports. These reports can contain a lot of different information, from the status of an LED to the battery level to the relative position of the mouse scroll wheel, and so on. Reading the HID descriptor and the report descriptor is the first step in decoding and understanding the HID reports. Figure 1 summarizes the different descriptors for a HID device.

Figure 1: Different Descriptors for a HID Device
The following example shows how to use the interface descriptor and how to access the HID descriptor.
/*********************************************************
* Obtain Interface Descriptor
* Read the Interface Descriptor to obtain the class and
* subclass, _since_ for HID devices the class type is
* defined there, and not in the Device Descriptor.
* Interface Descriptor Setup Packet
* bmRequestType = USB_DEV_REQ_DEV_TO_HOST (0x80)
* bRequest = USB_REQ_GET_DESCR (0x06)
* wValue = USB_DESCR_TYPE_IF (0x0004)
* wIndex = 0
* wLength = 7
*********************************************************/
init_cntrl_req(&setup_data, USB_DEV_REQ_DEV_TO_HOST,
USB_REQ_GET_DESCR, USB_DESCR_TYPE_IF, 0, 0x0007);
count = write(ctrl_fd, &setup_data, sizeof(setup_data));
if (count != sizeof(setup_data)) {
error = get_ctrl_stat(ctrlstat_fd);
// error handling
}
else {
/* Read INTERFACE_DESCRIPTOR */
count = read(ctrl_fd, &if_descr, sizeof(if_descr));
if (count != sizeof(if_descr)) {
error = get_ctrl_stat(ctrlstat_fd);
// error handling
}
}
if (if_descr.bInterfaceClass != 3) {
fprintf(stderr, "usbups: Device is not a HID device: %sn",
if_descr.bInterfaceClass);
exit(1);
}
/*********************************************************
* Obtain HID descriptor
* Read the HID descriptor to access the Report Descriptor
* (hid_descr.wReportDescriptorLength)
* Interface Descriptor Setup Packet
* bmRequestType = USB_DEV_REQ_DEV_TO_HOST (0x80)
* bRequest = USB_REQ_GET_DESCR (0x06)
* wValue = USB_DESCR_TYPE_HID (0x0021)
* wIndex = 0
* wLength = 9
*********************************************************/
init_cntrl_req(&setup_data, USB_DEV_REQ_DEV_TO_HOST,
USB_REQ_GET_DESCR, USB_DESCR_TYPE_HID, 0, 0x0009);
count = write(ctrl_fd, &setup_data, sizeof(setup_data));
if (count != sizeof(setup_data)) {
error = get_ctrl_stat(ctrlstat_fd);
// error handling
}
else {
/* Read HID_DESCRIPTOR
* Note: because of word alignment/padding, the
* sizeof(hid_descr)==10)
*/
count = read(ctrl_fd, &hid_descr, sizeof(hid_descr));
if ((count+1) != sizeof(hid_descr)) {
error = get_ctrl_stat(ctrlstat_fd);
// error handling
}
}
Reading the Report Descriptor
A report descriptor is a complete set of all information items for a device. By looking at the report descriptor alone, an application knows how to handle incoming data, as well as the data's possible uses. The following table shows you an example of a report descriptor. The report descriptor can be obtained by issuing the usual GET_DESCRIPTOR request. The length of the descriptor (wLength) was previously obtained from the HID descriptor (hid_descr->wReportDescriptorLength).
The UPS report descriptor can be dissected using the HID Usage Tables v1.1 and the Usage Tables for HID Power Devices v1.0 specifications (see the References section).
| Item |
Value (Hex) |
| Usage Page (05) (Power Device) (HID 6.2.2.7) |
0x5 0x84 |
| Usage (09) (UPS) (HID 6.2.2.8) |
0x9 0x4 |
| Collection (a1) (Physical) (HID 6.2.2.6) |
0xa1 0x0 |
| Usage (09) (Power converter) (HID 6.2.2.8) |
0x9 0x16 |
| Collection (a1) (Physical) |
0xa1 0x0 |
| Usage (Power converter ID) |
0x9 0x17 |
| Report ID (11) |
0x85 0xb |
| Report Size (8) |
0x75 0x8 |
| Report Count (1) |
0x95 0x01 |
| Logical_Minimum (0) |
0x15 0x00 |
| Logical_Maximum (255) |
0x26 0xff 0x00 |
| Unit (None) |
0x65 0x00 |
| Feature (Cnst, Var, Abs) |
0xb1 0x03 |
| ... |
... |
The HID specification details how the report descriptor information should be used based upon main (input, output, feature), global and local items. In the preceding example, a feature report with Report ID =1 will list the power converter ID using 8 bits.
An application can request an input, output, or feature report. An input report provides information on input items, that is, the state of switches. An output report lists output data such as the state of an LED. Feature data describes device input and output not intended for the end user. The Report ID is key to later interpretation of incoming reports.
To avoid complexity, the application cheated a little and hid some of the parsing. It is not the intention to build a complete HID parser here, but only to illustrate how to obtain information from the device. Often device manufacturers will publish their HID report descriptor, eliminating the need to write a generic HID parser. This example uses an Ellipse UPS from MGE UPS Systems. The Ellipse HID report descriptor is documented on the Network UPS Tools web site (see References section).
Parsing input items shows a total of 7 bytes, though listed in several types of input reports (depending on the Report ID). For example, when one receives an input report, with Report ID = 2, it will contain 2 bytes (9 bits of battery system information and 7 padding bits).
RemainingCapacity (8) (Report ID: 22)
RunTimeToEmpty(16) (Report ID: 22)
CommunicationLost(1) (Report ID: 1)
Undefined(7) // Padding
Battery System (9) (Report ID: 2)
ACPresent(1)
Charging(1)
Discharging(1)
BelowRemainingCapacityLimit (1)
NeedReplacement (1)
Power Device: Good (1)
Power Device: ShutdownImminent(1)
Power Device: Overload (1)
Power Device: Internal Failure (1)
Undefined(7) // Padding
SwitchOn/Off (1) (Report ID: 3)
Undefined(7) // Padding
In total: 8 bytes (7 bytes + Report ID byte)
This example focuses on the battery system:
- Is the incoming AC power live? (Input Report Item)
- Is the battery charging? (Input Report Item)
Reading the HID Input Report
The HID Input Report is read using the interrupt IN pipe. The UPS will send reports on the interrupt IN pipe when a specific event occurs. When an input report with report ID=2 is received, the battery status is printed.
/* Open Interrupt IN pipe */
strcpy(name_buffer, path);
strcat(name_buffer, "/");
strcat(name_buffer, "if0in1");
iin_fd = open(name_buffer, O_EXCL |O_RDONLY |O_NONBLOCK |O_NDELAY);
if (iin_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
/* Interrupt IN pipe status name is <path>/in0in1stat */
strcat(name_buffer, "stat");
iinstat_fd = open(name_buffer, O_RDONLY|O_EXCL);
if (iinstat_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
while(1) {
/* Reading Interrupt IN
* If no data is available, -1 is returned and
* USB_LC_STAT_INT_BUF_FULL is set */
count = read(iin_fd, input_report, sizeof(input_report));
if (count == -1) {
error = get_ctrl_stat(iinstat_fd);
if (error != USB_LC_STAT_INTR_BUF_FULL) {
/* real read error */
fprintf(stderr, "usbups: Error reading interrupt
IN pipe: %sn", error);
exit(1);
}
}
else {
if (input_report[0]==2){ /* Battery Sys Info */
if (input_report[1] & 0x01) {
/* ACPresent */
DPRINT("usbups: ACPresentn");
}
if (input_report[1] & 0x02) {
/* Charging */
DPRINT("usbups: Battery Chargingn");
}
if (input_report[1] & 0x04) {
/* Discharging */
DPRINT("usbups: Battery Dischargingn");
}
...
}
}
sleep(5);
}
Putting It All Together
The entire sample program will read and print out the different descriptors, as well as some debugging information. When the UPS is disconnected from the AC outlet, this is reported, and action can be taken (that is, reading out the battery level and notifying the user of uptime before shutting down). The source code generating the following output can be found in the Appendixes.
# ./usbups /dev/usb/4633.ffff/0
usbups: Opening the device status node successful
usbups: Device Status - Device is available
usbups: Opening the default control pipe successful
usbups: Opening the default control status pipe successful
usbups: Sending GET_DESCRIPTOR(device) packet
usbups: Sending GET_DESCRIPTOR(device) packet successful
Device descriptor: VID:0463, PID:ffff
Device release:256(0x100), USB release:1(0x1)
Descriptor Length:18, Type:1
Device Class:0, Subclass:0, Protocol:0, Num Cfgs:1
usbups: Sending GET_DESCRIPTOR(interface) packet
usbups: Sending GET_DESCRIPTOR(interface) packet successful
Interface descriptor:
Descriptor Length:9, Type:4
Interface Number: 0, Alternate Setting: 0
Number of Endpoints: 1
Interface Class: 3, Interface Subclass: 0
usbups: Device is a HID device
usbups: Sending GET_DESCRIPTOR(hid) packet
usbups: Sending GET_DESCRIPTOR(hid) packet successful
HID descriptor:
Descriptor Length:9, Type:33
Hid Class spec release: 100
Country Code: 33 (FYI 33 = US)
Number of class descriptors: 1
Class descriptor type: 34
Total size of Report Descriptor: 512
usbups: Sending GET_DESCRIPTOR(report) packet
usbups: Sending GET_DESCRIPTOR(report) packet successful
Report descriptor:
0x5 0x84 0x9 0x4 0xa1 0x0 0x9 0x16 0xa1 0x0 0x9 0x17 0x85 0xb 0x75 0x8 0x95 0x1 0x15
0x0 0x26 0xff 0x0 0x65 0x0 0xb1 0x3 0x9 0x1c 0xa1 0x0 0x9 0x1d 0xb1 0x3 0x9 0x30 0x85
0xe 0x67 0x21 0xd1 0xf0 0x0 0x55 0x7 0xb1 0x83 0x9 0x53 0x85 0x13 0xb1 0x82 0x9 0x54
0x75 0x10 0x26 0xff 0x7f 0xb1 0x82 0xc0 0xc0 0x9 0x1e 0xa1 0x84 0x9 0x1f 0x85 0xb 0x75
0x8 0x95 0x1 0x65 0x0 0x55 0x0 0x26 0xff 0x0 0xb1 0x3 0x9 0x40 0x85 0x12 0x67 0x21 0xd1
0xf0 0x0 0x55 0x7 0xb1 0x82 0x9 0x42 0x85 0xd 0x66 0x1 0xf0 0x55 0x0 0xb1 0x83 0x9 0x43
0x75 0x10 0x26 0xff 0x7f 0x66 0x21 0xd1 0x55 0x7 0xb1 0x83 0xc0 0x9 0x24 0xa1
usbups: Opening the Interrupt IN pipe successful
usbups: Opening the Interrupt IN status pipe successful
usbups: Reading Interrupt IN endpoint data successful: 0 bytes
<DISCONNECT UPS FROM AC OUTLET>
usbups: Reading Interrupt IN endpoint data successful: 3 bytes
Byte[0] = 2
Byte[1] = 24
Byte[2] = 0
usbups: Battery Discharging
<RECONNECTED UPS TO AC OUTLET>
usbups: Reading Interrupt IN endpoint data successful: 3 bytes
Byte[0] = 2
Byte[1] = 23
Byte[2] = 0
usbups: ACPresent
usbups: Battery Charging
Nice to Know
Ugen reports endpoint status via the endpoint status device logical names (that is,/dev/usb/463.ffff/cntrl0stat and /dev/usb/463.ffff/if0in1stat). Appendix C contains the complete list of Ugen error codes. Despite detailed Ugen error reporting, we ran into additional generic I/O and device errors, which we think are nice to know about:
- The
write/read error output is not always specific enough to debug a problem. For example, we learned that write will eventually time out and not complete when sending an incorrect SETUP packet down the pipe.
- Another error we ran into shows up at the USB OHCI level. When trying to obtain a report using the
GET_REPORT command, the device was brought into a confused state.
# breydel usba: usb0: getting device descriptor failed (<Command timed out> 0x10 -1)
# breydel last message repeated 1 time
breydel usba: usb0: getting device descriptor failed (<Endpoint returned stall PID> 0x14 -1)
breydel usba: WARNING: /pci@8,700000/usb@5,3 (ohci0): Connecting device on port 4 failed
To bring back the device, you can perform a reconfiguration reboot (reboot -- -r), plug the device behind an external hub, or power off the UPS (and wait for several minutes).
References
Note: Other device driver information is available on the Solaris Developer Connection Driver Development White Papers page.
/*
* Sample UGEN application
*
*/
/*
* USB Device Setup Packet
* Refer to USB 2.0/ 9.3
*
* All USB devices respond to requests from the host on the device's
* Default Control Pipe. These requests are made using control
* transfers. The request and the request's parameters are sent
* to the device in the Setup packet. Every Setup packet has eight
* bytes:
*
* Offset Field Size
* ----------------------------------
* 0 bmRequestType 1
* 1 bRequest 1
* 2 wValue 2
* 4 wIndex 2
* 6 wLength 2
*
* IMPORTANT: Pay attention to (little) endianness.
*
*/
typedef struct setup_data {
uint8_t bmRequestType; /* Request Type */
uint8_t bRequest; /* Setup Request */
uint16_t wValue; /* Request Info */
uint16_t wIndex; /* Index/Offset Info */
uint16_t wLength; /* # of bytes in transfer */
} setup_data_t;
/*
* bmRequestType: device request type
*/
#define USB_DEV_REQ_HOST_TO_DEV 0x00
#define USB_DEV_REQ_DEV_TO_HOST 0x80
#define USB_DEV_REQ_DIR_MASK 0x80
#define USB_DEV_REQ_TYPE_STANDARD 0x00
#define USB_DEV_REQ_TYPE_CLASS 0x20
#define USB_DEV_REQ_TYPE_VENDOR 0x40
#define USB_DEV_REQ_TYPE_MASK 0x60
#define USB_DEV_REQ_RCPT_DEV 0x00
#define USB_DEV_REQ_RCPT_IF 0x01
#define USB_DEV_REQ_RCPT_EP 0x02
#define USB_DEV_REQ_RCPT_OTHER 0x03
#define USB_DEV_REQ_RCPT_MASK 0x03
/* Refer to HID1.1/7.2.1 */
#define USB_HID_REQ_DEV_TO_HOST 0xa1
/*
* Defined bRequest Values
* Refer to USB 2.0/9.4
*/
#define USB_REQ_GET_STATUS 0x00
#define USB_REQ_CLEAR_FEATURE 0x01
#define USB_REQ_SET_FEATURE 0x03
#define USB_REQ_SET_ADDRESS 0x05
#define USB_REQ_GET_DESCR 0x06
#define USB_REQ_SET_DESCR 0x07
#define USB_REQ_GET_CFG 0x08
#define USB_REQ_SET_CFG 0x09
#define USB_REQ_GET_IF 0x0a
#define USB_REQ_SET_IF 0x0b
#define USB_REQ_SYNC_FRAME 0x0c
/* Refer to HID 1.1/7.2.1 */
#define USB_REQ_GET_REP 0x01
/*
* wValue
* In the setup packet, the descriptor type is passed in the high
* byte of the wValue field. Hence the addition of two zeroes.
* Descriptor types:
*/
#define USB_DESCR_TYPE_SETUP_DEV 0x0100
#define USB_DESCR_TYPE_SETUP_CFG 0x0200
#define USB_DESCR_TYPE_SETUP_STRING 0x0300
#define USB_DESCR_TYPE_SETUP_IF 0x0400
#define USB_DESCR_TYPE_SETUP_EP 0x0500
#define USB_DESCR_TYPE_SETUP_DEV_QLF 0x0600
#define USB_DESCR_TYPE_SETUP_OTHER_SPEED_CFG 0x0700
#define USB_DESCR_TYPE_SETUP_IFPWR 0x0800
#define USB_DESCR_TYPE_DEV 0x0100
#define USB_DESCR_TYPE_CFG 0x0200
#define USB_DESCR_TYPE_STRING 0x0300
#define USB_DESCR_TYPE_IF 0x0400
#define USB_DESCR_TYPE_EP 0x0500
#define USB_DESCR_TYPE_DEV_QLF 0x0600
#define USB_DESCR_TYPE_OTHER_SPEED_CFG 0x0700
#define USB_DESCR_TYPE_IF_PWR 0x0800
/* Refer to HID1.1/7.1 */
#define USB_DESCR_TYPE_HID 0x2100
#define USB_DESCR_TYPE_REP 0x2200
#define USB_DESCR_TYPE_PHY 0x2300
/* Refer to HID1.1/7.2.1 */
#define USB_DESCR_TYPE_REP_IN 0x0100
#define USB_DESCR_TYPE_REP_OUT 0x0200
#define USB_DESCR_TYPE_REP_FEA 0x0300
/*
* USB Descriptor Management
* Standard USB descriptors:
*
* USB devices present their configuration information in response to
* a GET_DESCRIPTOR request in a form which is little-endian and,
* for multibyte integers, unaligned. It is also position-dependent,
* which makes non-sequential access to particular interface or
* endpoint data inconvenient.
* A GET_DESCRIPTOR request may yield a chunk of data that contains
* multiple descriptor types. For example, a GET_DESCRIPTOR request
* for a CONFIGURATION descriptor could return the configuration
* descriptor followed by an interface descriptor and the relevant
* endpoint descriptors.
*
* usb_dev_descr:
* usb device descriptor, refer to USB 2.0/9.6.1,
*/
typedef struct usb_dev_descr {
uint8_t bLength; /* descriptor size */
uint8_t bDescriptorType;/* set to DEVICE */
uint16_t bcdUSB; /* USB spec rel. number in bcd */
uint8_t bDeviceClass; /* class code */
uint8_t bDeviceSubClass;/* sub class code */
uint8_t bDeviceProtocol;/* protocol code */
uint8_t bMaxPacketSize0;/* max pkt size of e/p 0 */
uint16_t idVendor; /* vendor ID */
uint16_t idProduct; /* product ID */
uint16_t bcdDevice; /* device release number in bcd */
uint8_t iManufacturer; /* manufacturing string */
uint8_t iProduct; /* product string */
uint8_t iSerialNumber; /* serial number string index */
uint8_t bNumConfigurations; /* #configs for device */
} usb_dev_descr_t;
/*
* Interface Descriptor
* Refer to USB 2.0/9.6.5
*/
typedef struct usb_if_descr {
uint8_t bLength; /* descriptor size */
uint8_t bDescriptorType;/* Interface descriptor type */
uint8_t bInterfaceNumber; /* Interface number */
uint8_t bAlternateSetting;/* */
uint8_t bNumEndpoints; /* Number of Endpoints */
uint8_t bInterfaceClass; /* class code */
uint8_t bInterfaceSubClass;/* Sunclass code */
} usb_if_descr_t;
./*
* HID Descriptor
* Refer to HID 1.1/6.2.1
* Watch out for padding! sizeof(usb_hid_descr) is 10.
*/
typedef struct usb_hid_descr {
uint8_t bLength; /* descriptor size */
uint8_t bDescriptorType;/* HID descriptor type */
uint16_t bcdHID; /* HID class spec release */
uint8_t bCountryCode; /* Country Code */
uint8_t bNumDescriptors;/* # of class descriptors */
uint8_t bClassDescriptorType;/* */
uint16_t wReportDescriptorLength;/* total size of Report Descr */
} usb_hid_descr_t;
#pragma ident "@(#)usbups.c 1.2 02/06/20 SMI"
/*
* Sample UGEN Application
* This application demonstrates the use of UGEN (USB Generic Device)
* Driver. The target device is a USB HID PCD device such as an
* Uninterruptible Power Supply (UPS).
*
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/systeminfo.h>
#include <sys/usb/clients/ugen/usb_ugen.h>
#include "usbups.h"
#define UPSDEBUG 1
#define BYTESWAP(in) (((in & 0xFF) << 8) + ((in & 0xFF00) >> 8))
#define DPRINT(m) if(UPSDEBUG) {printf(m);}
/*
* Function Prototypes
*/
boolean_t is_sparc();
void init_cntrl_req(setup_data_t *, uint8_t, uint8_t, uint16_t, uint16_t, uint16_t);
int get_dev_stat(int);
int get_ctrl_stat(int);
void print_dev_descr(usb_dev_descr_t);
void print_if_descr(usb_if_descr_t);
void print_hid_descr(usb_hid_descr_t);
void print_rep_descr(uint8_t *);
/*
* Return true if running on a SPARC system.
*/
#define IS_SPARC_BUFSIZE 6
boolean_t is_sparc()
{
char buf[IS_SPARC_BUFSIZE];
sysinfo(SI_ARCHITECTURE, buf, IS_SPARC_BUFSIZE);
return (strcmp(buf, "sparc") == 0);
}
/*
* Create control SETUP packet
*/
void
init_cntrl_req(setup_data_t *sd, uint8_t bmRequestType, uint8_t
bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength) {
(*sd).bmRequestType = bmRequestType;
(*sd).bRequest = bRequest;
/* Sparc is big endian, USB little endian */
if(is_sparc()) {
(*sd).wValue = BYTESWAP(wValue);
(*sd).wIndex = BYTESWAP(wIndex);
(*sd).wLength = BYTESWAP(wLength);
}
else {
(*sd).wValue = wValue;
(*sd).wIndex = wIndex;
(*sd).wLength = wLength;
}
}
/*
* Obtain Device status
*/
int
get_dev_stat(int fd) {
int error;
int status;
error = read(fd, &status, sizeof(status));
if (error != sizeof(status)) {
fprintf(stderr, "usbups: Could not read device
status: %sn", strerror(error));
status = -1;
}
else {
switch(status) {
case USB_DEV_STAT_ONLINE:
DPRINT("usbups: Device Status - Device is availablen");
break;
case USB_DEV_STAT_DISCONNECTED:
DPRINT("usbupsS: Device Status - Device
has been disconnectedn");
break;
case USB_DEV_STAT_RESUMED:
DPRINT("usbups: Device Status - Device
has been resumedn");
break;
case USB_DEV_STAT_UNAVAILABLE:
DPRINT("usbups: Device Status - Device
powered downn");
break;
} // end switch
} // end else
return status;
} // end print_dev_stat()
/*
* Obtain Control Endpoint Status
*/
int
get_ctrl_stat(int fdstat) {
int status, error;
DPRINT("usbups: Reading control status endpoint to get more
info on the errorn");
error = read(fdstat, &status, sizeof(status));
if (error == sizeof(status)) {
switch (status) {
case USB_LC_STAT_SUSPENDED:
DPRINT("usbups: CtrlStatus - Device was
suspendedn");
break;
case USB_LC_STAT_DISCONNECTED:
DPRINT("usbups: CtrlStatus - Device was
disconnectedn");
break;
case USB_LC_STAT_NO_RESOURCES:
DPRINT("usbups: CtrlStatus - No
resources available for
requestn");
break;
case USB_LC_STAT_DEV_NOT_RESP:
DPRINT("usbups: CtrlStatus - Device is
not respondingn");
break;
case USB_LC_STAT_TIMEOUT:
DPRINT("usbups: CtrlStatus - Command
time outn");
break;
case USB_LC_STAT_HW_ERR:
DPRINT("usbups: CtrlStatus - Host
Controller h/w errorn");
break;
case USB_LC_STAT_NOERROR:
DPRINT("usbups: CtrlStatus - No
errorn");
break;
default:
DPRINT("usbups: CtrlStatus - Error Not
Determinedn");
break;
}
}
return status;
} // end get_ctrl_stat
/* Descriptor Print functions */
void
print_dev_descr(usb_dev_descr_t dev_descr) {
/*
* Note that the Vendor ID and Product ID are in binary coded decimal,
* and that USB is opposite endian of SPARC. Swap bytes to properly
* display these fields on SPARC.
*/
printf("nDevice descriptor: ");
printf("VID:%4.4x, PID:%4.4xn", BYTESWAP(dev_descr.idVendor),
BYTESWAP(dev_descr.idProduct));
printf(" Device release:%d(0x%x), USB release:%d(0x%x)n",
dev_descr.bcdDevice, dev_descr.bcdDevice, dev_descr.bcdUSB, dev_descr.bcdUSB);
printf(" Descriptor Length:%d, Type:%dn",
dev_descr.bLength, dev_descr.bDescriptorType);
printf(" Device Class:%d, Subclass:%d, Protocol:%d, Num
Cfgs:%dnn", dev_descr.bDeviceClass,
dev_descr.bDeviceSubClass,
dev_descr.bDeviceProtocol,
dev_descr.bNumConfigurations);
} // end print_dev_descr()
void
print_if_descr(usb_if_descr_t if_descr) {
printf("nInterface descriptor:n");
printf(" Descriptor Length:%d, Type:%dn",if_descr.bLength,
if_descr.bDescriptorType);
printf(" Interface Number: %d, Alternate Setting: %dn",
if_descr.bInterfaceNumber,
if_descr.bAlternateSetting);
printf(" Number of Endpoints: %dn",
if_descr.bNumEndpoints);
printf(" Interface Class: %d, Interface Subclass: %dnn",
if_descr.bInterfaceClass,
if_descr.bInterfaceSubClass);
}
void
print_hid_descr(usb_hid_descr_t hid_descr) {
printf("nHID descriptor:n");
printf(" Descriptor Length:%d, Type:%dn",hid_descr.bLength,
hid_descr.bDescriptorType);
/*
* Note that the HID class spec release is in binary coded decimal,
* and that USB is opposite endian of SPARC. Swap bytes to properly
* display these fields on SPARC.
*/
printf(" Hid Class spec release: %xn",
BYTESWAP(hid_descr.bcdHID));
printf(" Country Code: %d (FYI 33 = US)n",
hid_descr.bCountryCode);
printf(" Number of class descriptors: %dn",
hid_descr.bNumDescriptors);
printf(" Class descriptor type: %dn",
hid_descr.bClassDescriptorType);
printf(" Total size of Report Descriptor: %dnn",
hid_descr.wReportDescriptorLength);
}
void
print_rep_descr(uint8_t *buf) {
int i;
printf("nReport descriptor:n");
/* First 128 to get a flavor of the report descriptor */
for (i=0;i<128;i++) {
printf("0x%x ", *buf);
buf++;
}
printf("nn");
}
void
main(int argc, char *argv[]) {
setup_data_t setup_data;
usb_dev_descr_t dev_descr;
usb_if_descr_t if_descr;
usb_hid_descr_t hid_descr;
uint8_t rep_descr[512];
uint8_t input_report[7];
int errno, error, status, count, i;
int devstat_fd, ctrl_fd, ctrlstat_fd,
iin_fd, iinstat_fd;
caddr_t path, name_buffer;
/* Validate argument, which is directory containing device
* fds. Borrowed from ugen sample app
*/
if (argc != 2) {
fprintf(stderr, "Usage: %s <directory of UPS device
nodes: i.e./dev/usb/463.ffff/0>n", argv[0]);
exit(EINVAL);
}
path = malloc(strlen(argv[1]));
strcpy(path, argv[1]);
name_buffer = malloc(strlen(argv[1]) + 12);
/* Obtain Device Status
* The default device status node is <path>/devstat
* i.e. /dev/usb/463.ffff/0/devstat
*/
strcpy(name_buffer, path);
strcat(name_buffer, "/");
strcat(name_buffer, "devstat");
devstat_fd = open(name_buffer, O_RDONLY | O_EXCL );
if (devstat_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
DPRINT("usbups: Opening the device status node
successfuln");
if (get_dev_stat(devstat_fd) != USB_DEV_STAT_ONLINE) {
fprintf(stderr, "usbups: Device is not availablen");
exit(1);
}
/* Open default control endpoint, in order to send
* GET_DESCRIPTOR requests.
* The default control pipe name is <path>/cntrl0
*/
strcpy(name_buffer, path);
strcat(name_buffer, "/");
strcat(name_buffer, "cntrl0");
ctrl_fd = open(name_buffer, O_RDWR|O_EXCL);
if (ctrl_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
DPRINT("usbups: Opening the default control pipe
successfuln");
/* Control pipe status name is <path>/cntrl0stat */
strcat(name_buffer, "stat");
ctrlstat_fd = open(name_buffer, O_RDONLY|O_EXCL);
if (ctrlstat_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
DPRINT("usbups: Opening the default control status pipe
successfuln");
/*********************************************************
* Obtain device descriptor
* Device Descriptor Setup Packet
* bmRequestType = USB_DEV_REQ_DEV_TO_HOST (0x80)
* bRequest = USB_REQ_GET_DESCR (0x06)
* wValue = USB_DESCR_TYPE_DEV (0x0001)
* wIndex = 0
* wLength = 12
*********************************************************/
init_cntrl_req(&setup_data, USB_DEV_REQ_DEV_TO_HOST,
USB_REQ_GET_DESCR, USB_DESCR_TYPE_DEV, 0, 0x0012);
DPRINT("usbups: Sending GET_DESCRIPTOR(device)
packetn");
count = write(ctrl_fd, &setup_data, sizeof(setup_data));
if (count != sizeof(setup_data)) {
error = get_ctrl_stat(ctrlstat_fd);
fprintf(stderr, "usbups: Error issuing
GET_DESCRIPTOR(device): %sn", error);
}
else {
DPRINT("usbups: Sending
GET_DESCRIPTOR(device) packet successfuln");
/* Read DEVICE_DESCRIPTOR */
count = read(ctrl_fd, &dev_descr,
sizeof(dev_descr));
if (count != sizeof(dev_descr)) {
error = get_ctrl_stat(ctrlstat_fd); fprintf(stderr,
"usbups: Error reading
device descriptor: %sn", error);
}
}
print_dev_descr(dev_descr);
/*********************************************************
* Obtain interface descriptor
* Read the Interface Descriptor to obtain the class and
* subclass. As for HID devices, the class type is
* defined not in the Device Descriptor, but in the
* interface descriptor.
* Interface Descriptor Setup Packet
* bmRequestType = USB_DEV_REQ_DEV_TO_HOST (0x80)
* bRequest = USB_REQ_GET_DESCR (0x06)
* wValue = USB_DESCR_TYPE_IF (0x0004)
* wIndex = 0
* wLength = 7
*********************************************************/
init_cntrl_req(&setup_data, USB_DEV_REQ_DEV_TO_HOST,
USB_REQ_GET_DESCR, USB_DESCR_TYPE_IF, 0, 0x0007);
DPRINT("usbups: Sending GET_DESCRIPTOR(interface)
packetn");
count = write(ctrl_fd, &setup_data, sizeof(setup_data));
if (count != sizeof(setup_data)) {
error = get_ctrl_stat(ctrlstat_fd);
fprintf(stderr, "usbups: Error issuing
GET_DESCRIPTOR(interface): %sn", error);
}
else {
DPRINT("usbups: Sending
GET_DESCRIPTOR(interface) packet successfuln"
/* Read INTERFACE_DESCRIPTOR */
count = read(ctrl_fd, &if_descr,
sizeof(if_descr));
if (count != sizeof(if_descr)) {
error = get_ctrl_stat(ctrlstat_fd); fprintf(stderr,
"usbups: Error reading
device descriptor: %sn", error);
}
}
print_if_descr(if_descr);
if (if_descr.bInterfaceClass != 3) {
fprintf(stderr, "usbups: Device is not a HID
device: %sn", if_descr.bInterfaceClass);
exit(1);
}
DPRINT("usbups: Device is a HID devicen");
/*********************************************************
* Obtain HID descriptor
* Read the HID descriptor to access the Report Descriptor
* (hid_descr.wReportDescriptorLength)
*
* Interface Descriptor Setup Packet
* bmRequestType = USB_DEV_REQ_DEV_TO_HOST (0x80)
* bRequest = USB_REQ_GET_DESCR (0x06)
* wValue = USB_DESCR_TYPE_HID (0x0021)
* wIndex = 0
* wLength = 9
*********************************************************/
init_cntrl_req(&setup_data, USB_DEV_REQ_DEV_TO_HOST,
USB_REQ_GET_DESCR, USB_DESCR_TYPE_HID, 0, 0x0009);
DPRINT("usbups: Sending GET_DESCRIPTOR(hid) packetn");
count = write(ctrl_fd, &setup_data, sizeof(setup_data));
if (count != sizeof(setup_data)) {
error = get_ctrl_stat(ctrlstat_fd);
fprintf(stderr, "usbups: Error issuing
GET_DESCRIPTOR(hid): %sn", error);
}
else {
DPRINT("usbups: Sending GET_DESCRIPTOR(hid)
packet successfuln");
/* Read HID_DESCRIPTOR
* Note: because of word alignment/padding, the
* sizeof(hid_descr)==10)
*/
count = read(ctrl_fd, &hid_descr,
sizeof(hid_descr));
if ((count+1) != sizeof(hid_descr)) {
error = get_ctrl_stat(ctrlstat_fd); fprintf(stderr,
"usbups: Error reading
hid descriptor: %sn", error);
}
}
print_hid_descr(hid_descr);
/*********************************************************
* Obtain Report descriptor
*
* Interface Descriptor Setup Packet
* bmRequestType : USB_DEV_REQ_DEV_TO_HOST |
* USB_DEV_REQ_RCPT_IF (0x80 | 0x01)
* bRequest : USB_REQ_GET_DESCR (0x06)
* wValue : USB_DESCR_TYPE_REP (0x0022)
* wIndex : 0
* wLength : hid_descr.wReportDescriptorLength
*********************************************************/
init_cntrl_req(&setup_data, USB_DEV_REQ_DEV_TO_HOST |
USB_DEV_REQ_RCPT_IF, USB_REQ_GET_DESCR,
USB_DESCR_TYPE_REP, 0,
hid_descr.wReportDescriptorLength);
DPRINT("usbups: Sending GET_DESCRIPTOR(report)
packetn");
count = write(ctrl_fd, &setup_data, sizeof(setup_data));
if (count != sizeof(setup_data)) {
error = get_ctrl_stat(ctrlstat_fd);
fprintf(stderr, "usbups: Error issuing
GET_DESCRIPTOR(report): %sn", error);
}
else {
DPRINT("usbups: Sending GET_DESCRIPTOR(report)
packet successfuln");
/* Read REPORT_DESCRIPTOR
* Arbitrary # of bytes, as we will not perform
* hid report parsing. We just want to have an
* idea of how the descriptor looks like
*/
count = read(ctrl_fd, rep_descr,
hid_descr.wReportDescriptorLength);
if (count != hid_descr.wReportDescriptorLength) {
error = get_ctrl_stat(ctrlstat_fd); fprintf(stderr,
"usbups: Error reading
report descriptor: %sn", error);
}
}
print_rep_descr(rep_descr);
/* Parsing the HID reports is outside the scope of this demo app.
* Once it is understood which type of reports can be received and
* how they should be read, we can query the device for such reports.
* In this example, it is assumed the parsing has been completed.
* Since the information needed is listed as an input item, we will
* listen
* for an input report )ID=2).
* ...
* UsagePage (Battery System)
* Usage (ACPresent)
* Usage (Charging)
* Usage (Discharging)
* Usage (BelowRemainingCapacityLimit)
* Usage (NeedReplacement)
* Usage (Power Device: Good)
* Usage (Power Device: ShutdownImminent)
* Usage (Power Device: Overload)
* Usage (Power Device: Internal Failure)
* Report_ID (2)
* Report_Size (1)
* Report_Count (9)
* Logical_Maximum (1)
* Input (Cnst, Var, Abs, Vol)
* ...
* When reading the interrupt IN pipe, an event needs to happen with
* the device in order for the device to send out the INPUT REPORT
* descriptor. In the case of our UPS, a change to the UPS needs to
* occur (i.e., loss of AC power) before an input report is sent out.
* Note the first byte of the input report is always the report ID.
*
*/
/* Open Interrupt IN pipe */
strcpy(name_buffer, path);
strcat(name_buffer, "/");
strcat(name_buffer, "if0in1");
iin_fd = open(name_buffer, O_EXCL |O_RDONLY |O_NONBLOCK
|O_NDELAY);
if (iin_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
DPRINT("usbups: Opening the Interrupt IN pipe successfuln");
/* Interrupt IN pipe status name is <path>/in0in1stat */
strcat(name_buffer, "stat");
iinstat_fd = open(name_buffer, O_RDONLY|O_EXCL);
if (iinstat_fd < 0) {
fprintf(stderr, "usbups: Error opening %s: %sn",
name_buffer, strerror(errno));
exit(errno);
}
DPRINT("usbups: Opening the Interrupt IN status pipe
successfuln");
while(1) {
/* Reading Interrupt IN
* If no data is available, -1 is returned and
* USB_LC_STAT_INT_BUF_FULL is set.
*/
count = read(iin_fd, input_report, sizeof(input_report));
if (count == -1) {
error = get_ctrl_stat(iinstat_fd);
if (error != USB_LC_STAT_INTR_BUF_FULL) {
/* real read error */
fprintf(stderr, "usbups: Error reading
interrupt IN pipe: %sn", error);
exit(1);
}
}
else {
printf("usbups: Reading Interrupt IN endpoint data
successful: %d bytesn", count);
for (i=0; i<count; i++) {
printf("Byte[%d] = %xn", i,
input_report[i]);
}
if (input_report[0]==2){
/* Battery System Information */
if (input_report[1] & 0x01) {
/* ACPresent */
DPRINT("usbups: ACPresentn");
}
if (input_report[1] & 0x02) {
/* Charging */
DPRINT("usbups: Battery Chargingn");
}
if (input_report[1] & 0x04) {
/* Discharging */
DPRINT("usbups: Battery
Dischargingn");
}
if (input_report[1] & 0x08) {
/* BelowRemainingCapacityLimit */
}
if (input_report[1] & 0x10) {
/* Need Replacement */
}
if (input_report[1] & 0x20) {
/* Power Device: Good */
}
if (input_report[1] & 0x40) {
/* Power Device: ShutdownImminent */
}
if (input_report[1] & 0x80) {
/* Power Device: Overload */
}
}
} // end else
sleep(5);
} // end while
/* Close Interrupt IN pipe */
close(iinstat_fd);
close(iin_fd);
DPRINT("usbups: Interrupt IN pipe closedn");
/* Close default control and default control status
* endpoints.
*/
close(ctrlstat_fd);
close(ctrl_fd);
DPRINT("usbups: Default control and control status
closedn");
/* Close device status node */
DPRINT("usbups: Device status node closedn");
Please download the latest Solaris 10 Express releases for the new and
ongoing enhancements in USB.
Information about the Solaris Express release can be found at
http://www.sun.com/software/solaris/solaris-express/sol_index.html.
|