Linux驱动之USB(个人)

                           USB概述
<USB简介>
a:背景
USB是Universal Serial Bus的缩写,是一种全新的,双向同步传输的,支持热插拔的PC串行通信协议,USB标准提出的主要目的是为了提供一种兼容低速和高速,可扩充并且使用方便外围设备接口共人们使用,同时解决之前计算机接口过多的难题(这也是造成USB协议很复杂的原因)
b:usb系统的组成
(1)USB主机
一个usb系统只有一个主机.USB和主机系统的接口称为主机控制器,主机控制器由硬件,固件,软件综合实现
(2)USB设备
USB设备有很多,怎么为这么多的USB设备提供标准接口呢?主要依靠以下依据:
——USB协议的合理应用
——标准USB操作反馈,如设备和复位
——标准性能的描述信息
(3)USB互联
主要是通过USB总线连接
c:usb总线的连接方式
usb总线总共有4种连接方式:控制传输,中断传输,等时传输,同步传输
(1)控制传输
控制传输主要用户主机向USB设备发送控制命令或者USB设备向主机返回状态信息,任何USB设备必须支持一个与控制类型的端点(主控端点)。所以可以看出,控制传输在所有USB设备中都会用到,主机对USB设备的 配置命令需要通过 控制传输来发送,而 设备的描述信息也需要通过控制传输给主机。
(2)中断传输
主要用来支持那些偶尔需要少量数据传输,服务时间受限的设备,比如键盘,鼠标,游戏杆.
(3)批量传输
批量传输用于需要大量传输数据的场合,面向数据传输率不固定的设备。
(4)同步传输
同步传输要求有恒定的传输速率,同时,该传输方式必须保证传输方和接收方数据的匹配,否者照成数据的丢失。所以这种传输用于实时性要求比较高,但是准确率比较低的场合(比如视频,音频设备)。
 
c:OHCI标准协议
虽然说现在有很多个版本的usb协议,但是在嵌入式开发中一般使用的是OHCI协议
(1)端点
端点是USB设备的"逻辑设备",一个USB设备有多个端点,对主机而言,其对应一个或多个逻辑设备。当设备与主机相连,主机分配给每个逻辑设备一个唯一的地址,而设备的端点号是唯一的。(主机与设备通过端点建立连接)
(2)通道
设备上的 逻辑地址主机之间形成了 usb通道,主机和设备之间进行信息交互也是通过这个通道。依据通信数据格式的不同,通道可以分为两种:
流:不具有usb定义格式的数据流
消息:具有某种usb定义格式的数数据流
(3)描述符
任何usb设备都包含设备描述符,主要用与说明设备树形(设备描述符,配置描述符,接口描述符,端点描述符之间的关系),通常都被固话在设备内部,当主机检测到有设备插入的时候,就会通过控制传输模式将设备描述信息读出来。
 
<Linux usb_device usb_bus usb_driver的三角关系示意图>
 
 
<USB总线拓扑结构>
a:USB主机控制器(host control)
每个主机主板上面都会有一个USB主机控制器,其本质是一个PCI设备(嵌入式设备也是如此)。通过PCI总线链接usb总线控制器,再通过总想控制器链接usb设备。主机控制器最主要的职责就是usb设备和cpu之间的通信(通常CPU不会直接控制usb设备,而是发送至指令给usb主机控制器 ,主机控制器接收到信号后,会完成相应的工作)
b:usb集线器
用来连接usb设备的其本身也是一个usb设备。
c:usb设备
每一hub可以链接127个usb设备
 
<USB驱动总体架构 >
<usb设备和usb驱动之间的通信>
a:usb设备和usb驱动之间通信需要两样东西:usb协议和usb设备固件程序
b:一般USB固件中包含“出厂信息”,“厂商ID”,"产品ID",主版本号,另USB设备固件中还包含有一组程序用于USB设备的协议处理和设备的读写操作(将数据发送到总线,或将总线上的数据存储到USB设备)。 USB设备驱动只是将USB设备规范定义的请求发送给固件程序。
c:至于USB设备和USB固件之间的通信是是通过usb协议完成的。
d:usb设备中的 各种*****description 都是在产品出场的时候烧写进去的,当设备接入系统,usb_core 就会去读取这些信息
 
                                                                            USB设备驱动模型    
 
<总线,设备和驱动>
a:详情将Linux设备驱动模型
<usb驱动和usb设备之间联系的建立>
a:这个联系的建立需要通过usb core(核心)
        usb核心会首先完成usb总想的初始化,然后扫描总线,当发现设备时就会为其分配想象的struct device.然后用指针将其链接到usb总线的双向链表。
        相比于设备的链接,驱动的链接就相对来说比较容易,当驱动注册到总线的驱动的双向链表中,驱动会扫描总总线上的设备,如果发现有匹配的设备就会通过指针将其与自己联系起来。
 
<usb设备驱动>
a:源代码
struct usb_driver {
const char *name;  //指向真正驱动的名称
int (*probe) (struct usb_interface *intf,
     const struct usb_device_id *id); //usb设备探测函数,但usb设备插入时候,usb核心会调用该函数来完成初
                                                                          //始化工作
void (*disconnect) (struct usb_interface *intf); //卸载设备的时候用(比如U盘的"安全移除设备")
int (*ioctl) (struct usb_interface *intf, unsigned int code, //对设备进行控制,usb驱动中,该函数没有用
void *buf);
int (*suspend) (struct usb_interface *intf, pm_message_t message);
int (*resume) (struct usb_interface *intf);
int (*reset_resume)(struct usb_interface *intf);
int (*pre_reset)(struct usb_interface *intf);
int (*post_reset)(struct usb_interface *intf);
const struct usb_device_id *id_table; //驱动支持的设备列表
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
unsigned int no_dynamic_id:1;
unsigned int supports_autosuspend:1;
unsigned int soft_unbind:1;
};
 
<struct usb_device_id * id_table>
a:usb驱动往往支持多个设备,所有支持的设备都在改结构中有记录,该结构体中包含了制造商ID,产品ID,产品版本等
 struct usb_device_id {
/* which fields to match against? */
__u16match_flags;    //该数据很重要,决定一下哪些项该被匹配
/* Used for product specific matches; range is inclusive */
__u16idVendor;
__u16idProduct;
__u16bcdDevice_lo;
__u16bcdDevice_hi;
/* Used for device class matches */
__u8bDeviceClass;
__u8bDeviceSubClass;
__u8bDeviceProtocol;
/* Used for interface class matches */
__u8bInterfaceClass;
__u8bInterfaceSubClass;
__u8bInterfaceProtocol;
/* not matched against */
kernel_ulong_tdriver_info;
};   
如下所示:
 如果一个驱动只需要匹配厂商ID,和产品ID:
.match_flags = (USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT)
b:设备固件中也包含 了相关信息,当设备插入的时候核心会读取这这些信息,这样就能完成设备与去哦的那个的匹配。
注意:
usb核心通过id_table 来进行设备与驱动的匹配,所以一般在定义struct  usb_device_id  {}的时候,会使用宏:MODULE_DEVICE_TABLE()
#define USB_SKEL_VENDER_ID Oxfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
static struct usb_device_id skel_table[]=
{
    {USB_DEVICE( USB_SKEL_VENDER_ID, USB_SKEL_PRODUCT_ID )},
    {}
};
MODULE_DEVICE_TABLE(usb,skel_table);
MODULE_DEVICE_TABLE()宏的作用:当usb设备插入的时候,为力使Linux-hotplug(linux 中PCI,USB,等设备热插拔支持)系统自动装载驱动程序,需要创建MODULE_DEVICE_TABLE(个人认为在Linux中这个表是一个全局变量)
<usb驱动注册函数usb_register()>
a:usb_register()该函数会调用一下函数
int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
const char *mod_name)
{
int retval = 0;
 
if (usb_disabled())
return -ENODEV;
 
new_driver->drvwrap.for_devices = 0;
new_driver->drvwrap.driver.name = (char *) new_driver->name;
new_driver->drvwrap.driver.bus = &usb_bus_type;
new_driver->drvwrap.driver.probe = usb_probe_interface;
new_driver->drvwrap.driver.remove = usb_unbind_interface;
new_driver->drvwrap.driver.owner = owner;
new_driver->drvwrap.driver.mod_name = mod_name;
spin_lock_init(&new_driver->dynids.lock);
INIT_LIST_HEAD(&new_driver->dynids.list);
 
retval = driver_register(&new_driver->drvwrap.driver);
 
if (!retval) {
pr_info("%s: registered new interface driver %s\n",
usbcore_name, new_driver->name);
usbfs_update_special();
usb_create_newid_file(new_driver);
} else {
printk(KERN_ERR "%s: error %d registering interface "
"driver %s\n",
usbcore_name, retval, new_driver->name);
}
 
return retval;
}
调用该函数后,会遍历usb总线上所有的设备,只要找到与" const struct usb_device_id *id_table"中相匹配的设备就会调用函数"probe()"来完成相应的设备初始化。
 
