Android 虛擬按鍵驅動實現


最近將Android touchscreen virtualkey驅動,向上層report keyvalue 改成 向上層report X,Y的坐標值。對sysfs文件系統進行了一番研究。

virtualkey 是基於sysfs文件系統實現的。上層要想訪問到virtualkey,必須在sys下面實現這樣的文件結點:

/sys/board_properties/virtualkeys.{deviceName}

 

virtualkey的實現方式為什么必須基於sysfs文件結點,這個是由上層的調用決定的。

在frameworks層,InputManger.java這個函數里定義了上層是怎么訪問virtualkey的。

public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) {
            ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>();
            
            try {
                FileInputStream fis = new FileInputStream(
                        "/sys/board_properties/virtualkeys." + deviceName);
    /*....*/
                }
     /*....*/
}

 

所以,要實現sys文件結點有2個函數

int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr);
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

這里我們使用的是

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

因為這個函數kobject對應的目錄里,還可以創建子目錄:virtualkeys.{deviceName}Linux內核里是用attribute_group來實現子目錄.

 

第一個參數是struct kobject *kobj,KobjectLinux內核里的定義如下:

struct kobject {
    const char        * k_name;
    char            name[KOBJ_NAME_LEN];
    struct kref        kref;
    struct list_head    entry;
    struct kobject        * parent;
    struct kset        * kset;
    struct kobj_type    * ktype;
    struct dentry        * dentry;
    wait_queue_head_t    poll;
};

一個kobject對應sysfs里的一個目錄,而目錄下的文件就是由sysfs_opsattribute來實現的,其中,attribute定義了kobject的屬性,在sysfs里對應一個文件,sysfs_ops用來定義讀寫這個文件的方法。

基於此理解,所以board_properties目錄,就定義為kobject:

struct kobject *properties_kobj;
    
properties_kobj = kobject_create_and_add("board_properties", NULL);

 這樣就實現了sys/board_properties目錄.

 

因為目錄下的文件是由sysfs_opsattribute來實現的,所以目錄下的文件virtualkeys.{deviceName}將定義成attribute,那么對attribute的fops怎么定義呢?

fops是定義了對以“virtualkeys.{deviceName}”為name的attribute的操作函數。 在內核的定義中,attribute 的fops就是對應每個attribute自己的show/store函數.

內核定義了一個kobj_attribute 的結構體。

struct kobj_attribute {
    struct attribute attr;
    ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
            char *buf);
    ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
             const char *buf, size_t count);
};

這樣,每一個attribute就會有各自屬於自己的show/store函數,這樣就極大的提高了靈活性。

可是,sysfs是通過kobject里的kobj_type->sysfs_ops來讀寫attribute的,那如果要利用kobj_attribute中的show/store來讀寫attribute的話,就必須在kobj_type->sysfs_ops里指定。

 kobj_attribute是內核提供給我們的一種更加靈活的處理attribute的方式。只有當我們使用kobject_create來創建kobject時,使用kobj_attribute才比較方便。

而在此處,由上面的代碼可知,我們在定義

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

這個函數的第一個參數kobject時正是使用了

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)

來創建kobject,所以我們在此處使用kobj_attribute會很方便。

所以在此處我們對kobj_attribute的定義是:

static struct kobj_attribute virtual_keys_attr = {
    .attr = {
        .name = "virtualkeys.{deviceName}",    //這里的devicename一定要與設備的devicename一致,否則訪問不到touchscreen的virtualkey
        .mode = S_IRUGO,
    },
    .show = &virtual_keys_show,  //相當於read操作
};

show相當於read操作,store相當於write操作。

這里我們並沒有定義store操作,上層只要read virtualkey的X,Y坐標值就可以了。不需要write操作。

同時一個kobject可能會對應多個attribute,所以同時定義了一個attribute的數組,來管理attribute。

struct attribute {
    const char        * name;
    struct module     * owner;
    mode_t            mode;
};

struct attribute_group {
    const char        * name;
    struct attribute    ** attrs;
};
static struct attribute *properties_attrs[] = {
        &virtual_keys_attr.attr,
        NULL
}; //properties_attrs的最后一項一定要賦為NULL,否則會造成內核oops
static struct attribute_group properties_attr_group = {
    .attrs = properties_attrs,
};

這樣函數

int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp);

的2個參數就全部定義好了,可以在驅動的probe函數里調用virtualkey init類的函數就可以了。

下面是完整的代碼(以FT5X0X的touchscreen為例):

