linux 按鍵驅動代碼分析

原文地址:http://blog.csdn.NET/woshidahuaidan2011/article/details/51695147

二、按鍵驅動

1、對按鍵驅動添加設備信息

linux-3.14.28對按鍵的驅動定義在Gpio_keys.c (drivers\input\keyboard)      文件中,在led驅動分析中,我們知道,只有平臺設備和平臺驅動兩者的name名字一致纔可以註冊成功一個驅動。這裏,內核代碼中沒有對按鍵平臺信息的定義,因此我們需要給他補充完整。

首先將按鍵驅動編譯到內核:

Device Drivers >

Input device support >

[*] Keyboards

<*> GPIO Buttons

 

爲了簡單起見,我們就在Common-smdk.c(arch\arm\mach-s3c24xx)下定義設備信息,這裏可以仿照Mach-mini2440.c(arch\arm\mach-s3c24xx)       文件中對按鍵平臺設備信息的定義,其中struct gpio_keys_button定義在Gpio_keys.h (include\Linux)文件中,其定義爲:

struct gpio_keys_button {

      /* Configurationparameters */

 

      unsigned int code;      /* input event code (KEY_*, SW_*) */

/*************************************************************************************

按鍵對應的編碼,其定義在Input.h(arch\arm\boot\dts\include\dt-bindings\input)中,這裏只是告訴內核本按鍵的編碼對應關係,可以根據Input.h的設置自己選擇編碼。

*******************************************************************************/

 

      int gpio;        /* -1 if this key does not support gpio*/

/*************************************************************************************

設置按鍵對應引腳,linux內核給每個gpio引腳設置了編號,每個編號對應一個引腳。

*******************************************************************************/

 

      int active_low;

/*************************************************************************************

設置按鍵低電平(低電平有效)

*******************************************************************************/

      const char *desc;

/*************************************************************************************

給按鍵起個名字,相當於led4 led5 led6

*******************************************************************************/

      unsigned int type;       /* input event type (EV_KEY, EV_SW,EV_ABS) */

/*************************************************************************************

給設備設置按鍵類型,各種類型定義在Input.h (include\uapi\linux)文件中,主要有

EV_SYN 0x00 同步事件 EV_KEY0x01按鍵事件 EV_REL 0x02相對座標 EV_ABS 0x03絕對座標 
EV_MSC 0x04
其它      EV_LED 0x11  LED    EV_SND 0x12聲音     EV_REP 0x14 Repeat 
EV_FF 0x15
力反饋 。。。。。。。

默認類型是EV_KEY(Documentation\devicetree\bindings\gpio \gpio_keys.txt)

*******************************************************************************/

      int wakeup;         /* configure the button as a wake-upsource */

/*************************************************************************************

設置是否設置爲喚醒源

*******************************************************************************/

      int debounce_interval;       /* debounce ticks interval in msecs */

/*************************************************************************************

設置消抖間隔時間,單位毫秒,默認爲5毫秒(Documentation\devicetree\bindings\gpio\gpio_keys.txt)

*******************************************************************************/

 

      bool can_disable;//設置是否共享中斷

      int value;             /* axis value for EV_ABS */

/*************************************************************************************

絕對座標值

*******************************************************************************/

 

      unsigned int irq;  /* Irq number in case of interrupt keys */

/*************************************************************************************

irq中斷號的設定

*******************************************************************************/

 

};

結構體介紹完畢,那麼就仿照Mach-mini2440.c在Common-smdk.c定義設備信息如下:

 

#include <linux/gpio_keys.h>

#include <linux/input.h>

 

static struct gpio_keys_button  JZ2440_buttons[] = {

      {

             .gpio             = S3C2410_GPF(0),         /*對應引腳編號,根據實際定義*/

             .code             = KEY_F1,              /*設置按鍵支持的類型,對應按鍵編號 */

             .desc             = "Button 1",             /*按鍵的名字隨便起*/

             .active_low   = 1,                     /*按下低電平*/

      },

      {

             .gpio             = S3C2410_GPF(2),         /* K2 */

             .code             = KEY_F2,

             .desc             = "Button 2",

             .active_low   = 1,

      },

      {

             .gpio             = S3C2410_GPG(3),        /* K3 */

             .code             = KEY_F3,

             .desc             = "Button 3",

             .active_low   = 1,

      },

};

有關按鍵的GPIO信息已經設置完了,接下來再來設置一下按鍵的設備平臺數據

static struct gpio_keys_platform_data JZ2440_button_data= {

      .buttons  = JZ2440_buttons,    /*對應按鍵的定義的gpio信息 */

      .nbuttons       = ARRAY_SIZE(JZ2440_buttons), /*獲取定義了幾個按鍵 */

};

接下來就可以定義平臺設備了:

static struct platform_device JZ2440_button_device= {

      .name            = "gpio-keys",    /*設備的名字,用來與驅動匹配 */        

.id        = -1,

      .dev       = {

             .platform_data     = & JZ2440_button_data,

      }

};

然後把定義的平臺數據放置到平臺設備數組裏面:

static struct platform_device __initdata *smdk_devs[] = {

      &s3c_device_nand,

      &smdk_led4,

      &smdk_led5,

      &smdk_led6,

      &smdk_led7,

    &JZ2440_button_device  //按鍵設備

};

      然後平臺設備信息就設置完畢,

 

 

2、  對按鍵驅動的測試

 

通過查看按鍵設備信息可以看到添加的按鍵驅動信息,

在/proc/bus/input目錄下執行 cat  devices可以看到:

/proc/bus/input # cat devices

I: Bus=0019 Vendor=0001 Product=0001 Version=0100

N: Name="gpio-keys"

P: Phys=gpio-keys/input0

S: Sysfs=/devices/platform/gpio-keys/input/input0

U: Uniq=

H: Handlers=kbd event0

B: PROP=0

B: EV=3

B: KEY=38000000 0

這裏列出了按鍵驅動的詳細信息,第一個I是

struct input_id {

      __u16 bustype;

      __u16 vendor;

      __u16 product;

      __u16 version;

}列出的信息

下面有名字,設備文件存在的路徑,等等信息。

可以通過按鍵動作進行測試。

當有按鍵按下時,我們可以通過讀取dev/input/event0文件來查看上報事件,此時我們以16進制查看類型讀取顯示該文件,使用hexdump命令:

/ # hexdump  /dev/input/event0

0000000 010b 0000 7714 0006 0001 003d 0001 0000

0000010 010b 0000 7714 0006 0000 0000 0000 0000

0000020 010b 0000 bf74 0008 0001 003d 0000 0000

0000030 010b 0000 bf74 0008 0000 0000 0000 0000

 

0000040 010c 0000 d29c 000a 0001 003c 0001 0000

0000050 010c 0000 d29c 000a 0000 0000 0000 0000

0000060 010c 0000 d975 000c 0001 003c 0000 0000

0000070 010c 0000 d975 000c 0000 0000 0000 0000

 

0000080 010d 0000 20c1 000b 0001 003b 0001 0000

0000090 010d 0000 20c1 000b 0000 0000 0000 0000

00000a0 010d 0000 e878 000d 0001 003b 0000 0000

00000b0 010d 0000 e878 000d 0000 0000 0000 0000

本代碼設置三個按鍵,當我按下三個按鍵的時候,會上報三組數據,就以第一組數據爲例,當我按下按鍵的時候(保持按下這個動作,不鬆開按鍵),會打印出兩組數據:

0000000 010b 0000 7714 0006 0001 003d 0001 0000

0000010 010b 0000 7714 0006 0000 0000 0000 0000

 

其中0001(倒數第4列)代表事件類型,因爲類型爲EV_KEY所以爲1

其中003d(倒數第3列)代表按鍵的編碼 設置按鍵功能爲 f1 f2 f3 則編碼爲0x3b 0x3c0x3d

其中0001(倒數第2列)代表按鍵值,當按鍵按下的時候按鍵值爲1

 

下面寫c代碼來測試按鍵程序:

/*key.c */

#include<stdint.h>

#include<string.h>

#include<fcntl.h>

#include<unistd.h>

#include<stdio.h>

#include<linux/input.h>

 

 

int main(void)

{

  int fd;

   int flag=0;

 struct input_event ev_key;

  fd=open("/dev/input/event0", O_RDWR); //讀寫方式打開

  if(fd < 0)

    {

        perror("open device buttons");

          close(fd);

        return -1;

    }

  while(1)

    {

     read(fd,&ev_key,sizeof(struct input_event));

     if(flag!=ev_key.type)  //這裏是判斷是否有上報按鍵事件的發生。

   printf("type:%d,code:%d,value:%d,sec:%d,nsec:%d\n",ev_key.type,ev_key.code,ev_key.value,ev_key.time.tv_sec,ev_key.time.tv_usec);

 

      flag=ev_key.type;

 

    }

 close(fd);

 return 0;

}

 

對應的Makefile文件內容爲:

CC=arm-linux-2440-gcc