<usb驱动卸载函数usb_deregister()>
a:该函数会触发usb_desconnect()该函数会解除驱动与设备的绑定。
 
<probe()函数原型>
a:int (*probe)(struct usb_interface *intf,const usb_device_id *id)
 
                                                                     usb协议中的设备
 
<usb设备中的逻辑结构>
a:usb 协议规定,usb设备的逻辑结构包含:设备,配置,接口,和端点分别用usb_device,usb_host_config,usb_interface,usb_host_endpiont。他们之间的关系如下图:
 < usb_device
struct usb_device {
intdevnum;
chardevpath [16];
u32route;
enum usb_device_statestate;
enum usb_device_speedspeed;
struct usb_tt*tt;
intttport;
unsigned int toggle[2];
struct usb_device *parent;
struct usb_bus *bus;
struct usb_host_endpoint ep0;
struct device dev;
struct usb_device_descriptor descriptor;
struct usb_host_config *config;
struct usb_host_config *actconfig;
struct usb_host_endpoint *ep_in[16];
struct usb_host_endpoint *ep_out[16];
char **rawdescriptors;
unsigned short bus_mA;
u8 portnum;
u8 level;
unsigned can_submit:1;
unsigned discon_suspended:1;
unsigned persist_enabled:1;
unsigned have_langid:1;
unsigned authorized:1;
 unsigned authenticated:1;
unsigned wusb:1;
int string_langid;
/* static strings from the device */
char *product;
char *manufacturer;
char *serial;
struct list_head filelist;
#ifdef CONFIG_USB_DEVICE_CLASS
struct device *usb_classdev;
#endif
#ifdef CONFIG_USB_DEVICEFS
struct dentry *usbfs_dentry;
#endif
int maxchild;
struct usb_device *children[USB_MAXCHILDREN];
int pm_usage_cnt;
u32 quirks;
atomic_t urbnum;
unsigned long active_duration;
#ifdef CONFIG_PM
struct delayed_work autosuspend;
struct work_struct autoresume;
struct mutex pm_mutex;
unsigned long last_busy;
int autosuspend_delay;
unsigned long connect_time;
unsigned auto_pm:1;
unsigned do_remote_wakeup:1;
unsigned reset_resume:1;
unsigned autosuspend_disabled:1;
unsigned autoresume_disabled:1;
unsigned skip_sys_resume:1;
#endif
struct wusb_dev *wusb_dev;
int slot_id;
};
a:往往一个usb设备会有多种功能,比如一个打印机,每一个功能都可以用一个struct device 来表示。但是上诉结构体只有一个struct device。如果真的每一中功能都使用一种一个struct device 来表示。这样在设备加载的时候会影响效率。于是想出了使用(usb_interface)来代替struct的一些功能。
 
<配置>
a:一个配置是功不同功能的组合,一个usb设备可以有多种配置(usb_host_config)。配置之间可以切换以改变设备的状态。
举例说明:
一个可以下载固件的升级的MP3来说,一般可以有3中配置。
第1种是播放配置0;
第2种是充电配置1;
第3种是下载固件配置2;
因此需要下载固件的时候需要将mp3设置为配置2状态(同一时刻只允许有一种配置)
b:在Linux中,一般使用usb_host_config表示配置,该结构体一般由usb核心维护。
struct usb_host_config {
struct usb_config_descriptordesc;
char *string; /* iConfiguration string, if present */
/* List of any Interface Association Descriptors in this
* configuration. */
struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS];
/* the interfaces associated with this configuration,
* stored in no particular order */
struct usb_interface *interface[USB_MAXINTERFACES];
/* Interface information available even when this is not the
* active configuration */
struct usb_interface_cache *intf_cache[USB_MAXINTERFACES];
unsigned char *extra;   /* Extra descriptors */
int extralen;
};
<接口>
a:在usb协议中一般一种接口表示一个基本功能。
举例说明:
一个打印机一般具有3中接口,分别是扫描功能接口,复印功能接口和打印动能接口。根据设备模型的概念,每一个usb_driver控制一个接口。因此该打印机需要3个不同的硬驱动。
b:内核使用usb_interface结构体表示usb接口,当设备插入的时候usb核心会读取信息并创建 usb_interface。接着会扫描是否有相应的驱动,如果有就会调用函数:
int(*probe)(struct usb_interface *intf,cost struct usb_device_id *id)
c:接口在内核中的表示
 
struct usb_interface {
/* array of alternate settings for this interface,
* stored in no particular order */
struct usb_host_interface *altsetting; //指向一个可选设置数组,设备大于配置,配置大于接口,接口大于设置
struct usb_host_interface *cur_altsetting; /* the currently * active alternate setting */
unsigned num_altsetting; /* number of alternate settings */
                                              /* If there is an interface association descriptor then it will list
                                              * the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;
int minor;/* minor number this interface is
* bound to */
enum usb_interface_condition condition;/* state of binding */
unsigned is_active:1;/* the interface is not suspended */
unsigned sysfs_files_created:1;/* the sysfs attributes exist */
unsigned ep_devs_created:1;/* endpoint "devices" exist */
unsigned unregistering:1;/* unregistration is in progress */
unsigned needs_remote_wakeup:1;/* driver requires remote wakeup */
unsigned needs_altsetting0:1;/* switch to altsetting 0 is pending */
unsigned needs_binding:1;/* needs delayed unbind/rebind */
unsigned reset_running:1;
struct device dev; /* interface specific device info */
struct device *usb_dev; //指向usb_register_dev()函数创建的usb_class_device,只有在usb_deregister_dev()的时候才会用到。
atomic_t pm_usage_cnt;/* usage counter for autosuspend */
 
        struct work_struct reset_ws;/* for resets in atomic context */
}; 
备注:设置在接口中的结构
struct usb_host_endpoint {
struct usb_endpoint_descriptordesc;
struct list_headurb_list;
void*hcpriv;
struct ep_device *ep_dev;/* For sysfs info */
struct usb_host_ss_ep_comp*ss_ep_comp;/* For SS devices */
 
unsigned char *extra;   /* Extra descriptors */
int extralen;
int enabled;
};
 
d:数据分析:
struct device *usb_dev;代表整个设备
struct usb_interface ;代表整个设备中的一个接口
struct device dev;根据设备模型,用来和struct driver 相互对应。
 
<端点>
a:端点是usb通信的最基本方式,主机只能通过usb端点和usb设备进行通信,也就是只能通过端点传输数据。
端点分为:
输出端点:主机到设备传输数据
输入端点:设备向主机传输数据
注:最为特殊的是端点0,叫做控制端点,用来控制usb设备的初始化参数控制。因此对于任何usb设备来说端点0是不可缺少的。
b:端点在内核中的表示
struct usb_host_endpoint {
struct usb_endpoint_descriptordesc; //端点描述在usb协议中很重要
struct list_headurb_list; //表示要处理的urb队列
void*hcpriv;
struct ep_device *ep_dev;/* For sysfs info */
struct usb_host_ss_ep_comp*ss_ep_comp;/* For SS devices */
 
unsigned char *extra;   /* Extra descriptors */
int extralen;
int enabled;
};
c:端点描述符内核中的结构
struct usb_endpoint_descriptor {
__u8  bLength;
__u8  bDescriptorType;
 
__u8  bEndpointAddress; //这个包含端点地址,输出方向,端点号
__u8  bmAttributes; //表示属性,00:控制,01:等时,10::批量,11:中断
__le16 wMaxPacketSize; //一次可以处理的最大字节数
__u8  bInterval; 
 
/* NOTE:  these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8  bRefresh;
__u8  bSynchAddress;
} __attribute__ ((packed));
注意:0号端点没有比较特殊没有自己的端点描述符
 
<usb协议中各层设备之间描述符的关系>
注意:描述符作为固件的一部分存储在usb设备之中
 
<端点的传输方式>
a:控制传输:
主要用来向设备发送配置信息,获取设备信息,发送命令到设备,获取设备的状态。
b:等时传输:
用来传输大批量数据,但是对于数据是否达到没有保证。
c:中断传输:
适合用于传输少量数据
d:批量传输:
传输大量数据,适合用于对实时性要求不高的情况
 
<Linux usb键盘驱动代码示例>
1. 指定USB键盘驱动所需的头文件: 
 
  #include /*内核头文件,含有内核一些常用函数的原型定义*/ 
 
  #include /*定义内存分配的一些函数*/ 
 
  #include /*模块编译必须的头文件*/ 
 
  #include /*输入设备相关函数的头文件*/ 
 
  #include /*linux初始化模块函数定义*/ 
 
  #include /*USB设备相关函数定义*/ 
 
  2. 定义键盘码表数组: 
 