#define FT5X0X_KEY_HOME    102
#define FT5X0X_KEY_MENU    139
#define FT5X0X_KEY_BACK    158
#define FT5X0X_KEY_SEARCH  217


static ssize_t virtual_keys_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    return sprintf(buf,
    __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_HOME) ":0:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_MENU) ":160:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_BACK) ":300:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_SEARCH) ":450:849:120:40"
     "\n");
}

static struct kobj_attribute virtual_keys_attr = {
    .attr = {
        .name = "virtualkeys.ft5x0x_ts",    //FT5X0X_NAME 一定要與觸摸屏設備名稱一致,不然會找不到指定的sys文件
        .mode = S_IRUGO,
    },
    .show = &virtual_keys_show,
};


static struct attribute *properties_attrs[] = {
        &virtual_keys_attr.attr,
        NULL
};

static struct attribute_group properties_attr_group = {
    .attrs = properties_attrs,
};

static void ft5x0x_ts_virtual_keys_init(void)
{
    int ret;
    struct kobject *properties_kobj;
    
    properties_kobj = kobject_create_and_add("board_properties", NULL);
    if (properties_kobj)
        ret = sysfs_create_group(properties_kobj,  &properties_attr_group);
    if (!properties_kobj || ret)
        pr_err("failed to create board_properties\n");    
}
static int ft5x0x_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    /*...*/

    ft5206_ts_virtual_keys_init();
 
  /*...*/
set_bit(KEY_HOME, input_dev
->keybit); set_bit(KEY_MENU, input_dev->keybit); set_bit(KEY_BACK, input_dev->keybit); set_bit(KEY_SEARCH, input_dev->keybit);//這里我把4個virtualkey的位圖初始化設置了一下,開始這里忘了,其它地方都正確,4個virtualkey還是沒效果 /*...*/
}

每一個虛擬按鍵有六個參數:

0x01: A version code. Must always be 0x01.
<Linux key code>: The Linux key code of the virtual key.
<centerX>: The X pixel coordinate of the center of the virtual key.
<centerY>: The Y pixel coordinate of the center of the virtual key.
<width>: The width of the virtual key in pixels.
<height>: The height of the virtual key in pixels.
#define FT5X0X_KEY_HOME    102
#define FT5X0X_KEY_MENU    139
#define FT5X0X_KEY_BACK    158
#define FT5X0X_KEY_SEARCH  217 //定義key code
    __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_HOME) ":0:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_MENU) ":160:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_BACK) ":300:849:120:40"
     ":" __stringify(EV_KEY) ":" __stringify(FT5X0X_KEY_SEARCH) ":450:849:120:40" //定義X,Y,width,height

下面還要配置 /system/usr/keylayout .kl文件和.kcm文件

如果沒有指定,Android會自動android系統會自動選擇默認的\android\sdk\emulator\keymaps\目錄下的qwerty.kl和qwerty.kcm文件。

 這個就是利用了 android 系統默認的 qwerty.kl 映射表。如果需要自己建立“驅動名稱.kl”文件,則可以按照 qwerty.kl

文件的樣式自己建立,把“驅動名稱.kl”文件放放置在 android 文件系統中/system/usr/keylayout 目錄下即可。如果想要android 源碼編譯過程中將“驅動名稱.kl”編譯到文件系統中,則只需將文件放置到 android 源碼
/vender/sec/sec_proprietary/utc100/keychars 目錄中,並修改該目錄下的 Android.mk 文件。

 

這是Android 系統對底層按鍵的處理方法

Android 按鍵的處理是由 Window Manager 負責,主要的鍵值映射轉換實現是在 android 源代碼

frameworks/base/libs/ui/EventHub.cpp 此文件處理來自底層的所有輸入事件,並根據來源對事件進行分類處理,

對於按鍵事件處理過程如下:
a)記錄驅動名稱(例如上面的驅動程序的驅動名稱為ft5x0x_ts)
b)獲取環境變量 ANDROID_ROOT 為系統路徑(默認是/system,在 android 源代碼 system/core/rootdir/init.rc 文件中)
c)查找路徑為“system/usr/keylayout/ft5x0x_ts.kl”的按鍵映射文件,如果該文件(“ft5x0x_ts.kl”)不存在,則默認用路徑為“系統路徑/usr/keylayout/qwerty.kl”文件中的鍵值映射表。

這樣關於virtualkey的底層驅動就實現了。同時基於此驅動,按4個virtualkey時,如果硬件上實現了馬達,並且上層有實現按鍵振動功能,就會有振動產生。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM