4、获取设备信息
使用udev_list_entry_get_name可以得到一个设备结点的sys路径,基于这个路径使用udev_device_new_from_syspath可以创建一个udev设备的映射,用于获取设备属性。获取设备属性使用udev_device_get_properties_list_entry,返回一个存储了设备所有属性信息的链表,使用udev_list_entry_foreach遍历链表,使用udev_list_entry_get_name和udev_list_entry_get_value获取属性的名称和值
On Unix and Unix-like systems, hardware devices are accessed through special files (also called device files or nodes) located in the /dev
directory. These files are read from and written to just like normal files, but instead of writing and reading data on a disk, they communicate directly with a kernel driver which then communicates with the hardware. There are many online resources describing /dev
files in more detail. Traditonally, these special files were created at install time by the distribution, using the mknod
command. In recent years, Linux systems began using udev
to manage these /dev
files at runtime. For example, udev
will create nodes when devices are detected and delete them when devices are removed (including hotplug devices at runtime). This way, the /dev
directory contains (for the most part) only entries for devices which actually exist on the system at the current time, as opposed to devices which could exist.
Udev also has a powerful scripting interface (with files commonly located in /etc/udev/rules.d
) which distributors (and end users) often use to customize the way device nodes are created. Customizable properties include file permissions, location within the filesystem, and symbolic links. As could be imagined, this customization can make it difficult for application writers to locate specific device files (or types of devices), because they could be easily moved by modifying the udev
rules. For example, in recent years, the js
(joystick) nodes were moved from /dev
to /dev/input
. Many older programs explicitly opened devices in /dev
(for example /dev/js0
). When these older programs are run today, and try to open /dev/js0
, they will simply not work since /dev/js0
has been moved.
Another problem is that when using multiple devices of the same type, the order in which they appear in /dev
is not guaranteed to be the same every time. This often happens with USB devices. Some USB devices will show up in a different order after a reboot even when plugged into the same USB ports. I‘ve observed this directly with FTDI USB serial ports. For example, with two of these ports plugged in, udev will create /dev/ttyUSB0
and /dev/ttyUSB1
, but the order is undefined. (This particular problem can be worked around by creating udev
rules which create symlinks based on something like a device serial number).
Another issue is that when dealing with things like HID devices, simply knowing that an entry such as /dev/hidraw0
exists tells you nothing about what kind of device it is. It could be any type of HID device.
Sysfs is a virtual filesystem exported by the kernel, similar to /proc
. The files in Sysfs contain information about devices and drivers. Some files in Sysfs are even writable, for configuration and control of devices attached to the system. Sysfs is always mounted on /sys
.
The directories in Sysfs contain the heirarchy of devices, as they are attached to the computer. For example, on my computer, the hidraw0
device is located under:
/sys/devices/pci0000:00/0000:00:12.2/usb1/1-5/1-5.4/1-5.4:1.0/0003:04D8:003F.0001/hidraw/hidraw0
Based on the path, the device is attached to (roughly, starting from the end) configuration 1 (:1.0)
of the device attached to port number 4 of device 1-5, connected to USB controller 1 (usb1), connected to the PCI bus. While interesting, this directory path doesn‘t do us very much good, since it‘s dependent on how the hardware is physically connected to the computer.
Fortunately, Sysfs also provides a large number of symlinks, for easy access to devices without having to know which PCI and USB ports they are connected to. In /sys/class
there is a directory for each different class of device. My /sys/class
directory looks like this:
alan@ato:/sys/class$ ls atm graphics ieee1394_protocol printer thermal backlight hidraw input rfkill tty bdi hwmon mem scsi_device usb block i2c-adapter misc scsi_disk vc bluetooth ide_port net scsi_generic video_output dma ieee1394 pci_bus scsi_host vtconsole dmi ieee1394_host power_supply sound firmware ieee1394_node ppdev spi_master
Following our example of using hidraw
, one can see that there is a hidraw
directory here. Inside it is a symbolic link named hidraw0
which points to
../../devices/pci0000:00/0000:00:12.2/usb1/1-5/1-5.4/1-5.4:1.0/0003:04D8:003F.0001/hidraw/hidraw0
This way, hidraw devices can easily be found under /sys/class/hidraw
without knowing anything about their USB or PCI heirarchy. It would be a good exercise to examine the contents of the /sys
directory, especially /sys/bus
, /sys/class
, and /sys/subsystem
. Since much of /sys
is symbolic links, it may also benefit you to use the utility realpath
to show physical directory paths, as opposed to symbolic link paths. This is useful when trying to find the actual parent directories of device directories. For example, to find the containing USB device entry for hidraw0
, one could use realpath
to do something like the following:
alan@ato:/sys$ cd /sys/class/hidraw/hidraw0/ alan@ato:/sys/class/hidraw/hidraw0$ ls dev device power subsystem uevent alan@ato:/sys/class/hidraw/hidraw0$ cd `realpath $PWD` alan@ato:/sys/devices/pci0000:00/0000:00:12.2/usb2/2-5/2-5.4/2-5.4:1.0/0003:04D8:003F.0001/hidraw/hidraw0$ ls dev device power subsystem uevent alan@ato:/sys/devices/pci0000:00/0000:00:12.2/usb2/2-5/2-5.4/2-5.4:1.0/0003:04D8:003F.0001/hidraw/hidraw0$ cd ../../../../ alan@ato:/sys/devices/pci0000:00/0000:00:12.2/usb2/2-5/2-5.4$ ls 2-5.4:1.0 bDeviceSubClass configuration idProduct remove authorized bmAttributes descriptors idVendor serial avoid_reset_quirk bMaxPacketSize0 dev manufacturer speed bcdDevice bMaxPower devnum maxchild subsystem bConfigurationValue bNumConfigurations devpath power uevent bDeviceClass bNumInterfaces driver product urbnum bDeviceProtocol busnum ep_00 quirks version alan@ato:/sys/devices/pci0000:00/0000:00:12.2/usb2/2-5/2-5.4$
Because it‘s cumbersome and error-prone to walk the Sysfs tree from within an application‘s code, there‘s a convenient library called libudev
to do this task for us. Currently, the closest thing to a manual for libudev
is the gtk-doc-genereated API reference located here:
https://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ch01.html
The documentation there is not really enough for the average developer to get started, so hopefully this guide and its example will make it a bit easier.
For the remainder of this guide, we‘ll be using libudev
to access hidraw
devices. Using libudev, we‘ll be able to inspect the devices, including their Vendor ID (VID), Product ID (PID), serial number, and device strings, without ever opening the device. Further,libudev
will tell us exactly where inside /dev
the device‘s node is located, giving the application a robust and distribution-independent way of accessing the device.
Building with libudev is as simple as including libudev.h
and passing -ludev
to the compiler to link with the libudev
library.
The first example gets a list of the hidraw
objects connected to the system, and prints out their device node path, manufacturer strings, and serial number. To do this, a udev_enumerate
object is created, and the text string "hidraw"
is specified to be used as its filter.libudev
will then return a list of udev_device
objects which match the filter. In our example, this will be a list of all the hidraw
devices attached to the system. The example code performs the following tasks:
struct udev
./dev/hidraw0
).#include <libudev.h> #include <stdio.h> #include <stdlib.h> #include <locale.h> #include <unistd.h> int main (void) { struct udev *udev; struct udev_enumerate *enumerate; struct udev_list_entry *devices, *dev_list_entry; struct udev_device *dev; /* Create the udev object */ udev = udev_new(); if (!udev) { printf("Can‘t create udev\n"); exit(1); } /* Create a list of the devices in the ‘hidraw‘ subsystem. */ enumerate = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(enumerate, "hidraw"); udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); /* For each item enumerated, print out its information. udev_list_entry_foreach is a macro which expands to a loop. The loop will be executed for each member in devices, setting dev_list_entry to a list entry which contains the device‘s path in /sys. */ udev_list_entry_foreach(dev_list_entry, devices) { const char *path; /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */ path = udev_list_entry_get_name(dev_list_entry); dev = udev_device_new_from_syspath(udev, path); /* usb_device_get_devnode() returns the path to the device node itself in /dev. */ printf("Device Node Path: %s\n", udev_device_get_devnode(dev)); /* The device pointed to by dev contains information about the hidraw device. In order to get information about the USB device, get the parent device with the subsystem/devtype pair of "usb"/"usb_device". This will be several levels up the tree, but the function will find it.*/ dev = udev_device_get_parent_with_subsystem_devtype( dev, "usb", "usb_device"); if (!dev) { printf("Unable to find parent usb device."); exit(1); } /* From here, we can call get_sysattr_value() for each file in the device‘s /sys entry. The strings passed into these functions (idProduct, idVendor, serial, etc.) correspond directly to the files in the directory which represents the USB device. Note that USB strings are Unicode, UCS2 encoded, but the strings returned from udev_device_get_sysattr_value() are UTF-8 encoded. */ printf(" VID/PID: %s %s\n", udev_device_get_sysattr_value(dev,"idVendor"), udev_device_get_sysattr_value(dev, "idProduct")); printf(" %s\n %s\n", udev_device_get_sysattr_value(dev,"manufacturer"), udev_device_get_sysattr_value(dev,"product")); printf(" serial: %s\n", udev_device_get_sysattr_value(dev, "serial")); udev_device_unref(dev); } /* Free the enumerator object */ udev_enumerate_unref(enumerate); udev_unref(udev); return 0; }
libudev
programs can be compiled using the following:
gcc -Wall -g -o udev_example udev_example.c -ludev
On my system, I have a Microchip Application Demo connected, so my output is the following (notice how the non-ASCII, Unicode character from the USB serial number is propagated through to userspace as UTF-8):
alan@alan-desktop:~/tmp$ ./test_udev Device Node Path: /dev/hidraw0 VID/PID: 04d8 003f Microchip Technology Inc. Simple HID Device Demo serial: 1234?
Some Notes on libudev
Before moving on, it seems appropriate to mention some important things about libudev.
libudev
‘s functions are string-based. Since the data is coming directly from sysfs (which contains (virtual) files with text in them), all the data which comes from libudev
is in text string format. This means that the user will have to manually convert strings to integer types if desired.udev_device_get_sysattr_value()
correspond to file names in the sysfs tree. In this example, idVendor
corresponds to
/sys/devices/pci0000:00/0000:00:12.2/usb1/1-5/1-5.4/idVendor
which can also be reached as (using the sysfs symlinks)
/sys/bus/usb/devices/1-5.4/idVendor
In order to find out which properties are available for a device, simply find the device in sysfs, and look at which files exist. On my system, my /sys/bus/usb/devices/1-5.4/
directory looks like the following:
Any non-directory file or link in that directory can be queried with1-5.4:1.0 bDeviceSubClass configuration idProduct remove authorized bmAttributes descriptors idVendor serial avoid_reset_quirk bMaxPacketSize0 dev manufacturer speed bcdDevice bMaxPower devnum maxchild subsystem bConfigurationValue bNumConfigurations devpath power uevent bDeviceClass bNumInterfaces driver product urbnum bDeviceProtocol busnum ep_00 quirks version
udev_device_get_sysattr_value()
to determine the properties of the device.libudev
is reference counted. Instead of specifically allocating and freeing objects, ref() and unref() functions (such as udev_ref()
and udev_unref()
) are used for keeping track of how many references to an object exist. When the reference count drops to zero, the object is freed. Functions which return a new object return it with a reference count of 1, so calling its unref() function will effectively free it. See the libudev
documentation (referenced above).libudev
- Monitoring Interfacelibudev
also provides a monitoring interface. The monitoring interface will report events to the application when the status of a device changes. This is useful for receiving notification when devices are connected or disconnected from the system. Like the enumeration interface described above, the monitoring interface also provides a filtering mechanisn, so that an application can subscribe to only events with which it is concerned. For example, if an application added "hidraw"
to the filter, only events concerning hidraw
devices would be delivered to the application. When a device changes state, the udev_monitor_receive_device()
function will return a handle to a udev_device
which represents the object which changed. The returned object can then be queried with the udev_device_get_action()
function to determine which action occurred. The actions are returned as the following strings:
add
- Device is connected to the systemremove
- Device is disconnected from the systemchange
- Something about the device changedmove
- Device node was moved, renamed, or re-parentedThe udev_monitor_receive_device()
function is blocking. That is, when called, program execution will stop until there is an event to be returned. This use case does not seem to be very useful. Fortunately, the udev_monitor
object can provide a file descriptor, suitable for use with the select()
system call. select()
can be used to determine if a call to udev_monitor_receive_device()
will block, providing a way to receive events in a non-blocking way.
The following code shows an example of the libudev
monitor interface. The example runs a loop which executes select()
to determine if there has been an event. If there has, it calls udev_monitor_receive_device()
to receive the event and prints it out. At the end of the loop it sleep()
‘s for 250 milliseconds. In real life, a simple program like this would be just fine to not use select()
and just let udev_monitor_receive_device()
block, but it is written this way to show an example of how to get non-blocking behavior from thelibudev
monitoring interface.
/* Set up a monitor to monitor hidraw devices */ mon = udev_monitor_new_from_netlink(udev, "udev"); udev_monitor_filter_add_match_subsystem_devtype(mon, "hidraw", NULL); udev_monitor_enable_receiving(mon); /* Get the file descriptor (fd) for the monitor. This fd will get passed to select() */ fd = udev_monitor_get_fd(mon); /* This section will run continuously, calling usleep() at the end of each pass. This is to demonstrate how to use a udev_monitor in a non-blocking way. */ while (1) { /* Set up the call to select(). In this case, select() will only operate on a single file descriptor, the one associated with our udev_monitor. Note that the timeval object is set to 0, which will cause select() to not block. */ fd_set fds; struct timeval tv; int ret; FD_ZERO(&fds); FD_SET(fd, &fds); tv.tv_sec = 0; tv.tv_usec = 0; ret = select(fd+1, &fds, NULL, NULL, &tv); /* Check if our file descriptor has received data. */ if (ret > 0 && FD_ISSET(fd, &fds)) { printf("\nselect() says there should be data\n"); /* Make the call to receive the device. select() ensured that this will not block. */ dev = udev_monitor_receive_device(mon); if (dev) { printf("Got Device\n"); printf(" Node: %s\n", udev_device_get_devnode(dev)); printf(" Subsystem: %s\n", udev_device_get_subsystem(dev)); printf(" Devtype: %s\n", udev_device_get_devtype(dev)); printf(" Action: %s\n",udev_device_get_action(dev)); udev_device_unref(dev); } else { printf("No Device from receive_device(). An error occured.\n"); } } usleep(250*1000); printf("."); fflush(stdout); }
It‘s important to note that when using monitoring and enumeration together, that monitoring should be enabled before enumeration. This way, any events (for example devices being attached to the system) which happen during enumeration will not be lost. If enumeration is done before monitoring is enabled, any device attached between the time the enumeration happens and when monitoring starts will be missed. The algorithm should be:
The example file (linked at the end of this document) uses enumeration and monitoring together, and follows this algorithm. The code above shows only monitoring, for simplicity.
Output
The code above will run forever. (Terminate it with Ctrl-C
). With the above section of code running, the following data is printed out as I disconnect and reconnect my HID device (note that a . character is printed every 250 milliseconds):
........................... select() says there should be data Got Device Node: /dev/hidraw0 Subsystem: hidraw Devtype: (null) Action: remove ............. select() says there should be data Got Device Node: /dev/hidraw0 Subsystem: hidraw Devtype: (null) Action: add ......^C
The libudev interface is very useful for creating robust software which needs to access a specific hardware device or monitor the real-time connection and disconnection status of hot-pluggable hardware. I hope you find this document useful. The full source code of the demo is available through the following link:
1 /******************************************* 2 libudev example. 3 4 This example prints out properties of 5 each of the hidraw devices. It then 6 creates a monitor which will report when 7 hidraw devices are connected or removed 8 from the system. 9 10 This code is meant to be a teaching 11 resource. It can be used for anyone for 12 any reason, including embedding into 13 a commercial product. 14 15 The document describing this file, and 16 updated versions can be found at: 17 http://www.signal11.us/oss/udev/ 18 19 Alan Ott 20 Signal 11 Software 21 2010-05-22 - Initial Revision 22 2010-05-27 - Monitoring initializaion 23 moved to before enumeration. 24 *******************************************/ 25 26 #include <libudev.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <locale.h> 30 #include <unistd.h> 31 32 int main (void) 33 { 34 struct udev *udev; 35 struct udev_enumerate *enumerate; 36 struct udev_list_entry *devices, *dev_list_entry; 37 struct udev_device *dev; 38 39 struct udev_monitor *mon; 40 int fd; 41 42 /* Create the udev object */ 43 udev = udev_new(); 44 if (!udev) { 45 printf("Can‘t create udev\n"); 46 exit(1); 47 } 48 49 /* This section sets up a monitor which will report events when 50 devices attached to the system change. Events include "add", 51 "remove", "change", "online", and "offline". 52 53 This section sets up and starts the monitoring. Events are 54 polled for (and delivered) later in the file. 55 56 It is important that the monitor be set up before the call to 57 udev_enumerate_scan_devices() so that events (and devices) are 58 not missed. For example, if enumeration happened first, there 59 would be no event generated for a device which was attached after 60 enumeration but before monitoring began. 61 62 Note that a filter is added so that we only get events for 63 "hidraw" devices. */ 64 65 /* Set up a monitor to monitor hidraw devices */ 66 mon = udev_monitor_new_from_netlink(udev, "udev"); 67 udev_monitor_filter_add_match_subsystem_devtype(mon, "hidraw", NULL); 68 udev_monitor_enable_receiving(mon); 69 /* Get the file descriptor (fd) for the monitor. 70 This fd will get passed to select() */ 71 fd = udev_monitor_get_fd(mon); 72 73 74 /* Create a list of the devices in the ‘hidraw‘ subsystem. */ 75 enumerate = udev_enumerate_new(udev); 76 udev_enumerate_add_match_subsystem(enumerate, "hidraw"); 77 udev_enumerate_scan_devices(enumerate); 78 devices = udev_enumerate_get_list_entry(enumerate); 79 /* For each item enumerated, print out its information. 80 udev_list_entry_foreach is a macro which expands to 81 a loop. The loop will be executed for each member in 82 devices, setting dev_list_entry to a list entry 83 which contains the device‘s path in /sys. */ 84 udev_list_entry_foreach(dev_list_entry, devices) { 85 const char *path; 86 87 /* Get the filename of the /sys entry for the device 88 and create a udev_device object (dev) representing it */ 89 path = udev_list_entry_get_name(dev_list_entry); 90 dev = udev_device_new_from_syspath(udev, path); 91 92 /* usb_device_get_devnode() returns the path to the device node 93 itself in /dev. */ 94 printf("Device Node Path: %s\n", udev_device_get_devnode(dev)); 95 96 /* The device pointed to by dev contains information about 97 the hidraw device. In order to get information about the 98 USB device, get the parent device with the 99 subsystem/devtype pair of "usb"/"usb_device". This will 100 be several levels up the tree, but the function will find 101 it.*/ 102 dev = udev_device_get_parent_with_subsystem_devtype( 103 dev, 104 "usb", 105 "usb_device"); 106 if (!dev) { 107 printf("Unable to find parent usb device."); 108 exit(1); 109 } 110 111 /* From here, we can call get_sysattr_value() for each file 112 in the device‘s /sys entry. The strings passed into these 113 functions (idProduct, idVendor, serial, etc.) correspond 114 directly to the files in the /sys directory which 115 represents the USB device. Note that USB strings are 116 Unicode, UCS2 encoded, but the strings returned from 117 udev_device_get_sysattr_value() are UTF-8 encoded. */ 118 printf(" VID/PID: %s %s\n", 119 udev_device_get_sysattr_value(dev,"idVendor"), 120 udev_device_get_sysattr_value(dev, "idProduct")); 121 printf(" %s\n %s\n", 122 udev_device_get_sysattr_value(dev,"manufacturer"), 123 udev_device_get_sysattr_value(dev,"product")); 124 printf(" serial: %s\n", 125 udev_device_get_sysattr_value(dev, "serial")); 126 udev_device_unref(dev); 127 } 128 /* Free the enumerator object */ 129 udev_enumerate_unref(enumerate); 130 131 /* Begin polling for udev events. Events occur when devices 132 attached to the system are added, removed, or change state. 133 udev_monitor_receive_device() will return a device 134 object representing the device which changed and what type of 135 change occured. 136 137 The select() system call is used to ensure that the call to 138 udev_monitor_receive_device() will not block. 139 140 The monitor was set up earler in this file, and monitoring is 141 already underway. 142 143 This section will run continuously, calling usleep() at the end 144 of each pass. This is to demonstrate how to use a udev_monitor 145 in a non-blocking way. */ 146 while (1) { 147 /* Set up the call to select(). In this case, select() will 148 only operate on a single file descriptor, the one 149 associated with our udev_monitor. Note that the timeval 150 object is set to 0, which will cause select() to not 151 block. */ 152 fd_set fds; 153 struct timeval tv; 154 int ret; 155 156 FD_ZERO(&fds); 157 FD_SET(fd, &fds); 158 tv.tv_sec = 0; 159 tv.tv_usec = 0; 160 161 ret = select(fd+1, &fds, NULL, NULL, &tv); 162 163 /* Check if our file descriptor has received data. */ 164 if (ret > 0 && FD_ISSET(fd, &fds)) { 165 printf("\nselect() says there should be data\n"); 166 167 /* Make the call to receive the device. 168 select() ensured that this will not block. */ 169 dev = udev_monitor_receive_device(mon); 170 if (dev) { 171 printf("Got Device\n"); 172 printf(" Node: %s\n", udev_device_get_devnode(dev)); 173 printf(" Subsystem: %s\n", udev_device_get_subsystem(dev)); 174 printf(" Devtype: %s\n", udev_device_get_devtype(dev)); 175 176 printf(" Action: %s\n", udev_device_get_action(dev)); 177 udev_device_unref(dev); 178 } 179 else { 180 printf("No Device from receive_device(). An error occured.\n"); 181 } 182 } 183 usleep(250*1000); 184 printf("."); 185 fflush(stdout); 186 } 187 188 189 udev_unref(udev); 190 191 return 0; 192 }
Alan Ott alan@signal11.us Signal 11 Software 2010-05-23
原文:http://www.cnblogs.com/jlmgary/p/6269136.html