Linux USB驱动程序实践-下篇

Linux USB驱动程序实践-上篇

提交和控制urb-Linux USB驱动

当Linux USB驱动程序有数据要发送到USB设备时(大多数情况是在Linux驱动程序的写函数中),要分配一个urb来把数据传输给设备:
/* 创建一个urb,并且给它分配一个缓存*/
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
retval = -ENOMEM;
goto error;
}

urb被成功分配后,还要创建一个DMA缓冲区来以高效的方式发送数据到设备,传递给驱动程序的数据要复制到这块缓冲中去:
buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
if (!buf) {
retval = -ENOMEM;
goto error;
}

if (copy_from_user(buf, user_buffer, count)) {
retval = -EFAULT;
goto error;
}

当数据从用户空间正确复制到局部缓冲区后,urb必须在可以被提交给USB核心之前被正确初始化:
/* 初始化urb */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, count, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

然后urb就可以被提交给USB核心以传输到设备了:
/* 把数据从批量OUT端口发出 */
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval) {
err(“%s – failed submitting write urb, error %d”, __FUNCTION__, retval);
goto error;
}

当urb被成功传输到USB设备之后,urb回调函数将被USB核心调用,在我们的例子中,我们初始化urb,使它指向skel_write_bulk_callback函数,以下就是该函数:
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
struct usb_skel *dev;

dev = (struct usb_skel *)urb->context;

if (urb->status &&
!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN)) {
dbg(“%s – nonzero write bulk status received: %d”,
__FUNCTION__, urb->status);
}
/* 释放已分配的缓冲区 */

usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
}

有时候USB驱动程序只是要发送或者接收一些简单的数据,驱动程序也可以不用urb来进行数据的传输,这是里涉及到两个简单的接口函数:usb_bulk_msg和usb_control_msg ,在这个USB框架程序里读操作就是这样的一个应用:
/* 进行阻塞的批量读以从设备获取数据 */
retval = usb_bulk_msg(dev->udev,
usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
dev->bulk_in_buffer,
min(dev->bulk_in_size, count),
&count, HZ*10);

/*如果读成功,复制到用户空间 */
if (!retval) {
if (copy_to_user(buffer, dev->bulk_in_buffer, count))
retval = -EFAULT;
else
retval = count;
}
usb_bulk_msg接口函数的定义如下:
int usb_bulk_msg(struct usb_device *usb_dev,unsigned int pipe,
void *data,int len,int *actual_length,int timeout);
其参数为:

  • struct usb_device *usb_dev:指向批量消息所发送的目标USB设备指针。
  • unsigned int pipe:批量消息所发送目标USB设备的特定端点,此值是调用usb_sndbulkpipe或者usb_rcvbulkpipe来创建的。
  • void *data:如果是一个OUT端点,它是指向即将发送到设备的数据的指针。如果是IN端点,它是指向从设备读取的数据应该存放的位置的指针。
  • int len:data参数所指缓冲区的大小。
  • int *actual_length:指向保存实际传输字节数的位置的指针,至于是传输到设备还是从设备接收取决于端点的方向。
  • int timeout:以Jiffies为单位的等待的超时时间,如果该值为0,该函数一直等待消息的结束。

如果该接口函数调用成功,返回值为0,否则返回一个负的错误值。
usb_control_msg接口函数定义如下:
int usb_control_msg(struct usb_device *dev,unsigned int pipe,__u8    request,__u8requesttype,__u16 value,__u16 index,void *data,__u16 size,int timeout)