key:key.c

        ${CC} -o $@  $<

        cp key  /work/root/work/   #這裏是複製的路徑。nfs文件系統的路徑(因人而異)

clean:

        rm -rf key

 

 

 

這裏的C語言邏輯比較簡單,就是讀取其設備文件,read的返回值是一個

struct input_event {

      struct timeval time;

      __u16 type;

      __u16 code;

      __s32 value;

};

其中struct timeval time是系統開機時間,其定義爲:

struct timeval {

      __kernel_time_t          tv_sec;          /* seconds */

      __kernel_suseconds_t       tv_usec; /*microseconds */

};

可以看出來,這裏是按鍵按下時候的系統時間(第一個參數單位是秒,第二個參數單位是微秒)後面的參數爲事件驅動類型(對應的是ev_key)後面兩個是按鍵的編碼和按鍵的值(這裏的按鍵值是鬆開是0或者按鍵按鍵其值爲1)。

 

3、  對按鍵驅動分析

 

分析按鍵驅動之前,我覺得有必要先學習一下有關輸入系統的介紹:

Input子系統分爲三層,從下至上分別是設備驅動層,輸入核心層以及事件處理層,即inputdevice Driver -> InputCore -> Eventhandler -> userspace。輸入設備主要的工作過程都是動作產生(按鍵,觸屏……)-->產生中斷-->讀取數值(鍵值,座標……)-->將數值傳遞給應用程序。一個大致的工作流程就是,input device向上層報告-->input core接收報告,並根據在註冊inputdevice時建立好的連接選擇哪一類handler來處理事件-->通過handler將數據存放在相應的dev(evdev,mousedev…)實例的緩衝區中,等待應用程序來讀取。這三層中的輸入核心層和事件處理層都是內核已經完成了的,因此需要我們完成的只有設備驅動層。

