Android才会想到把对硬件的支持分成硬件抽象层和内核驱动层,内核驱动层只提供简单的访问硬件逻辑,例如读写硬件寄存器的通道,至于从硬件
中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中去了,
驱动程序的功能主要是向上层提供访问设备的寄存器的值,包括读和写。这里,提供了三种访问设备寄存器的方法,一是通过proc文件系统来访问,
二是通过传统的设备文件的方法来访问,三是通过devfs文件系统来访问。下面分段描述该驱动程序的实现。
定义传统的设备文件访问方法,主要是定义hello_open、hello_release、hello_read和hello_write这四个打开、释放、读和写设备文件的方法:
定义通过devfs文件系统访问方法,这里把设备的寄存器val看成是设备的一个属性,通过读写这个属性来对设备进行访问,主要是实现
hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val:
定义通过proc文件系统访问方法,主要实现了hello_proc_read和hello_proc_write两个方法,同时定义了在proc文件系统创建和删除文件的方法hello_create_proc和hello_remove_proc:
01./*读取设备寄存器val的值,保存在page缓冲区中*/
02.static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {
03. if(off > 0) {
04. *eof = 1;
05. return 0;
06. }
07.
08. return __hello_get_val(hello_dev, page);
09.}
10.
11./*把缓冲区的值buff保存到设备寄存器val中去*/
12.static ssize_t hello_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) {
13. int err = 0;
14. char* page = NULL;
15.
16. if(len > PAGE_SIZE) {
17. printk(KERN_ALERT"The buff is too large: %lu.\n", len);
18. return -EFAULT;
19. }
20.
21. page = (char*)__get_free_page(GFP_KERNEL);
22. if(!page) {
23. printk(KERN_ALERT"Failed to alloc page.\n");
24. return -ENOMEM;
25. }
26.
27. /*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/
28. if(copy_from_user(page, buff, len)) {
29. printk(KERN_ALERT"Failed to copy buff from user.\n");
30. err = -EFAULT;
31. goto out;
32. }
33.
34. err = __hello_set_val(hello_dev, page, len);
35.
36.out:
37. free_page((unsigned long)page);
38. return err;
39.}
40.
41./*创建/proc/hello文件*/
42.static void hello_create_proc(void) {
43. struct proc_dir_entry* entry;
44.
45. entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
46. if(entry) {
47. entry->owner = THIS_MODULE;
48. entry->read_proc = hello_proc_read;
49. entry->write_proc = hello_proc_write;
50. }
51.}
52.
53./*删除/proc/hello文件*/
54.static void hello_remove_proc(void) {
55. remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
56.}
通过proc访问的话,在注册设备的时候还需建立起文件目录。
过设备文件/dev/hello来连接硬件抽象层模块和Linux内核驱动程序模块,hardware/libhardware/include/hardware目录下,
07./*定义模块ID*/
08.#define HELLO_HARDWARE_MODULE_ID "hello"
09.
10./*硬件模块结构体*/
11.struct hello_module_t {
12. struct hw_module_t common;
13.};
14.
15./*硬件接口结构体*/
16.struct hello_device_t {
17. struct hw_device_t common;
18. int fd;
19. int (*set_val)(struct hello_device_t* dev, int val);
20. int (*get_val)(struct hello_device_t* dev, int* val);
21.};
Android硬件抽象层规范的要求,分别定义模块ID、模块结构体以及硬件接口结构体。在硬件接口结构体中,fd表示设备文件描述符,
对应我们将要处理的设备文件"/dev/hello",set_val和get_val为该HAL对上提供的函数接口。
进入到hardware/libhardware/modules目录,新建hello目录,并添加hello.c文件。 hello.c的内容较多,我们分段来看
首先是包含相关头文件和定义相关结构
14./*设备打开和关闭接口*/
15.static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);
16.static int hello_device_close(struct hw_device_t* device);
17.
18./*设备访问接口*/
19.static int hello_set_val(struct hello_device_t* dev, int val);
20.static int hello_get_val(struct hello_device_t* dev, int* val);
21.
22./*模块方法表*/
23.static struct hw_module_methods_t hello_module_methods = {
24. open: hello_device_open
25.};
26.
27./*模块实例变量*/
28.struct hello_module_t HAL_MODULE_INFO_SYM = {
29. common: {
30. tag: HARDWARE_MODULE_TAG,
31. version_major: 1,
32. version_minor: 0,
33. id: HELLO_HARDWARE_MODULE_ID,
34. name: MODULE_NAME,
35. author: MODULE_AUTHOR,
36. methods: &hello_module_methods,
37. }
38.};
这里,实例变量名必须为HAL_MODULE_INFO_SYM,tag也必须为HARDWARE_MODULE_TAG,这是Android硬件抽象层规范规定的。
定义hello_device_open函数:
[cpp] view plaincopy
01.static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) {
02. struct hello_device_t* dev;dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t));
03.
04. if(!dev) {
05. LOGE("Hello Stub: failed to alloc space");
06. return -EFAULT;
07. }
08.
09. memset(dev, 0, sizeof(struct hello_device_t));
10. dev->common.tag = HARDWARE_DEVICE_TAG;
11. dev->common.version = 0;
12. dev->common.module = (hw_module_t*)module;
13. dev->common.close = hello_device_close;
14. dev->set_val = hello_set_val;dev->get_val = hello_get_val;
15.
16. if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
17. LOGE("Hello Stub: failed to open /dev/hello -- %s.", strerror(errno));free(dev);
18. return -EFAULT;
19. }
20.
21. *device = &(dev->common);
22. LOGI("Hello Stub: open /dev/hello successfully.");
23.
24. return 0;
25.}
DEVICE_NAME定义为"/dev/hello"。由于设备文件是在内核驱动里面通过device_create创建的,而device_create创建的设备文件默认只有root用户可读写,而hello_device_open一般是由上层APP来调用的,这些APP一般不具有root权限,这时候就导致打开设备文
件失败:
Hello Stub: failed to open /dev/hello -- Permission denied.
解决办法是类似于Linux的udev规则,打开Android源代码工程目录下,进入到system/core/rootdir目录,里面有一个名为ueventd.rc文件,往里面添加一行:
/dev/hello 0666 root root
定义hello_device_close、hello_set_val和hello_get_val这三个函数:
[cpp] view plaincopy
01.static int hello_device_close(struct hw_device_t* device) {
02. struct hello_device_t* hello_device = (struct hello_device_t*)device;
03.
04. if(hello_device) {
05. close(hello_device->fd);
06. free(hello_device);
07. }
08.
09. return 0;
10.}
11.
12.static int hello_set_val(struct hello_device_t* dev, int val) {
13. LOGI("Hello Stub: set value %d to device.", val);
14.
15. write(dev->fd, &val, sizeof(val));
16.
17. return 0;
18.}
19.
20.static int hello_get_val(struct hello_device_t* dev, int* val) {
21. if(!val) {
22. LOGE("Hello Stub: error val pointer");
23. return -EFAULT;
24. }
25.
26. read(dev->fd, val, sizeof(*val));
27.
28. LOGI("Hello Stub: get value %d from device", *val);
29.
30. return 0;
31.}
继续在hello目录下新建Android.mk文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hello.c
LOCAL_MODULE := hello.default
include $(BUILD_SHARED_LIBRARY)
注意,LOCAL_MODULE的定义规则,hello后面跟有default,hello.default能够保证我们的模块总能被硬象抽象层加载到。
五. 编译:
USER-NAME@MACHINE-NAME:~/Android$ mmm hardware/libhardware/modules/hello
编译成功后,就可以在out/target/product/generic/system/lib/hw目录下看到hello.default.so文件了。
六. 重新打包Android系统镜像system.img:
USER-NAME@MACHINE-NAME:~/Android$ make snod
重新打包后,system.img就包含我们定义的硬件抽象层模块hello.default了。
虽然我们在Android系统为我们自己的硬件增加了一个硬件抽象层模块,但是现在Java应用程序还不能访问到我们的硬件。我们还必须编写JNI方法和在Android的Application Frameworks层增加API接口,才能让上层Application访问我们的硬件。在接下来的文章中
,我们还将完成这一系统过程,使得我们能够在Java应用程序中访问我们自己定制的硬件。
在Android系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序提供服务。因此,调用这些硬件服务的应用程序与这些硬件服务之间的通信需要通过代理来进行。为此,我们要先定义好通信接口。进入到frameworks/base/core/java/android/os目录,新增
IHelloService.aidl接口定义文件:
USER-NAME@MACHINE-NAME:~/Android$ cd frameworks/base/core/java/android/os
USER-NAME@MACHINE-NAME:~/Android/frameworks/base/core/java/android/os$ vi IHelloService.aidl
IHelloService.aidl定义了IHelloService接口:
01.package android.os;
02.
03.interface IHelloService {
04. void setVal(int val);
05. int getVal();
06.}
IHelloService接口主要提供了设备和获取硬件寄存器val的值的功能,分别通过setVal和getVal两个函数来实现。
三.返回到frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增加IHelloService.aidl源文件:
这样,就会根据IHelloService.aidl生成相应的IHelloService.Stub接口。
五.进入到frameworks/base/services/java/com/android/server目录,新增HelloService.java文件:
05.public class HelloService extends IHelloService.Stub {
06. private static final String TAG = "HelloService";
07. HelloService() {
08. init_native();
09. }
10. public void setVal(int val) {
11. setVal_native(val);
12. }
13. public int getVal() {
14. return getVal_native();
15. }
16.
17. private static native boolean init_native();
18. private static native void setVal_native(int val);
19. private static native int getVal_native();
20.};
HelloService主要是通过调用JNI方法init_native、setVal_native和getVal_native
六. 修改同目录的SystemServer.java文件,在ServerThread::run函数中增加加载HelloService的代码:
@Override
public void run() {
....................................................................................
try {
Slog.i(TAG, "DiskStats Service");
ServiceManager.addService("diskstats", new DiskStatsService(context));
} catch (Throwable e) {
Slog.e(TAG, "Failure starting DiskStats Service", e);
}
try {
Slog.i(TAG, "Hello Service");
ServiceManager.addService("hello", new HelloService());
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Hello Service", e);
}
......................................................................................
}
七. 编译HelloService和重新打包system.img:
USER-NAME@MACHINE-NAME:~/Android$ mmm frameworks/base/services/java
USER-NAME@MACHINE-NAME:~/Android$ make snod