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