(上段來至博客:http://m.blog.csdn.net/blog/jin615567975/37922023

下面網上有張圖來解釋輸入子系統的框架:


(圖片來源:http://www.embedu.org/column/column289.htm

通過上面的圖可以看出來,input有像按鍵等的輸入設備event;觸摸屏等的輸入設備ts;遙控杆等輸入設備js;鼠標等輸入設備mouse和鍵盤等不會在/dev/input下產生節點,而是作爲ttyn終端(不包括串口終端)的輸入。

通過上面的介紹,結合具體的函數或結構體來解釋就是設備驅動層爲具體用戶設備驅動,輸入設備由struct input-dev 結構表示,並由input_register_device和input_unregister_device來註冊和卸載;input hander事件處理層主要和用戶空間交互,接收用戶空間下發的file operation操作命令,生成/dev/input/xx設備節點供用戶空間進行file operations操作; input core層負責管理系統中的input dev設備 和input hander事件處理,並起到承上啓下作用,負責輸入設備和input handler之間信息傳輸,框架圖如下:


(上段來自博客:http://blog.csdn.net/fanqipin/article/details/8019512

 

大概介紹到這裏,下面來分析代碼:

按鍵的驅動定義在Gpio_keys.c(drivers\input\keyboard)文件中,老規矩首先看到這個宏:

late_initcall(gpio_keys_init);

module_exit(gpio_keys_exit);

這裏有個宏late_initcall,這個宏的跟之前遇見的subsys_initcall的功能是一樣的,只不過其優先級不同,具體的他們全部定義在Init.h (include\linux)中:

#define early_initcall(fn)         __define_initcall(fn,early)

#define pure_initcall(fn)          __define_initcall(fn,0)

 

#define core_initcall(fn)          __define_initcall(fn,1)

#define core_initcall_sync(fn)               __define_initcall(fn,1s)

#define postcore_initcall(fn)           __define_initcall(fn,2)

#define postcore_initcall_sync(fn)        __define_initcall(fn,2s)

#define arch_initcall(fn)          __define_initcall(fn,3)

#define arch_initcall_sync(fn)               __define_initcall(fn,3s)

#define subsys_initcall(fn)             __define_initcall(fn, 4)

#define subsys_initcall_sync(fn)    __define_initcall(fn,4s)

#define fs_initcall(fn)                     __define_initcall(fn,5)

#define fs_initcall_sync(fn)            __define_initcall(fn,5s)

#define rootfs_initcall(fn)               __define_initcall(fn,rootfs)

#define device_initcall(fn)              __define_initcall(fn, 6)

#define device_initcall_sync(fn)    __define_initcall(fn,6s)

#define late_initcall(fn)           __define_initcall(fn, 7)

#define late_initcall_sync(fn)         __define_initcall(fn,7s)

#define module_init(x)     __initcall(x);

#define __initcall(fn) device_initcall(fn)

通過上面的宏可以看出他們的後面的參數不同,係數越小優先級越大,可以看出:

優先級由大到小爲:subsys_initcall>module_init > late_initcall

接下來就來看gpio_keys_init 和gpio_keys_exit:

static int __init gpio_keys_init(void)

{

      returnplatform_driver_register(&gpio_keys_device_driver);

}

 

static void __exit gpio_keys_exit(void)

{

      platform_driver_unregister(&gpio_keys_device_driver);

}

他們分別調用了平臺驅動註冊和註銷函數,先看平臺驅動註冊函數的參數結構體:

static struct platform_driver gpio_keys_device_driver = {

      .probe           = gpio_keys_probe,

      .remove        = gpio_keys_remove,

      .driver           = {

             .name     = "gpio-keys",

             .owner   = THIS_MODULE,

             .pm = &gpio_keys_pm_ops,

             .of_match_table =of_match_ptr(gpio_keys_of_match),

      }

};

從這個結構體看,跟led的格式就一樣的,顯示比較name是否都是"gpio-keys"平臺設備跟平臺驅動的名字比較搭配的話,在註冊的時候就會去執行gpio_keys_probe函數,在註銷的時候就會執行gpio_keys_remove的函數,往下看就是對應驅動的名字了;然後gpio_keys_pm_ops,這個由宏

static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend,gpio_keys_resume);

生成的,該宏定義在Pm.h (include\linux)       ,根據該宏的定義,最終會調用gpio_keys_remove和gpio_keys_suspend函數控制電源的進入工作狀態或者低功耗狀態。下一個定義了設備和驅動匹配函數,匹配方法可以用兩個字方法:如果driver中定義了of_device_id,則通過driver中的of_device_id和device中的device_node內容進行匹配判斷,匹配工作由of_match_node來完成,該函數會遍歷of_device_id列表,查找是否有成員與device_node相匹配,具體由matches的name,type和compatioble來進行對比,如果找到則返回相應的表項,否則返回null.如果沒有定義of_device_id,device_node或不能找到對應的匹配項,則通過第二種方式platform_device_id來進行對比匹配,通過platform_match_id來完成。

接下來詳細說明上面提到的幾個函數。

先看探測函數gpio_keys_probe:

static int gpio_keys_probe(struct platform_device *pdev)

{

      struct device *dev =&pdev->dev;

      const structgpio_keys_platform_data *pdata = dev_get_platdata(dev);

/*************************************************************************************

獲取平臺設備信息,也就是得到JZ2440_button_data的數據信息。

*******************************************************************************/

      struct gpio_keys_drvdata*ddata;

/*************************************************************************************

structgpio_keys_drvdata這個是驅動信息的數據結構體,這個結構體的定義是解釋按鍵驅動的關鍵一步,看一下這個結構體的定義:

structgpio_keys_drvdata {

      const struct gpio_keys_platform_data*pdata;

      struct input_dev *input;

      struct mutex disable_lock;

      struct gpio_button_data data[0];

};

gpio_keys_platform_data這個就是在Common-smdk.c (arch\arm\mach-s3c24xx)定義的平臺信息,包括引腳等的信息;struct input_dev *input;這裏分配了一個輸入設備;struct mutex disable_lock分配一個互斥鎖;struct gpio_button_data data[0];分配一個按鍵數據結構體。

*******************************************************************************/

 

      struct input_dev *input;

/*************************************************************************************

structinput_dev *input;這裏定義了一個輸入設備,一個輸入設備就是使用input_dev來表示的,在Input.h (include\linux)       文件中定義了input_dev結構體,其具體含義爲(有背景色的爲本次按鍵定義的)

structinput_dev {
        constchar *name;                       //
提供給用戶的輸入設備的名稱
        constchar *phys;                       //
提供給編程者的設備節點的路徑及其名稱
        const char *uniq;                       //
指定唯一的ID號,就像MAC地址一樣
        structinput_id id;                       //
輸入設備標識ID,用於和事件處理層進行匹配。
     

unsignedlong propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; //位圖,設備屬性

unsignedlong evbit[BITS_TO_LONGS(EV_CNT)]; //位圖,記錄設備支持的事件類型
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   //
位圖,記錄設備支持的按鍵類型

unsignedlong relbit[BITS_TO_LONGS(REL_CNT)]; //位圖,記錄設備支持的相對座標

unsignedlong absbit[BITS_TO_LONGS(ABS_CNT)];   //位圖,記錄設備支持的絕對座標

unsignedlong mscbit[BITS_TO_LONGS(MSC_CNT)]; //位圖,記錄設備支持的其他功能

unsignedlong ledbit[BITS_TO_LONGS(LED_CNT)]; //位圖,記錄設備支持的指示燈

unsignedlong sndbit[BITS_TO_LONGS(SND_CNT)]; //位圖,記錄設備支持的聲音或警報

unsignedlong ffbit[BITS_TO_LONGS(FF_CNT)]; //位圖,記錄設備支持的作用力功能

unsignedlong swbit[BITS_TO_LONGS(SW_CNT)];   //位圖,記錄設備支持的開關功能

 

unsignedint hint_events_per_packet;     //每個數據包記錄數據量的個數(上報一次輸入事件在EV_SYN/SYN_REPORT之間的數據的個數)

unsignedint keycodemax;                //設備支持的最大按鍵值個數
        unsigned int keycodesize;               //
每個按鍵的字節大小
        void *keycode;                               //
指向按鍵池,即指向按鍵值數組首地址
        int (*setkeycode)(struct input_dev *dev, intscancode, int keycode);        //
修改按鍵值
        int (*getkeycode)(struct input_dev *dev, int scancode,int *keycode);        //
獲取按鍵值

        struct ff_device *ff;                       //
假如設備支持力反饋,這裏與裏反饋結構相關的東西

        unsigned int repeat_key;               //
支持重複按鍵
        struct timer_list timer;               //
設置當有連擊時的延時定時器

intrep[REP_CNT]; //記錄重複按鍵的參數值比如延時時間和速率

structinput_mt *mt; //指向多觸點操作狀態

 

structinput_absinfo *absinfo;//對於絕對座標而言,該指針指向的是桌表包括當前座標值,最大值最小值,濾波毛刺,分辨率等,具體的可以查看結構體input_absinfo

 

unsignedlong key[BITS_TO_LONGS(KEY_CNT)];位圖,按鍵的狀態

unsignedlong led[BITS_TO_LONGS(LED_CNT)];位圖,led的狀態

unsignedlong snd[BITS_TO_LONGS(SND_CNT)];位圖,聲音的狀態

unsignedlong sw[BITS_TO_LONGS(SW_CNT)];位圖,開關的狀態
        int (*open)(struct input_dev *dev);                       //
輸入設備打開函數
        void(*close)(struct input_dev *dev);                       //
輸入設備關閉函數
        int (*flush)(struct input_dev *dev, struct file*file);        //
輸入設備斷開後清除刷新函數
        int (*event)(struct input_dev *dev, unsigned inttype, unsigned int code, int value);        //
事件處理

        structinput_handle __rcu *grab;

spinlock_t event_lock; //當輸入核處理輸入時間時候的自旋鎖

structmutex mutex; //用於opencloseflush函數的連續訪問互斥

unsignedint users;// input handlers打開設備的次數

boolgoing_away; //輸入設備註銷的時候的標誌位

 

struct device dev; //輸入設備的類信息

 

structlist_head       h_list; //handle鏈表

structlist_head       node; //input_dev鏈表

 

unsignedint num_vals;//當前隊列的排隊數

unsignedint max_vals;//允許最大的排隊個數

structinput_value *vals;//當前隊列排隊的存儲數組

 

booldevres_managed;//表明設備正在受到驅動管理,不需要註銷或者釋放

    


     };

 

 

*******************************************************************************/

 

      int i, error;

      int wakeup = 0;

 

      if (!pdata) {

             pdata =gpio_keys_get_devtree_pdata(dev);

             if (IS_ERR(pdata))

                    returnPTR_ERR(pdata);

      }

/*************************************************************************************

假如conststruct gpio_keys_platform_data *pdata = dev_get_platdata(dev)沒有獲取平臺信息成功,就用設備樹獲取。

*******************************************************************************/

 

      ddata =kzalloc(sizeof(struct gpio_keys_drvdata) +

                    pdata->nbuttons* sizeof(struct gpio_button_data),

                    GFP_KERNEL);

/*************************************************************************************

這裏是爲pdata申請一塊內存,平且將其內容清零。kzalloc實際調用的是kmalloc函數。kmalloc函數返回的是虛擬地址,這裏要注意kmalloc(釋放內存爲kfreevmalloc(釋放內存爲vfree)的區別:kmalloc最大隻能開闢128k-16字節,其分配的內存在屋裏地址上是線序的,vmalloc是申請的可能是非連續的地址,具體的可參考:

http://blog.chinaunix.net/uid-20671208-id-3522841.html

*******************************************************************************/

 

      input = input_allocate_device();

/*************************************************************************************

返回值是input_dev結構體,到這裏是申請爲輸入設備。

*******************************************************************************/

 

      if (!ddata || !input) {

             dev_err(dev,"failed to allocate state\n");

             error = -ENOMEM;

             goto fail1;

      }

/*************************************************************************************

判斷是否申請成功,假如不成功的話就打印出失敗語句,並且gotofail1,執行input_free_device(input) kfree(ddata) kfree(pdata)把申請的資源或者得到的數據釋放掉。

*******************************************************************************/

 

      ddata->pdata = pdata;

      ddata->input = input;

/*************************************************************************************

ddata相應的成員賦值

**************************************************************************/

 

      mutex_init(&ddata->disable_lock);

/*************************************************************************************

這是在爲ddata初始化互斥鎖(互斥體),mutex的使用場合跟信號量基本相同,一般用戶那些進程之間競爭,且佔用時間較長的場合,當佔用時間較短是,一般使用互旋鎖。

對於linux鎖機制部分可參考http://blog.csdn.Net/cyxlxp8411/article/details/8068224

**************************************************************************/

 

      platform_set_drvdata(pdev,ddata);

/*************************************************************************************

將驅動數據保存到驅動平臺數據中,後期將會使用保存的函數。

**************************************************************************/

 

      input_set_drvdata(input,ddata);

/*************************************************************************************

將驅動數據保存到輸入設備中中,後期將會使用保存的函數。

**************************************************************************/

 

      input->name =pdata->name ? : pdev->name;

input->phys ="gpio-keys/input0";

      input->dev.parent =&pdev->dev;

      input->open =gpio_keys_open;//在掛起或者喚醒的時候會調用openclose函數

      input->close =gpio_keys_close;

 

      input->id.bustype =BUS_HOST;

      input->id.vendor =0x0001;

      input->id.product =0x0001;

      input->id.version =0x0100;

/*************************************************************************************

這裏是在給input賦值,這裏主要設備了輸入的名字爲gpio-keys;設備節點及其路徑,驅動父類;然後設置了openclose函數;最後設置了id

**************************************************************************/

 

 

      /* Enable auto repeatfeature of Linux input subsystem */

      if (pdata->rep)

             __set_bit(EV_REP,input->evbit);

/*************************************************************************************

給按鍵設置可重複多次按下的特性。

**************************************************************************/

 

      for (i = 0; i <pdata->nbuttons; i++) {

             const structgpio_keys_button *button = &pdata->buttons[i];

             structgpio_button_data *bdata = &ddata->data[i];

 

             error = gpio_keys_setup_key(pdev,input, bdata, button);

             if (error)

                    goto fail2;

 

             if(button->wakeup)

                    wakeup = 1;

      }

/*************************************************************************************

這個循環獲取每個按鍵的信息,並通過gpio_keys_setup_key函數爲每個按鍵初始化引腳,濾波消抖,申請外部中斷,申請定時器中斷平且設定中斷定時器服務函數。gpio_keys_setup_key該函數將會在下面單獨介紹。假如創建失敗會調用gpio_remove_key(&ddata->data[i]);釋放掉申請的引腳,取消申請的隊列等等

 

**************************************************************************/

 

error = sysfs_create_group(&pdev->dev.kobj,&gpio_keys_attr_group);

      if (error) {

             dev_err(dev,"Unable to export keys/switches, error: %d\n",

                    error);

             goto fail2;

      }

/*************************************************************************************

sysfs_create_group()kobj目錄下創建一個屬性集合,並顯示集合中的屬性文件。如果文件已存在,會報錯。以函數爲例,這裏將會在gpio-keys/目錄下創建一個屬性文件gpio_keys_attr_groupgpio_keys_attr_group最終會調用:

staticstruct attribute *gpio_keys_attrs[] = {

      &dev_attr_keys.attr,

      &dev_attr_switches.attr,

      &dev_attr_disabled_keys.attr,

      &dev_attr_disabled_switches.attr,

      NULL,

};

也就是會生成:

/sys/devices/platform/gpio-keys/disabled_keys[rw]

/sys/devices/platform/gpio-keys/disables_switches[rw]

/sys/devices/platform/gpio-keys/keys[ro]

/sys/devices/platform/gpio-keys/switches[ro]

假如創建失敗假如創建失敗會調用gpio_remove_key(&ddata->data[i]);釋放掉申請的引腳,取消申請的隊列等等

**************************************************************************/

 

      error =input_register_device(input);

      if (error) {

             dev_err(dev,"Unable to register input device, error: %d\n",

                    error);

             goto fail3;

      }

/*************************************************************************************

這是是真正的向內核註冊一個輸入設備。該函數將input_dev結構體註冊到輸入子系統核心中,input_dev由前面介紹的input_allocate_device()函數來分配。input_register_device()函數如果註冊失敗,必須調用input_free_device()函數釋放分配的空間。假如函數註冊成功,調用input_unregister_device()函數來註銷輸入設備結構體。 input_register_device()函數中的dev_set_name設置input_dev中的device的名字,名字以input0input1input2·······等的形式出現在sysfs文件系統中。並且input_register_device()函數中list_add_tail()函數將input_dev加入input_dev_list鏈表中,的input_dev_list鏈表中包含了系統中所有的input_dev設備。

假如註冊失敗會調用sysfs_remove_group(&pdev->dev.kobj,&gpio_keys_attr_group);來刪除之前創建的屬性文件。

**************************************************************************/

 

      device_init_wakeup(&pdev->dev,wakeup);

/*************************************************************************************

這函數跟電源管理有關,在Pm_wakeup.h (include\linux)文件中:

staticinline int device_init_wakeup(struct device *dev, bool val)
{

   device_set_wakeup_capable(dev, val);  //設置設備能不能被喚醒
    device_set_wakeup_enable(dev, val);     //
設置設備使不使用喚醒;
    return 0;

}

 

**************************************************************************/

 

 

      return 0;

 

 fail3:

      sysfs_remove_group(&pdev->dev.kobj,&gpio_keys_attr_group);

 fail2:

      while (--i >= 0)

             gpio_remove_key(&ddata->data[i]);

 

 fail1:

      input_free_device(input);

      kfree(ddata);

      /* If we have no platformdata, we allocated pdata dynamically. */

      if(!dev_get_platdata(&pdev->dev))

             kfree(pdata);

 

      return error;

}

probe函數做完了整個驅動要做的事情,現在總結一下probe都做了些什麼事:

首先獲取平臺設備信息:dev_get_platdata(dev)或者gpio_keys_get_devtree_pdata(dev)

爲定義的設備信息申請一塊內存:kzalloc

申請分配一個輸入設備: input_allocate_device

爲該輸入設備設置屬性:input->······

初始化按鍵相關的引腳、中斷、及其有關的定時器信息:gpio_keys_setup_key

爲該設備創建一個屬性集合:sysfs_create_group

  正式申請爲輸入設備:input_register_device

剛纔有看到在probe函數中,調用了好多自定義的一些函數,接下來逐個分析一下這些函數的實現。

先看一下設置按鍵的函數gpio_keys_setup_key:

static int gpio_keys_setup_key(struct platform_device *pdev,

                           structinput_dev *input,

                           structgpio_button_data *bdata,

                           conststruct gpio_keys_button *button)

{

      const char *desc =button->desc ? button->desc : "gpio_keys";

      struct device *dev =&pdev->dev;

      irq_handler_t  isr;

      unsigned long   irqflags;

      int irq, error;

/*************************************************************************************

constchar *desc = button->desc ? button->desc : "gpio_keys";這裏是獲取按鍵的描述的名字個設備信息應該爲Button 1Button 2Button 3,假如獲取不到其描述符就是gpio_keys

並且定義了中斷申請需要的變量。

irq_handler_t定義在Interrupt.h (include\linux)      文件中:

typedefirqreturn_t (*irq_handler_t)(int, void *);

其中爲irqreturn_t定義在Irqreturn.h (include\linux)文件中

/**

 * enum irqreturn

 * @IRQ_NONE            interruptwas not from this device

 * @IRQ_HANDLED          interruptwas handled by this device

 * @IRQ_WAKE_THREAD handler requests to wake the handler thread

 */

enumirqreturn {

      IRQ_NONE        =(0 << 0),

      IRQ_HANDLED             = (1 << 0),

      IRQ_WAKE_THREAD          = (1 << 1),

};

 

typedefenum irqreturn irqreturn_t;

也就是說返回不同的值代表的含義:

IRQ_NONE ,還沒有發生中斷

IRQ_HANDLED     中斷已經處理完畢

IRQ_WAKE_THREAD中斷需要喚醒處理線程

 

**************************************************************************/

 

      bdata->input = input;

      bdata->button =button;

/*************************************************************************************

gpio_button_data::bdata的參數賦值

**************************************************************************/

 

      spin_lock_init(&bdata->lock);

/*************************************************************************************

初始化自旋鎖,自旋鎖適用於臨界區不是很大的情況(臨界區的執行時間比較短)

總結自旋鎖使用流程:

1、首先定義一個自旋鎖:

spinlock_t lock

2、初始化自旋鎖:

   spin_ lock_init(lock)

3、獲取自旋鎖

  (1)  spin_trylock(lock)//假如獲得鎖返回真,否則返回假,返回假貨不會原地打轉的等着獲得鎖

  (2)  spin_lock(lock)//假如獲得鎖返回真,否則返回假,返回假貨將會原地打轉的等着獲得鎖

##############################################################

假如獲得會執行臨界區(要鎖存的區域)的操作

。。。。。。。。。

###############################################################

4、釋放掉自旋鎖

   spin_unlock(lock);

**************************************************************************/

 

      if(gpio_is_valid(button->gpio)) {

 

             error =gpio_request_one(button->gpio, GPIOF_IN, desc);  //申請一個pgio,配置爲輸入

             if (error < 0){

                    dev_err(dev,"Failed to request GPIO %d, error %d\n",

                           button->gpio,error);

                    returnerror;

             }

 

             if(button->debounce_interval) {

                    error =gpio_set_debounce(button->gpio,

                                  button->debounce_interval* 1000); //按鍵消抖處理

                    /* usetimer if gpiolib doesn't provide debounce */

                    if (error< 0)

                           bdata->timer_debounce=

                                         button->debounce_interval;

             }

 

             irq =gpio_to_irq(button->gpio);//申請GPIO中斷,正確返回中斷編號,錯誤返回錯誤編碼(負)

             if (irq < 0) {

                    error =irq;

                    dev_err(dev,

                           "Unableto get irq number for GPIO %d, error %d\n",

                           button->gpio,error);

                    goto fail;

             }

             bdata->irq =irq;//賦值中斷號

 

             INIT_WORK(&bdata->work,gpio_keys_gpio_work_func);

/*************************************************************************************

將函數gpio_keys_gpio_work_func假如工作隊列,讓中斷的第二階段去執行該函數。實際上&bdata->work是一個描述工作隊列的結構體,變量定義爲struct work_struct work,該結構體定義在Workqueue.h (include\linux)文件中:

structwork_struct {

      atomic_long_t data;

      struct list_head entry;

      work_func_t func;

#ifdefCONFIG_LOCKDEP

      struct lockdep_map lockdep_map;

#endif

};

work_struct結構體中的work_func_t func指向要假如中斷隊列的函數,這樣的話函數gpio_keys_gpio_work_func就和&bdata->work綁定在一起了,一般情況下,gpio_keys_gpio_work_func會在中斷下半部執行,所以在中斷服務函數中會調用schedule_work(&bdata->work)來執行該函數。

最後在卸載的時候會調用cancel_work_sync(&bdata->work);來取消加進去的工作隊列。

總結工作隊列過程(注:這裏是使用內核線程,並不是自定義一個線程,假如需要創建隊列可參考:

http://blog.chinaunix.net/uid-24148050-id-296982.html)

1、首先定義一個工作結構體:

struct work_struct work

2將定義的工作加入內核工作隊列並且綁定放入隊列的函數:

    INIT_WORK(work, work_func);

或者

INIT_DELAYED_WORK(work, work_func)

3、當需要執行工作函數的時:

   (1)對應的INIT_WORK

執行schedule_work(work)會馬上調用work_func函數

      

   (2) 對應的NIT_DELAYED_WORK

      執行schedule_delayed_worktime,work)會在time時間後執行work_func

##############################################################

執行函數work_func的操作

。。。。。。。。。

###############################################################

4、取消工作隊列中的工作:

     (1)對應的INIT_WORK

cancel_work_sync(work)

 

      

   (2) 對應的NIT_DELAYED_WORK

     cancel_delayed_work_sync//取消延時工作並且等待其完成工作

cancel_delayed_work(work)//取消延時工作

     

http://blog.csdn.net/bingqingsuimeng/article/details/7891157

***********************************************************************/

 

             setup_timer(&bdata->timer,

                        gpio_keys_gpio_timer, (unsignedlong)bdata);

/*************************************************************************************

由於按鍵消抖需要定時器,這是是初始化定時器,首先看一下&bdata->timer,其類型struct timer_list定義在Timer.h (include\linux)  文件中:

 

structtimer_list {

      /*

       *All fields that change during normal runtime grouped to the

       *same cacheline

       */

      struct list_head entry;  //雙鏈表,用來將多個定時器連接成一條雙向循環隊列。

 

      unsigned long expires; //定時時間

structtvec_base *base;  // 這個tvec_base是動態定時器的主要數據結構,每個cpu上有一個,它包含相應cpu中處理動態定時器需要的所有數據

 

      void (*function)(unsigned long); //超時時要處理的函數

      unsigned long data;  //超時處理函數參數

 

      int slack; //跟定時器的內核處理方式有關的變量

 

#ifdefCONFIG_TIMER_STATS

      int start_pid;

      void *start_site;

      char start_comm[16];

#endif

#ifdefCONFIG_LOCKDEP

      struct lockdep_map lockdep_map;

#endif

};

當運行setup_timer(&bdata->timer,gpio_keys_gpio_timer, (unsigned long)bdata);這個宏就是設定的定時器爲&bdata->timer,定時器到時間後運行的函數爲gpio_keys_gpio_timer,定時器函數所需要的數據爲(unsigned long)bdata

有關輸入子系統的介紹就到這裏,下面總結一下定時器的使用方法:

1、定義一個定時器timer_listmytimer

2、初始化定時器並賦值成員setup_timer(mytimer, timer_func, data);

3、增加定時器add_timer(mytimer)

4、該修改定時器的定時時間expire  mod_timer(mytimer,expire)

5、取消定時器,有兩個可選擇

1.       del_timer(mytimer) 直接刪除定時器

2.       del_timer_sync(mytimer)等待本次定時器處理完畢再取消(不適用中斷上下文)

 

 

 

**************************************************************************/

 

             isr =gpio_keys_gpio_isr;

/*************************************************************************************

 這是是設置中斷服務函數

**************************************************************************/

 

             irqflags = IRQF_TRIGGER_RISING| IRQF_TRIGGER_FALLING;

/*************************************************************************************

 這是設定中斷處理的屬性,也就是中斷觸發方式處理方式等等。這裏設置上升沿觸發或者下降沿觸發。假如設置IRQF_SHARED表明多個設備共享一箇中斷,此時會用到dev_id;當設置IRQF_DISABLED時候表明,中斷爲快速中斷。

**************************************************************************/

 

      } else {

             if(!button->irq) {

                    dev_err(dev,"No IRQ specified\n");

                    return-EINVAL;

             }

             bdata->irq =button->irq;

 

             if(button->type && button->type != EV_KEY) {

                    dev_err(dev,"Only EV_KEY allowed for IRQ buttons.\n");

                    return-EINVAL;

             }

 

             bdata->timer_debounce= button->debounce_interval;

             setup_timer(&bdata->timer,

                        gpio_keys_irq_timer, (unsigned long)bdata);

 

             isr =gpio_keys_irq_isr;

             irqflags = 0;

      }

 

      input_set_capability(input,button->type ?: EV_KEY, button->code);

/*************************************************************************************

設定該按鍵具有什麼能力,以本例來說,button1 button2 button3具有按鍵F1 F2 F3的能力

**************************************************************************/

      /*

       * If platform has specified that the buttoncan be disabled,

       * we don't want it to share the interruptline.

       */

      if(!button->can_disable)

             irqflags |=IRQF_SHARED;

/*************************************************************************************

設置是否共享中斷,這裏涉及到中斷共享機制:

多箇中斷共享一箇中斷線必須用IRQF_SHARED做標記,以IRQF_SHARED作爲標記的中斷假如要向內核申請成功一箇中斷需要兩個條件之一:

該中斷還沒有被申請

該中斷雖然被申請了,但是已經申請的中斷也有IRQF_SHARED做標記

當發生共享中斷時,所用掛載到此中斷的中斷服務函數都會得到響應(遍歷所有該中斷線上的中斷),所以說,當該中斷設備爲共享中斷的時候,中斷服務函數首先需要判斷是否是自己的dev_id假如不是自己的dev_id那麼返回IRQ_NOTE(表明不是本中斷),假如檢測到時本中斷的話就會執行中斷裏面的函數,最後返回IRQ_HANDLED

**************************************************************************/

 

      error =request_any_context_irq(bdata->irq, isr, irqflags, desc, bdata);

/*************************************************************************************

申請一箇中斷線,其中bdata->irq爲要申請的中斷線(中斷號);isr爲中斷服務函數;irqflags中斷的觸發方式或者處理方式;要申請中斷的描述符;bdatadev_id,區分共享中斷線線而設定的。

看一下申請外部中斷的流程:

1、申請某個引腳爲外部中斷 intirq = gpio_to_irq(button->gpio);

2、設備外部中斷函數irq_handler_tisr = gpio_keys_gpio_isr;

3、設置中斷髮出類型 unsignedlong flags =

4、描述該中斷的一個assic字符串的名字  constchar *name=

5、設備dev-id 是一個空函數指針  void*dev_id =

**************************************************************************/

     

if (error < 0) {

             dev_err(dev,"Unable to claim irq %d; error %d\n",

                    bdata->irq,error);

             goto fail;

      }

 

      return 0;

 

fail:

      if(gpio_is_valid(button->gpio))

             gpio_free(button->gpio);

 

      return error;

}

現在總結一下設置按鍵的函數gpio_keys_setup_key作用:

該函數主要是申請外部中斷,設定中斷服務函數,由於在消抖的時候會用到定時器,這個函數還初始化了定時器。並且綁定了定時器中斷服務函數。

接下來看一下按鍵服務函數:

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)

