天天看點

十五、Linux驅動之USB滑鼠驅動1. 如何編寫USB滑鼠驅動2. 編寫代碼3. 測試

1. 如何編寫USB滑鼠驅動

    結合十四、Linux驅動之USB驅動分析中的分析,我們開始寫一個USB滑鼠驅動。

     USB的驅動可以分為3類:SoC的USB控制器的驅動,主機端USB裝置的驅動,裝置上的USB Gadget驅動,通常,對于USB這種标準化的裝置,核心已經将主機控制器的驅動編寫好了,裝置上的Gadget驅動通常隻運作固件程式而不是基于Linux, 是以驅動工程師的主要工作就是編寫主機端的USB裝置驅動。也就是說在開發闆上接上一個USB滑鼠後,我們不必關系USB滑鼠是如何檢測事件如何發送資料,隻需要懂得在主機端(即對應開發闆)如何接收USB滑鼠發來的資料與知道接收到的資料的含義即可。

1.1 如何接收USB滑鼠發來的資料

    linux 核心中的USB代碼和所有的USB裝置通訊使用稱為USB請求塊(USB request block,urb)。URB是USB裝置驅動中用來描述與USB裝置通信所用的基本載體和核心資料結構,非常類似于網絡裝置驅動中的sk_buff結構體。

    URB資料結構如下:

struct urb {
/* 私有的:隻能由USB 核心和主機控制器通路的字段 */
struct kref kref; /*urb 引用計數 */
void *hcpriv; /* 主機控制器私有資料 */
atomic_t use_count; /* 并發傳輸計數 */
u8 reject; /* 傳輸将失敗*/
int unlink; /* unlink 錯誤碼 */
 /* 公共的: 可以被驅動使用的字段 */
 struct list_head urb_list; /* 連結清單頭*/
struct usb_anchor *anchor;
 struct usb_device *dev; /* 關聯的USB 裝置 */
 struct usb_host_endpoint *ep;
unsigned int pipe; /* 管道資訊 */
 int status; /* URB 的目前狀态 */
 unsigned int transfer_flags; /* URB_SHORT_NOT_OK | ...*/
 void *transfer_buffer; /* 發送資料到裝置或從裝置接收資料的緩沖區 */
 dma_addr_t transfer_dma; /*用來以DMA 方式向裝置傳輸資料的緩沖區 */
 int transfer_buffer_length;/*transfer_buffer 或transfer_dma 指向緩沖區的大小 */
 
 int actual_length; /* URB 結束後,發送或接收資料的實際長度 */
 unsigned char *setup_packet; /* 指向控制URB 的設定資料包的指針*/
 dma_addr_t setup_dma; /*控制URB 的設定資料包的DMA 緩沖區*/
 int start_frame; /*等時傳輸中用于設定或傳回初始幀*/
 int number_of_packets; /*等時傳輸中等時緩沖區數量 */
 int interval; /* URB 被輪詢到的時間間隔(對中斷和等時urb 有效) */
 int error_count; /* 等時傳輸錯誤數量 */
 void *context; /* completion 函數上下文 */
 usb_complete_t complete; /* 當URB 被完全傳輸或發生錯誤時,被調用 */
 /*單個URB 一次可定義多個等時傳輸時,描述各個等時傳輸 */
 struct usb_iso_packet_descriptor iso_frame_desc[0];
};
           

1.1.1 建立URB

建立URB結構體的函數為:

struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
           

參數:

    iso_packets:是這個urb 應當包含的等時資料包的數目,若為0 表示不建立等時資料包。

    mem_flags:參數是配置設定記憶體的标志,和kmalloc()函數的配置設定标志參數含義相同。如果配置設定成功,該函數傳回一個urb 結構體指針,否則傳回0。

    usb_alloc_urb()的“反函數”為:

void usb_free_urb(struct urb *urb);    登出urb
           

1.1.2 填充URB

    在十四、Linux驅動之USB驅動分析中介紹了USB資料有四種傳輸類型,填充URB函數(初始化特定USB裝置的特定端點)也分為4種:

(1) 中斷類型(USB滑鼠使用的就是該類型)

void usb_fill_int_urb(struct urb *urb, 
                      struct usb_device *dev,
                      unsigned int pipe, 
                      void *transfer_buffer,
                      int buffer_length,
                      usb_complete_t complete,
                      void *context, 
                      int interval);
           

參數:

    urb:指向要被初始化的urb的指針;

    dev:指向這個urb要被發送到的USB裝置;

    pipe:是這個urb要被發送到的USB裝置的特定端點;

    transfer_buffer:是指向發送資料或接收資料的緩沖區的指針,和urb一樣,它也不能是靜态緩沖區,必須使用kmalloc()來配置設定;

    buffer_length:是transfer_buffer指針所指向緩沖區的大小;

    complete:指針指向當這個urb完成時被調用的完成處理函數;

    context:是完成處理函數的“上下文”;

    interval:是這個urb應當被排程的間隔。

