1. 程式人生 > >Android Linux 裝置驅動

Android Linux 裝置驅動

今天記錄下如何寫一個 Android 下的裝置字元驅動(也算是工作總結),下面假設有一個 test 裝置 內容如下:

一、驅動模組初始化

//驅動載入
static int __init test_init(void){  
     //本函式中就可以做一些初始化操作,如申請 工作佇列等;若掛載在 平臺裝置上面,則新增程式碼如下
    if (platform_driver_register(&test_driver)) {
            printk(KERN_ERR "add test driver error\n");
            return -1;
    }   
    return 0;
}

static
void __exit test_exit(void){ platform_driver_unregister(&test_driver); } //模組的載入和解除安裝 會呼叫 test_init 和 test_exit 函式 module_init(test_init); module_exit(test_exit); //下面這個LICENSE是必須要寫的 MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("test device driver"); MODULE_AUTHOR("loumihua");

test_init 函式就是驅動模組的初始化工作,
1.如果驅動(就是這個驅動沒有對應的具體裝置等)不需要掛載到 平臺裝置 或者 i2c 裝置,這裡就可以進行初始化操作,如申請工作佇列 、建立裝置節點;
2.如果本裝置(這個驅動要操作的裝置)是掛載在 i2c 裝置上面,則通過函式 i2c_add_driver(&test_driver) 進行匹配,成功後在 對應的 probe 函式中進行初始化
3.如果本裝置(這個驅動要操作的裝置)是掛載在 平臺裝置上面,則通過函式 platform_driver_register 註冊裝置,成功後再 probe 函式中進行初始化

//匹配的 compatible,注意這個名稱一定要和dts 中寫的一致
#ifdef CONFIG_OF
static const struct of_device_id test_of_match[] = {
    {.compatible = "testsensor"},
    {}
};
#endif