  /*使用第一套键盘扫描码表:A-1E;B-30;C-2E…*/ 
 
  static unsigned char usb_kbd_keycode[256] = { 
 
  0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 
 
  50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, 
 
  4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26, 
 
  27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64, 
 
  65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106, 
 
  105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71, 
 
  72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190, 
 
  191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113, 
 
  115,114, 0, 0, 0,121, 0, 89, 93,124, 92, 94, 95, 0, 0, 0, 
 
  122,123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
 
  29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113, 
 
  150,158,159,128,136,177,178,176,142,152,173,140 
 
  }; 
 
  3. 编写设备ID表: static struct usb_device_id usb_kbd_id_table [] = { 
 
  { USB_INTERFACE_INFO(3, 1, 1) },/*3,1,1分别表示接口类,接口子类,接口协议;3,1,1为键盘接口类;鼠标为3,1,2*/ 
 
  { } /* Terminating entry */ 
 
  };//必须与0结束 
 
  MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);/*指定设备ID表*/ 
 
  4. 定义USB键盘结构体: 
 
  struct usb_kbd { 
 
  struct input_dev *dev; /*定义一个输入设备*/ 
 
  struct usb_device *usbdev;/*定义一个usb设备*/ 
 
  unsigned char old[8]; /*按键离开时所用之数据缓冲区*/ 
 
  struct urb *irq/*usb键盘之中断请求块*/, *led/*usb键盘之指示灯请求块*/; 
 
  unsigned char newleds;/*目标指定灯状态*/ 
 
  char name[128];/*存放厂商名字及产品名字*/ 
 
  char phys[64];/*设备之节点*/ 
 
  unsigned char *new;/* 
 
  按键按下时所用之数据缓冲区*/ 
 
  struct usb_ctrlrequest *cr;/*控制请求结构*/ 
 
  unsigned char *leds;/*当前指示灯状态*/ 
 
  dma_addr_t cr_dma; /*控制请求DMA缓冲地址*/ 
 
  dma_addr_t new_dma; /*中断urb会使用该DMA缓冲区*/ 
 
  dma_addr_t leds_dma; /*指示灯DAM缓冲地址*/ 
 
  }; 
 
  5. 编写USB键盘驱动结构(任何一个LINUX下的驱动都有个类似的驱动结构): 
 
  /*USB键盘驱动结构体*/ 
 
  static struct usb_driver usb_kbd_driver = { 
 
  .name = "usbkbd",/*驱动名字*/ 
 
  .probe = usb_kbd_probe,/*驱动探测函数,加载时用到*/ 
 
  .disconnect = usb_kbd_disconnect,/*驱动断开函数,在卸载时用到,可选*/ 
 
  .id_table = usb_kbd_id_table,/*驱动设备ID表,用来指定设备或接口*/ 
 
  }; 
 
  6. 编写模块加载函数(每个驱动都会有一个加载函数,由module_init调用): 
 
  /*驱动程序生命周期的开始点,向 USB core 注册这个键盘驱动程序。*/ 
 
  static int __init usb_kbd_init(void) 
 
  { 
 
  int result = usb_register(&usb_kbd_driver);/*注册USB键盘驱动*/ 
 
  if (result == 0) /*注册失败*/ 
 
  info(DRIVER_VERSION ":" DRIVER_DESC); 
 
  return result; 
 
  } 
 
  7. 编写模块卸载函数(每个驱动都会有一个卸载函数,由module_exit调用): 
 
  /* 驱动程序生命周期的结束点,向 USB core 注销这个键盘驱动程序。 */ 
 
  static void __exit usb_kbd_exit(void) 
 
  { 
 
  printk("SUNWILL-USBKBD:usb_kbd_exit begin...\n"); 
 
  usb_deregister(&usb_kbd_driver);/*注销USB键盘驱动*/ 
 
  } 
 
  8. 指定模块初始化函数(被指定的函数在insmod驱动时调用): 
 
  module_init(usb_kbd_init); 
 
  9. 指定模块退出函数(被指定的函数在rmmod驱动时调用): 
 
  module_exit(usb_kbd_exit); 
 
  10. 编写中断请求处理函数: 
 
  在介绍中断处理程序之前我们先来看看usb请求结结构体urb是什么样子的 
 
  struct urb 
 
  { 
 
  spinlock_t lock; // lock for URB,自旋锁,用于并发访问临界资源 
 
  void *hcpriv;// private data for host controler与主机控制器相关数据,对USB内核层是透明的 
 
  struct list_head urb_list;//list pointer to all active urbs 双向指针,用于将此USB联结到处于活动的URB双向链表中,在内核结构模型中很多地方都有到这个结构体,这个结构体有两个指针,一个指向下一个 list_head结构体,一个指向前一个list_head结构体,在进程列表,工作队列中都用到这个结构体。 
 
  struct urb *next;// pointer to associated USB device 接受此URB的USB设备指针; 
 
  unsigned int pipe;//pipe information 表示设备的某个端点和客户设备驱动程序之间的通道 
 
  int status;// return status 返回状态 
 
  unsigned int transfer_flags;//USB_DISABLE_SPD 拒绝短数据包,即比最大传输包长度小的数据包 
 
  这个变量可以被设置为许多不同的位值,取决与usb驱动程序对urb的具体操作,可以用的值包括 
 
    URB_SHORT_NOT_OK 该值说明任何可能发生的对于IN端点的间断的读取应该被usb核心当作是一个错误处理,该值只对用usb设备 
 
  读取的urb起作用。,对于写入urb没有意义 
 
  URB_ISO_ASAP 如果urb是等时的,当驱动程序想要该urb被调度时可以设置这个位,只要带宽允许它这么做,而且想要在此时设置urb 
 
  中的start_frame变量,如果一个等时的urb没有设置该位驱动程序必须制定start_frame的值,如果在传输的当时不能启动的话必须能够 
 
  正确的恢复。 
 
  URB_NO_TRANSFER_DMA_MAP 顾名思意这个时设置DMA缓冲区的,当urb使用DMA缓冲区的时候设置该位。usb核心使用 
 
  transfer_dma的变量所指向的缓冲区 
 
    URB_NO_SETUP_DMA_MAP 和URB_NO_TRANDFER_DMA_MAP位类似,该位用于控制带有已经设置好的DMA缓冲区的urb,如果它被设置了,USB 
 
  核心使用setup_dma变量所指向的缓冲区,而不是setup_packet变量 
 
  URB_ASYNC_UNLINK 如果该位被设置,对urb的usb_unlink_urb的调用几乎是立即返回,该urb的链接在后台被解开,否则此函数一直等 
 
  待urb被完全解开链接和结束才返回,使用该位时要小心,因为它可能会造成伴以调试的同步问题 
 
    URB_NO_FSBR 仅由UHCI USB主控制器驱动程序使用,指示它不要企图使用前端的总线回收逻辑。该位通常不应该被设置,因为带有 
 
  UBCI主控制器的及其会导致大量的CPU负荷,而PCI总线忙于等待一个设置了该位的URB 
 
  URB_ZERO_PACKET 如果该位被设置,一个批量输出urb以发送一个不包含的小数据包来结束,这是数据对齐到一个端点数据包边 
 
  界,一些断线的USB涉黑需要该位才能正确的工作。 
 
  URB_NO_INTERRUPT 如果该位被设置,当urb结束的时候,硬件肯那个不会产生一个中断,对该位的使用应当小心谨慎,只有把多个 
 
  urb排队到同一个端点时才使用。usb核心的函数使用该位来进行DMA缓冲区的传输。 
 
  void *transfer_buffer;// associated data buffer 传输数据缓冲区,接收或发送设备的数据,它必须是物理连续的,不可换页的内存块,用kmalloc(,GFP_KERNEL)分配内核内存空间 
 
  int transfer_buffer_length;//data buffer length transfer_buffer或者transfer_dma变量所指向的缓冲区长度,在任何时候一个urb只能用一个 
 
  int actual_length;// actual data buffer length 当urb结束后,该变量被设置为urb所大宋的数据或者urb接收的实际数据长度 
 
  int bandwidth;//bandwidth for this transfer request(INT or ISO),此请求每次占用一帧的带宽,只适合于用实时/中断传输 
 
  unsigned char *setup_packet;//setup packet(control only)用于指向传输中控制命令的指针.只适用控制传输. 
 
  int start_frame;// start frame(iso/irq only),此请求开始传输的帧号.中断传输的时候,表示返回启动此请求的第一次中断传输的帧号.实时传输的时候,指明处理第一个实时请求数据包的帧号, 如果设置了USB_ISO_ASAP,此变量表示返回启动第一次实时传输的帧号 
 
  int number_of_packets;//number of packets in this request (iso)此请求包含的数据包数,只适合实时传输,在urb被发送到usb核心之前必须被USB驱动程序设置 
 
  int interval;// polling interval(irq only)urb被轮询的时间间隔,仅对中断或者等时传输有效,在urb被发送到usb核心之前必须由usb驱动程序设置; 
 
  int error_count;//number of errors in this transfer(iso only)由usb核心设置,仅用于等时urb结束后,他表示报告了任何一种类型的错误的等时传输的数量。 
 
  int timeout; 
 
  void *context;// context for completion routine指向一个可以被usb驱动程序设置的数据块,它可以在结束处理程序历程中当urb被返回到驱动程序的时候使用。 
 
  usb_complete_t complete;//pointer to completion routine,指向回调函数的指针,当数据完成的时候或者出现出错的时候usb核心调用此函 
 
  数,在该函数内,usb驱动程序可以检查urb,释放它,或者把它重新提交到两个一个传输中去 
 
  usb_complete_t 定义如下 
 
  typedef voud (*usb_complete_t)(struct urb * , struct pt_regs *); 
 
  iso_packet_descriptor_t iso_frame_desc[0];//要进行实时传输的数据结构,每个结构表示一次数据传输 
 
  int status 
 
    当urb结束之后,或者正在被usb核心处理时,该变量被设置为urb的当前状态,usb驱动程序可以安全第访问该变量的唯一适 合就是在urb结束处理的历程中。该限制是为了防止当urb正在被usb核心处理时的竞态的发生。对于等是urb,该变量的一个成功之0表示urb室友已 经被解开链接,如果在获取等时urb的详细状态。应该检查iso_frame_desc变量,该变量的有效值包含值: 
 
    0 urb传输成功 
 
    -ENOENT urb被usb_kill_urb调用终止 
 
  -ECONNRESET urb被usb_unlink_urn调用解开连接,urb的transfer_flags变量被设置为URB_ASYNC_URBLINK; 
 
    -EINPROGRESS urb仍然咋被usb主控制器处理,如果驱动程序中检查到该值,说明存在代码缺陷 
 
  -EPROTO urb发生了以下错误 
 
  在传输中发生了bitstuff错误 
 
      硬件没有及时接收到响应数据包 
 
    -EILSEQ urb在传输发生了CRC校验不匹配 
 
  -EPIPE 端点被中止,如果涉及的端点不是控制端点,可以调用usb_clear_halt函数来清楚该错误 
 
  -ECOMM 传输时数据的接收速度比它写到系统内存的速度快,该错误值仅发生在IN urb上 
 
  -ENOSR 传输时从系统内存获取的数据速度不够快,跟不上所要求的USB数据速率,该错误仅发生在OUT utb上 
 
  -EOVERFLOW urb发生“串扰(babble)”错误。“串扰”错误发生在端点接收了超过端点指定的数据包尺寸的数据时 
 
  -EREMOTEIO 仅发生在urb的transfer_flags变量设置为URB_SHORT_NOT_OK标识时。表示urb没有接收到所要求的全部数据量 
 
  -ENODEV USB设备已经从系统移出 
 
  -EXDEV 仅发生在等时urb上,表示传输仅部分完成,为了确定所传输的内容,驱动程序必须单看单个帧的状态 
 
  -EINVAL urb发生了很糟糕的事情,URB内核文档描述的该值,如果发生这种情况,那就退出系统,然后回家去 
 
  -ESHUTDOWN usb主控制器驱动程序发生了严重的错误,设备已经被禁止,或者已经从系统脱离,而urb在设备被移出之后提交,如果当urb被提交到设备的配置被改变,也可能发生这个错误。 
 
  一般来说,错误值-EPROTO ,-EILSSEQ 和 -EOVERFLOW表示设备,设备的固件或者吧设备连接到了计算机的电缆发生了硬件故障。 
 
  /*中断请求处理函数,有中断请求到达时调用该函数*/ 
 
  static void usb_kbd_irq(struct urb *urb, struct pt_regs *regs) 
 
  { 
 
  struct usb_kbd *kbd = urb->context; 
 
  int i; 
 
  switch (urb->status) { 
 
  case 0: /* success */ 
 
  break; 
 
  case -ECONNRESET: /* unlink */ 
 
  case -ENOENT: 
 
  case -ESHUTDOWN: 
 
  return; 
 
  /* -EPIPE: should clear the halt */ 
 
  default: /* error */ 
 
  goto resubmit; 
 
  } 
 
  //input_regs(kbd->dev, regs); 
 
  /* 
 
  不知道其用意, 注释掉该部分仍可正常工作*/ 
 
  for (i = 0; i < 8; i++)/*8次的值依次是:29-42-56-125-97-54-100-126*/ 
 
  { 
 
  input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1); 
 
  } 
 
  /*若同时只按下1个按键则在第[2]个字节,若同时有两个按键则第二个在第[3]字节,类推最多可有6个按键同时按下*/ 
 
  for (i = 2; i < 8; i++) { 
 
  /*获取键盘离开的中断*/ 
 
  if (kbd->old > 3 && memscan(kbd->new + 2, kbd->old, 6) == kbd->new + 8) {/*同时没有该KEY的按下状态*/ 
 
  if (usb_kbd_keycode[kbd->old]) 
 
  { 
 
  input_report_key(kbd->dev, usb_kbd_keycode[kbd->old], 0); 
 
  } 
 
  else 
 
  info("Unknown key (scancode %#x) released.", kbd->old); 
 
  } 
 
  /* 
 
  获取键盘按下的中断*/ 
 
  if (kbd->new > 3 && memscan(kbd->old + 2, kbd->new, 6) == kbd->old + 8) {/*同时没有该KEY的离开状态*/ 
 
  if (usb_kbd_keycode[kbd->new]) 
 
  { 
 
  input_report_key(kbd->dev, usb_kbd_keycode[kbd->new], 1); 
 
  } 
 
  else 
 
  info("Unknown key (scancode %#x) pressed.", kbd->new); 
 
  } 
 
  } 
 
  /*同步设备,告知事件的接收者驱动已经发出了一个完整的报告*/ 
 
  input_sync(kbd->dev); 
 
  memcpy(kbd->old, kbd->new, 8);/*防止未松开时被当成新的按键处理*/ 
 
  resubmit: 
 
  i = usb_submit_urb (urb, GFP_ATOMIC);/*发送USB请求块*/ 
 
  if (i) 
 
  err ("can't resubmit intr, %s-%s/input0, status %d", 
 
  kbd->usbdev->bus->bus_name, 
 
  kbd->usbdev->devpath, i); 
 
  } 
 
  11. 编写事件处理函数: 
 
  /*事件处理函数*/ 
 
  static int usb_kbd_event(struct input_dev *dev, unsigned int type, 
 
  unsigned int code, int value) 
 
  { 
 
  struct usb_kbd *kbd = dev->private; 
 
  if (type != EV_LED) /*不支持LED事件 */ 
 
  return -1; 
 
  /*获取指示灯的目标状态*/ 
 
  kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) | 
 
  (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) | 
 
  (!!test_bit(LED_NUML, dev->led)); 
 
  if (kbd->led->status == -EINPROGRESS) 
 
  return 0; 
 
  /* 
 
  指示灯状态已经是目标状态则不需要再做任何操作*/ 
 
  if (*(kbd->leds) == kbd->newleds) 
 
  return 0; 
 
  *(kbd->leds) = kbd->newleds; 
 
  kbd->led->dev = kbd->usbdev; 
 
  /*发送usb请求块*/ 
 
  if (usb_submit_urb(kbd->led, GFP_ATOMIC)) 
 
  err("usb_submit_urb(leds) failed"); 
 
  return 0; 
 
  } 
 
  12. 编写LED事件处理函数: 
 
  /*接在event之后操作,该功能其实usb_kbd_event中已经有了,该函数的作用可能是防止event的操作失败,一般注释掉该函数中的所有行都可以正常工作*/ 
 
  static void usb_kbd_led(struct urb *urb, struct pt_regs *regs) 
 
  { 
 
  struct usb_kbd *kbd = urb->context; 
 
  if (urb->status) 
 
  warn("led urb status %d received", urb->status); 
 
  if (*(kbd->leds) == kbd->newleds)/* 
 
  指示灯状态已经是目标状态则不需要再做任何操作*/ 
 
  return; 
 
  *(kbd->leds) = kbd->newleds; 
 
  kbd->led->dev = kbd->usbdev; 
 
  if (usb_submit_urb(kbd->led, GFP_ATOMIC)) 
 
  err("usb_submit_urb(leds) failed"); 
 
  } 
 
  13. 编写USB设备打开函数: 
 
  /*打开键盘设备时,开始提交在 probe 函数中构建的 urb,进入 urb 周期。 */ 
 
  static int usb_kbd_open(struct input_dev *dev) 
 
  { 
 
  struct usb_kbd *kbd = dev->private; 
 
  kbd->irq->dev = kbd->usbdev; 
 
  if (usb_submit_urb(kbd->irq, GFP_KERNEL)) 
 
  return -EIO; 
 
  return 0; 
 
  } 
 
  14. 编写USB设备关闭函数 
 
  /*关闭键盘设备时,结束 urb 生命周期。 */ 
 
  static void usb_kbd_close(struct input_dev *dev) 
 
  { 
 
  struct usb_kbd *kbd = dev->private; 
 
  usb_kill_urb(kbd->irq); /*取消kbd->irq这个usb请求块*/ 
 
  } 
 
  15. 创建URB 
 
  /*分配URB内存空间即创建URB*/ 
 
  static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd) 
 
  { 
 
  if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL))) 
 
  return -1; 
 
  if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL))) 
 
  return -1; 
 
  if (!(kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma))) 
 
  return -1; 
 
  if (!(kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma))) 
 
  return -1; 
 
  if (!(kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma))) 
 
  return -1; 
 
  return 0; 
 
  } 
 
  16. 销毁URB 
 
  /*释放URB内存空间即销毁URB*/ 
 
  static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd) 
 
  { 
 
  if (kbd->irq) 
 
  usb_free_urb(kbd->irq); 
 
  if (kbd->led) 
 
  usb_free_urb(kbd->led); 
 
  if (kbd->new) 
 
  usb_buffer_free(dev, 8, kbd->new, kbd->new_dma); 
 
  if (kbd->cr) 
 
  usb_buffer_free(dev, sizeof(struct usb_ctrlrequest), kbd->cr, kbd->cr_dma); 
 
  if (kbd->leds) 
 
  usb_buffer_free(dev, 1, kbd->leds, kbd->leds_dma); 
 
  } 
 
  17. USB键盘驱动探测函数: 
 
  /*USB键盘驱动探测函数,初始化设备并指定一些处理函数的地址*/ 
 
  static int usb_kbd_probe(struct usb_interface *iface, 
 
  const struct usb_device_id *id) 
 
  { 
 
  struct usb_device *dev = interface_to_usbdev(iface); 
 
  struct usb_host_interface *interface; 
 
  struct usb_endpoint_descriptor *endpoint; 
 
  struct usb_kbd *kbd; 
 
  struct input_dev *input_dev; 
 
  int i, pipe, maxp; 
 
  /*当前选择的interface*/ 
 
  interface = iface->cur_altsetting; 
 
  /*键盘只有一个中断IN端点*/ 
 
  if (interface->desc.bNumEndpoints != 1) 
 
  return -ENODEV; 
 
  /*获取端点描述符*/ 
 
  endpoint = &interface->endpoint[0].desc; 
 
  if (!(endpoint->bEndpointAddress & USB_DIR_IN)) 
 
  return -ENODEV; 
 
  if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) 
 
  return -ENODEV; 
 
  /*将endpoint设置为中断IN端点*/ 
 
  pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); 
 
  /*获取包的最大值*/ 
 
  maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); 
 
  kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL); 
 
  input_dev = input_allocate_device(); 
 
  if (!kbd || !input_dev) 
 
  goto fail1; 
 
  if (usb_kbd_alloc_mem(dev, kbd)) 
 
  goto fail2; 
 
  /* 填充 usb 设备结构体和输入设备结构体 */ 
 
  kbd->usbdev = dev; 
 
  kbd->dev = input_dev; 
 
  /* 
 
  以"厂商名字 产品名字"的格式将其写入kbd->name*/ 
 
  if (dev->manufacturer) 
 
  strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); 
 
  if (dev->product) { 
 
  if (dev->manufacturer) 
 
  strlcat(kbd->name, " ", sizeof(kbd->name)); 
 
  strlcat(kbd->name, dev->product, sizeof(kbd->name)); 
 
  } 
 
  /* 
 
  检测不到厂商名字*/ 
 
  if (!strlen(kbd->name)) 
 
  snprintf(kbd->name, sizeof(kbd->name), 
 
  "USB HIDBP Keyboard %04x:%04x", 
 
  le16_to_cpu(dev->descriptor.idVendor), 
 
  le16_to_cpu(dev->descriptor.idProduct)); 
 
  /*设备链接地址*/ 
 
  usb_make_path(dev, kbd->phys, sizeof(kbd->phys)); 
 
  strlcpy(kbd->phys, "/input0", sizeof(kbd->phys)); 
 
  input_dev->name = kbd->name; 
 
  input_dev->phys = kbd->phys; 
 
  /* 
 
  * input_dev 中的 input_id 结构体,用来存储厂商、设备类型和设备的编号,这个函数是将设备描述符 
 
  * 中的编号赋给内嵌的输入子系统结构体 
 
  */ 
 
  usb_to_input_id(dev, &input_dev->id); 
 
  /* cdev 是设备所属类别(class device) */ 
 
  input_dev->cdev.dev = &iface->dev; 
 
  /* input_dev 的 private 数据项用于表示当前输入设备的种类,这里将键盘结构体对象赋给它 */ 
 
  input_dev->private = kbd; 
 
  input_dev->evbit[0] = BIT(EV_KEY)/*键码事件*/ | BIT(EV_LED)/*LED事件*/ | BIT(EV_REP)/*自动重覆数值*/; 
 
  input_dev->ledbit[0] = BIT(LED_NUML)/*数字灯*/ | BIT(LED_CAPSL)/*大小写灯*/ | BIT(LED_SCROLLL)/*滚动灯*/ | BIT(LED_COMPOSE) | BIT(LED_KANA); 
 
  for (i = 0; i < 255; i++) 
 
  set_bit(usb_kbd_keycode, input_dev->keybit); 
 
  clear_bit(0, input_dev->keybit); 
 
  input_dev->event = usb_kbd_event;/* 
 
  注册事件处理函数入口*/ 
 
  input_dev->open = usb_kbd_open;/*注册设备打开函数入口*/ 
 
  input_dev->close = usb_kbd_close;/*注册设备关闭函数入口*/ 
 
  /* 
 
  初始化中断URB*/ 
 
  usb_fill_int_urb(kbd->irq/*初始化kbd->irq这个urb*/, dev/*这个urb要发送到dev这个设备*/, pipe/*这个urb要发送到pipe这个端点*/, 
 
  kbd->new/*指向缓冲的指针*/, (maxp > 8 ? 8 : maxp)/*缓冲长度*/, 
 
  usb_kbd_irq/*这个urb完成时调用的处理函数*/, kbd/*指向数据块的指针,被添加到这个urb结构可被完成处理函数获取*/, endpoint->bInterval/*urb应当被调度的间隔*/); 
 
  kbd->irq->transfer_dma = kbd->new_dma; /*指定urb需要传输的DMA缓冲区*/ 
 
  kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;/*本urb有一个DMA缓冲区需要传输*/ 
 
  kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;/*操作的是类接口对象*/ 
 
  kbd->cr->bRequest = 0x09; /*中断请求编号*/ 
 
  kbd->cr->wValue = cpu_to_le16(0x200); 
 
  kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);/*接口号*/ 
 
  kbd->cr->wLength = cpu_to_le16(1);/*数据传输阶段传输多少个bytes*/ 
 
  /* 
 
  初始化控制URB*/ 
 
  usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0), 
 
  (void *) kbd->cr, kbd->leds, 1, 
 
  usb_kbd_led, kbd); 
 
  kbd->led->setup_dma = kbd->cr_dma; 
 
  kbd->led->transfer_dma = kbd->leds_dma; 
 
  kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP/*如果使用DMA传输则urb中setup_dma指针所指向的缓冲区是DMA缓冲区而不是 setup_packet所指向的缓冲区*/); 
 
  /* 
 
  注册输入设备*/ 
 
  input_register_device(kbd->dev); 
 
  usb_set_intfdata(iface, kbd);/*设置接口私有数据*/ 
 
  return 0; 
 
  fail2: usb_kbd_free_mem(dev, kbd); 
 
  fail1: input_free_device(input_dev); 
 
  kfree(kbd); 
 
  return -ENOMEM; 
 
  } 
 
  18. 编写断开连接的函数: 
 
  /*断开连接(如键盘设备拔出)的处理函数*/ 
 
  static void usb_kbd_disconnect(struct usb_interface *intf) 
 
  { 
 
  struct usb_kbd *kbd = usb_get_intfdata (intf);/*获取接口的私有数据给kbd*/ 
 
  usb_set_intfdata(intf, NULL);/*设置接口的私有数据为NULL*/ 
 
  if (kbd) { 
 
  usb_kill_urb(kbd->irq);/*取消中断请求*/ 
 
  input_unregister_device(kbd->dev);/*注销设备*/ 
 
  usb_kbd_free_mem(interface_to_usbdev(intf), kbd);/*释放内存空间*/ 
 
  kfree(kbd); 
 
  } 
 
  } 
 
  19. 编写Makefile: 
 
  ############################## 
 
  #usbkdb Makefile for linux 
 
  ############################## 
 
  obj-m:=usbkbd.o 
 
  KERNELDIR ?= /lib/modules/$(shell uname -r)/build 
 
  PWD:=$(shell pwd) 
 
  default: 
 
  $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
 