除了允许驱动程序发送和接收USB控制消息之外,usb_control_msg函数的运作和usb_bulk_msg函数类似,其参数和usb_bulk_msg的参数有几个重要区别:

  • struct usb_device *dev:指向控制消息所发送的目标USB设备的指针。
  • unsigned int pipe:控制消息所发送的目标USB设备的特定端点,该值是调用usb_sndctrlpipe或usb_rcvctrlpipe来创建的。
  • __u8 request:控制消息的USB请求值。
  • __u8 requesttype:控制消息的USB请求类型值。
  • __u16 value:控制消息的USB消息值。
  • __u16 index:控制消息的USB消息索引值。
  • void *data:如果是一个OUT端点,它是指身即将发送到设备的数据的指针。如果是一个IN端点,它是指向从设备读取的数据应该存放的位置的指针。
  • __u16 size:data参数所指缓冲区的大小。
  • int timeout:以Jiffies为单位的应该等待的超时时间,如果为0,该函数将一直等待消息结束。

如果该接口函数调用成功,返回传输到设备或者从设备读取的字节数;如果不成功它返回一个负的错误值。
这两个接口函数都不能在一个中断上下文中或者持有自旋锁的情况下调用,同样,该函数也不能被任何其它函数取消,使用时要谨慎。
我们要给未知的USB设备写驱动程序,只需要把这个框架程序稍做修改就可以用了,前面我们已经说过要修改制造商和产品的ID号,把0xfff0这两个值改为未知USB的ID号。
#define USB_SKEL_VENDOR_ID      0xfff0
#define USB_SKEL_PRODUCT_ID     0xfff0

还有就是在探测函数中把需要探测的接口端点类型写好,在这个框架程序中只探测了批量(USB_ENDPOINT_XFER_BULK)IN和OUT端点,可以在此处使用掩码(USB_ENDPOINT_XFERTYPE_MASK)让其探测其它的端点类型,驱动程序会对USB设备的每一个接口进行一次探测,当探测成功后,驱动程序就被绑定到这个接口上。再有就是urb的初始化问题,如果你只写简单的USB驱动,这块不用多加考虑,框架程序里的东西已经够用了,这里我们简单介绍三个初始化urb的辅助函数:
usb_fill_int_urb :它的函数原型是这样的:
void usb_fill_int_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buff,
int buffer_length,usb_complete_t complete,
void *context,int interval);

这个函数用来正确的初始化即将被发送到Linux USB设备的中断端点的urb。
usb_fill_bulk_urb :它的函数原型是这样的:
void usb_fill_bulk_urb(struct urb *urb,struct usb_device *dev,
unsigned int pipe,void *transfer_buffer,
int buffer_length,usb_complete_t complete)

这个函数是用来正确的初始化批量urb端点的。
usb_fill_control_urb :它的函数原型是这样的:
void usb_fill_control_urb(struct urb *urb,struct usb_device *dev,unsigned int pipe,unsigned char *setup_packet,void *transfer_buffer,int buffer_length,usb_complete_t complete,void *context);

这个函数是用来正确初始化控制urb端点的。

还有一个初始化等时urb的,它现在还没有初始化函数,所以它们在被提交到USB核心前,必须在驱动程序中手工地进行初始化,可以参考内核源代码树下的/usr/src/~/drivers/usb/media下的konicawc.c文件。

Linux驱动模块的编译、配置和使用

现在我们的驱动程序已经大体写好了,然后在linux下把它编译成模块就可以把驱动模块插入到内核中运行了,编译的Makefile文件可以这样来写:
ifneq ($(KERNELRELEASE),)
obj-m := xxx.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.mod.* *.o *.ko .*.ko.* .tmp* .*.mod.o.* .*.o.*

其中xxx是源文件的文件名,在linux下直接执行make就可以生成驱动模块(xxx.ko)了。生成驱动模块后使用insmod xxx.ko就可以插入到内核中运行了,用lsmod可以看到你插入到内核中的模块,也可以从系统中用命令rmmod xxx把模块卸载掉;

如果把编译出来的Linux USB驱动模块拷贝到/lib/modules/~/kernel/drivers/usb/下,然后depmod一下,那么你在插入USB设备的时候,系统就会自动为你加载驱动模块的;