{

/*************************************************************************************

首先看中斷服務函數的返回值:

IRQ_NONE ,還沒有發生中斷

IRQ_HANDLED     中斷已經處理完畢

IRQ_WAKE_THREAD中斷需要喚醒處理線程

**************************************************************************/

 

      struct gpio_button_data*bdata = dev_id;

 

      BUG_ON(irq !=bdata->irq);//代碼調試用的

 

      if(bdata->button->wakeup)  //根據wakeup保存非睡眠狀態

             pm_stay_awake(bdata->input->dev.parent);

 

      if (bdata->timer_debounce)

             mod_timer(&bdata->timer,

                    jiffies +msecs_to_jiffies(bdata->timer_debounce));

      else

             schedule_work(&bdata->work);

/*************************************************************************************

 先看if的條件,條件爲消抖的時間,假如需要消抖(消抖的時間非0),那麼就執行       mod_timer(&bdata->timer,jiffies+ msecs_to_jiffies(bdata->timer_debounce));

mod_timer用於改變或者設定定時器的定時時間,其中&bdata->timer爲要修改的定時器,jiffies +msecs_to_jiffies(bdata->timer_debounce)爲修改後的時間msecs_to_jiffies函數是吧毫秒轉換爲jiffies,單單看這個函數就是在過msecs_to_jiffies(bdata->timer_debounce)的時間觸發定時器,定時器時間到後就會執行定時器服務函數(在按鍵設置函數gpio_keys_setup_key中已經設置定時器服務函數爲gpio_keys_gpio_timer),定時器服務函數的內容爲:

static void gpio_keys_gpio_timer(unsigned long_data)

{

      structgpio_button_data *bdata = (struct gpio_button_data *)_data;

 

      schedule_work(&bdata->work);

}

可以看到時間一到會執行schedule_work(&bdata->work);執行中斷底部的隊列部分;

這裏可以看到,假如不需要消抖,那麼直接執行中斷底部的隊列部分schedule_work(&bdata->work)

(稍後介紹工作工作隊列的工作)

**************************************************************************/

 

      return IRQ_HANDLED;

}

