李丽宏,郝志刚
(太原理工大学 信息工程学院,山西 太原 030024)
随着计算机外围硬件的扩展,各种外围设备使用不同的总线接口,导致计算机外部各种总线繁多,管理困难,USB总线可以解决这些问题,因此而诞生。USB总线提供统一的外没的接口方式,并且支持热插拔,方便了厂商开发设备和用户使用设备。USB(通用串行总线)是由Microsoft,Compad,Inter和NEC等推出的外围总线接口,目前已发展到2.0标准最高支持480 Mb/s的速率,最多可以支持127个外设。
嵌入式Linux是一款源代码完全免费的新兴操作系统,用户可以用户可以通过网络等其他途径免费获得,并可以任意修改其源代码,这是其他的操作系统做不到的。正是由于这一点,Linux得到了广泛的应用。
USB接口标准支持外部设备和主机之间进行数据传送。在USB结构中主机预设各种类型外设使用的总线宽度。当外设和主机在运行时,USB总线允许使用,设置,添加和拆除外设。
在USB体系结构中一个USB系统可以分成USB设备、USB主机和USB互联3个部分。USB互联是USB设备和USB主机之间进行连接通信的操作[1],主要包括:
1)总线拓扑结构:USB主机和USB设备之间的连接方式;
2)数据流模式:描述USB通信系统数据如何从产生方传递到使用方;
3)USB调度:USB总线是一个共享连接,对可以使用的连按进行调度以支持同步数据传输,并避兔优先级判断的开销。
图1 USB体系拓扑结构图Fig.1 USB system topological structure
USB的物理连接是有层次的星型结构,如图1所示。从图中可以看出USB集线器在一个节点上连接多个设备,每条线段都是点点连接,每个USB集线器在星形的中心。从主机到设备或者USB集线器,或USB集线器到设备都是点点连接。
USB总线在技术层面上是非常简单的,它是一个单主方式实现的,主机轮询各种不同的外围设备,USB另外一个重要的特性是它只担当设备和主控制器之间通讯通道的角色,对所发送的数据没有任何特殊的内容和结构上的要求。
Linux支持两种类型的USB驱动,宿主系统[2]上的驱动程序和设备上的驱动程序。宿主USB驱动程序控制插入其中的USB设备,而USB设备的驱动程序控制设备如何作为一个USB设备和主机通讯。这里主要讨论设备驱动。
图2 USB驱动程序层次图Fig.2 USB driver hierarchical graph
USB的基本通信的形式基本通过端点的东西。USB端点[3]只能往一个方向传输数据,从主机到设备或从设备到主机。USB端点分别具有不同的传输数据的方式,他们有4种类型,分别是:1)控制端点用来控制对USB设备不同部分的访问。他们用于配置设备,获取设备信息,获取设备的状态报告,发送命令到设备。它是一种非周期性的可靠的传输。2)中断端点就是设备传输数据时以一个固定的速率来传输少量的数据。这些端点是鼠标和USB键盘所使用的主要传输方式。它通常用于发送数据到USB设备以控制设备,一般不用来传输大量数据。USB协议保证这些传输有足够的保留带宽来传输数据。3)等时端点同样可以传输大批量的数据,但数据是否到达没有保障,这些端点用于可以应付数据丢失的情况,这类设备更注重于保持一定的恒定的数据流,实时的数据收集都使用这类端点。4)批量端点传输大量的数据。这些端点通常比中断端点大的多他们常用于需要确保没有数据丢失的传输设备。USB协议不保证这些传输始终可以在特定的时间内完成。如果总线上的空间不足以发送整个批量包。它将被分割为多个包进行传输。
当一个USB设备连接到主机时,主机会给这个设备分配一个1~127之间的唯一的设备号同时读取该设备的描述符,该设备描述符是描述设备信息及其属性的数据结构,USB以一种层次化的结构定义设备的描述符,设备描述符给出了USB设备的一般信息,包括对设备及所有设备配置起全程作用的信息,一个USB设备只能有一个设备描述符,配置描述符中的信息与设备特定的配置相关,一个USB设备可以有一个或多个配置描述符,每个配置描述符又由一个或多个接口描述符组成,接口描述符的信息是与设备驱动程序的开发密切相关,可以一个接口对应一个设备驱动程序也可以多个接口对应一个设备驱动程序,接口描述符由零个或多个端点描述符组成,端点描述符定义了在一个给定的设备里实现的实际寄存器.这些描述符定义了每个寄存器的功能和特定的信息如端点要求的传输类型、传输方向、带宽要求、查询间隔等。另外,还有一个可选的字符串描述符,它以UNCOND码的格式给出了一些可读的信息,这些信息通常是有关设备生产厂商、设备名设备序列号等,通过这些不同层次的描述符.主机设备驱动程序就可以知道具体设备的相关信息,从而对设备进行相应控制。
1.3.1 基本数据结构
usb-skel设备使用自定义结构usb_skel记录设备驱动用到的所有描述符,该结构定义如下:struct usb_skel{
struct usb_device*udev; //USB设备描述符
struct usb_interface*interface; //USB接口描述符
struct semaphore limit_sem; //互斥信号量
unsigned char* bulk_in_buffer;//数据接收缓冲区
size_t bulk_in_size; //数据接收缓冲区大小
_u8 bulk_in_endpointAddr; //入端点地址
_u8 bulk_out_endpointAddr;//出端点地址
struct kref kref;
};
1.3.2 驱动程序初始化和注销
同其他所有的Linux设备驱动程序一样,usb-skel驱动使用 module_init()宏初始化函数,使用 module_exit()宏注销函数。usb-skel驱动的初始化函数usb_skel_init()函数,定义如下:
static int_init usb_skel_init(void)
{
int result;
result-usb_register(&skel_driver);//注册 USB 设备驱动if(result)
err(“usb_register failed.Error number%d”,result);return result;
}
sb_skel_init()函数调用内核提供的 usb_register()函数注册了一个usb_driver类型的结构变量,该变量定义如下:static struct usb_driver skel_driver={
.name= “skeleton”, //USB 设备名称
.probe=skel_probe, //USB设备初始化函数
.disconnect=skel_disconnect,//USB设备注销函数
.id_table=skel_table, //USB设备ID映射表};
其中usb_skel设备的USB ID映射表定义如下:static struct usb_device_id skel_table[]={
{USB_DEVICE(USB_SKEL_VENDOR_ID,USB_SKEL_PRODUCT_ID)};
在USB驱动中调用usb_deregister()函数注销usb-skel设备驱动,函数定义如下:
static void_exit usb_skel_exit(void)
usb_deregister(&skel_driver); //注销 USB设备};
1.3.3 初始化设备
从skel_driver结构可以知道usb-skel设备的初始化函数是skel_probe()函数,设备初始化主要是探测设备类型,分配USB设备用到的urb资源[5],注册USB设备操作函数等。skel_class结构变量记录了usb-skel设备信息,定义如下:
static struct usb_class_driver skel_class={
.name= “skel%d”, //设备名称
.fops= &skel_fops,//设备操作函数
.minor_base=USB_SKEL_MINOR_BASE,};
name变量使用%d通配符表示一个整型变量,当一个usb-skel类型的设备连接到USB总先后会2按照子设备编号自动设置设备名称。Fops设备操作函数结构变量,定义如下:
static struct file_operations skel_fops={
.owener=THIS_MODULE,
.read=skel_read, //读操作
.write=skel_write, //写操作
.open=skel_open, //打开操作
.release=skel_release, //关闭操作};
1.3.4 设备注销
skel_disconnect()函数在注销设备时被调用,定义如下:static void skel_disconnect(struct usb_interface*interface){
struct usb_skel*dev;
int minor=interface->minor;
lock_kernel(); //在操作之前加锁
dev=usb_get_intfdata(interface);//获得USB设备接口描述
usb_set_intfdata(interface,NULL); //设置USB设备接口描述无效
usb_deregister_dev(interface,&skel_class);//注销 USB设备操作描述
unlock kernel(); //操作完毕解锁
kref_put(&dev->kref,skel_delete); //减小引用计数
info(“USB Skeleton#%d now disconnected”,minor);};
usb_serial_init()函数是一个典型的USB设备驱动初始化函数,定义如下:
static int_int usb_setial_init(viod){
int i;
int result;
usb_tty_driver=alloc_tty_driver(SERIAL_TTY_MINORS);//申请tty设备驱动描述
if(!usb_tty_driver)
return-ENOMEM;
result=bus_reqister(&usb_serial_bus_type);//注册总线
if(result)
{
err(“Regist bus driver failed”);
qoto exit_bus;
}
usb_tty_driver=>owener=THIS_MODULE;
usb_tty_driver->driver_name= “usbserial”;//串口驱动名称
usb_tty_driver->devfs_name= “usb/tts”;//设备文件系统存放路径
usb_tty_driver->name= “ttyUSB”; //串口设备名称
usb_tty_driver->major=SERIAL_TTY_MAJOR;//串口设备主设备号
usb_tty_driver->minor_start=0;//串口设备从设备号起始ID
usb_tty_driver->type= TTY_DRIVER_TYPE_SERIAL;//设备类型
usb_tty_driver->subtype = SERIAL_TYPE_NORAML;//设备子类型
usb_tty_driver->flags=TTY_DREVER_REAL_RAW |TTY_DRIVER_NO_DEVFS;//设备初始化标志
usb_tty_driver->init_terminos=tty_stb_termios;//串口设备描述
usb_tty_driver->init_termios.c_cflag =B9600 |CSB |CREAD|HUPCL|CLOCAL;//串口设备初始化参数
tty_set_operations(usb_tty_driver,&serial_ops); //串口设备操作函数
result=tty_register_driver(usb_tty_driver);//注册串口驱动
if(result)
{
err(“Regist tty driver failed”);
goto exit_reg_driver;
}
result=usb_register(&usb_serial_driver);//注册USB驱动
if(result<0)
{
err(“Register driver failed”);
goto exit_tty;
}return result;exit_generic:
usb_deregister(&usb_serial_driver);//注销串口设备exit_tty:
tty_unregister_driver(usb_tty_driver);//注销USB串口设备exit_reg_driver:
bus_unregister(&usb_serial_bus_type);//注销总线exit_bus:
err(“Error Code:%d”,result);put_tty_driver(usb_tty_driver);return result;
}
函数首先调用alloc_tty_driver()函数分配一个串口驱动描述符;然后设置串口驱动的属性,包括驱动的主从设备号、设备类型、串口初始化参数等;串口驱动描述符设置完毕后,调用usb_register()函数注册USB串口设备。
static void__exit usb_serial_exit(void){
usb_serial_console_exit();
usb_serial_generic_deregister();
usb_deregister(&usb_serial_driver);//注销 USB 设备驱动
tty_unregister_driver(usb_tty_driver);//注销串口设备
put_tty_driver(usb_tty_driver);//减少引用计数
bus_unregister(&usb_serial_bus_type);//注销总线}
USB串口设备驱动使用了一个tty_operations类型的结构,该结构包含了串口的所有操作,定义如下:
static sturct tty_operations serial_ops=
{
.open=serial_open, //打开串口
.close=serial_close, //关闭串口
.write=serial_write, //串口写操作
.write_room=serial_write_room,
.ioctl=serial_ioctl, //I/O控制操作
.set_termios=serial_set_termios, //设置串口参数
.throttle=serial_throttle,
.unthrottle=serial_unthrottle,
.break_ctl=serial_break, //break信号处理
.chars_in_buffer=serial_chars_in_buffer, //缓冲处理
.read_pros=serial_read_proc, //串口读操作
.tiocmget=serial_tiocmget, //获取I/O控制参数
.tiocmset=serial_tiocmset, //设置I/O控制参数};
按上述的步骤和方法通过Insmod命令成功实现了USB驱动程序的加载[4],成功的通过USB串口进行了数据的读写。
新出的Linux2.6[6]内核加入了对USB2.0[7]的支持,重新定义了usb_class_driver结构体。同时对探测函数probe和usb_submit_urb做了修改,包含了advanced linux sound Archiecture可以更安全的使用USB设备。
[1]弓雷.arm嵌入式linux系统开发详解[M].北京:清华大学出版社,2010.
[2]Corbet J,Rubini A,Kroah-Hartma.G.Linux 设备驱动程序[M].魏永明,耿岳,钟书毅,译.北京:中国电力出版社,2006.
[3]韦东山.嵌入式Linux应用开发完全手册[M].北京:人民邮电大学出版社,2008.
[4]杨水清.ARM嵌入式Linux系统开发技术详解[M].北京:电子工业出版社,2008.
[5]季春志.基于Linux平台USB视频设备驱动技术的研究与实现[D].合肥:合肥工业大学,2009.
[6]博韦,西斯特.深入分析Linux内核[M].陈莉君,张琼声,张宏伟,译.北京:中国电力,2009.
[7]熊玉朋,陈兴欣,庞俊锐.一种新型移动保密存储设备[J].现代电子技术,2010(5):89-91.XIONG Yu-peng,CHEN Xing-xin,PANG Jun-rui.New Removable Privacy Storage Equipment[J].Modern Electronics Technique,2010(5):89-91.