这里我们是来学习android 驱动和android系统框架的,这里我只针对于整个android设备驱动的一个流程,从上到下的调用,而且在这里我们去使用android源码环境,首先还是来谈一下android的底层和系统层,下图供参考,基本每个设计android的工程师都知道这张图
android使用的是Linux 内核,虽然说稍微改了点东西,增加了些移动嵌入式设备的特性,比如说early suspend、进程间通信(bind)、特有的log机制(logcat)等,但是Linux主流的一些东西都没有改变,所以,这里我们还是使用Linux下得驱动作为底层来学习,具体的android的特性,之后的博客中会做深入。
Linux内核驱动之上应该是android的HAL层,也就是传说中得硬件抽象层,我把这玩意一直理解成是Linux 应用层,虽然,有的厂家的代码写的比较庞大,比较好得还是使用C++的类来封装,典型的设备是传感器模块,一般都会有一个HAL层来衔接,这里我们使用Linux 的应用层编程 c/c++来实现调用驱动来收发数据等操作。 在上面应该是JNI层啦,在我们的学习中,JNI的基础知识在我的另外一个博客专栏中有提到,在这里我们也是使用JNI来实现接口。 然后是framework,我们还是使用java代码封装JNI,然后写一些java的测试代码,当然了,没有android的UI,对于java的UI编程,我也是没玩过,对于java我只是一个初学者,写测试代码还凑合,一般都是一边google一边写的 ^0^ 接下来就开始行动吧,首先是我们的驱动部分,这些代码都是我自己写的,所以难免会有很多写的不对的地方,希望大家觉得我哪边写的不好的多提些意见,毕竟我也只是一个小菜鸟。 这个驱动完成的主要任务是从内核空间向用户空间发送一个坐标信息(模拟鼠标的动作),当然了,我们的驱动是虚拟的,当然不会主动的向用户空间poll数据,所以,我们要先自己往文件系统中写数据,然后驱动会通过input 子系统向user space发送数据。 下面是我驱动的一个结构图,一般我写个驱动都会先考虑很多,然后再着手去做,三思而后行嘛,驱动的整体架构如果出了问题,到最后是很难再修正的,只有重新写。
可以看到,我们的思路很清晰,初始化的时候开一个线程,注意这个线程是一个死循环,但是在循环当中有一个试图获得信号量的动作,如果得不到,就会进入休眠,如下是thread的代码:
- <span style="font-size:16px;">static int work_thread(void *data)
- {
- int x, y, ret;
- struct virtual_dev *pvdev = (struct virtual_dev *)data;
- struct semaphore sema = pvdev->sem;
- // poll the data into user space
- printk(KERN_INFO "work thread running!!");
-
- while(pvdev->running) {
- do{
- ret = down_interruptible(&sema);
- } while(ret == -EINTR);
- //printk("done!\n");
- //poll the x and y data into user space
- x = pvdev->x;
- y = pvdev->y;
- input_report_abs(pvdev->input, ABS_X, x);
- input_report_abs(pvdev->input, ABS_Y, y);
- input_sync(pvdev->input);
- printk("position: %d | %d\n", x, y);
- }
- return 0;
- }</span>
复制代码
唤醒这个线程的地方,就是调用up的地方:
- <span style="font-size:16px;">static ssize_t write_position(struct device *dev,
- struct device_attribute *attr, const char *buffer, ssize_t count)
- {
- int x,y;
- sscanf(buffer, "%d%d", &x, &y);
- vdev->x = x;
- vdev->y = y;
- //do something with x and y ===> poll the data;
- up(&vdev->sem);
- return count;
- }</span>
复制代码
可以看到,这个write_position是被注册当position文件被执行写操作的时候执行的。
- <span style="font-size:16px;">/* attach the sysfs */
- DEVICE_ATTR(position, 0666, read_position, write_position);
- DEVICE_ATTR(color, 0666, read_color, write_color);
- DEVICE_ATTR(bcolor, 0666, read_bcolor, write_bcolor);</span>
复制代码
相信看了结构图就知道我们的代码是如何写的吧,这个驱动比较简单,看下完整的代码
- <span style="font-size:16px;">/*
- * This driver is named virtual touch, which can send some message into user space from kernel space,
- * for this driver just for study Linux device driver...
- * Jay Zhang
- * mail: [email protected]
- */
- #include <linux/module.h>
- #include <linux/err.h>
- #include <linux/input.h>
- #include <linux/hwmon.h>
- #include <linux/kthread.h>
- #include <linux/platform_device.h>
- #include <linux/semaphore.h>
- #include <linux/slab.h>
- struct virtual_dev {
- struct platform_device *vt_dev;
- struct task_struct *run_thread;
- struct input_dev *input;
- struct semaphore sem;
- int x,y; //point position
- int color; //line color
- int bcolor; //background color
- int size; //line size
- int running;
- };
- struct virtual_dev *vdev = NULL;
- /* position read/write function */
- static ssize_t read_position (struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- return sprintf(buf, "(%d, %d)\n", vdev->x, vdev->y);
- }
- static ssize_t write_position(struct device *dev,
- struct device_attribute *attr, const char *buffer, ssize_t count)
- {
- int x,y;
- sscanf(buffer, "%d%d", &x, &y);
- vdev->x = x;
- vdev->y = y;
- //do something with x and y ===> poll the data;
- up(&vdev->sem);
- return count;
- }
- /* color read/write function */
- static ssize_t read_color(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- return sprintf(buf, "line color is %d\n", vdev->color);
- }
- static ssize_t write_color(struct device *dev,
- struct device_attribute *attr, const char *buffer, ssize_t count)
- {
- int color;
- sscanf(buffer, "%d", &color);
- vdev->color = color;
- return count;
- }
- /* bcolor read/write function */
- static ssize_t read_bcolor(struct device *dev,
- struct device_attribute *attr, char *buf)
- {
- return sprintf(buf, "background color is %d\n", vdev->bcolor);
- }
- static ssize_t write_bcolor(struct device *dev,
- struct device_attribute *attr, const char *buffer, ssize_t count)
- {
- int bcolor;
- sscanf(buffer, "%d", &bcolor);
- vdev->bcolor = bcolor;
- return count;
- }
- /* attach the sysfs */
- DEVICE_ATTR(position, 0666, read_position, write_position);
- DEVICE_ATTR(color, 0666, read_color, write_color);
- DEVICE_ATTR(bcolor, 0666, read_bcolor, write_bcolor);
- //DEVICE_ATTR(size, 0666, read_size, write_size);
- /* attribute description */
- static struct attribute *vdev_attrs[] = {
- &dev_attr_position.attr,
- &dev_attr_color.attr,
- &dev_attr_bcolor.attr,
- // &dev_attr,size,
- NULL
- };
- /* attribute group */
- static struct attribute_group vdev_attr_group = {
- .attrs = vdev_attrs,
- };
- static int work_thread(void *data)
- {
- int x, y, ret;
- struct virtual_dev *pvdev = (struct virtual_dev *)data;
- struct semaphore sema = pvdev->sem;
- // poll the data into user space
- printk(KERN_INFO "work thread running!!");
-
- while(pvdev->running) {
- do{
- ret = down_interruptible(&sema);
- } while(ret == -EINTR);
- //printk("done!\n");
- //poll the x and y data into user space
- x = pvdev->x;
- y = pvdev->y;
- input_report_abs(pvdev->input, ABS_X, x);
- input_report_abs(pvdev->input, ABS_Y, y);
- input_sync(pvdev->input);
- printk("position: %d | %d\n", x, y);
- }
- return 0;
- }
- /*
- static int virtual_probe(struct platform_device *pdev)
- {
- int ret;
- //malloc for vdev
- vdev = kzalloc(sizeof(struct virtual_dev), GFP_KERNEL);
- if(!vdev) {
- vdev = NULL;
- printk(KERN_INFO "kzalloc for vdev failed.\n");
- ret = -ENOMEM;
- goto kzalloc_failed;
- }
- //initialized for semaphore
- sema_init(&(vdev->sem), 0);
- //initialized for input subsystem
- vdev->input = input_allocate_device();
- if(!(vdev->input)) {
- vdev->input = NULL;
- printk(KERN_INFO "allocate input device failed.\n");
- ret = -ENOMEM;
- goto allocate_input_failed;
- }
- set_bit(EV_ABS, vdev->input->evbit);
- //for x
- input_set_abs_params(vdev->input, ABS_X, -1024, 1024, 0, 0);
- //for y
- input_set_abs_params(vdev->input, ABS_Y, -1024, 1024, 0, 0);
- //set name
- vdev->input->name = "virtual-touch";
- ret = input_register_device(vdev->input);
- if(ret) {
- printk(KERN_ERR "%s: Unable to register input device: %s\n",__func__, vdev->input->name);
- goto input_register_failed;
- //return ret;
- }
- //initialized for sysfs of our virtual driver
- vdev->vt_dev = pdev;
- sysfs_create_group(&vdev->vt_dev->dev.kobj, &vdev_attr_group);
- //run thread to poll data
- vdev->run_thread = kthread_run(work_thread, vdev, "vt_thread");
- vdev->running = 1;
- platform_set_drvdata(pdev, vdev);
- printk(KERN_INFO "virtual touch device probe successful.\n");
- return 0;
- input_register_failed:
- input_free_device(vdev->input);
- allocate_input_failed:
- kfree(vdev);
- kzalloc_failed:
- return ret;
- }
- static struct platform_driver virtual_driver = {
- .probe = virtual_probe,
- .driver = {
- .name = "virtual touch",
- },
- };
- */
- static int virtual_init(void)
- {
- int ret;
- //malloc for vdev
- vdev = kzalloc(sizeof(struct virtual_dev), GFP_KERNEL);
- if(!vdev) {
- vdev = NULL;
- printk(KERN_INFO "kzalloc for vdev failed.\n");
- ret = -ENOMEM;
- goto kzalloc_failed;
- }
- //register a platform device
- vdev->vt_dev = platform_device_register_simple("virtual-touch", -1, NULL, 0);
- if(IS_ERR(vdev->vt_dev)) {
- PTR_ERR(vdev->vt_dev);
- printk("register device failed.\n");
- }
- //initialized for semaphore
- sema_init(&(vdev->sem), 0);
- //initialized for input subsystem
- vdev->input = input_allocate_device();
- if(!(vdev->input)) {
- vdev->input = NULL;
- printk(KERN_INFO "allocate input device failed.\n");
- ret = -ENOMEM;
- goto allocate_input_failed;
- }
- set_bit(EV_ABS, vdev->input->evbit);
- //for x
- input_set_abs_params(vdev->input, ABS_X, -1024, 1024, 0, 0);
- //for y
- input_set_abs_params(vdev->input, ABS_Y, -1024, 1024, 0, 0);
- //set name
- vdev->input->name = "virtual-touch";
- ret = input_register_device(vdev->input);
- if(ret) {
- printk(KERN_ERR "%s: Unable to register input device: %s\n",__func__, vdev->input->name);
- goto input_register_failed;
- //return ret;
- }
- //initialized for sysfs of our virtual driver
- // vdev->vt_dev = pdev;
- sysfs_create_group(&vdev->vt_dev->dev.kobj, &vdev_attr_group);
- //run thread to poll data
- vdev->run_thread = kthread_run(work_thread, vdev, "vt_thread");
- vdev->running = 1;
- // platform_set_drvdata(pdev, vdev);
- printk(KERN_INFO "virtual touch device probe successful.\n");
- return 0;
- input_register_failed:
- input_free_device(vdev->input);
- allocate_input_failed:
- kfree(vdev);
- kzalloc_failed:
- return ret;
- }
- static void virtual_exit(void)
- {
- vdev->running = 0;
- sysfs_remove_group(&(vdev->vt_dev->dev.kobj), &vdev_attr_group);
- input_unregister_device(vdev->input);
- platform_device_unregister(vdev->vt_dev);
- }
- module_init(virtual_init);
- module_exit(virtual_exit);
- MODULE_LICENSE("GPL");</span>
复制代码
下面是Makefile
- <span style="font-size:16px;">obj-m := virtualtouch.o
- KERNEL_DIR := /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- all:
- make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules
- clean:
- rm *.o *.ko *.mod.c *.symvers modules.order</span>
复制代码
然后我们来编译模块再加载
# make # insmod virtualtouch.ko 然后看下我们生成的文件系统 root@jay-Vbox:/sys/devices/platform/virtual-touch# pwd /sys/devices/platform/virtual-touch root@jay-Vbox:/sys/devices/platform/virtual-touch# tree . ├── bcolor ├── color ├── modalias ├── position ├── power │ ├── async │ ├── autosuspend_delay_ms │ ├── control │ ├── runtime_active_kids │ ├── runtime_active_time │ ├── runtime_enabled │ ├── runtime_status │ ├── runtime_suspended_time │ └── runtime_usage ├── subsystem -> ../../../bus/platform └── uevent 2 directories, 14 files 还有我们的input subsystem的文件系统,使用dmesg查看 [ 413.650710] input: virtual-touch as /devices/virtual/input/input6 [ 413.662695] virtual touch device probe successful. [ 413.663616] work thread running!! 按照log可以知道,我们生成的event6 root@jay-Vbox:/dev/input# pwd /dev/input root@jay-Vbox:/dev/input# ls -l event6 crw-r----- 1 root root 13, 70 Jul 2 22:07 event6 OK,了解了我们生成的文件系统之后,我们写一个测试程序来测试下,嘿嘿 ^0^ 贴代码如下:
- <span style="font-size:16px;">include <stdlib.h>
- #include <fcntl.h>
- #include <linux/input.h>
- int main(void)
- {
- struct input_event ev_temp;
- int fd = open("/dev/input/event6", O_RDWR);
- if(fd < 0) {
- printf("open device failed.\n");
- return 0;
- }
- printf("open done!\n");
- //return 0;
- int count, x, y;
- while(1) {
- printf("read!\n");
- count = read(fd, &ev_temp, sizeof(struct input_event)) ;
- if(EV_ABS == ev_temp.type) {
- if(ev_temp.code == ABS_X)
- x = ev_temp.value;
- else if(ev_temp.code == ABS_Y)
- y = ev_temp.value;
- printf("position : (%d, %d)\n", x, y);
- } else if(EV_SYN == ev_temp.type) {
- printf("sync!\n");
- }
- }
- return 0;
- }</span>
复制代码
这个。。。这个小测试程序就不多说了,open ----> read ----> show
测试程序的编写一般是参考驱动中report出去的到底是什么类型的数据。 # gcc main -o virtual-touch.c
root@jay-Vbox:/home/jay/workspace/virtual/main# ./main open done!
read! 这个时候被阻塞在read函数这边,因为没有数据被读出,所以会阻塞在那边得不到返回,当然了,我们也可以使用非阻塞的去读(open的时候标志设置成 | NOBLOCK),接着接着,咱们来出发input 设备 root@jay-Vbox:/home/jay/workspace/virtual/main# echo 1 2 > /sys/devices/platform/virtual-touch/position root@jay-Vbox:/home/jay/workspace/virtual/main# position : (1, 32767) read! position : (1, 2) read! sync! read! 看到没,看到没,数据已经打印出来了,具体的流程大家可以自己分析驱动代码,最主要的还是那张驱动架构图。 好了,现在,我们来写一个jni来封装下read函数 。。。等等,咱们还是先看我们的java代码,一般jni是提供接口的,但是接口到底是怎么样的还是取决于java中的需要。 这里我写了一个class来封装open,read等函数。 GetPosition.java
- <span style="font-size:16px;">class GetPosition
- {
- private int x;
- private int y;
- private native int readEvent();
- private native void openEvent(String path);
- private native void closeEvent();
- GetPosition(String path) {
- x = 0;
- y = 0;
- openEvent(path);
- }
- public void close()
- {
- closeEvent();
- }
- public void setXY(int x, int y) {
- this.x = x;
- this.y = y;
- }
- public int getX()
- {
- return this.x;
- }
- public int getY()
- {
- return this.y;
- }
-
- public void read()
- {
- int retVal = readEvent();
- setXY(retVal/256, retVal%256);
- }
- static {
- System.loadLibrary("virtouch");
- }
- }</span>
复制代码
然后咱们使用javah来生成jni的头文件。 # javah -jni GetPosition
- <span style="font-size:16px;">/* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class GetPosition */
- #ifndef _Included_GetPosition
- #define _Included_GetPosition
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: GetPosition
- * Method: readEvent
- * Signature: ()I
- */
- JNIEXPORT jint JNICALL Java_GetPosition_readEvent
- (JNIEnv *, jobject);
- /*
- * Class: GetPosition
- * Method: openEvent
- * Signature: (Ljava/lang/String;)V
- */
- JNIEXPORT void JNICALL Java_GetPosition_openEvent
- (JNIEnv *, jobject, jstring);
- /*
- * Class: GetPosition
- * Method: closeEvent
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_GetPosition_closeEvent
- (JNIEnv *, jobject);
- #ifdef __cplusplus
- }
- #endif</span>
复制代码
然后我们按照这个头文件来完成我们的jni
virtual-touch.c
- <span style="font-size:16px;">#include <stdio.h>
- #include "GetPosition.h"
- #include <stdlib.h>
- #include <linux/input.h>
- #include <fcntl.h>
- int fd;
- JNIEXPORT jint JNICALL
- Java_GetPosition_readEvent(JNIEnv *env, jobject obj)
- {
- static int x, y;
- int count;
- struct input_event ev_temp;
- count = read(fd, &ev_temp, sizeof(struct input_event));
- //AGAIN:
- if(EV_ABS == ev_temp.type) {
- if(ev_temp.code == ABS_X) {
- x = ev_temp.value;
- } else if(ev_temp.code == ABS_Y) {
- y = ev_temp.value;
- }
- printf("x: %d, y: %d\n", x, y);
- }
- //else if(EV_SYN == ev_temp.type) {
- return (x*256 + y);
- //}
- //goto AGAIN;
- }
- JNIEXPORT void JNICALL
- Java_GetPosition_openEvent(JNIEnv *env, jobject obj, jstring prompt)
- {
- char path[64];
- const jbyte *str;
- str = (*env)->GetStringUTFChars(env, prompt, NULL);
- if(str == NULL) {
- printf("error: str is NULL!\n");
- return ;
- }
- sprintf(path, "%s", str);
- fd = open(path, O_RDWR);
- if(fd < 0) {
- printf("open %s failed...\n", path);
- }
- (*env)->ReleaseStringUTFChars(env, prompt, str);
- }
- JNIEXPORT void JNICALL
- Java_GetPosition_closeEvent(JNIEnv *env, jobject obj)
- {
- close(fd);
- }</span>
复制代码
用命令来生成动态库 这里给大家提供一个shell脚本来使用
- <span style="font-size:16px;">#/bin/bash
- if [ $# != 2 ]
- then
- echo "input argument error!"
- else
- cc -I /usr/lib/jvm/java-6-sun/include/linux -I /usr/lib/jvm/java-6-sun/include/ -fPIC -shared -o $1lib$2.so $2.c
- fi
- </span>
复制代码
好了,最后是我们的java测试程序
- <span style="font-size:16px;">class Test {
- // private native int openEvent(String path);
- // private native int closeEvent();
- // private int getPosition();
- public static void main(String[] args)
- {
- GetPosition getPosition = new GetPosition("/dev/input/event5");
- //openEvent("/dev/input/event5");
- while(true) {
- getPosition.read();
- System.out.println("[ " + getPosition.getX() + " , " + getPosition.getY() + "]");
- }
- //getPosition.close();
- //System.out.println()
- }
- /*
- static {
- System.loadLibrary("virtouch");
- }
- */
- }</span>
复制代码
继续测试下
可以得到跟之前一样的结果。 |