<Linux usb鼠标驱动>
USB 总线引出两个重要的链表!
 
一个 USB 总线引出两个重要的链表,一个为 USB 设备链表,一个为 USB 驱动链表。设备链表包含各种系统中的USB 设备以及这些设备的所有接口,驱动链表包含 USB 设备驱动程序(usb device driver)和 USB 驱动程序(usb driver)。
 
USB 设备驱动程序(usb device driver)和 USB 驱动程序(usb driver)的区别是什么?
 
 
USB 设备驱动程序包含 USB 设备的一些通用特性,将与所有 USB 设备相匹配。在 USB core 定义了:struct usb_device_driver usb_generic_driver。usb_generic_driver 是 USB 子系统中唯一的一个设备驱动程序对象。而 USB 驱动程序则是与接口相匹配,接口是一个完成特定功能的端点的集合。
设备是如何添加到设备链表上去的?
在设备插入 USB 控制器之后,USB core 即会将设备在系统中注册,添加到 USB 设备链表上去。
 
USB 设备驱动程序(usb device driver)是如何添加到驱动链表上去的?
在系统启动注册 USB core 时,USB 设备驱动程序即将被注册,也就添加到驱动链表上去了。
 
接口是如何添加到设备链表上去的?
在 USB 设备驱动程序和 USB 设备的匹配之后,USB core 会对设备进行配置,分析设备的结构之后会将设备所有接口都添加到设备链表上去。比如鼠标设备中有一个接口,USB core 对鼠标设备配置后,会将这个接口添加到设备链表上去。
 
