1. 程式人生 > >android系統移植之按鍵驅動篇

android系統移植之按鍵驅動篇

平臺:MX53_QSB開發板

MX53_QSB開發板上一起有四個按鍵,分別為RESET,POWER,USER1,USER2。其中RESET為純硬體復位按鍵,無須軟體控制。POWER,USER1,USER2三個按鍵均需要程式控制。預設BSP包中將三個按鈕全設定為上升和下降沿觸發,當系統起來後,按下POWER鍵,進入睡眠狀態,這時再按下POWER鍵喚醒時,系統系統被喚醒,但是一旦手鬆下,又觸發了POWER鍵的中斷,系統又睡下去了。在進入睡眠狀態後,只有按USER1和USER2這兩個鍵,才能正常喚醒。因此,這裡有BUG需修復。

按鍵驅動有兩個,一個為矩陣鍵盤驅動,路徑為:

\drivers\input\keyboard\mxc_keyb.c

一個為GPIO介面的鍵盤驅動,路徑為:

\drivers\input\keyboard\gpio_keys.c

前者用於多按鍵的情況,如果按鍵比較少,後者就可以了,一般情況下,android系統只需幾個按鍵就可以了,所以大多數情況下,都是使用的gpio_keys.c。下面我們將詳細分析該驅動的工作流程。

在module_init函式中,在總線上註冊名為gpio-keys的驅動,這時將夫在總線上查詢是否存在同名的裝置。系統初始化時,mx53_loco.c中,mxc_board_init函式已呼叫了按鍵初始化函式loco_add_device_buttons(),同時pdev的資料結構體中定義了按鍵的相關資訊如下:

#define GPIO_BUTTON(gpio_num, ev_code, act_low,descr, wake)   \

{                                                                         \

         .gpio           = gpio_num,                                    \

         .type           = EV_KEY,                                     \

         .code          = ev_code,                             \

         .active_low = act_low,                              \

         .desc           = "btn " descr,                                 \

         .wakeup               = wake,                                           \

}

static struct gpio_keys_button loco_buttons[] = {

         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),

         GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),

         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),

};

static struct gpio_keys_platform_dataloco_button_data = {

         .buttons      = loco_buttons,

         .nbuttons    = ARRAY_SIZE(loco_buttons),

};

可見,結構體定義了三個GPIO,分別為power,back以及home。注意GPIO_BUTTON函式中的實參,第一個為對應的GPIO,純硬體特性,第二個為按鍵的鍵值,在linux/input.h中定義:

#defineKEY_POWER         116

#defineKEY_HOME           102

#defineKEY_BACK            158

第三個引數為1,表明按下去為1,擡起為0;第四個引數為按鍵名稱描述,無關緊要;第5個引數為wakeup,看名稱好像與休眠喚醒有關,實際測試修改為1後,沒有發現有什麼異常。這幾個引數是後續新增新的按鍵,或者更改按鍵功能的關鍵。

再回到gpio-keys.c中,找到同名裝置後,探測函式gpio_keys_probe得到執行,呼叫input_allocate_device函式建立一個input裝置,再通過一個for迴圈,呼叫gpio_keys_setup_key和input_set_capability函式,設定上表中列出的三個IO口的中斷函式以及按鍵功能。後面用到了sysfs_create_group函式建立了基於sys系統的檔案屬性組,具體可以在?sys/devices/platform/gpio-keys目錄下找到gpio_keys_attr_group結構體中attrs組對應的gpio_keys_attrs結構體中的幾個屬性檔案,gpio_keys_attrs結構體描述如下:

static struct attribute *gpio_keys_attrs[] = {

         &dev_attr_keys.attr,

         &dev_attr_switches.attr,

         &dev_attr_disabled_keys.attr,

         &dev_attr_disabled_switches.attr,

         NULL,

};

dev_attr_disabled_keys和dev_attr_disabled_switches在前面做了如下宣告:

static DEVICE_ATTR(disabled_keys, S_IWUSR |S_IRUGO,

                      gpio_keys_show_disabled_keys,

                      gpio_keys_store_disabled_keys);

static DEVICE_ATTR(disabled_switches, S_IWUSR |S_IRUGO,

                      gpio_keys_show_disabled_switches,

                      gpio_keys_store_disabled_switches);

在android系統終端,我們可以進入該路徑檢視是否存在,以及他們的檔案屬性如下:


注意上面四個檔案的讀寫屬性。

可見,這裡留有在系統中操作按鍵的後門,具體以後再分析。接下來,呼叫input_register_device函式向輸入子系統註冊input_dev,結束探測函式初始化。

探測函式的關鍵點在gpio_keys_setup_key函式中,相關程式碼如下:

static int __devinit gpio_keys_setup_key(structplatform_device *pdev,

                                                struct gpio_button_data *bdata,

                                                struct gpio_keys_button *button)

