编写真正的驱动程序——在用户空间
上次我们讨论了如何创建一个简单的 USB 驱动程序来控制由 Delcom Engineering 制造的 USB LED 设备 [LJ,2004 年 4 月]。 我想感谢所有读者对本专栏的反馈。 它甚至使一位读者能够编写一个现在位于主内核树中的驱动程序。 我还要感谢所有为我提供关于未来专栏中要介绍哪些类型设备的想法的人,但请记住,让我们尝试介绍简单的设备。 我们没有足够的空间来研究如何逆向工程一个具有大约 12 种不同操作模式的流媒体摄像头。
在上期专栏的结尾,我说从用户空间与 USB 设备通信很简单,无需自定义内核驱动程序。 早在原始 Linux USB 代码的开发过程中,一些开发人员就认识到,除了控制设备之外,允许用户程序访问所有设备中的原始 USB 数据是明智的。 为此,创建了 usbfs 文件系统。 它最初被称为 usbdevfs,但许多人将其与 devfs 文件系统以及该文件系统带来的所有麻烦混淆了,因此它被重命名为 usbfs。
传统上,usbfs 挂载在您机器上的 /proc/bus/usb 目录中。 在该主目录中,存在一个名为 devices 的文件和一个目录,该目录对应于连接到机器的每个不同的 USB 总线。 这些总线目录以数字命名,该数字对应于内核为该特定 USB 总线提供的编号。 在每个总线目录中,都有一个文件对应于连接到总线的每个不同的 USB 设备。 例如,一个有六个不同 USB 总线和几个连接的 USB 设备的盒子可能看起来像这样
$ tree /proc/bus/usb/ /proc/bus/usb/ |-- 001 | `-- 001 |-- 002 | `-- 001 |-- 003 | `-- 001 |-- 004 | |-- 001 | |-- 002 | `-- 003 |-- 005 | `-- 001 |-- 006 | `-- 001 `-- devices
如果您没有加载任何 USB 主机控制器驱动程序,则主 /proc/bus/usb/ 目录应该是空的。
/proc/bus/usb/devices 文件包含在当时连接的所有 USB 设备的列表。 它还显示了设备之间是如何连接的,以及有关每个设备的大量其他 USB 特定信息。 有关如何解释此文件中数据的详细信息,请参阅内核树中 Documentation/usb/proc_usb_info.txt 中的文档。 诸如 usbview 或 usbtree 之类的程序使用此文件来显示有关 USB 设备的信息。
/proc/bus/usb/BBB/ 目录中的文件(其中 BBB 是 USB 总线的编号)允许程序直接与不同的 USB 设备通信。 文件的名称与分配给设备的 USB 编号相同:第一个设备为 001,第二个设备为 002,依此类推。 不要依赖这些数字是唯一的,因为 USB 子系统在设备移除和添加时会重复使用这些数字。 如果设备 003 被移除,并且添加了另一个不同的设备,则它会获得 003 编号。
如果您从设备文件读取,则会返回原始 USB 描述符——首先是 USB 设备描述符,然后是不同的配置描述符。 有关这些描述符的格式以及所有数据的含义的详细描述,请参阅 USB 规范,该规范可在 USB 开发人员网站 (www.usb.org/developers) 下载。
设备文件还支持范围广泛的 ioctl 调用,允许程序从设备发送和接收 USB 数据。 这些 ioctl 以及 ioctl 所需的结构在内核文件 include/linux/usbdevice_fs.h 中描述。
掌握了这些 ioctl、此头文件中定义的结构以及 USB 规范的副本,我们就可以编写用户空间程序来与我们的设备通信了。 但我们真的想这样做吗? 如果有人在此接口之上编写一个库,使我们能够编写健全的程序,那不是很好吗? 幸运的是,一群开发人员创建了这样一个库,允许程序员忽略 usbfs 使用的 ioctl 混乱。 这个库叫做 libusb。
libusb 是一个可在多种不同操作系统上运行的库:Linux、各种 BSD 和 Mac OS X。 它允许以可移植的方式编写程序,并且仍然可以在截然不同的操作系统上控制 USB 设备。 使用这个库可以让我们创建一个程序来控制 USB LED 设备。 如果您的 Linux 发行版中未包含 libusb,则可以从 libusb.sf.net 下载。
任何 libusb 程序必须做的第一件事是初始化库并使其扫描所有 USB 总线以查找所有 USB 设备。 这是通过以下三个函数调用完成的
usb_init(); usb_find_busses(); usb_find_devices();
调用完成后,程序需要找到一个与所需描述匹配的 USB 设备。 由于所有 USB 设备都具有唯一的供应商和产品标识值,因此通常最容易查找这些值。 正如我们从上次创建的内核驱动程序中记得的那样,USB LED 设备具有以下供应商和产品值
#define LED_VENDOR_ID 0x0fc5 #define LED_PRODUCT_ID 0x1223
有了这些信息,使用 libusb 查找此设备的代码如下
for (usb_bus = usb_busses; usb_bus; usb_bus = usb_bus->next) { for (dev = usb_bus->devices; dev; dev = dev->next) { if ((dev->descriptor.idVendor == LED_VENDOR_ID) && (dev->descriptor.idProduct == LED_PRODUCT_ID)) return dev; } } return NULL;
如果在系统中找到该设备,则返回指向它的指针,否则返回 NULL。 此指针的类型为 struct usb_device。 找到此结构后,必须打开 USB 设备,并且 libusb 必须创建一个句柄,以便程序与设备通信。 这是通过以下简单代码完成的
usb_handle = usb_open(usb_dev); if (usb_handle == NULL) { fprintf(stderr, "Not able to claim the USB device\n"); goto exit; }
此 usb_handle 变量的类型为 struct usb_dev_handle,libusb 使用它来确定应与哪个 USB 设备通信。 此句柄是设置我们的 USB 设备以准备好与其通信所需的全部。
当程序完成 USB 设备的操作后,调用usb_close(usb_handle);是清理我们所有结构并通知 libusb 设备不再需要的全部操作。
上次我们使用以下代码从我们的内核驱动程序中设置了 USB LED 设备的颜色
usb_control_msg(led->udev, usb_sndctrlpipe(led->udev, 0), 0x12, 0xc8, (0x02 * 0x100) + 0x0a, (0x00 * 0x100) + color, buffer, 8, 2 * HZ);
libusb 为我们提供了一个几乎相同的函数调用,用于向 USB 设备发送控制消息。 它也称为 usb_control_msg(),并且为了发送与我们从内核内部发送的相同类型的颜色消息,我们的用户空间程序会这样做
usb_control_msg(handle, 0xc8, 0x12, (0x02 * 0x100) + 0x0a, (0c00 * 0x100) + color, buffer, 8, 5000);
列表 1. 控制 USB LED 设备
/* * Set LED - program to control a USB LED device * from user space using libusb * * Copyright (C) 2004 * Greg Kroah-Hartman (greg@kroah.com) * * This program is free software; you can * redistribute it and/or modify it under the terms * of the GNU General Public License as published by * the Free Software Foundation, version 2 of the * License. * */ #include <stdio.h> #include <string.h> #include <usb.h> #define NONE 0x00 #define BLUE 0x04 #define RED 0x02 #define GREEN 0x01 #define LED_VENDOR_ID 0x0fc5 #define LED_PRODUCT_ID 0x1223 static void change_color (struct usb_dev_handle *handle, unsigned char color) { char *dummy; usb_control_msg(handle, 0x000000c8, 0x00000012, (0x02 * 0x100) + 0x0a, 0xff & (~color), dummy, 0x00000008, 5000); } static struct usb_device *device_init(void) { struct usb_bus *usb_bus; struct usb_device *dev; usb_init(); usb_find_busses(); usb_find_devices(); for (usb_bus = usb_busses; usb_bus; usb_bus = usb_bus->next) { for (dev = usb_bus->devices; dev; dev = dev->next) { if ((dev->descriptor.idVendor == LED_VENDOR_ID) && (dev->descriptor.idProduct == LED_PRODUCT_ID)) return dev; } } return NULL; } int main(int argc, char **argv) { struct usb_device *usb_dev; struct usb_dev_handle *usb_handle; int retval = 1; int i; unsigned char color = NONE; usb_dev = device_init(); if (usb_dev == NULL) { fprintf(stderr, "Device not foundn\n"); goto exit; } usb_handle = usb_open(usb_dev); if (usb_handle == NULL) { fprintf(stderr, goto exit; } usb_handle = usb_open(usb_dev); if (usb_handle == NULL) { fprintf(stderr, "Not able to claim the USB device\n"); goto exit; } if (argc == 1) { fprintf(stderr, "specify at least 1 color\n"); goto exit; } for (i = 1; i < argc; ++i) { if (strcasecmp(argv[i], "red") == 0) color |= RED; if (strcasecmp(argv[i], "blue") == 0) color |= BLUE; if (strcasecmp(argv[i], "green") == 0) color |= GREEN; if (strcasecmp(argv[i], "none") == 0) color = NONE; } change_color(usb_handle, color); retval = 0; exit: usb_close(usb_handle); return retval; }
列表 1 允许设置此设备提供的三种可能颜色的任意组合。 只需将颜色作为命令行参数传递即可进行更改
To set the red led: set_led red To set the green and blue led: set_led green blue To turn off all leds: set_led none
Greg Kroah-Hartman 目前是各种不同驱动程序子系统的 Linux 内核维护者。 他在 IBM 工作,从事与 Linux 内核相关的工作,可以通过 greg@kroah.com 联系到他。