当然这个得有hotplug的支持;加载驱动模块成功后就会在/dev/下生成设备文件了,如果用命令cat /proc/bus/usb/devices,我们可以看到Linux USB驱动程序已经绑定到接口上了:
T:  Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#=  2 Spd=12  MxCh= 0
D:  Ver= 1.10 Cls=02(comm.) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=1234 ProdID=2345 Rev= 1.10
C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr=  0mA
I:  If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=test_usb_driver /*我们的驱动*/
E:  Ad=01(O) Atr=02(Bulk) MxPS=  64 Ivl=0ms
E:  Ad=82(I) Atr=02(Bulk) MxPS=  64 Ivl=0ms

此框架程序生成的是skel0(可以自由修改)的设备文件,现在就可以对这个Linux USB设备文件进行打开、读写、关闭等的操作了。

 

Linux USB驱动程序实践结束语

面对层出不穷的新的USB设备,必须有人不断编写新的Linux USB设备驱动程序以便让这些设备能够在linux下正常的工作,从这个意义上讲,Linux驱动程序的编写本身就是一件非常有意义的工作,本文只是起到一个抛砖引玉的作用,帮助那些有志于写Linux驱动程序的开发人员进一步了解USB驱动程序的设计思路,从而吸引更多的人加入到这个队伍中来。linux不仅为我们提供了一个顶级质量的操作系统,而且也为我们提供了参与到其未来开发过程的机会,我们完全可以从中得到无尽的快乐!
Internet 上有大量可用的信息,下面将列出部分站点。当然,Internet 站点的信息在以爆炸式的方式增加,而印刷书籍却难以及时更新。这样,下面的清单可在本书过时的情况下发挥作用。

http://www.kernel.org “ftp://ftp.kernel.org”
本站点是 Linux 内核开发的主站点,其中包含了最新的内核发行版本以及相关信息。注意该 FTP 站点的镜像遍布全球,因此,应该选择最近的镜像站点下载 Linux 源代码。

http://www.linuxdoc.org
“Linux Documentation Project”拥有大量称作“HOWTO”的文档,其中一些是技术性的,并涉及到一些内核主题。

http://www.linux-mag.com/depts/gear.html
“Gearheads only”中经常发布一些转载自《Linux Magazine》的、由知名开发人员编写的关于内核的文章。

http://www.linux.it/kerneldocs
其中包含有许多 Aleesandro 所著的有关内核的杂志文章。

http://lwn.net
该新闻站点由本书的作者之一编辑维护,提供了定期的内核开发相关报道。

http://kt.zork.net
“Kernel Traffic”是一个大众性的站点,它提供了每周 Linux 内核开发邮件列表中的讨论总结。

http://www.atnf.csiro.au/~rgooch/linux/docs/kernel-newsflash.html
“Kernel Newsflash”站点是一个内核新闻的集散地。该站点尤其专注于当前内核版本中的兼容性问题,人们可以非常容易地看到为什么自己的驱动程序不能在最新的内核当中正常工作。

http://www.kernelnotes.org
“Kernel Notes”是一个关于内核版本信息、非正式补丁等的经典站点。

http://www.kernelnewbies.org
该站点面向新的内核开发人员。其中包含有针对初学者的内容和 FAQ,而且还有一个 IRC 频道,可获得即时的帮助。

http://lksr.org
“Linux Kernel Source Referenct”是几乎所有内核历史版本的 CVS 归档的 web 接口。如果你想知道某个特定主题的历史变迁,这个站点再合适不过了。

http://www.linux-mm.org
该网页是面向 Linux 内存管理开发的,其中包含有大量有用信息,并且还包含有许多内核相关的 Web 站点链接。

http://www.conecta.it/linux
这个意大利站点包含了几乎所有正在开发的 Linux 相关项目的信息,并且更新及时。也许读者已经知道了包含有大量 Linux 开发的 HTTP 链接的站点,如果没有,这个站点将是一个非常好的选择。

此条目发表在 Linux驱动开发 分类目录,贴了 , , , 标签。将固定链接加入收藏夹。

发表评论