{

         char *desc= button->desc ? button->desc : "gpio_keys";//從loco_buttons陣列中獲得按鍵的描述名稱

         structdevice *dev = &pdev->dev;

         unsignedlong irqflags;

         intirq, error;

         //傳入引數: 過期時間,回撥函式,上下文

         //當計時器過期時,回撥函式gpio_keys_timer將得到執行

         setup_timer(&bdata->timer,gpio_keys_timer, (unsigned long)bdata);//初始化計時器

         INIT_WORK(&bdata->work,gpio_keys_work_func);

         error= gpio_request(button->gpio, desc);//請求使用GPIO

         if(error < 0)

         {

                   dev_err(dev,"failed to request GPIO %d, error %d\n",button->gpio, error);

                   gotofail2;

         }

         error= gpio_direction_input(button->gpio);//設定指定的GPIO為輸入模式

         if(error < 0)

         {

                   dev_err(dev,"failed to configure direction for GPIO %d, error %d\n",

                            button->gpio,error);

                   gotofail3;

         }

         irq =gpio_to_irq(button->gpio);//獲得GPIO對應的中斷號

         if (irq< 0)

         {

                   error= irq;

                   dev_err(dev,"Unable to get irq number for GPIO %d, error %d\n",button->gpio,error);

                   gotofail3;

         }

         irqflags= IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;

         //irqflags= IRQF_TRIGGER_FALLING;//lqm changed.

         /*

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

         error= request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);//請求按鍵中斷

     ……

}

該函式使用了帶定時器延時的中斷機制,用於按鍵去抖動。即中斷的執行在定時器到來之後執行,這樣能夠有效的去除按鍵抖動。同時,中斷使用工作佇列的機制。

整個按鍵中斷的工作呼叫有點複雜,下面逐步解析:

首先,上面函式中setup_timer函式初始化定時器,第二個實參為一個名為gpio_keys_timer的函式,一旦計時器過期,該函式將得到執行。setup_timer函式需和mod_timer函式配合使用,在按鍵中斷的頂半部,即gpio_keys_isr函式,會判斷debounce_interval是否為0,若為非0,則呼叫mod_timer函式延時debounce_interval ms,再觸發定時器中斷,即gpio_keys_timer函式得到執行。否則,直接排程工作佇列,執行中斷底半部。

回到gpio_keys_setup_key函式,在初始化完計時器後,再呼叫INIT_WORK函式初始化工作佇列,初始化函式有一個實參函式gpio_keys_work_func,即一旦排程相應佇列名,該函式將得到執行。

接下來是IO口的中斷初始化,呼叫gpio_request函式申請相應的GPIO,呼叫gpio_direction_input函式將對應的GPIO設定為輸入,gpio_to_irq函式通過指定GPIO口對映到指定的IRQ中斷號,request_irq函式申請中斷。

值得注意的是,三個按鍵通過request_irq申請中斷時,共用了同一個中斷函式gpio_keys_isr,那麼程式是怎麼判斷具體是哪個按鍵觸發的呢?

帶著這個問題,我們將整個中斷的執行過程疏理一遍:

第一步:硬體板按下按鍵,電平由高變低,中斷被觸發,中斷函式gpio_keys_isr被呼叫。程式碼如下:

static irqreturn_t gpio_keys_isr(int irq, void*dev_id)

{

         structgpio_button_data *bdata = dev_id;

         structgpio_keys_button *button = bdata->button;

         BUG_ON(irq!= gpio_to_irq(button->gpio));

         if(button->debounce_interval)//產生按鍵中斷後,用計時器延時button->debounce_interval ms之後,再執行按鍵處理

                  mod_timer(&bdata->timer,jiffies+ msecs_to_jiffies(button->debounce_interval));

         else

                   schedule_work(&bdata->work);

         returnIRQ_HANDLED;

}

中斷函式會判斷debounce_interval是否為0,debounce_interval代表計時器需要延時的時間,單位為毫秒。為了確定程式具體如何執行,我們首先需分析出它的值。以下是推理邏輯:

button->debounce_interval  à   找到button結構體來源

*bdata = dev_id&& *button = bdata->button  à  *button = dev_id->button

staticirqreturn_t gpio_keys_isr(int irq, void *dev_id);

error =request_irq(irq, gpio_keys_isr, irqflags, desc, bdata);

上面兩個函式,gpio_keys_isr為request_irq的一個實參,根到request_irq程式碼中,最終會呼叫kernel\irq\manage.c中的request_threaded_irq函式,部分程式碼如下:

int request_threaded_irq(unsigned int irq,irq_handler_t handler,

                             irq_handler_t thread_fn, unsigned longirqflags,

                             const char *devname, void *dev_id)

{

     ……

         action= kzalloc(sizeof(struct irqaction), GFP_KERNEL);

         if(!action)

                   return-ENOMEM;

         action->handler= handler;

         action->thread_fn= thread_fn;

         action->flags= irqflags;

         action->name= devname;

         action->dev_id= dev_id;

         chip_bus_lock(irq,desc);

         retval= __setup_irq(irq, desc, action);

         chip_bus_sync_unlock(irq,desc);

         if(retval)

                   kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ

         if(!retval && (irqflags & IRQF_SHARED)) {

                   /*

                    * It's a shared IRQ -- the driver ought to beprepared for it

                    * to happen immediately, so let's makesure....

                    * We disable the irq to make sure that a'real' IRQ doesn't

                    * run in parallel with our fake.

                    */