對於按鍵中斷服務函數gpio_keys_gpio_isr可以看出來,中斷函數確實是分爲中斷頂半部(top half)和中斷底半部(bottom half),對於按鍵來說,中斷頂半部就是消抖(消抖還用到了定時間,所以說需要的執行時間很短),中斷的底半部就是用來執行工作隊列裏面的工作了,接下來看一下隊列的工作都做了什麼:

static void gpio_keys_gpio_work_func(struct work_struct *work)

{

      struct gpio_button_data*bdata =

             container_of(work,struct gpio_button_data, work);

/*************************************************************************************

 container_of是根據一個結構體變量中的一個域成員變量的指針來獲取指向整個結構體變量的指針,就本例而言,就是根據gpio_button_data->work這個成員的指針來獲取整個結構體gpio_button_data的指針首地址。

假如想了解container_of的原理,可參考:http://www.embedu.org/Column/Column433.htm

**************************************************************************/

      gpio_keys_gpio_report_event(bdata);

/*************************************************************************************

這個是整個輸入子系統的關鍵的一個函數(時間上報函數),稍後將會介紹。

**************************************************************************/

      if(bdata->button->wakeup)   //電源管理有關

             pm_relax(bdata->input->dev.parent);

}

可以看到隊列工作就是爲了調用時間上報函數,看一下時間上報函數的實現:

static void gpio_keys_gpio_report_event(struct gpio_button_data*bdata)