(2) 批量類型

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,
                       void *context);
           

    除了沒有對應于排程間隔的interval參數以外,該函數的參數和usb_fill_int_urb()函數的參數含義相同。上述函數參數中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函數來建立。

(3) 控制類型

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);
           

    除了增加了新的setup_packet參數以外,該函數的參數和usb_fill_bulk_urb()函數的參數含義相同。setup_packet參數指向即将被發送到端點的設定資料包。

(4) 等時類型

    針對等時型端點的urb,需要手動初始化。

1.1.3 送出URB

    在完成第上面兩步的建立和初始化urb後,urb便可以送出給USB核心,通過usb_submit_urb()函數來完成,函數原型如下:

int usb_submit_urb(struct urb *urb, int mem_flags);
           

參數:

    urb:是指向urb 的指針;

    mem_flags:與傳遞給kmalloc()函數參數的意義相同,它用于告知USB 核心如何在此時配置設定記憶體緩沖區。

    usb_submit_urb()在原子上下文和程序上下文中都可以被調用,mem_flags變量需根據調用環境進行相應的設定,如下所示:

    GFP_ATOMIC:在中斷處理函數、底半部、tasklet、定時器處理函數以及urb完成函數中,在調用者持有自旋鎖或者讀寫鎖時以及當驅動将current→state 修改為非 TASK_RUNNING 時,應使用此标志;

    GFP_NOIO:在儲存設備的塊I/O和錯誤處理路徑中,應使用此标志;

    GFP_KERNEL:如果沒有任何理由使用GFP_ATOMIC 和GFP_NOIO,就使用GFP_KERNEL。

    在送出urb到USB核心後,直到完成函數被調用之前,不要通路urb中的任何成員。如果usb_submit_urb()調用成功,即urb的控制權被移交給USB核心,該函數傳回0,否則傳回錯誤号。

1.1.4 URB處理完成

    完成上面的步驟,當USB滑鼠被按下,将會觸發usb_fill_int_urb()填充URB函數中第6個參數傳入的urb處理完成函數,在該函數裡面對資料處理,就能得到USB滑鼠的操作資料了。

1.2 接收到資料的含義

    接收到的資料存在usb_fill_int_urb()填充URB函數中的第4個參數傳入的緩沖區(使用kmalloc()來配置設定)中,滑鼠操作資料都存在該緩沖區内,緩沖區内資料含義如下:

    bit0表示滑鼠左鍵, 該位為1表示按下, 為0表示松開

    bit1表示滑鼠右鍵, 該位為1表示按下, 為0表示松開

    bit2表示滑鼠中鍵, 該位為1表示按下, 為0表示松開

1.3 使用輸入子系統進行上報事件

    在urb處理完成函數中使用輸入子系統将事件上報。

2. 編寫代碼

2.1 代碼架構

代碼的流程架構如下:

2.1.1 在入口函數中

    通過usb_register()函數注冊usb_driver結構體。

2.1.2 在usb_driver的probe函數中

    1. 配置設定一個input_dev結構體

    2. 設定input_dev支援L、S、回車、3個按鍵事件

    3. 注冊input_dev結構體

    4. 設定USB資料傳輸:

        4.1 通過usb_rcvintpipe()建立一個接收中斷類型的端點管道,用來端點和資料緩沖區之間的連接配接

        4.2 通過usb_buffer_alloc()申請USB緩沖區

        4.3 申請并初始化urb結構體,urb:用來傳輸資料

        4.4 因為我們2440支援DMA,是以要告訴urb結構體,使用DMA緩沖區位址

        4.5 使用usb_submit_urb()送出urb

2.1.3 在滑鼠中斷函數中

    1. 判斷緩存區資料是否改變,若改變則上傳滑鼠事件

    2. 使用usb_submit_urb()送出urb

2.1.4 在usb_driver的disconnect函數中

    1. 通過usb_kill_urb()殺掉送出到核心中的urb

    2. 釋放urb

    3. 釋放USB緩存區

    4. 登出input_device,釋放input_device

2.1.5 在出口函數中

    通過usb_deregister()函數登出usb_driver結構體。

2.2 編寫代碼

    USB滑鼠驅動程式(滑鼠按鍵模拟鍵盤按鍵l、s、Enter鍵)如下:

/*
 * 參考drivers\hid\usbhid\usbmouse.c
 */
#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>

static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;

static struct usb_device_id usbmouse_as_key_id_table [] = {
	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
		USB_INTERFACE_PROTOCOL_MOUSE) },
	{ }	/* Terminating entry */
};

