天天看點

Android Fingerprint -- HAL層的初始化工作

轉自: http://www.cnblogs.com/happy-leon/p/5655614.html   http://blog.csdn.net/liuxd3000/article/details/42424179

序文:如何調用Hal層庫檔案

每個Hal層庫檔案有一個入口,即HAL_MODULE_INFO_SYM,上層在調用hal層庫檔案時會在/system/lib/hw/下面尋找對應庫檔案,找到對應庫檔案後便從入口HAL_MODULE_INFO_SYM調用Hal層裡面的open, init, write, read等接口,Hal層再通過這個接口去讀寫裝置節點。

一、 fingerprint.default.so

1、上一篇講 Frameworks層初始化指紋子產品的時候,Fingerprintd 調用hw_get_module函數擷取了一個fingerprint_module_t類型的資料結構。 這個就是在fingerprint.default.so中,由指紋晶片廠商填充實作的。

//根據名稱擷取指紋hal層子產品。hw_module這個一般由指紋晶片廠商根據 fingerprint.h實作
if ( != (err = hw_get_module(FINGERPRINT_HARDWARE_MODULE_ID, &hw_module))) {
    ALOGE("Can't open fingerprint HW Module, error: %d", err);
    return ;
}
           

我們繼續往下看fingerprint.default.so。

static struct hw_module_methods_t fingerprint_module_methods = {
.open = fingerprint_open,
};

fingerprint_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag                = HARDWARE_MODULE_TAG,
        .module_api_version = FINGERPRINT_MODULE_API_VERSION_2_0,
        .hal_api_version    = HARDWARE_HAL_API_VERSION,
        .id                 = FINGERPRINT_HARDWARE_MODULE_ID,
        .name               = "Fingerprint HAL",
        .author             = "xxx",
        .methods            = &fingerprint_module_methods,
        .dso                = NULL
    },
};
           

hw_get_module就是根據.id = FINGERPRINT_HARDWARE_MODULE_ID這個id來找到對應的fingerprint_module_t。hal層可能有多個指紋晶片廠商的子產品,可以根據這個id來做相容,選擇性的加載不同的指紋模組。

2、fingerprintd得到了相應的fingerprint_module_t,之後就會去調用它的open函數。我們來看一下初始化指紋最核心的fingerprint_open。

static int fingerprint_open(const hw_module_t* module, const char __unused *id,
                        hw_device_t** device)
{
    ALOGV("fingerprint_open");

    if (device == NULL) {
        ALOGE("NULL device on open");
        return -EINVAL;
    }

    fingerprint_device_t *dev = (fingerprint_device_t *)
        malloc(sizeof(fingerprint_device_t));
    memset(dev, , sizeof(fingerprint_device_t));

    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = FINGERPRINT_MODULE_API_VERSION_2_0;
    dev->common.module = (struct hw_module_t*) module;
    dev->common.close = fingerprint_close;

    dev->pre_enroll = fingerprint_pre_enroll;
    dev->enroll = fingerprint_enroll;
    dev->post_enroll = fingerprint_post_enroll;
    dev->get_authenticator_id = fingerprint_get_auth_id;
    dev->cancel = fingerprint_cancel;
    dev->remove = fingerprint_remove;
    dev->set_active_group = fingerprint_set_active_group;
    dev->authenticate = fingerprint_authenticate;
    dev->set_notify = set_notify_callback;
    dev->notify = NULL;

    g_device = dev;
    if(g_device == NULL) {
        ALOGV("g_device is NULL");
    } else {
        ALOGV("g_device is not NULL");
    }

    *device = (hw_device_t*) dev;
    
    hal_init(mDevice);

    return ;
}
           

就是填充實作android 在fingerprint_device.h定義fingerprint_device_t需要實作的這些接口。然後賦給指針device。上層,也就是fingerprintd,就能用這個device來操作hal層的指紋子產品了。

二、重要的hal_init函數。

hal init有如下幾個重要的工作要做:

1、 hal_device_open()的工作很簡單,就是打開指紋驅動層的裝置節點,然後初始化一個用來接收驅動層消息的消息隊列。當然在此之前,指紋的驅動層肯定已經正常probe,生成了相應的裝置節點。

fd = open(/dev/xxx_fp, O_RDWR);
...
TAILQ_INIT(&head);
...
           

2、檢查指紋晶片是否已經正常工作了(在驅動層probe階段,就會給晶片上電複位,并且加載相應的指紋固件和配置,正常指紋晶片已經開始正常工作了)。如果沒有正常工作,就會給晶片複位。将其重新拉到正常的工作狀态。

err = hal_get_fw_info(&download_fw_flag);
    if (err != GF_SUCCESS) {
        LOG_E(LOG_TAG "[%s] failed to get firmware info", __func_);
    }
    if (!download_fw_flag) {
        hal_reset_chip();
    }
           