{

      const structgpio_keys_button *button = bdata->button;

      struct input_dev *input =bdata->input;

      unsigned int type =button->type ?: EV_KEY;

      int state =(gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;

/*************************************************************************************

gpio_get_value_cansleep是獲取按鍵的值,其gpio_get_value區別是,有些芯片的引腳與cpu是依靠一些總線連接的比如iic總線,那麼這些引腳就有可能產生休眠,因此要用gpio_get_value_cansleep來獲取按鍵的值,獲取後異或button->active_low(之前設定的是1),那麼當有按鍵按下時,gpio_get_value_cansleep得到的是低電平也就是0,所以0^1=1,這裏也是再說名,當按鍵按下時,獲取的state應該是1

**************************************************************************/

 

      if (type == EV_ABS) {  //這個類型判斷是不是EV_ABS(絕對座標)事件,本次是EV_KEY事件

             if (state)

                    input_event(input,type, button->code, button->value);

      } else {

             input_event(input,type, button->code, !!state);

      }

/*************************************************************************************

 這是就是上報事件了,這個爲執行input_event(input, type, button->code,!!state);

input_event函數的參數爲,input上報的設備(也就是input_dev);type爲上報事件的類型,這裏是EV_KEY(按鍵事件),button->code按鍵的編碼,就本次而言對應的額是F1 F2 F3state爲上報值

**************************************************************************/

 

      input_sync(input);

/*************************************************************************************

input_sync(input)實際定義是:

staticinline void input_sync(struct input_dev *dev)

{

      input_event(dev, EV_SYN, SYN_REPORT, 0);

}

用來告訴上層,本次的事件已經完成了.

這裏要注意的就是,設置觸發方式的時候,設置的是下降沿觸發或上升沿觸發,也就是當執行一次按鍵按下鬆開的動作時,會觸發兩次中斷(一次按下,一次鬆開),每次中斷都會執行本函數,執行一次本函數上報兩個輸入事件(執行了兩次input_event),也就是說,按下鬆開一共會上報四個數據。

**************************************************************************/

}

到這裏上報時間就完工了,回想一下,整個輸入子系統的流程可以總結爲兩部:

首先定義申請註冊一個輸入設備。

申請按鍵爲外部中斷,當按鍵按下時,在中斷服務函數中處理,並上報按鍵事件。

 

有關按鍵的主題部分已經分析完畢,接下來看一下其他的一些函數:

在介紹gpio_keys_probe函數中的sysfs_create_group時,會生成一些屬性文件,看一下這個屬性文件是如何使用在驅動程序中的,先看一下生成哪些屬性文件

/sys/devices/platform/gpio-keys/disabled_keys[rw]

/sys/devices/platform/gpio-keys/disables_switches[rw]

/sys/devices/platform/gpio-keys/keys[ro]

/sys/devices/platform/gpio-keys/switches[ro]

這裏就以disabled_keys文件爲例介紹如何使用這些屬性文件來設置驅動程序。

由上節的led的分析我們知道,對於sysfs下的屬性文件,當要讀文件的實收會執行*show函數,當要寫文件的時候會執行*store程序,接下來分別看下一下這兩個函數,首先看show函數:

static ssize_t gpio_keys_show_##name(struct device *dev,          \

                                struct device_attribute *attr, \

                                char *buf)                       \

{                                                            \

      struct platform_device*pdev = to_platform_device(dev);              \

/*************************************************************************************

 先看下定義

#define to_platform_device(x)container_of((x), struct platform_device, dev)

可以看到to_platform_device就是通過platform_device的成員dev來得到platform_device結構體的首地址。

**************************************************************************/

      struct gpio_keys_drvdata*ddata = platform_get_drvdata(pdev);    \

/*************************************************************************************

probe有執行platform_set_drvdata(pdev, ddata);ddata的保存起來,這裏是獲取保存的數據。

**************************************************************************/

                                                              \

      returngpio_keys_attr_show_helper(ddata, buf,                \

                                    type, only_disabled);            \

/*************************************************************************************

這個函數就是控制使能與否的關鍵函數了。下面介紹本函數

**************************************************************************/

 

}

 

static ssize_t gpio_keys_attr_show_helper(structgpio_keys_drvdata *ddata,

                                    char *buf, unsigned int type,

                                    bool only_disabled)

{

      int n_events =get_n_events_by_type(type);

/*************************************************************************************

get_n_events_by_type這是是返回事件類型的最大值(因爲後面要申請內存,所以這裏需要對應本類時間的最大值)。可以查看實際上返回的書是0x3000

**************************************************************************/

 

      unsigned long *bits;

      ssize_t ret;

      int i;

 

      bits = kcalloc(BITS_TO_LONGS(n_events), sizeof(*bits), GFP_KERNEL);

/*************************************************************************************

kcalloc申請一個數組的內存空間,並把申請得到的內存都初始化爲零,其參數爲要申請的數量,第二個參數爲單位數量佔有的字節數,第三個參數分配內存時的控制方式。

BITS_TO_LONGS函數是求一個數是幾個long型的長度,sizeof(*bits)就是sizeof(long)=4,這個函數要是申請的字節數就是BITS_TO_LONGS(n_events)乘以sizeof(*bits)得到的字節數。

實際上,可用下面的例子來測試:

 

#include <stdio.h>

#define DIV_ROUND_UP(n, d) (((n)+(d)-1) / (d))

#define BITS_PER_BYTE           8

#define BITS_TO_LONGS(nr)       DIV_ROUND_UP(nr, BITS_PER_BYTE *sizeof(long))

void main()

{

      intj = 0x300;

      unsignedlong *bits;

      printf("sizeof long is %ld\n", sizeof(*bits));

      printf("result is%ld\n", BITS_TO_LONGS(j));

while(1);

}

運行結果爲:size of long is 4      result is 24 

對於第三個參數的標誌位,這些標誌位定義在Gfp.h (include\linux)文件中:

具體的介紹這在裏摘錄博客的一段話:http://m.blog.csdn.net/blog/gs_119/23698667
GFP
的標記有兩種:帶雙下劃線前綴的和不帶雙下劃線前綴的;
不帶雙下劃線前綴的GFP標誌:
GFP_ATOMIC:
用於在中斷上下文和進程上下文之外的其它代碼中分配內存;從不睡眠;
GFP_KERNEL:
內核正常分配內存;可能睡眠;
GFP_USER  :
用於爲用戶空間頁分配內存;可能睡眠;
GFP_HIGHUSER:
如同GFP_USER,但它是從高端內存中申請;
GFP_NOIO
GFP_NOFS:功能如同GFP_KERNEL,但是它倆增加限制到內核能做的來滿足請求;GFP_NOFS分配不允許進行任何文件系統調用,GFP_NOIO分配根本不允許進行任何IO初始化;它倆主要用於文件系統和虛擬內存代碼,那裏允許一個分配睡眠,但是遞歸的文件系統調用會是個壞主意;
帶有雙下劃線前綴的GFP標誌:
__GFP_DMA:
這個標誌要求分配的內存在能夠進行DMA的內存區;平臺依賴的;
__GFP_HIGHMEM:
這個標誌指示分配的內存可以位於高端內存區;平臺依賴的;
__GFP_COLD:
正常地,內存分配器盡力返回"緩衝熱"的頁---可能在處理器緩衝中找到的頁;相反,這個標誌請求一個""---在一段時間內沒被使用的頁;它對分配頁做DMA讀是很有用的,此時在處理器緩衝中出現是沒用的;
__GFP_NOWARN:
這個標誌用於分配內存時阻止內核發出警告,當一個分配請求無法滿足時;
__GFP_HIGH:
這個標誌標識了一個高優先級請求,它被允許來消耗甚至被內核保留給緊急狀況的最後的內存頁;
__GFP_REPEAT:
分配器的動作;當分配器有困難滿足一個分配請求時,通過重複嘗試的方式來"盡力嘗試",但是分配操作仍然有可能失敗;
__GFP_NOFAIL:
分配器的動作;當分配器有困難滿足一個分配請求時,這個標誌告訴分配器不要失敗,盡最大努力來滿足分配請求;
__GFP_NORETRY:
分配器的動作;當分配器有困難滿足一個分配請求時,這個標誌告訴分配器立即放棄,不再做任何嘗試;
通常,一個或多個帶雙下劃線前綴的標記相或,即可得到對應的不帶雙下劃線前綴的標記;
最常用的標記就是GFP_KERNEL,它的意思就是當前的這個分配代表運行在內核空間的進程而進行的;換句話說,這意味着調用函數是代表一個進程在執行一個系統調用;使用GFP_KERNEL標記,就意味着kmalloc能夠使當前進程在少內存的情況下通過睡眠來等待一個內存頁;因此,一個使用GFP_KERNEL的函數必須是可重入的,且不能在原子上下文中運行;噹噹前進程睡眠,內核採取正確的動作來定位一些空閒的內存頁,或者通過刷新緩存到磁盤或者交換出去一個用戶進程的內存頁;
如果一個內存分配動作發生在中斷處理或內核定時器的上下文中時,當前進程就不能被設置爲睡眠,也就不能再使用GFP_KERNEL標誌了,此時應該使用GFP_ATOMIC標誌來代替;正常地,內核試圖保持一些空閒頁以便來滿足原子的分配;當使用GFP_ATOMIC標誌時,kmalloc標誌能夠使用甚至最後一個空閒頁;如果這最後一個空閒頁不存在,那分配就會失敗;

 

**************************************************************************/

 

      if (!bits)

             return -ENOMEM;

 

      for (i = 0; i <ddata->pdata->nbuttons; i++) {

             structgpio_button_data *bdata = &ddata->data[i];

 

             if(bdata->button->type != type)  //假如不是預定類型跳過本循環

                    continue;

 

             if (only_disabled&& !bdata->disabled) //假如不使能跳過本循環

 

                    continue;

 

             __set_bit(bdata->button->code,bits);

/*************************************************************************************

__set_bit是非原子型的按位操作(set_bit函數是原子操作),這裏有點難理解,就是將申請內存的是bits相應的按鍵編碼位置1,就以本函數的實際代表意思來解釋,就是在判定本按鍵有效的情況下,就是把剛纔申請的bits所指向的內存對應的F1或者F2或者F3相應的位置1

**************************************************************************/

 

 

      }

 

      ret =bitmap_scnlistprintf(buf, PAGE_SIZE - 2, bits, n_events);

/*************************************************************************************

將位圖轉花紋字符串。

**************************************************************************/

 

      buf[ret++] = '\n';

      buf[ret] = '\0';

 

      kfree(bits);

 

      return ret; //返回值是換成的字符串的長度

}