                   unsignedlong flags;

                   disable_irq(irq);

                   local_irq_save(flags);

                   handler(irq,dev_id);

                   local_irq_restore(flags);

                   enable_irq(irq);

         }

#endif

         returnretval;

}

繼續根到__setup_irq函式,後面不繼續分析了,最終會將request_irq中的兩個實參irq和bdata賦給中斷服務函式gpio_keys_isr中的兩個實參,也就是說,*button =dev_id->button等價於*button=bdata->button。

在gpio_keys_probe函式中,bdata->button = button,*button = &pdata->buttons[i];可以推斷出*button= pdata->buttons[i],而*pdata = pdev->dev.platform_data,那麼*button= pdev->dev.platform_data-> buttons[i],也就是mx53_loco.c中的如下結構體陣列中的一組:

static struct gpio_keys_button loco_buttons[] = {

         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),

         //GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),

         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),

         GPIO_BUTTON(USER_UI1,KEY_RIGHT, 1, "right", 0),//test by lqm.

};

由此可以推斷出,前面的button->debounce_interval即loco_buttons[i]-> debounce_interval。陣列loco_buttons的結構體如下:

struct gpio_keys_button {

         /*Configuration parameters */

         intcode;               /* input event code(KEY_*, SW_*) */

         intgpio;

         intactive_low;

         char*desc;

         inttype;               /* input event type(EV_KEY, EV_SW) */

         intwakeup;                   /* configure thebutton as a wake-up source */

         intdebounce_interval;   /* debounce ticksinterval in msecs */

         boolcan_disable;

};

由於程式中並沒有對debounce_interval賦值,因此預設debounce_interval為0。回到gpio_keys_isr函式,由於button->debounce_interval為0,那麼計時器機制沒有啟動,直接執行排程函式schedule_work。

第二步:中斷佇列gpio_keys_work_func函式得到執行。它又會呼叫gpio_keys_report_event函式,程式碼如下:

static void gpio_keys_report_event(structgpio_button_data *bdata)

{

         structgpio_keys_button *button = bdata->button;

         structinput_dev *input = bdata->input;

         unsignedint type = button->type ?: EV_KEY;

         intstate = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;//獲得按鍵資訊,同時與1異或?

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

         input_sync(input);//事件同步,它告知事件的接收者驅動已經發出了一個完整的報告

}

這裡是中斷底半部,變數state經gpio_get_value函式獲得當前IO口的電平狀態,再通過input_event函式將當前電平狀態以及button->code上傳給輸入子系統。button->code即loco_buttons數組裡面GPIO_BUTTON中的第二個引數,它定義了按鍵的作用。最後呼叫input_sync函式同事事件,結束一次按鍵的操作。

由於前面分析的debounce_interval值為0,因此執行流程比較簡單,如果它不會0,將會啟用計時器機制,流程會複雜一些。

預設三個按鍵的功能為開關機,主頁和返回三個功能,如果我們需要修改對應按鍵的功能,是否修改上面的GPIO_BUTTON->ev_code就可以了呢?比如,我們需要將back鍵改為right鍵,做如下修改:

static struct gpio_keys_button loco_buttons[] = {

         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),

         //GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),

         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),

         GPIO_BUTTON(USER_UI1,KEY_RIGHT, 1, "right", 0),//test by lqm.

};

編譯核心,這樣按鍵功能就改變了嗎?答案是否定的。因為android並沒有直接使用對映後的鍵值,而且對其再進行了一次對映,從核心標準鍵值到android所用鍵值的對映表定義在android檔案系統的/system/usr/keylayout目錄下。標準的對映檔案為qwerty.kl,定義如下:

key 399  GRAVE

key 2     1

key 3     2

key 4     3

key 5     4

key 6     5

key 7     6

key 8     7

key 9     8

key 10    9

key 11    0

key 158  BACK              WAKE_DROPPED

key 230  SOFT_RIGHT        WAKE

key 60   SOFT_RIGHT        WAKE

