1. USB發展史
USB(Universal Serial Bus ),通用串行總線,是一種外部總線標准,用於規范電腦與外部設備的連接和通訊。
USB是在1994年底由英特爾、康柏、IBM、Microsoft等多家公司聯合提出的,自1996年推出後,已成功替代串口和並口,成為當今個人電腦和大量智能設備的必配接口之一。
?USB 1.0出現在1996年的,速度只有1.5Mb/s1998年升級為USB 1.1,速度也提升到12Mb/s,稱之為”full speed”
?USB 2.0規范是由USB1.1規范演變而來的。它的傳輸速率達到了480Mbps,稱之為”high speed”
?USB 3.0提供了十倍於USB 2.0的傳輸速度和更高的節能效率,被稱為”super speed”
2. USB信號線
3. USB系統拓撲結構
對於每個USB系統來說,都有一個稱為主機控制器的設備,該控制器和一個根Hub作為一個整體。這個根Hub下可以接多級的Hub,每個子Hub又可以接子Hub。每個USB設備作為一個節點接在不同級別的Hub上。每條USB總線上最多可以接127個設備。
常見的USB主控制器規格有:
OHCI:主要是非PC系統上的USB芯片
UHCI:大多是Intel和Via主板上的USB控制器芯片。他們都是由USB1.1規格的。
EHCI是有Intel等幾個廠商研發,兼容OHCI 、UHCI ,遵循USB2.0規范。
2. USB協議分析
在USB設備的邏輯組織中,包含設備、配置、接口和端點4個層次。設備通常有一個或者多個配置,配置通常有一個或多個接口,接口有0或多個端點。
設備邏輯結構:
每個USB設備都可以包含一個或多個配置,不同的配置使設備表現出不同的功能組合,配置有多個接口組成,在USB協議中,接口代表一個基本的功能,一個功能復雜的USB設備可以具有多個接口,而接口是端點的匯集。
一個USB播放器帶有音頻,視頻功能,還有旋鈕和按鈕。
配置1:音頻(接口)+ 旋鈕(接口)
配置2:視頻(接口)+ 旋鈕(接口)
配置3:音頻(接口)+ 視頻(接口) + 按鈕(接口)
音頻接口,視頻接口,按鈕接口, 旋鈕接口均需要一個驅動程序。
USB設備中的唯一可尋址的部分是設備端點,端點的作用類似於寄存器。每個端點在設備內部有唯一的端點號,這個端點號是在設備設計時給定的。主機和設備的通信最終都作用於設備上的各個端點。每個端點所支持的操作都是單向的,要麼只讀,要麼只寫。
USB描述符
當我們把USB設備插到我們的PC時,主機能夠自動識別出我們的USB設備類型???
在每一個USB設備內部,包含的固定格式的數據,通過這些數據,USB主機就可以獲取USB設備的類型、生產廠商等信息。這些固定格式的數據我們就稱之為USB描述符。標准的USB設備有5種USB描述符:
設備描述符、配置描述符、接口描述符、端點描述符、字符串描述符
一個USB設備只有一個設備描述符,設備描述符長度為18個字節,
USB配置描述符長度為8個字節,格式可以百度得到,這裡介紹每個字節的的含義
接口描述符8個字節
usb端點描述符:
2 USB數據通訊:
2.1. 通訊模型
2.2. 傳輸
USB的數據通訊首先是基於傳輸(Transfer)的,傳輸的類型有:中斷傳輸,批量傳輸,同步傳輸,控制傳輸
2.3. 事物
一次傳輸由一個或多個事物(Transaction)構成,事務可分為:In 事務,Out事務,Setup事務
2.4. 包
一個事務由一個或多個包構成,包可分為:令牌包(setup)、數據包(data)、握手包(ACK)和特殊包
2.5. 域
一個包由多個域構成,域可分為:同步域(SYNC),標識域(PID),地址域,端點域(ENDP),幀號(FRAM),數據域(DATA),校驗域(CRC)。
3. USB設備枚舉
USB設備在正常工作以前, 第一件要做的事就是枚舉。枚舉是讓主機認得這個USB設備, 並且為該設備准備資源,建立好主機和設備之間的數據傳遞通道。
這裡展示一個USB抓包工具抓的鼠標USB 數據包
下面來介紹USB軟件系統架構
上面兩個框框代表了兩種USB架構模型 第一種是比如開發板上通過USB掛載U盤,第二種是開發板直接通過usb連PC機,通過配置內核選項可以將USB接口模擬網口與PC機進行通信!
USB驅動程序設計
1. USB驅動模型
USB設備包括配置、接口和端點,一個USB設備驅動程序對應一個USB接口,而非整個USB設備
在Linux內核中,使用struct usb_driver結構描述一個USB驅動
2. URB
2.1 URB通訊模型
USB請求塊(USB request block-URB)是USB設備驅動中用來與USB設備通信所用的基本載體和核心數據結 構,非常類似於網絡設備驅動中的sk_buff結構體,是USB主機與設備通信的“電波”。
1. USB 設備驅動程序創建並初始化一個訪問特定端點的urb,並提交給USB core;
2. USB core提交該urb到USB主控制器驅動程序;
3. USB 主控制器驅動程序根據該urb描述的信息,來訪問USB設備;
4. 當設備訪問結束後,USB 主控制器驅動程序通知USB 設備驅動程序。
2.2 創建URB
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
參數:
iso_packets:urb所包含的等時數據包的個數。
mem_flags:內存分配標識(如GFP_KERNEL),參考kmalloc。
2.3 初始化URB
2.4 提交URB
在完成urb的創建和初始化後,USB驅動需要將urb提交給USB核心.
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
參數:
urb:要提交urb的指針
mem_flags: 內存分配標識(如GFP_KERNEL),參考kmalloc
URB被提交到USB核心後,USB核心指定usb主控制器驅動程序來處理該urb,處理完之後,urb完成函數將被調用
3. HID協議
HID(Human Interface Device), 屬於人機交互類的設備,如USB鼠標,USB鍵盤,USB游戲操縱桿等。該類設備必須
遵循HID設計規范。
4. 鼠標驅動分析
/* * Copyright (c) 1999-2001 Vojtech Pavlik * * USB HIDBP Mouse support */ /* * 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; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include鼠標驅動總結:#include #include #include #include #include /* for apple IDs */ #ifdef CONFIG_USB_HID_MODULE #include "../hid-ids.h" #endif /* * Version Information */ #define DRIVER_VERSION "v1.6" #define DRIVER_AUTHOR "Vojtech Pavlik " #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]; struct usb_device *usbdev; struct input_dev *dev; struct urb *irq; signed char *data; dma_addr_t data_dma; }; static void usb_mouse_irq(struct urb *urb) { struct usb_mouse *mouse = urb->context; signed char *data = mouse->data; struct input_dev *dev = mouse->dev; int status; /* 檢測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; } /* 報告按鍵狀態 */ 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]); input_sync(dev); 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); } static int usb_mouse_open(struct input_dev *dev) { struct usb_mouse *mouse = input_get_drvdata(dev); mouse->irq->dev = mouse->usbdev; if (usb_submit_urb(mouse->irq, GFP_KERNEL)) return -EIO; return 0; } static void usb_mouse_close(struct input_dev *dev) { struct usb_mouse *mouse = input_get_drvdata(dev); usb_kill_urb(mouse->irq); } static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id) { /* 設備描述 usb_device */ /* 接口描述 usb_interface */ 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; int error = -ENOMEM; /* 獲取當前接口設置 */ interface = intf->cur_altsetting; /* 根據HID規范,鼠標只有一個端點(不包含0號控制端點)*/ if (interface->desc.bNumEndpoints != 1) return -ENODEV; /* 獲取端點0描述符 */ endpoint = &interface->endpoint[0].desc; /* 根據HID規范,鼠標唯一的端點應為中斷端點 */ if (!usb_endpoint_is_int_in(endpoint)) return -ENODEV; /* 生成中斷管道 */ pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /* 返回該端點能夠傳輸的最大的包長度,鼠標的返回的最大數據包為4個字節。*/ maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); /* 創建input設備 */ mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL); input_dev = input_allocate_device(); if (!mouse || !input_dev) goto fail1; /* 申請內存空間用於數據傳輸,data 為指向該空間的地址*/ mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma); if (!mouse->data) goto fail1; /* 分配URB */ mouse->irq = usb_alloc_urb(0, GFP_KERNEL); if (!mouse->irq) goto fail2; 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_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; usb_to_input_id(dev, &input_dev->id); input_dev->dev.parent = &intf->dev; input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE); input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA); input_dev->relbit[0] |= BIT_MASK(REL_WHEEL); input_set_drvdata(input_dev, mouse); input_dev->open = usb_mouse_open; input_dev->close = usb_mouse_close; /* 初始化中斷URB */ /* 思考實驗:將interval參數設置為1分鐘,觀察現象 */ 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; error = input_register_device(mouse->dev); if (error) goto fail3; /*將mouse指針保存到intf的dev成員中*/ usb_set_intfdata(intf, mouse); return 0; fail3: usb_free_urb(mouse->irq); fail2: usb_buffer_free(dev, 8, mouse->data, mouse->data_dma); fail1: input_free_device(input_dev); kfree(mouse); return error; } static void usb_mouse_disconnect(struct usb_interface *intf) { struct usb_mouse *mouse = usb_get_intfdata (intf); usb_set_intfdata(intf, NULL); if (mouse) { usb_kill_urb(mouse->irq); input_unregister_device(mouse->dev); usb_free_urb(mouse->irq); usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma); kfree(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 */ }; 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, /* 設備列表 */ }; static int __init usb_mouse_init(void) { /* 注冊鼠標驅動程序 */ int retval = usb_register(&usb_mouse_driver); if (retval == 0) printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":" DRIVER_DESC "\n"); return retval; } static void __exit usb_mouse_exit(void) { usb_deregister(&usb_mouse_driver); } module_init(usb_mouse_init); module_exit(usb_mouse_exit);
鼠標是一個USB設備 usb設備驅動就是urb的創建、初始化、提交到usb_core
同時鼠標設備還是一個輸入設備, 輸入設備就包括注冊、上報, 其中還有一個HID鼠標規范協議
這裡小總結一下:
USB模型模型分為三個部分:USB主控制器驅動、USB核心、USB設備驅動,而整個USB驅動是圍繞URB來進行的。
前面用到的dnw驅動源碼 dnw_usb.c
#include#include #include #include #include #include #define BULKOUT_BUFFER_SIZE 512 char *bulkout_buffer; struct usb_device *udev; __u8 bulk_out_endaddr; static struct usb_device_id dnw_id_table [] = { { USB_DEVICE(0x5345, 0x1234) }, { } }; static int dnw_open(struct inode* inode, struct file *file) { bulkout_buffer = kmalloc(BULKOUT_BUFFER_SIZE,GFP_KERNEL); return 0; } static int dnw_release (struct inode* inode, struct file *file) { kfree(bulkout_buffer); return 0; } static ssize_t dnw_write(struct file *file, const char __user *buf, size_t len, loff_t *pos) { size_t to_write; size_t total_write = 0; size_t act_len; while(len>0) { to_write = min(len,(size_t)BULKOUT_BUFFER_SIZE); copy_from_user(bulkout_buffer,buf+total_write,to_write); usb_bulk_msg(udev,usb_sndbulkpipe(udev,bulk_out_endaddr),bulkout_buffer,to_write,&act_len,3*HZ); len -= to_write; total_write += to_write; } return total_write; } static struct file_operations dnw_ops = { .owner = THIS_MODULE, .write = dnw_write, .open = dnw_open, .release = dnw_release, }; static struct usb_class_driver dnw_class = { .name = "secbulk%d", .fops = &dnw_ops, .minor_base = 100, }; static int dnw_probe(struct usb_interface *intf, const struct usb_device_id *id) { /* 接口設置描述 */ struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; int i; interface = intf->cur_altsetting; for(i=0;i desc.bNumEndpoints;i++) { endpoint = &interface->endpoint[i].desc; if(usb_endpoint_is_bulk_out(endpoint)) { bulk_out_endaddr = endpoint->bEndpointAddress; break; } } usb_register_dev(intf,&dnw_class); udev = usb_get_dev(interface_to_usbdev(intf)); } static void dnw_disconnect(struct usb_interface *intf) { usb_deregister_dev(intf,&dnw_class); } struct usb_driver dnw_driver = { .name = "dnw", /* 驅動名 */ .probe = dnw_probe, /* 捕獲函數 */ .disconnect = dnw_disconnect, /* 卸載函數 */ .id_table = dnw_id_table, /* 設備列表 */ }; int dnw_init() { usb_register(&dnw_driver); return 0; } void dnw_exit() { usb_deregister(&dnw_driver); } module_init(dnw_init); module_exit(dnw_exit); MODULE_LICENSE("GPL");
#include#include #include #include #include #include #include #include const char* dev = "/dev/dnw0"; int main(int argc, char* argv[]) { unsigned char* file_buffer = NULL; long int addr = 0; if( 3 != argc ) { printf("Usage: dwn address\n"); return 1; } int fd = open(argv[1], O_RDONLY); if(-1 == fd) { printf("Can not open file - %s\n", argv[1]); return 1; } addr = strtol((char *) argv[2] ,NULL, 16); printf("addr = %x \n", addr); // get file size struct stat file_stat; if( -1 == fstat(fd, &file_stat) ) { printf("Get file size filed!\n"); return 1; } file_buffer = (unsigned char*)malloc(file_stat.st_size+10); if(NULL == file_buffer) { printf("malloc failed!\n"); goto error; } //memset(file_buffer, '\0', sizeof(file_buffer)); // bad code ! corrected by Qulory memset(file_buffer, '\0', sizeof(char)*(file_stat.st_size+10)); // the first 8 bytes in the file_buffer is reserved, the last 2 bytes also; if( file_stat.st_size != read(fd, file_buffer+8, file_stat.st_size)) { printf("Read file failed!\n"); goto error; } printf("File name : %s\n", argv[1]); printf("File size : %ld bytes\n", file_stat.st_size);// off_t is long int int fd_dev = open(dev, O_WRONLY); if( -1 == fd_dev) { printf("Can not open %s\n", dev); goto error; } /* * Note: the first 4 bytes store the dest addr ; * the following 4 bytes store the file size ; * and the last 2 bytes store the sum of each bytes of the file ; */ *((unsigned long*)file_buffer) = addr; //load address *((unsigned long*)file_buffer+1) = file_stat.st_size+10; //file size unsigned short sum = 0; int i; for(i=8; i 0) { size_t to_write = remain_size > block_size ? block_size:remain_size; size_t real_write = write(fd_dev, file_buffer+written, to_write); if( to_write != real_write) { printf(" write /dev/secbulk0 failed! to_write = %u real_write = %u \n" , to_write ,real_write ); return 1; } remain_size -= to_write; written += to_write; printf("\rSent %lu%% \t %u bytes !", written*100/(file_stat.st_size+10), written); fflush(stdout); } printf("OK\n"); return 0; error: if(-1 != fd_dev) { close(fd_dev); } if(fd != -1) { close(fd); } if( NULL != file_buffer ) { free(file_buffer); } return -1; }