這個函數的實際含義就是當你對/sys/devices/platform/gpio-keys/disabled_keys 文件進行讀的時候就用調用open函數,舉一個直觀些的例子就是:

int strong read(fd,char *buf,&ev_key, only_disabled); //fd爲打開的設備文件

上面的函數是讀/sys/devices/platform/gpio-keys/disabled_keys要執行的函數,下面看下寫該文件要執行的函數:

static ssize_tgpio_keys_store_##name(struct device *dev,           \

                                struct device_attribute *attr,      \

                                const char *buf,                   \

                                size_t count)                 \

{                                                            \

      struct platform_device *pdev = to_platform_device(dev);              \

/*************************************************************************************

 先看下定義

#define to_platform_device(x)container_of((x), struct platform_device, dev)

可以看到to_platform_device就是通過platform_device的成員dev來得到platform_device結構體的首地址。

**************************************************************************/

 

      struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);    \

/*************************************************************************************

probe有執行platform_set_drvdata(pdev,ddata);ddata的保存起來,這裏是獲取保存的數據。

**************************************************************************/

 

      ssize_t error;                                           \

                                                              \

      error = gpio_keys_attr_store_helper(ddata, buf, type);            \

/*************************************************************************************

這個函數就是控制使能與否的關鍵函數了。下面介紹本函數

**************************************************************************/

 

      if (error)                                           \

             return error;                                     \

                                                              \

      return count;                                           \

}

 

 

static ssize_tgpio_keys_attr_store_helper(struct gpio_keys_drvdata *ddata,

                                    const char *buf, unsigned int type)

{

      int n_events = get_n_events_by_type(type);

/*************************************************************************************

get_n_events_by_type這是是返回事件類型的最大值(因爲後面要申請內存,所以這裏需要對應本類時間的最大值)。可以查看實際上返回的書是0x3000

**************************************************************************/

 

      unsigned long *bits;

      ssize_t error;

      int i;

 

      bits = kcalloc(BITS_TO_LONGS(n_events), sizeof(*bits),GFP_KERNEL);

      if (!bits)

             return -ENOMEM;

/*************************************************************************************

申請內存,跟上個函數一樣,不清楚的查看上個函數的解析。

**************************************************************************/

 

      error = bitmap_parselist(buf, bits, n_events);

/*************************************************************************************

bitmap_parselist就是吧buf的數據,寫入到bits所指向的內存中,n_events是寫入的數量。

bitmap_parselist函數定義在Bitmap.c (lib)     文件中,原型爲:

int bitmap_parselist(const char *bp, unsignedlong *maskp, int nmaskbits)

{

      char*nl  = strchr(bp, '\n');//

功能:查找字符bp中首次出現字符'\n'的位置

說明:返回首次出現'\n'的位置的指針,返回的地址是被查找字符串指針開始的第一個與'\n'相同字符的指針,如果bp中不存在'\n'則返回NULL

返回值:成功則返回要查找字符第一次出現的位置,失敗返回NULL.

      intlen;

 

      if(nl)

             len= nl - bp; //字符串低一個字符到字符'\n'的長度

      else

             len= strlen(bp);

 

      return__bitmap_parselist(bp, len, 0, maskp, nmaskbits);//該函數將在下面分析

}

__bitmap_parselist函數定義在Bitmap.c (fs\reiserfs)中,現在只列出該參數:

static int __bitmap_parselist(const char *buf,unsigned int buflen,int is_user, unsigned long *maskp,

             intnmaskbits)

該函數的作用是將asccic的字符串轉換成爲位圖形似,看一下其參數的意義:

第一個參數const char *buf是要轉換的字符串

第二個參數unsigned int buflen要轉化的字符串的長度

第三個參數int is_user,用戶,0代表是內核空間

第四個參數unsigned long *maskp:將要寫入的數據地址

第五個參數int nmaskbits:要寫入數據的數量

返回0表示成功

 

**************************************************************************/

 

      if (error)

             goto out;

 

      /* First validate */

      for (i = 0; i < ddata->pdata->nbuttons; i++) {

             struct gpio_button_data *bdata = &ddata->data[i];

 

             if (bdata->button->type != type)

                    continue;

 

             if (test_bit(bdata->button->code, bits) &&

                !bdata->button->can_disable) {

                    error = -EINVAL;

                    goto out;

             }

/*************************************************************************************

test_bit是爲測試,就是測試bitsbdata->button->code位是的值,返回的數據bitsbdata->button->code位的值(0or1)。

**************************************************************************/

 

      }

 

      mutex_lock(&ddata->disable_lock);

/*************************************************************************************

這裏是申請互斥體,在前面的probe函數裏面有mutex_init(&ddata->disable_lock)初始化了互斥體。

互斥鎖的使用步驟如下:

1、定義互斥體 struct mutex  my_mutex

2、初始化互斥體 mutex_initmy_mutex

3、嘗試獲取鎖 mutex_lockmy_mutex

#########################################################################

臨界區操作

#########################################################################

4、釋放掉互斥體

 

 

**************************************************************************/

 

 

 

 

      for (i = 0; i < ddata->pdata->nbuttons; i++) {

             struct gpio_button_data *bdata = &ddata->data[i];

 

             if (bdata->button->type != type)

                    continue;

 

             if (test_bit(bdata->button->code, bits))

                    gpio_keys_disable_button(bdata);

             else

                    gpio_keys_enable_button(bdata);

      }

/*************************************************************************************

根據輸入的數據選擇是執行gpio_keys_disable_button函數還是執行gpio_keys_enable_button函數,其中gpio_keys_disable_button關掉外部中斷和取消定時器(這裏的取消是指上次設定的時間沒到的那次定時器作用),gpio_keys_enable_button函數就是使能外部按鍵中斷,這裏

 

**************************************************************************/

 

      mutex_unlock(&ddata->disable_lock); //釋放掉互斥體

 

out:

      kfree(bits); //釋放掉申請的bits的內存

      return error;

}

有關按鍵的驅動的驅動初始化及其上報事件讀寫部分代碼已經分析完畢,解析來看一下是卸載函數要執行的代碼:

 

static intgpio_keys_remove(struct platform_device *pdev)

{

      struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);

      struct input_dev *input = ddata->input;

      int i;

 

      sysfs_remove_group(&pdev->dev.kobj,&gpio_keys_attr_group);

 

/*************************************************************************************

相對於sysfs_create_group(&pdev->dev.kobj,&gpio_keys_attr_group);這裏是吧初始化時建立的屬性文件刪除掉

 

**************************************************************************/

      device_init_wakeup(&pdev->dev, 0);

 

      for (i = 0; i < ddata->pdata->nbuttons; i++)

             gpio_remove_key(&ddata->data[i]);

/*************************************************************************************

static void gpio_remove_key(structgpio_button_data *bdata)

{

      free_irq(bdata->irq,bdata);

      if(bdata->timer_debounce)

             del_timer_sync(&bdata->timer);

      cancel_work_sync(&bdata->work);

      if(gpio_is_valid(bdata->button->gpio))

             gpio_free(bdata->button->gpio);

}

該函數主要是刪除定時器,取消工作隊列的工作,釋放申請的引腳

**************************************************************************/

 

      input_unregister_device(input);//註銷輸入設備

 

      /* If we have no platform data, we allocated pdata dynamically.*/

      if (!dev_get_platdata(&pdev->dev))

             kfree(ddata->pdata); //釋放pdata

 

      kfree(ddata);//釋放在probe申請的ddata的內存

 

      return 0;

}

 

上面的是設備驅動層的核心代碼,已經做出詳細的介紹,在說明文檔裏面的Documentation\input\input-programming.txt,文件中有實例代碼,有時間,可以去參考分析一下,這裏就不在贅述。

 

有關輸入子系統的介紹就到這裏,在文章一開始的時候就有介紹過,Input子系統分爲三層,從下至上分別是設備驅動層,輸入核心層以及事件處理層,上面的介紹是圍繞着設備驅動層進行的,接下來看一下事件驅動層。核心層的文件定義在Input.c (drivers\input)文件中,這裏僅僅對該文件做大概的介紹,看初始化函數:

static int __init input_init(void)

{

      int err;

 

      err =class_register(&input_class);

/*************************************************************************************

註冊input類,這裏也就是sysfs/class/目錄下生成input目錄。

這裏,class_register註冊類和class_create是一樣的,實際上class_create調用的是class_register來實現類的註冊的,class_register對應的註銷函數是class_unregisterclass_create對應的註銷函數是class_destroy

**************************************************************************/

 

      if (err) {

             pr_err("unableto register input_dev class\n");

             return err;

      }

 

      err = input_proc_init();

/*************************************************************************************

創建proc/bus/input文件路徑,並且在該路徑下deviceshandlers設備文件及其他們分別對應的file_operations結構體。

 

**************************************************************************/

 

      if (err)

             goto fail1;

 

      err =register_chrdev_region(MKDEV(INPUT_MAJOR, 0),  //

                                INPUT_MAX_CHAR_DEVICES,"input");

/*************************************************************************************

一個字符驅動獲取一個或多個設備編號,

第一個參數MKDEV(INPUT_MAJOR, 0):是你要分配的起始設備編號,次設備號一般爲0dev_t是個 32 位量,其中 12位用來表示主設備號,20位用來表示次設備號。

第二個參數INPUT_MAX_CHAR_DEVICES,:請求的連續設備編號的總數

第三個參數"input":設備的名字;它會出現在 /proc/devices sysfs

假如成功返回0

**************************************************************************/

 

      if (err) {

             pr_err("unableto register char major %d", INPUT_MAJOR);

             goto fail2;

      }

 

      return 0;

 

 fail2:   input_proc_exit();

 fail1:   class_unregister(&input_class);

      return err;

}

這個初始化代碼比較簡單,就是申請一個輸入字符設備,在sysfs/class/目錄下生成input目錄,在proc/bus/input生成目錄並且生成相應的屬性文件。主要是該文件還有許多定義的代碼,當用戶驅動代碼的時候會調用這些程序,在這裏不做介紹,待用到哪個函數時在去花時間去分析它。

接下來,看一下事件處理層,事件處理層文件主要是用來支持輸入設備並與用戶空間交互,這部分代碼一般不需要我們自己去編寫,因爲Linux內核已經自帶有一些事件處理器,可以支持大部分輸入設備,比如Evdev.c(按鍵等對應的處理函數)、mousedev.c(鼠標等對應的處理函數)、joydev.c(搖桿等對應的處理函數)等。對按鍵來說,用到的是Evdev.c文件(drivers/input)。

首先看該文件對應的初始化函數:

static int __initevdev_init(void)

{

   return input_register_handler(&evdev_handler);

/*************************************************************************************

這裏是註冊一個新的input handlerevdev_handler就是要註冊的handler,接下倆就要看一下這個時間處理(接口)結構體。

**************************************************************************/

}

對於evdev_handler,其爲input_handler類型的結構體,先看一下input_handler(Input.h (include\linux))結構體:

struct input_handler {

 

      void *private;  //指向對應每一個驅動的所特有的數據

      void (*event)(structinput_handle *handle, unsigned int type, unsigned int code, int value);

/*************************************************************************************

輸入事件向內核報告會,內核需要調用的函數

**************************************************************************/

 

      void (*events)(structinput_handle *handle,

                    const struct input_value *vals, unsignedint count);

      bool (*filter)(structinput_handle *handle, unsigned int type, unsigned int code, int value);

/*************************************************************************************

類似event,負責從filters分離出一般時間處理函數

**************************************************************************/

 

      bool (*match)(structinput_handler *handler, struct input_dev *dev); //比較設備和處理函數的id

      int (*connect)(structinput_handler *handler, struct input_dev *dev, const struct input_device_id*id);

//   handler  input_dev

      void (*disconnect)(structinput_handle *handle);

      void (*start)(structinput_handle *handle);

 

      bool legacy_minors;

      int minor;

      const char *name;  //時間處理函數的名字

 

      const struct input_device_id*id_table;

 

      struct list_head    h_list;///用於鏈接和此input_handler相關的input_handle

 

      struct list_head    node;//用於將該input_handler鏈入input_handler_list

 

}

 

我們知道對應每一個輸入設備,上報事件後最終內核用調用相應的函數去處理,在這裏就是每一個input_event都會有對應的input_handler(可以是一對多,也可以是多對一)來處理相應的事件。那麼input_event要找到與之對應的input_handler就需要一個東西來確定兩者的對應關係,這個東西就是input_handle。其定義在Input.h(include\linux\usb)中:

struct input_handle {

 

      void *private;  //處理者(handler)的特有數據

 

      int open;//記錄句柄(handle)是否被打開,這樣話就決定是否傳遞驅動事件

 

      const char *name;  //由處理者(handler)給句柄(handle)創建的名字

 

      struct input_dev *dev;  //輸入設別結構體

      struct input_handler*handler; //指向handler

 

      struct list_head    d_node; // 用於將此input_handle鏈入所屬input_devh_list鏈表

 

      struct list_head    h_node;//用於將此input_handle鏈入所屬input_handlerh_list鏈表

 

};

下面來縷縷input_handler  input_handle input_dev它們三個之間的關係,input_handler  擁有struct list_head    d_node(用於鏈接和此input_handler相關的input_handle;input_dev也有 struct list_head    h_list;(用於鏈接和此input_handler相關的input_handle;相應的input_handle擁有分別連接它們倆的structlist_head       d_node(用於將此input_handle鏈入所屬input_devh_list鏈表),struct list_head    h_node;(用於將此input_handle鏈入所屬input_handlerh_list鏈表)。這樣的話,input_handle 就把input_dev和input_handler綁定到一塊了。現在的疑問是問什麼它們倆不直接相互綁定,而需要一箇中介input_handle來綁定它們倆呢,這裏涉及到兩個方面,其一是因爲一個輸入設備input_dev可能對應多個input_handler,對調用驅動的用戶來說,用戶可以選擇本input_dev根據自身的需要調用哪一個input_handler;其二,一個輸入處理函數input_handler可以對應多個輸入設備input_dev,也就是說多個input_dev可共享一個input_handler。下面借鑑一張圖:


(圖片來源:http://www.cnblogs.com/jason-lu/articles/3155228.html

 

最後在分析一個內核源碼目錄的提供的申請輸入設備框架程序,該例位於Documentation\input\input-programming.txt中

#include <linux/input.h>

#include <linux/module.h>

#include <linux/init.h>

 

#include <asm/irq.h>

#include <asm/io.h>

 

static struct input_dev *button_dev;

 

*************************************************************************************

外部中斷服務函數,當按鍵按下時,會觸發中斷,該函數就是上報按鍵事件

**************************************************************************/

static irqreturn_t button_interrupt(int irq, void *dummy)

{

      input_report_key(button_dev,BTN_0, inb(BUTTON_PORT) & 1);

      input_sync(button_dev);

      return IRQ_HANDLED;

}

 

static int __init button_init(void)

{

      int error;

 

      if (request_irq(BUTTON_IRQ,button_interrupt, 0, "button", NULL)) {

               printk(KERN_ERR "button.c: Can't allocate irq %d\n",button_irq);

                return -EBUSY;

        }

*************************************************************************************

申請外部中斷

**************************************************************************/

 

      button_dev =input_allocate_device();//申請爲輸入設備

      if (!button_dev) {

             printk(KERN_ERR"button.c: Not enough memory\n");

             error = -ENOMEM;

             goto err_free_irq;

      }

 

      button_dev->evbit[0] =BIT_MASK(EV_KEY);

      button_dev->keybit[BIT_WORD(BTN_0)]= BIT_MASK(BTN_0);

 

      error = input_register_device(button_dev); //註冊爲輸入設備

      if (error) {

             printk(KERN_ERR"button.c: Failed to register device\n");

             goto err_free_dev;

      }

 

      return 0;

 

 err_free_dev:

      input_free_device(button_dev);

 err_free_irq:

      free_irq(BUTTON_IRQ,button_interrupt);

      return error;

}

 

static void __exit button_exit(void)

{

       input_unregister_device(button_dev);

      free_irq(BUTTON_IRQ,button_interrupt);

}

 

module_init(button_init);

module_exit(button_exit);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章