key 107  ENDCALL           WAKE_DROPPED

key 62   ENDCALL           WAKE_DROPPED

key 229  MENU              WAKE_DROPPED

key 139  MENU              WAKE_DROPPED

key 59   MENU              WAKE_DROPPED

key 127  SEARCH            WAKE_DROPPED

key 217  SEARCH            WAKE_DROPPED

key 228  POUND

key 227  STAR

key 231  CALL              WAKE_DROPPED

key 61   CALL              WAKE_DROPPED

key 232  DPAD_CENTER       WAKE_DROPPED

key 108  DPAD_DOWN         WAKE_DROPPED

key 103  DPAD_UP           WAKE_DROPPED

key 102  HOME              WAKE

key 105  DPAD_LEFT         WAKE_DROPPED

key 106  DPAD_RIGHT        WAKE_DROPPED

key 115  VOLUME_UP

key 114  VOLUME_DOWN

key 116  POWER             WAKE

key 212  CAMERA

key 16    Q

key 17    W

key 18    E

key 19    R

key 20    T

key 21    Y

key 22    U

key 23    I

key 24    O

key 25    P

key 26   LEFT_BRACKET

key 27   RIGHT_BRACKET

key 43   BACKSLASH

key 30    A

key 31    S

key 32    D

key 33    F

key 34    G

key 35    H

key 36    J

key 37    K

key 38    L

key 39   SEMICOLON

key 40   APOSTROPHE

key 14   DEL

key 44    Z

key 45    X

key 46    C

key 47    V

key 48    B

key 49    N

key 50    M

key 51   COMMA

key 52   PERIOD

key 53   SLASH

key 28   ENTER

key 56   ALT_LEFT

key 100  ALT_RIGHT

key 42   SHIFT_LEFT

key 54   SHIFT_RIGHT

key 15   TAB

key 57   SPACE

key 150  EXPLORER

key 155  ENVELOPE       

key 12   MINUS

key 13   EQUALS

key 215   AT

android按鍵的處理是Window Manager負責,主要的對映轉換實現在android原始碼frameworks/base/libs/ui/EventHub.cpp此檔案處理來自底層的所有輸入事件,並根據來源對事件進行分類處理,對於按鍵事件,它首先記錄驅動名稱,再獲取環境變數ANDROID_ROOT為系統路徑,預設是/system,定義在android原始碼/system/core/rootdir/init.rc檔案中。然後查詢路徑為"系統路徑/usr/keylayout/驅動名稱.kl"的按鍵對映檔案,如果不存在則預設用路徑為"系統路徑/usr/keylayout/qwerty.kl"。這個預設的按鍵對映檔案,對映完成後再把經對映得到的android按鍵碼值發給上層應用程式。所以我們可以在核心中定義多個按鍵裝置,然後為每個裝置設定不同的按鍵對映檔案,不定義則會預設用qwerty.kl。

有了上面的分析,我們不難發現,上述更改是不可能有效果的,只能越改越不能用。相反,如果僅僅只需要更改某個按鍵的功能,根本就不用改核心,只需重新定義android系統的gpio-keys.kl即可。

進android系統後找到該檔案,


可以發現,在在gpio-keys.kl和qwerty.kl兩個檔案,因此a ndroid系統會從gpio-keys.kl中查詢鍵值進行對映。裡面內容如下:

key 102  HOME              WAKE

key 158   BACK              WAKE

這不正是開發板上android的兩個功能鍵嗎?對於第三個POWER鍵,是用於開關機的,不屬android功能鍵範疇,因此無須對映。

修改按鍵功能有兩種方法:

方法一:直接修改gpio-keys.kl檔案的內容,比如我們想將BACK鍵改成右鍵,我們只需做如下修改:

key 102  HOME              WAKE

key 106  DPAD_RIGHT       WAKE

注意上面的106以及按鍵名稱,都是從qwerty.kl裡面找的,千萬不要在linux核心的input.h中查詢。DPAD_RIGHT不能更改為RIGHT,否則android無法識別。

方法二:修改核心中mx53_loco.c中的陣列如下:

static struct gpio_keys_button loco_buttons[] = {

         GPIO_BUTTON(MX53_nONKEY,KEY_POWER, 1, "power", 0),

         //GPIO_BUTTON(USER_UI1,KEY_BACK, 1, "back", 0),

         GPIO_BUTTON(USER_UI2,KEY_HOME, 1, "home", 0),

         GPIO_BUTTON(USER_UI1,KEY_RIGHT, 1, "right", 0),//test by lqm.

};

再修改上面的gpio-keys.kl檔案,內容如下:

key 102  HOME              WAKE

key 106  DPAD_RIGHT       WAKE

方法二相比方法一,多了一個步驟,但是推薦採用方法二,這樣更容易理解,也不易混淆。