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 下 简单设备的驱动,不过仅仅是框架思路,如果要实现一个设备驱动,可以在此思路基础上,比照一个完整的驱动进行修改即可

猜你喜欢

转载自blog.csdn.net/shiluohuashengmi/article/details/81143420