static void usbmouse_as_key_irq(struct urb *urb)
{
	static unsigned char pre_val;

	/* USB滑鼠資料含義
	 * data[1]: bit0-左鍵, 1-按下, 0-松開
	 *          bit1-右鍵, 1-按下, 0-松開
	 *          bit2-中鍵, 1-按下, 0-松開 
	 *
     */
	if ((pre_val & (1<<0)) != (usb_buf[1] & (1<<0)))
	{
		/* 左鍵發生了變化,上報鍵值KEY_L */
		input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[1] & (1<<0)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<1)) != (usb_buf[1] & (1<<1)))
	{
		/* 右鍵發生了變化,上報鍵值KEY_S*/
		input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[1] & (1<<1)) ? 1 : 0);
		input_sync(uk_dev);
	}

	if ((pre_val & (1<<2)) != (usb_buf[1] & (1<<2)))
	{
		/* 中鍵發生了變化,上報鍵值KEY_ENTER */
		input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[1] & (1<<2)) ? 1 : 0);
		input_sync(uk_dev);
	}
	
	pre_val = usb_buf[1];

	/* 重新送出urb */
	usb_submit_urb(uk_urb, GFP_KERNEL);
}

static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	int pipe;
	
	interface = intf->cur_altsetting;
	endpoint = &interface->endpoint[0].desc;

	/* a. 配置設定一個input_dev */
	uk_dev = input_allocate_device();
	
	/* b. 設定 */
	/* b.1 能産生哪類事件 */
	set_bit(EV_KEY, uk_dev->evbit);
	set_bit(EV_REP, uk_dev->evbit);
	
	/* b.2 能産生哪些事件 */
	set_bit(KEY_L, uk_dev->keybit);
	set_bit(KEY_S, uk_dev->keybit);
	set_bit(KEY_ENTER, uk_dev->keybit);
	
	/* c. 注冊 */
	input_register_device(uk_dev);
	
	/* d. 硬體相關操作 */
	/* 資料傳輸3要素: 源,目的,長度 */
	/* 源: USB裝置的某個端點 */
	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

	/* 長度: */
	len = endpoint->wMaxPacketSize;

	/* 目的: */
	usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);

	/* 使用"3要素" */
	/* 配置設定usb request block */
	uk_urb = usb_alloc_urb(0, GFP_KERNEL);
	/* 使用"3要素設定urb" */
	usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
	uk_urb->transfer_dma = usb_buf_phys;
	uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* 使用URB */
	usb_submit_urb(uk_urb, GFP_KERNEL);
	
	return 0;
}

static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
	struct usb_device *dev = interface_to_usbdev(intf);

	//printk("disconnect usbmouse!\n");
	usb_kill_urb(uk_urb);
	usb_free_urb(uk_urb);

	usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
	input_unregister_device(uk_dev);
	input_free_device(uk_dev);
}

/* 1. 配置設定/設定usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
	.name		= "usbmouse_as_key",
	.probe		= usbmouse_as_key_probe,
	.disconnect	= usbmouse_as_key_disconnect,
	.id_table	= usbmouse_as_key_id_table,
};


static int usbmouse_as_key_init(void)
{
	/* 2. 注冊 */
	usb_register(&usbmouse_as_key_driver);
	return 0;
}

static void usbmouse_as_key_exit(void)
{
	usb_deregister(&usbmouse_as_key_driver);	
}

module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);

MODULE_LICENSE("GPL");

           

    Makefile代碼如下:

KERN_DIR = /work/system/linux-2.6.22.6    //核心目錄

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= usbmouse_as_key.o
           

3. 測試

核心:linux-2.6.22.6

編譯器:arm-linux-gcc-3.4.5

環境:ubuntu9.10

3.1 配置核心

    1. 重新配置核心,去掉核心自帶的usbmouse滑鼠驅動。在linux-2.6.22.6核心目錄下執行:

      make menuconfig

    2. 配置步驟如下:

    Device Drivers  --->

        HID Devices  --->

            < > USB Human Interface Device (full HID) support

3.2 重燒核心

    1. 編譯核心與子產品

      make uImage

    2. 将linux-2.6.22.6/arch/arm/boot下的uImage燒寫到開發闆,網絡檔案系統啟動。

3.3 測試

    首先編譯自己寫的USB滑鼠驅動。在驅動檔案目錄下執行:

      make

    在開發闆上執行:

      insmod usbmouse_as_key.ko:

方法1:

      cat /dev/tty1 (此時按下滑鼠左鍵、右鍵、中鍵,序列槽就會有顯示“ls”了)

十五、Linux驅動之USB滑鼠驅動1. 如何編寫USB滑鼠驅動2. 編寫代碼3. 測試

方法2:

      hexdump /dev/event0    (/dev/event0為對應的usb驅動)

十五、Linux驅動之USB滑鼠驅動1. 如何編寫USB滑鼠驅動2. 編寫代碼3. 測試

繼續閱讀