3、與指紋ta建立session,然後調用接口初始化指紋ta。

result = TEEC_OpenSession(g_context, g_session,
             &UUID, TEEC_LOGIN_PUBLIC, NULL, &operation, NULL);
...

TEEC_Operation operation = {  };
operation.paramTypes = GF_CMD_TEEC_PARAM_TYPES;
operation.params[].tmpref.buffer = GF_CMD_INIT;
operation.params[].tmpref.size = len;
ret = TEEC_InvokeCommand(g_session, GF_OPERATION_ID, &operation, NULL);
...
           

對android指紋子產品不了解的人可能會問指紋ta是什麼?我們先說一下TEE, Trusted Execution Environment (TEE)是主要晶片廠商(mtk,高通等)提供的一個安全的硬體運作環境。指紋ta就是運作在這樣一個硬體安全環境下的程式。它保證了指紋敏感資料的安全性。

4、與指紋驅動層建立通信。這裡給大家看一種基于netlink,巧妙而簡潔的方式。

4.1.1、通信的接收端(hal層)做了哪些處理?我們往下看

//初始化信号量 g_sem,配合消息隊列,用于從消息接受者hal_netlink_recv
//到消息處理者handle_thread的消息傳遞
if ( != sem_init(&g_sem, , )) {
    LOG_E(LOG_TAG, "[%s] init semaphore failed", __func__);
    break;
}

//消息處理線程handle_thread
if (pthread_create(&g_handle_thread, NULL, handle_thread, NULL) != ) {
    LOG_E(LOG_TAG, "[%s] pthread_create failed", __func__);
    break;
}
//用ioctl的方式将netlink描述符g_netlink_route傳遞給驅動層。
//這樣驅動層就能用這個g_netlink_route與hal層建立消息管道
if (ioctl(fd, GF_IOC_INIT, &g_netlink_route) != ) {
    LOG_E(LOG_TAG, "[%s] GF_IOC_INIT ioctl failed", __func__);
    err = GF_ERROR_OPEN_DEVICE_FAILED;
    break;
}
LOG_I(LOG_TAG, "[%s] g_netlink_route = %d", __func__, g_netlink_route);

//消息接收線程hal_netlink_recv
if (pthread_create(&g_netlink_thread, NULL, hal_netlink_recv, NULL) != ) {
    LOG_E(LOG_TAG, "[%s] pthread_create failed", __func__);
    break;
}
           

4.1.2、我們先看消息接收線程hal_netlink_recv做了什麼。

/* 初始化netlink并binder 下面這些都是netlink的标準流程*/
        g_netlink_sock_id = socket(AF_NETLINK, SOCK_RAW, g_netlink_route);
        if (g_netlink_sock_id < ) {
            break;
        }

        memset(&local, , sizeof(struct sockaddr_nl));
        local.nl_family = AF_NETLINK;
        local.nl_pid = getpid();/*local process id*/

        local.nl_groups = ;

        ret = bind(g_netlink_sock_id, (struct sockaddr*) &local, 
                    sizeof(struct sockaddr_nl));
        if (ret != ) {
            break;
        }
        

        /* send init message */
        memset(&dest, , sizeof(struct sockaddr_nl));
        dest.nl_family = AF_NETLINK;
        dest.nl_pid = ; /*destination is kernel so set to 0*/
        dest.nl_groups = ;

        nlh = (struct nlmsghdr *) malloc(NLMSG_SPACE(MAX_NL_MSG_LEN));
        if (NULL == nlh) {
            LOG_E(LOG_TAG, "[%s] nlh out of memery", __func__);
            break;
        }
        nlh->nlmsg_len = NLMSG_SPACE(MAX_NL_MSG_LEN);
        nlh->nlmsg_pid = getpid();
        nlh->nlmsg_flags = ;
        strcpy(NLMSG_DATA(nlh), "GF");

        iov.iov_base = (void*) nlh;
        iov.iov_len = nlh->nlmsg_len;

        memset(&msg, , sizeof(struct msghdr));
        msg.msg_iov = &iov;
        msg.msg_iovlen = ;
        msg.msg_name = (void*) &dest;
        msg.msg_namelen = sizeof(struct sockaddr_nl);
        
        //發送一個包含pid的消息給驅動層,相當于握手,告訴驅動層,我這邊已經準備ok了。
        if (sendmsg(g_netlink_sock_id, &msg, ) < ) {
            break;
        }
        LOG_D(LOG_TAG, "[%s] send init msg to kernel", __func__);

        /* 開啟一個循環,接收來自驅動層的消息 */
        memset(nlh, , NLMSG_SPACE(MAX_NL_MSG_LEN));
        
        while () {
            //LOG_D(LOG_TAG, "here wait message from kernel");
            ret = recvmsg(g_netlink_sock_id, &msg, );
            if (ret < ) {
                LOG_E(LOG_TAG, "[%s] recvmsg failed, ret %d", __func__, ret);
                continue;
            }
            if ( == ret) {
                LOG_E(LOG_TAG, "[%s] recvmsg failed, ret %d", __func__, ret);
                continue;
            }
            value = *((char *) NLMSG_DATA(nlh));
            //根據消息類别做處理
            if (GF_NETLINK_TEST == value) {
                LOG_D(LOG_TAG, "[%s] received GF_NETLINK_TEST command", __func__);

            } else if (NETLINK_IRQ == value || NETLINK_SCREEN_OFF == value
                    || NETLINK_SCREEN_ON == value) {
                //如果是中斷消息,或者亮滅屏事件,就把消息值push到消息隊列。
                //然後post信号量,讓消息處理線程去處理了。
                enqueue(value);
                sem_post(&g_netlink_sem);
                LOG_D(LOG_TAG, "[%s] send message : %d", __func__, value);
            } else {
                LOG_E(LOG_TAG, "[%s] wrong netlink command %d", __func__, value);
            }
        }
           