USB 驱动程序(usb driver)是如何添加到驱动链表上去的?
在每个 USB 驱动程序的被注册时,USB 驱动程序即会添加到驱动链表上去。比如鼠标驱动程序,usb_mouse_init 函数将通过 usb_register(&usb_mouse_driver) 将鼠标驱动程序注册到 USB core 中,然后就添加到驱动链表中去了。其中 usb_mouse_driver 是描述鼠标驱动程序的结构体。
 
已配置状态(configured status)之后话
当鼠标的设备、接口都添加到设备链表,并且鼠标驱动程序也添加到驱动链表上去了, 系统就进入一种叫做已配置(configured)的状态。要达到已配置状态,将经历复杂的过程,USB core 为 USB 设备奉献着无怨无悔。在这个过程中,系统将会建立起该设备的的设备、配置、接口、设置、端点的描述信息,它们分别被 usb_device、usb_configuration、usb_interface、usb_host_interface、 usb_host_endpoint 结构体描述。
设备达到已配置状态后,首先当然就要进行 USB 驱动程序和相应接口的配对,对于鼠标设备来说则是鼠标驱动程序和鼠标中的接口的配对。USB core 会调用usb_bus 总线的usb_device_match 函数,通过比较设备中的接口信息和 USB 驱动程序中的 id_table,来初步决定该 USB 驱动程序是不是跟相应接口相匹配。通过这一道关卡后,USB core 会认为这个设备应该由这个驱动程序负责。
然而,仅仅这一步是不够的,接着,将会调用 USB 驱动程序中的 probe 函数对相应接口进行进一步检查。如果该驱动程序确实适合设备接口,对设备做一些初始化工作,分配 urb 准备数据传输。
当 鼠标设备在用户空间打开时,将提交 probe 函数构建的 urb 请求块,urb 将开始为传送数据而忙碌了。urb 请求块就像一个装东西的“袋子”,USB 驱动程序把“空袋子”提交给 USB core,然后再交给主控制器,主控制器把数据放入这个“袋子”后再将装满数据的“袋子”通过 USB core 交还给 USB 驱动程序,这样一次数据传输就完成了。
 
  
 