//電源管理 對裝置上電和下電,無裝置的驅動是不需要的
#ifdef CONFIG_PM
static const struct dev_pm_ops test_pm_ops = {
    .suspend = test_suspend,
    .resume = test_resume,
};
#endif
static struct platform_driver test_driver = { .driver = { .owner = THIS_MODULE, .name = "test_sensor", #ifdef CONFIG_PM .pm = &test_pm_ops, #endif //dts 中通過 compatible 屬性進行匹配 #ifdef CONFIG_OF .of_match_table = test_of_match, #endif }, .probe = test_probe, //當 通過compatible或id(沒有實現)匹配成功後呼叫該函式 .remove = test_remove, };

如果是裝置驅動 宣告一個 platform_driver 的型別 test_driver, 若是掛載在 i2c 上面的裝置,宣告 i2c_driver 結構體資料struct i2c_driver test_driver = { … }; 其中 掛載在 i2c 上的裝置值 一個裝置需要通過 i2c匯流排傳輸資料,如Android 手機的加速度、地磁等一般都是掛載在 i2c 總線上,通過 i2c 匯流排與cpu進行通訊(資料傳輸);

當 註冊 成功後,會呼叫 probe 函式(如果驅動進入 init 函式後,沒有進入到該函式,主要去看 dts 配置是否正確,還有上面的compatible 對應的名稱等 )

static int test_probe(struct platform_device *pdev)
{

    int err;
    struct workqueue_struct *mworkque = NULL;

    //一般會定義一個 test_device 結構體,該結構體物件 test_dev 中存放常用的變數
    test_dev = kzalloc(sizeof(struct test_device), GFP_KERNEL); 
    if (test_dev == NULL) {
        printk(KERN_ERR "allocate memory for test device fail\n");
        return -ENOMEM;
    }

    // 下面部分其實就是對 結構體物件test_dev 中變數進行初始化
    test_dev->delay_ns = 0;
    test_dev->latency_ns = 0;
    test_dev->first_enable = true;
    //如果是 具體的裝置,一般裝置是需要設定初始化的,如加速度裝置需要配置暫存器,設定量程等   
    err = test_init_device();
    if(err){
        printk(KERN_ERR "test init device fail\n");
        goto sen_event_fail;
    }
    atomic_set(&test_dev->enable, 1);
    mutex_init(&test_dev->mutex_lock);

    /*設定工作佇列,註冊中斷(若裝置是中斷方式),或者定時器實現輪詢方式*/
    mworkque = create_workqueue("test_workqueue");
    test_dev->workque = mworkque;

    if(!test_dev->workque){
        printk(KERN_ERR "test create work queue err\n");
        goto err_workque;
    }

    INIT_WORK(&test_dev->work, test_work_handler);  
    //初始化定時器
    hrtimer_init(&test_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    test_dev->timer.function = test_timer_func;
    test_dev->debounce_time = ktime_set(0, 50000000);
    //註冊input device
    test_dev->input_dev = input_allocate_device();
    err = input_register_device(test_dev->input_dev);            
    if (err < 0) {
        printk("could not register input device\n");
    }

     //register misc device
     err = misc_register(&test_misc_device);
     if(err){        
         printk(KERN_ERR "%s: can not register sensor misc device\n", __func__);
         goto err_misc_register;
     }

     //在sys目錄下建立節點
     err = sysfs_create_group(&test_misc_device.this_device->kobj, &test_attribute_group);
    if (err) {
        printk(KERN_ERR "sysfs create fail\n");
        goto err_sysgroup;
    }
     return 0;

err_sysgroup:
       sysfs_remove_group(&test_misc_device.this_device->kobj, &test_attribute_group);     
err_misc_register:
       misc_deregister(&test_misc_device);     
err_workque:
       destroy_workqueue(test_dev->workque);    
     return -1;
}

對於Android 下的 linux 裝置,probe 中 一般為下面操作
1.裝置進行暫存器設定等初始化;對宣告的裝置對應的結構體分配記憶體並進行初始化
2.建立工作佇列,然後根據裝置使用中斷方式或輪詢方式,中斷則註冊中斷;輪詢建立定時器;在工作任務中上報資料
3.建立misc 裝置及屬性節點
4.一般會註冊input 裝置,主要是驅動中裝置向上層上報資料時,其實是將資料存到input一個快取中

input 是一個event 驅動,上層會通過節點input節點讀取 裝置上報的資料,在傳輸到 Android java層

下面是 宣告 miscdevice 裝置並定義對應的 檔案操作

static struct file_operations test_fops = {
    .owner      = THIS_MODULE,
    .open       = test_open,
    .read       = test_read,
    .release    = test_release,
    .unlocked_ioctl = test_ioctl,
 #ifdef CONFIG_COMPAT
    .compat_ioctl   = test_ioctl,
 #endif
};

static struct miscdevice test_misc_device = {
    .minor  = MISC_DYNAMIC_MINOR,
    .name   = TEST_MISC_DEV_NAME,
    .fops   = &test_fops,
};

下面是對應的屬性節點

static DEVICE_ATTR(testactive, S_IWUSR | S_IRUGO, test_show_active, test_store_active);

static struct attribute *test_attribute[] = {
    &dev_attr_testactive.attr,
    NULL,
};

static struct attribute_group test_attribute_group = {
    .attrs = test_attribute
};

上層(一般是hardware層)對裝置進行操作可以通過屬性節點,或者通過 ioctl 函式對裝置進行通訊(即讀寫裝置,如加速度);當上層(hardware、jni、java)去通過節點或ioctl去操作裝置是,有些敏感的操作會涉及到 selinux 許可權問題,需要在許可權檔案中新增許可權

下面是函式的實現

//work佇列回撥函式,主要是上報資料,如果是輪詢就再次開啟定時器,中斷就enable中斷,這裡是輪詢
static void test_work_handler(struct work_struct *work){

    int err = 0;
    int pdata[2] = {0};
    u32 data;
    u32 val;
    int en;
    struct timespec time;
    struct temp_data tpdata;
    /*report data*/
    int64_t cur_ns ,m_pre_ns = 0;
    int64_t delay_ms = 100;

    time.tv_sec = time.tv_nsec = 0;
    get_monotonic_boottime(&time);
    cur_ns = time.tv_sec*1000000000LL+time.tv_nsec;

    m_pre_ns = test_dev->time;
    test_dev->time = cur_ns;

    test_read_data(addr_w, cmd_t0, addr_r,pdata);

    test_dev->t0 = val;
    tpdata.x = val;
    tpdata.status = 0;
    tpdata.reserved = 0;
     tpdata.handle = test_dev->handle;
//上報資料
    while((cur_ns - m_pre_ns) >= delay_ms*1800000LL) {  
        m_pre_ns += delay_ms*1000000LL; 
        tpdata.timestamp = m_pre_ns;        
        test_report_data(&tpdata);
    }
    //啟動定時器
    if(en){
        hrtimer_start(&test_dev->timer, test_dev->debounce_time, HRTIMER_MODE_REL);
    }
    return;
}
//定時器時間到,就啟動一個工作 將其加入到工作佇列,然後就會調到上面這個函式
static enum hrtimer_restart test_timer_func(struct hrtimer *data)
{       
    queue_work(test_dev->workque, &test_dev->work); 
    return HRTIMER_NORESTART;
}
//下面是屬性節點的實現,這個是從屬性節點中獲取值,一般在終端中 cat 這個節點
static ssize_t test_show_active(struct device *dev, struct device_attribute *attr, char *buf)
{
    int err = 0;
    int enable = 0;
    enable = atomic_read(&test_dev->enable);
    //通過 sprintf 函式將enable 的值傳到使用者空間 ,在終端中可以看到
    err = sprintf(buf,"%d\n",enable);

    if(err){
        printk(KERN_ERR "test active fail\n");
    }
    return err;
}
//下面是屬性節點的實現,這個是向屬性節點中寫值,一般在終端中 eaho 這個節點
static ssize_t test_store_active(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{   
    int ret = 0;
    unsigned int enable;
    //獲取到使用者空間寫入的值,這個值是在buf中,通過格式化轉化到 enable 中
    ret = sscanf(buf, "%d", &enable);
    //這樣寫主要是當Android上層enable(開啟)裝置(如sensor)時,開啟定時器進行上報資料
    if(enable == 1){
        atomic_set(&test_dev->enable,1);
        hrtimer_start(&test_dev->timer, test_dev->debounce_time, HRTIMER_MODE_REL);
    }else if(enable == 0)
    {//上層關閉裝置,不在上報資料
        atomic_set(&test_dev->enable,0);
    }
    //下面這個返回值一定要這樣寫,否則會報錯   
    return count;
}

//上報資料
static int test_report_data(struct test_data *data){

    int err = 0;    
    mutex_lock(&test_dev->mutex_lock);
    ....... 
    input_report_abs(data->input_dev, ABS_DISTANCE, event);
    input_sync(data->input_dev);
    mutex_unlock(&test_dev->mutex_lock);    

    return err;
}

//當上層呼叫 ioctl 函式時,會呼叫到該函式中,主要也是獲取資料或者向裝置寫值操作裝置
static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int err = 0;
    void __user *data;
    switch(cmd){
        //如通過ioctl函式進行enable裝置      
        case TESTDEV_IOCTL_ENABLE:{
           int ret = 0;
           int buf = 0;
           data = (void __user *)arg;   //使用者空間資料在arg
           //從使用者空間獲取資料,呼叫下面這個函式,資料傳到 buf 中
           ret = copy_from_user(&buf, data, sizeof(buf));
           if (ret)
            {
                err = -EINVAL;
                break;
            }
           ret = test_enable(buf);
           if(ret){
               printk(KERN_ERR "test calibrate err\n");
               err = -EINVAL;
           }
        }  
        break;      
        //ioctl 函式讀取資料
        case TESTDEV_IOCTL_READ_RAWDATA:{
            int ret = 0;
            u32 val;
            data = (void __user *)arg;  //使用者空間資料
            //獲取資料  
            ret = test_get_data(&val);

            if(ret < 0){
                printk(KERN_ERR "get test data fail\n");
                err = -EINVAL;
                break;
            }
            //將獲取的資料通過下面函式copy到使用者空間
            ret = copy_to_user(data, &val, sizeof(val));
            if (ret < 0)
            {
                err = -EINVAL;
                break;
            }
        }
        break;          
    }
    return err;
}

//檔案操作函式
static int test_open(struct inode* inode, struct file* filp)
{
    nonseekable_open(inode, filp);
    return 0;
}

static int test_release(struct inode* inode, struct file* filp){
    return 0;
}

static ssize_t test_read(struct file *file, char __user *buffer,
              size_t count, loff_t *ppos)
{
    ssize_t read_cnt = 0;
    ...
    return read_cnt;
}

//下面主要是裝置的電源操作,如當手機滅屏時,對加速度等進行下電操作(僅僅是舉例)
 #ifdef CONFIG_PM
static int test_suspend(struct device *dev){    
    int ret = 0;
    //complete device power down
    return ret;
}

static int test_resume(struct device *dev){ 
    int ret = 0;
    //complete device power up  
    return ret;
}
 #endif

上面就是一個 Android 下 簡單裝置的驅動,不過僅僅是框架思路,如果要實現一個裝置驅動,可以在此思路基礎上,比照一個完整的驅動進行修改即可