4.1.3、再看處理線程,等待信号量,收到之後就從消息隊列裡邊取出消息。然後根據不同的值調用相應的處理函數。

void *handle_thread(void *handle) {

    while () {
        sem_wait(&g_netlink_sem);

        err = dequeue(&value);
        if (err != GF_SUCCESS) {
            continue;
        }

        if (GF_NETLINK_IRQ == value) {
            hal_irq();

        } else if (GF_NETLINK_SCREEN_OFF == value) {
            hal_screen_off();

        } else if (GF_NETLINK_SCREEN_ON == value) {
            hal_screen_on();
        }
    } 
}
           

hal層的設計很清晰。由于中斷來的很快,頻率也很高,是以這邊使用快速接收中斷,緩存起來,再慢慢處理的方式進行中斷事件,類似于核心中斷上下文的處理方式。

4.2.1、講到這裡,肯定對驅動層怎麼發送接收消息産生了好奇?本來打算在寫驅動層的地方講的,但是這樣這部分内容就中斷了,還是現在這裡寫完吧。很簡單,直接看下面的代碼注釋就能了解。

static int netlink_init(void)
{
    struct netlink_kernel_cfg cfg;
    memset(&cfg, , sizeof(struct netlink_kernel_cfg));
    cfg.input = netlink_recv;
    //建立netlink 驅動層的接收hal層消息函數,注意NETLINK_ROUTE要與hal層一緻。
    g_dev->nl_sk = netlink_kernel_create(&init_net, NETLINK_ROUTE, &cfg);
}
           

4.2.2、接收消息的處理:

static void netlink_recv(struct sk_buff *__skb)
{
    
    skb = skb_get(__skb);

    //消息大于5byte才做處理

    if (skb->len >= NLMSG_SPACE()) {
        nlh = nlmsg_hdr(skb);
        memcpy(str, NLMSG_DATA(nlh), sizeof(str));
        //拿到了hal層穿下來的pid,儲存起來。
        g_gf_dev->pid = nlh->nlmsg_pid;
        
    } else {
        debug(ERR_LOG, "[%s] : not enough data length\n", __func__);
    }

    kfree_skb(skb);
}
           

4.2.3、收到中斷或者亮滅屏事件,就調用netlink_send通知hal層:

void netlink_send(const int command)
{
    //netlink kernel層發送消息的典型流程,就是構造一個消息結構體,然後
    //用api netlink_unicast發出去
    skb = alloc_skb(MAX_NL_MSG_LEN, GFP_ATOMIC);
    if (skb == NULL) {
        gf_debug(ERR_LOG, "[%s] : allocate skb failed\n", __func__);
        return;
    }

    nlh = nlmsg_put(skb, , , , MAX_NL_MSG_LEN, );
    if (!nlh) {
        kfree_skb(skb);
        return;
    }

    NETLINK_CB(skb).portid = ;
    NETLINK_CB(skb).dst_group = ;
    //消息類型的指派,中斷,亮滅屏等
    *(char *)NLMSG_DATA(nlh) = command;
    ret = netlink_unicast(g_gf_dev->nl_sk, skb, g_gf_dev->pid, MSG_DONTWAIT);
}
           

這樣,hal層和驅動層就建立好了通信管道。以後中斷等事件就能從驅動層報給hal層,hal層會根據事件類型,做相應處理。

5、調用ta init,初始化ta。

6、開啟看門狗,監聽ic狀态,如果ic挂了就重新開機ic。

至此,hal層就算初始化完畢了。接下來,上層就可以開始注冊指紋了。

繼續閱讀