以下是完全注释后的鼠标驱动程序代码 usbmouse.c
 
view plaincopy to clipboardprint?
/*  
 * $Id: usbmouse.c,v 1.15 2001/12/27 10:37:41 vojtech Exp $  
 *  
 *  Copyright (c) 1999-2001 Vojtech Pavlik 
 
 
 
 *  
 *  USB HIDBP Mouse support  
 */  
  
#include <linux/kernel.h>    
#include <linux/slab.h>    
#include <linux/module.h>    
#include <linux/init.h>    
#include <linux/usb/input.h>    
#include <linux/hid.h>    
  
/*  
 * Version Information  
 */  
#define DRIVER_VERSION "v1.6"    
#define DRIVER_AUTHOR "Vojtech Pavlik < [email protected]>"    
#define DRIVER_DESC "USB HID Boot Protocol mouse driver"    
#define DRIVER_LICENSE "GPL"    
  
MODULE_AUTHOR(DRIVER_AUTHOR);   
MODULE_DESCRIPTION(DRIVER_DESC);   
MODULE_LICENSE(DRIVER_LICENSE);   
  
/*  
 * 鼠标结构体,用于描述鼠标设备。  
 */  
struct usb_mouse    
{   
    /* 鼠标设备的名称,包括生产厂商、产品类别、产品等信息 */  
    char name[128];    
    /* 设备节点名称 */  
    char phys[64];     
    /* USB 鼠标是一种 USB 设备,需要内嵌一个 USB 设备结构体来描述其 USB 属性 */  
    struct usb_device *usbdev;  
    /* USB 鼠标同时又是一种输入设备,需要内嵌一个输入设备结构体来描述其输入设备的属性 */  
    struct input_dev *dev;    
    /* URB 请求包结构体,用于传送数据 */  
    struct urb *irq;   
    /* 普通传输用的地址 */  
    signed char *data;  
    /* dma 传输用的地址 */  
    dma_addr_t data_dma;           
};   
  
/*  
 * urb 回调函数,在完成提交 urb 后,urb 回调函数将被调用。  
 * 此函数作为 usb_fill_int_urb 函数的形参,为构建的 urb 制定的回调函数。  
 */  
static void usb_mouse_irq(struct urb *urb)   
{   
    /*  
     * urb 中的 context 指针用于为 USB 驱动程序保存一些数据。比如在这个回调函数的形参没有传递在 probe中为 mouse 结构体分配的那块内存的地址指针,而又需要用到那块内存区域中的数据,context 指针则帮了大忙了! 
     * 在填充 urb 时将 context 指针指向 mouse 结构体数据区,在这又创建一个局部 mouse 指针指向在 probe函数中为 mouse 申请的那块内存,那块内存保存着非常重要数据。 
     * 当 urb 通过 USB core 提交给 hc 之后,如果结果正常,mouse->data 指向的内存区域将保存着鼠标的按键和移动坐标信息,系统则依靠这些信息对鼠标的行为作出反应。  
     * mouse 中内嵌的 dev 指针,指向 input_dev 所属于的内存区域。 
     */  
    struct usb_mouse *mouse = urb->context;   
    signed char *data = mouse->data;   
    struct input_dev *dev = mouse->dev;   
    int status;   
  
    /*  
     * status 值为 0 表示 urb 成功返回,直接跳出循环把鼠标事件报告给输入子系统。  
     * ECONNRESET 出错信息表示 urb 被 usb_unlink_urb 函数给 unlink 了,ENOENT 出错信息表示 urb 被usb_kill_urb 函数给 kill 了。usb_kill_urb 表示彻底结束 urb 的生命周期,而 usb_unlink_urb 则是停止 urb,这个函数不等 urb 完全终止就会返回给回调函数。这在运行中断处理程序时或者等待某自旋锁时非常有用,在这两种情况下是不能睡眠的,而等待一个 urb 完全停止很可能会出现睡眠的情况。 
 
     * ESHUTDOWN 这种错误表示 USB 主控制器驱动程序发生了严重的错误,或者提交完 urb 的一瞬间设备被拔出。 
     * 遇见除了以上三种错误以外的错误,将申请重传 urb。  
     */  
    switch (urb->status)   
    {   
    case 0:     /* success */  
        break;   
    case -ECONNRESET:   /* unlink */  
    case -ENOENT:   
    case -ESHUTDOWN:   
        return;   
    /* -EPIPE:  should clear the halt */  
    default:        /* error */  
        goto resubmit;   
    }   
  
    /*  
     * 向输入子系统汇报鼠标事件情况,以便作出反应。  
     * data 数组的第0个字节:bit 0、1、2、3、4分别代表左、右、中、SIDE、EXTRA键的按下情况;  
     * data 数组的第1个字节:表示鼠标的水平位移;  
     * data 数组的第2个字节:表示鼠标的垂直位移;  
     * data 数组的第3个字节:REL_WHEEL位移。  
     */  
    input_report_key(dev, BTN_LEFT,   data[0] & 0x01);   
    input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);   
    input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);   
    input_report_key(dev, BTN_SIDE,   data[0] & 0x08);   
    input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);   
    input_report_rel(dev, REL_X,     data[1]);   
    input_report_rel(dev, REL_Y,     data[2]);   
    input_report_rel(dev, REL_WHEEL, data[3]);   
  
    /*  
     * 这里是用于事件同步。上面几行是一次完整的鼠标事件,包括按键信息、绝对坐标信息和滚轮信息,输入子系统正是通过这个同步信号来在多个完整事件报告中区分每一次完整事件报告。示意如下: 
     * 按键信息 坐标位移信息 滚轮信息 EV_SYC | 按键信息 坐标位移信息 滚轮信息 EV_SYC ...  
     */  
    input_sync(dev);   
  
    /*  
     * 系统需要周期性不断地获取鼠标的事件信息,因此在 urb 回调函数的末尾再次提交 urb 请求块,这样又会  
     * 调用新的回调函数,周而复始。  
     * 在回调函数中提交 urb 一定只能是 GFP_ATOMIC 优先级的,因为 urb 回调函数运行于中断上下文中,在提交 urb 过程中可能会需要申请内存、保持信号量,这些操作或许会导致 USB core 睡眠,一切导致睡眠的行为都是不允许的。 
     */  
resubmit:   
    status = usb_submit_urb (urb, GFP_ATOMIC);   
    if (status)   
        err ("can't resubmit intr, %s-%s/input0, status %d",   
                mouse->usbdev->bus->bus_name,   
                mouse->usbdev->devpath, status);   
}   
  
/*  
 * 打开鼠标设备时,开始提交在 probe 函数中构建的 urb,进入 urb 周期。  
 */  
static int usb_mouse_open(struct input_dev *dev)   
{   
    struct usb_mouse *mouse = dev->private;   
  
    mouse->irq->dev = mouse->usbdev;   
    if (usb_submit_urb(mouse->irq, GFP_KERNEL))   
        return -EIO;   
  
    return 0;   
}   
  
/*  
 * 关闭鼠标设备时,结束 urb 生命周期。  
 */  
static void usb_mouse_close(struct input_dev *dev)   
{   
    struct usb_mouse *mouse = dev->private;   
  
    usb_kill_urb(mouse->irq);   
}   
  
/*  
 * 驱动程序的探测函数  
 */  
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)  
{   
    /*   
     * 接口结构体包含于设备结构体中,interface_to_usbdev 是通过接口结构体获得它的设备结构体。 
     * usb_host_interface 是用于描述接口设置的结构体,内嵌在接口结构体 usb_interface 中。  
     * usb_endpoint_descriptor 是端点描述符结构体,内嵌在端点结构体 usb_host_endpoint 中,而端点结构体内嵌在接口设置结构体中。 
     */  
    struct usb_device *dev = interface_to_usbdev(intf);   
    struct usb_host_interface *interface;   
    struct usb_endpoint_descriptor *endpoint;   
    struct usb_mouse *mouse;   
    struct input_dev *input_dev;   
    int pipe, maxp;   
  
    interface = intf->cur_altsetting;   
  
    /* 鼠标仅有一个 interrupt 类型的 in 端点,不满足此要求的设备均报错 */  
    if (interface->desc.bNumEndpoints != 1)   
        return -ENODEV;   
  
    endpoint = &interface->endpoint[0].desc;   
    if (!usb_endpoint_is_int_in(endpoint))   
        return -ENODEV;   
  
    /*  
     * 返回对应端点能够传输的最大的数据包,鼠标的返回的最大数据包为4个字节,数据包具体内容在 urb  
     * 回调函数中有详细说明。  
     */  
    pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);  
 
 
bEndpointAddress这是这个特定端点的 USB 地址. 还包含在这个 8-位 值的是端点的方向. 位掩码 USB_DIR_OUT 和 USB_DIR_IN 可用来和这个成员比对, 来决定给这个端点的数据是到设备还是到主机.
 
pipe,要访问的端点所对应的管道,使用usb_sndintpipe()或usb_rcvintpipe()创建
 
    maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));  
  
    /* 为 mouse 设备结构体分配内存 */  
    mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);   
    /* input_dev */  
    input_dev = input_allocate_device();   
    if (!mouse || !input_dev)   
        goto fail1;   
  
    /*  
     * 申请内存空间用于数据传输,data 为指向该空间的地址,data_dma 则是这块内存空间的 dma 映射,即这块内存空间对应的 dma 地址。在使用 dma 传输的情况下,则使用 data_dma 指向的 dma 区域,否则使用 data 指向的普通内存区域进行传输。 
GFP_ATOMIC 表示不等待,GFP_KERNEL 是普通的优先级,可以睡眠等待,由于鼠标使用中断传输方式,不允许睡眠状态,data 又是周期性获取鼠标事件的存储区,因此使用 GFP_ATOMIC 优先级,如果不能分配到内存则立即返回 0。 
     */  
    mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma);   
    if (!mouse->data)   
        goto fail1;   
  
    /*  
     * 为 urb 结构体申请内存空间,第一个参数表示等时传输时需要传送包的数量,其它传输方式则为0。申请的内存将通过下面即将见到的 usb_fill_int_urb 函数进行填充。  
    mouse->irq = usb_alloc_urb(0, GFP_KERNEL);   
    if (!mouse->irq)   
        goto fail2;   
  
    /* 填充 usb 设备结构体和输入设备结构体 */  
    mouse->usbdev = dev;   
    mouse->dev = input_dev;   
  
    /* 获取鼠标设备的名称 */  
    if (dev->manufacturer)   
        strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));   
  
    if (dev->product)    
    {   
        if (dev->manufacturer)   
            strlcat(mouse->name, " ", sizeof(mouse->name));   
        strlcat(mouse->name, dev->product, sizeof(mouse->name));   
    }   
  
    if (!strlen(mouse->name))   
        snprintf(mouse->name, sizeof(mouse->name),   
             "USB HIDBP Mouse %04x:%04x",   
             le16_to_cpu(dev->descriptor.idVendor),   
             le16_to_cpu(dev->descriptor.idProduct));   
  
    /*  
     * 填充鼠标设备结构体中的节点名。usb_make_path 用来获取 USB 设备在 Sysfs 中的路径,格式  
     * 为:usb-usb 总线号-路径名。  
     */  
    usb_make_path(dev, mouse->phys, sizeof(mouse->phys));   
    strlcat(mouse->phys, "/input0", sizeof(mouse->phys));   
  
    /* 将鼠标设备的名称赋给鼠标设备内嵌的输入子系统结构体 */  
    input_dev->name = mouse->name;   
    /* 将鼠标设备的设备节点名赋给鼠标设备内嵌的输入子系统结构体 */  
    input_dev->phys = mouse->phys;   
    /*  
     * input_dev 中的 input_id 结构体,用来存储厂商、设备类型和设备的编号,这个函数是将设备描述符中的编号赋给内嵌的输入子系统结构体  
     */  
    usb_to_input_id(dev, &input_dev->id);   
    /* cdev 是设备所属类别(class device) */  
    input_dev->cdev.dev = &intf->dev;   
  
    /* evbit 用来描述事件,EV_KEY 是按键事件,EV_REL 是相对坐标事件 */  
    input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);   
    /* keybit 表示键值,包括左键、右键和中键 */  
    input_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);  
    /* relbit 用于表示相对坐标值 */  
    input_dev->relbit[0] = BIT(REL_X) | BIT(REL_Y);   
    /* 有的鼠标还有其它按键 */  
    input_dev->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);   
    /* 中键滚轮的滚动值 */  
    input_dev->relbit[0] |= BIT(REL_WHEEL);   
  
    /* input_dev 的 private 数据项用于表示当前输入设备的种类,这里将鼠标结构体对象赋给它 */ 
    input_dev->private = mouse;   
    /* 填充输入设备打开函数指针 */  
    input_dev->open = usb_mouse_open;   
    /* 填充输入设备关闭函数指针 */  
    input_dev->close = usb_mouse_close;   
  
    /*  
     * 填充构建 urb,将刚才填充好的 mouse 结构体的数据填充进 urb 结构体中,在 open 中递交 urb。  
     * 当 urb 包含一个即将传输的 DMA 缓冲区时应该设置URB_NO_TRANSFER_DMA_MAP。USB核心使用  
     * transfer_dma变量所指向的缓冲区,而不是transfer_buffer变量所指向的。  
     * URB_NO_SETUP_DMA_MAP 用于 Setup 包,URB_NO_TRANSFER_DMA_MAP 用于所有 Data 包。  
     */  
    usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,  
             (maxp > 8 ? 8 : maxp),  
             usb_mouse_irq, mouse, endpoint->bInterval);  
    mouse->irq->transfer_dma = mouse->data_dma;   
    mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;   
  
    /* 向系统注册输入设备 */  
    input_register_device(mouse->dev);   
  
    /*  
     * 一般在 probe 函数中,都需要将设备相关信息保存在一个 usb_interface 结构体中,以便以后通过  
     * usb_get_intfdata 获取使用。这里鼠标设备结构体信息将保存在 intf 接口结构体内嵌的设备结构体中的 driver_data 数据成员中,即 intf->dev->dirver_data = mouse。 
    usb_set_intfdata(intf, mouse);   
    return 0;   
  
fail2:  usb_buffer_free(dev, 8, mouse->data, mouse->data_dma);   
fail1:  input_free_device(input_dev);   
    kfree(mouse);   
    return -ENOMEM;   
}   
  
/*  
 * 鼠标设备拔出时的处理函数  
 */  
static void usb_mouse_disconnect(struct usb_interface *intf)   
{   
    /* 获取鼠标设备结构体 */  
    struct usb_mouse *mouse = usb_get_intfdata (intf);   
  
    /* intf->dev->dirver_data = NULL,将接口结构体中的鼠标设备指针置空。*/  
    usb_set_intfdata(intf, NULL);   
    if (mouse)   
    {   
        /* 结束 urb 生命周期 */  
        usb_kill_urb(mouse->irq);   
        /* 将鼠标设备从输入子系统中注销 */  
        input_unregister_device(mouse->dev);   
        /* 释放 urb 存储空间 */  
        usb_free_urb(mouse->irq);   
        /* 释放存放鼠标事件的 data 存储空间 */  
        usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);  
        /* 释放存放鼠标结构体的存储空间 */  
        kfree(mouse);   
    }   
}   
  
/*  
 * usb_device_id 结构体用于表示该驱动程序所支持的设备,USB_INTERFACE_INFO 可以用来匹配特定类型的接口, 
 * 这个宏的参数意思为 (类别, 子类别, 协议)。  
 * USB_INTERFACE_CLASS_HID 表示是一种 HID (Human Interface Device),即人机交互设备类别; 
 * USB_INTERFACE_SUBCLASS_BOOT 是子类别,表示是一种 boot 阶段使用的 HID; 
 * USB_INTERFACE_PROTOCOL_MOUSE 表示是鼠标设备,遵循鼠标的协议。 
 */  
static struct usb_device_id usb_mouse_id_table [] = {   
    { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,   
        USB_INTERFACE_PROTOCOL_MOUSE) },   
    { } /* Terminating entry */  
};   
  
/*  
 * 这个宏用来让运行在用户空间的程序知道这个驱动程序能够支持的设备,对于 USB 驱动程序来说,第一个参数必须  
 * 是 usb。  
 */  
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);  
  
/*  
 * 鼠标驱动程序结构体  
 */  
static struct usb_driver usb_mouse_driver = {   
    .name       = "usbmouse",   
    .probe      = usb_mouse_probe,   
    .disconnect = usb_mouse_disconnect,   
    .id_table   = usb_mouse_id_table,   
};   
  
/*  
 * 驱动程序生命周期的开始点,向 USB core 注册这个鼠标驱动程序。  
 */  
static int __init usb_mouse_init(void)   
{   
    int retval = usb_register(&usb_mouse_driver);   
    if (retval == 0)   
        info(DRIVER_VERSION ":" DRIVER_DESC);   
    return retval;   
}   
  
/*  
 * 驱动程序生命周期的结束点,向 USB core 注销这个鼠标驱动程序。  
 */  
static void __exit usb_mouse_exit(void)   
{   
    usb_deregister(&usb_mouse_driver);   
}   
  
module_init(usb_mouse_init);   
module_exit(usb_mouse_exit);
 
 
 
 
 
 
 
 
 
void *usb_buffer_alloc (struct usb_device *dev, size_t size, gfp_t mem_flags, dma_addr_t *dma);
 
这个函数是usbcore提供的.从名字上就能知道它是用来申请内存的,内核中给出了一些介绍。
 
* usb_buffer_alloc - allocate dma-consistent buffer for URB_NO_xxx_DMA_MAP
 * @dev: device the buffer will be used with
 * @size: requested buffer size
 * @mem_flags: affect whether allocation may block
 * @dma: used to return DMA address of buffer
 *
 * Return value is either null (indicating no buffer could be allocated), or
 * the cpu-space pointer to a buffer that may be used to perform DMA to the
 * specified device.  Such cpu-space buffers are returned along with the DMA
 * address (through the pointer provided).
 *
 * These buffers are used with URB_NO_xxx_DMA_MAP set in urb->transfer_flags
 * to avoid behaviors like using "DMA bounce buffers", or tying down I/O
 * mapping hardware for long idle periods.  The implementation varies between
 * platforms, depending on details of how DMA will work to this device.
 * Using these buffers also helps prevent cacheline sharing problems on
 * architectures where CPU caches are not DMA-coherent.
 *
 * When the buffer is no longer used, free it with usb_buffer_free().
 
第一个参数就是struct usb_device结构体的指针,第二个参数申请的buffer的大小,第三个参数,GFP_KERNEL,是一个内存申请的flag,通常内存申请都用这个flag,除非是中断上下文,不能睡眠,那就得用GPF_ATOMIC,这里没那么多要求.而usb_buffer_alloc()的第四个参数涉及到dma传输.该函数不仅进行内存分配,还会进行DMA映射,这里第四个参数将被设置为DMA地址。
 
这个地址用于传输DMA缓冲区数据的urb。
 
用usb_buffer_alloc申请的内存空间需要用它的搭档usb_buffer_free()来释放.
 
 
 
 
 
 
 
struct input_dev *input_allocate_device(void);
 
void input_free_device(struct input_dev *dev);
 
input_allocate_device()返回的是1个input_dev的结构体,此结构体用于表征1个输入设备。
 
注册/注销输入设备用的如下接口:
 
int __must_check input_register_device(struct input_dev *);
 
void input_unregister_device(struct input_dev *);
 
报告输入事件用的如下接口:
 
/* 报告指定type、code的输入事件 */
 
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
 
/* 报告键值 */
 
void input_report_key(struct input_dev *dev, unsigned int code, int value);
 
/* 报告相对坐标 */
 
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
 
/* 报告绝对坐标 */
 
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
 
/* 报告同步事件 */
 
void input_sync(struct input_dev *dev);
 
而所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event,形如代码清单7。
 
代码清单7 input_event结构体
 
1 struct input_event {
 
2 struct timeval time;
 
3 __u16 type;
 
4 __u16 code;
 
5 __s32 value;
 
6 };
 
 
 
kzalloc函数  
2010-05-12 18:13:02|  分类:驱动程序|字号 订阅
 
这个函数就是原来的两个函数的整合,即原来我们每次申请内存的时候都会这么做,先是用kmalloc()申请空间,然后用memset()来初始化,而现在省事了,一步到位,直接调用kzalloc(),效果等同于原来那两个函数,所有申请的元素都被初始化为0.其实对写驱动的来说,知道现在应该用kzalloc()代替原来的kmalloc()和memset()就可以了,这是内核中内存管理部分做出的改变,确切的说是改进,负责内存管理那部分的兄弟们的目标无非就是让内核跑起来更快一些,而从kmalloc/memset到kzalloc的改变确实也是为了实现这方面的优化.
 
 
 
对于中断urb,使用usb_fill_int_urb函数来初始化:
 
static inline void usb_fill_int_urb (struct urb *urb,要初始化的urb指针。
                     struct usb_device *dev,所要访问的设备
                     unsigned int      pipe,要访问的端点所对应的管道,使用usb_sndintpipe()或usb_rcvintpipe()创建
                     void              *transfer_buffer,要传输的数据缓冲区
                     int               buffer_length,缓冲区长度
                     usb_complete_t    complete_fn,当完成该urb所请求的操作时,要调用的回调函数
                     void              *context,complet_fn函数所需的上下文,通常取值为dev
                     int               interval)urb被调度的时间间隔
 
 
 
首先,是它们的原型: size_t strlcpy (char *dst, const char *src, size_t size);
size_t strlcat (char *dst, const char *src, size_t size); 
 
strlcpy和strlcat二者都把目标缓存的整个大小作为参数(而不是要复制的字符最大数目),并保证结果以NIL终止(只要其大小大于0)。记住,应该把NIL的一个字节包括在大小里。
 
strlcpy函数从NUL结尾的字符串src复制size-1个字符到dst,用NIL作为结果的结尾。strlcat函数把NIL结尾的字符串src附加到dst的末尾。最多附加size - strlen(dst) - 1个字符,并用NIL作为结果的结尾。
 
 
snprintf()
  int snprintf(char *str, size_t size, const char *format, ...);  将可变个参数(...)按照format格式化成字符串,然后将其复制到str中  (1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');  (2) 如果格式化后的字符串长度 => size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0')  函数返回值:若成功则返回欲写入的字符串长度,若出错则返回负值。

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">