MTK Android添加驱动模块
[编写linux驱动程序]
一、编写驱动核心程序
这里说的驱动核心程序是指运行在内核空间的,完全按linux驱动格式编写的,基本上与android没什么关系,一般包括xxx.h和xxx.c文件。
进入到kernel/drivers目录,新建snsled目录,然后建立对应的snsled.h和snsled.c文件:
//snsled.h
#ifndef _SNSLED_H_ #define _SNSLED_H_ #define SNSLED_NUM (1) #define SNSLED_CLASS_NAME "snsled" #define SNSLED_DEVICE_NAME "snsled" #define SNSLED_NODE_NAME "snsled" #define SNSLED_PROC_NAME "snsled" #define SNSLED_IOC_MAGIC 'k' #define SNSLED_IO_ON 2323 //_IO(SNSLED_IOC_MAGIC, 0) #define SNSLED_IO_OFF 2324 //_IO(SNSLED_IOC_MAGIC, 1) #define SNSLED_IOW_PWM 2325 //_IOW(SNSLED_IOC_MAGIC, 2, int) #define SNSLED_IOR_PWM 2326 //_IOR(SNSLED_IOC_MAGIC, 3, int) struct snsled_cntx { int r1; struct semaphore sem; struct cdev cdev; }; #endif
//snsled.c
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/slab.h> /* kmalloc() */ #include <linux/fs.h> /* everything... */ #include <linux/errno.h> /* error codes */ #include <linux/types.h> /* size_t */ #include <linux/proc_fs.h> #include <linux/fcntl.h> /* O_ACCMODE */ #include <linux/seq_file.h> #include <linux/cdev.h> #include <asm/system.h> /* cli(), *_flags */ #include <asm/uaccess.h> /* copy_*_user */ //#include <asm/semaphore.h> /* semaphore */ #include <linux/semaphore.h> #include <linux/device.h> /*class_create*/ #include "snsled.h" /* local definitions */ /* #include <linux/module.h> #include <linux/platform_device.h> #include <linux/delay.h> #include <linux/string.h> #include <linux/ctype.h> #include <linux/leds.h> #include <linux/leds-mt65xx.h> #include <linux/workqueue.h> #include <linux/wakelock.h> #include <linux/slab.h> #include <cust_leds.h>*/ #if defined (CONFIG_ARCH_MT6573) #include <mach/mt6573_pwm.h> #include <mach/mt6573_gpio.h> #include <mach/pmu6573_sw.h> #elif defined (CONFIG_ARCH_MT6516) #include <mach/mt6516_pwm.h> #include <mach/mt6516_gpio.h> #endif /*====macros====*/ #define BUF_SIZE (64) #define SNS_LED_CONTROL_LINE GPIO99 //GPIO39 #define SNS_LED_CONTROL_LINE_GPIO_MODE GPIO_MODE_00 #define SNS_LED_CONTROL_LINE_PWM_MODE GPIO_MODE_01 /*====declares====*/ ssize_t snsled_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t snsled_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); long snsled_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); int snsled_open(struct inode *inode, struct file *filp); int snsled_release(struct inode *inode, struct file *filp); int snsled_turn_on(void); int snsled_turn_off(void); int snsled_set_pwm(int arg); /*====global====*/ static int g_snsled_major = 0; static int g_snsled_minor = 0; struct snsled_cntx *g_snsled_ptr = NULL; struct class *g_snsled_class = 0; static struct file_operations g_snsled_fops = { .owner = THIS_MODULE, .read = snsled_read, .write = snsled_write, .unlocked_ioctl = snsled_unlocked_ioctl, .open = snsled_open, .release = snsled_release, }; /*====implements====*/ ssize_t snsled_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { #if 1 printk(KERN_ALERT "Snsled read.\n"); #else char tmp_buf[512] = {0}; int len =sprintf(tmp_buf, "snsled read.\n"); if (copy_to_user(buf, tmp_buf, count)) { //do nothing } #endif return 0; } ssize_t snsled_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { printk(KERN_ALERT "Snsled write.\n"); return count; } long snsled_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; long retval = 0; printk(KERN_ALERT "Snsled ioctl:cmd=%d, arg=%d.\n", cmd, arg); /* //extract the type and number bitfields, and don't decode //wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() if (_IOC_TYPE(cmd) != SNSLED_IOC_MAGIC) return -ENOTTY; //if (_IOC_NR(cmd) > SNSLED_IOC_MAXNR) return -ENOTTY; //to verify *arg is in user space if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; */ switch(cmd) { case SNSLED_IO_ON: printk(KERN_ALERT "Snsled ioctl:on.\n"); //if(mt_set_gpio_out(SNS_LED_CONTROL_LINE,GPIO_OUT_ONE)){printk("Snsled set gpio failed!! \n");} snsled_turn_on(); break; case SNSLED_IO_OFF: printk(KERN_ALERT "Snsled ioctl:off.\n"); //if(mt_set_gpio_out(SNS_LED_CONTROL_LINE,GPIO_OUT_ZERO)){printk("Snsled set gpio failed!! \n");} snsled_turn_off(); break; case SNSLED_IOW_PWM: printk(KERN_ALERT "Snsled ioctl:set pwm, arg=%d.\n", arg); //retval = __get_user(g_snsled_ptr->r1, (int __user *)arg); snsled_set_pwm((int __user *)arg); break; case SNSLED_IOR_PWM: #if 0 retval = __put_user(g_snsled_ptr->r1, (int __user *)arg); printk(KERN_ALERT "Snsled ioctl:read r1:%i.\n", g_snsled_ptr->r1); #endif printk(KERN_ALERT "Snsled ioctl:read pwm -- not configured yet.\n"); break; default: printk(KERN_ALERT "Snsled ioctl:you got the wrong command.\n"); break; } return retval; } int snsled_open(struct inode *inode, struct file *filp) { printk(KERN_ALERT "Snsled: snsled_open.\n"); #if 0 if(mt_set_gpio_mode(SNS_LED_CONTROL_LINE,SNS_LED_CONTROL_LINE_GPIO_MODE)){printk("Snsled set gpio mode failed!! \n");} if(mt_set_gpio_dir(SNS_LED_CONTROL_LINE,GPIO_DIR_OUT)){printk("Snsled set gpio dir failed!! \n");} if(mt_set_gpio_out(SNS_LED_CONTROL_LINE,GPIO_OUT_ONE)){printk("Snsled set gpio failed!! \n");} #endif return 0; } int snsled_release(struct inode *inode, struct file *filp) { printk(KERN_ALERT "Snsled: snsled_release.\n"); #if 0 if(mt_set_gpio_mode(SNS_LED_CONTROL_LINE,SNS_LED_CONTROL_LINE_GPIO_MODE)){printk("Snsled set gpio mode failed!! \n");} if(mt_set_gpio_dir(SNS_LED_CONTROL_LINE,GPIO_DIR_OUT)){printk("Snsled set gpio dir failed!! \n");} if(mt_set_gpio_out(SNS_LED_CONTROL_LINE,GPIO_OUT_ZERO)){printk("Snsled set gpio failed!! \n");} #endif return 0; } int snsled_turn_on(void) { printk(KERN_ALERT "Snsled: snsled_turn_on.\n"); if(mt_set_gpio_mode(SNS_LED_CONTROL_LINE,SNS_LED_CONTROL_LINE_PWM_MODE)){printk("Snsled set gpio mode failed!! \n");} if(mt_set_gpio_dir(SNS_LED_CONTROL_LINE,GPIO_DIR_OUT)){printk("Snsled set gpio dir failed!! \n");} if(mt_set_gpio_out(SNS_LED_CONTROL_LINE,GPIO_OUT_ZERO)){printk("Snsled set gpio failed!! \n");} return 0; } int snsled_turn_off(void) { printk(KERN_ALERT "Snsled: snsled_turn_off.\n"); if(mt_set_gpio_mode(SNS_LED_CONTROL_LINE,SNS_LED_CONTROL_LINE_GPIO_MODE)){printk("Snsled set gpio mode failed!! \n");} if(mt_set_gpio_dir(SNS_LED_CONTROL_LINE,GPIO_DIR_OUT)){printk("Snsled set gpio dir failed!! \n");} if(mt_set_gpio_out(SNS_LED_CONTROL_LINE,GPIO_OUT_ZERO)){printk("Snsled set gpio failed!! \n");} return 0; } //for old mode /** struct _PWM_OLDMODE_REGS { U16 IDLE_VALUE; //0 U16 GUARD_VALUE; //0 U16 GDURATION; //~ U16 WAVE_NUM; //0 U16 DATA_WIDTH; //high level, 13bits, 0~8191 U16 THRESH; //t }PWM_MODE_OLD_REGS; **/ int snsled_set_pwm(int arg) { struct pwm_spec_config pwm_setting; pwm_setting.pwm_no = PWM1; printk(KERN_ALERT "Snsled: snsled_open begin.\n"); pwm_setting.mode = PWM_MODE_OLD; pwm_setting.clk_div = CLK_DIV16;//CLK_DIV128; pwm_setting.clk_src = PWM_CLK_OLD_MODE_32K; pwm_setting.PWM_MODE_OLD_REGS.IDLE_VALUE = 0; pwm_setting.PWM_MODE_OLD_REGS.GUARD_VALUE = 0; pwm_setting.PWM_MODE_OLD_REGS.GDURATION = 8100; pwm_setting.PWM_MODE_OLD_REGS.WAVE_NUM = 0; pwm_setting.PWM_MODE_OLD_REGS.DATA_WIDTH = 8100; pwm_setting.PWM_MODE_OLD_REGS.THRESH = 8100; pwm_set_spec_config(&pwm_setting); printk(KERN_ALERT "Snsled: snsled_open done.\n"); return 0; } //alloc device major static int vircdex_alloc_major(void) { dev_t devt = 0; int result = 0; result = alloc_chrdev_region(&devt, g_snsled_minor, SNSLED_NUM, SNSLED_NODE_NAME); g_snsled_major = MAJOR(devt); return result; } static int snsled_release_major(void) { dev_t devt = MKDEV(g_snsled_major, g_snsled_minor); unregister_chrdev_region(devt, 1); return 0; } static int snsled_setup_dev(struct snsled_cntx *dev) { int err, devno = MKDEV(g_snsled_major, g_snsled_minor); cdev_init(&(dev->cdev), &g_snsled_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&dev->cdev, devno, 1); if(err){ return err; } //init_MUTEX(&(dev->sem)); sema_init(&(dev->sem), 1); return 0; } static int snsled_unsetup_dev(struct snsled_cntx *dev) { cdev_del(&(dev->cdev)); return 0; } static int snsled_create_devfiles(dev_t devt) {//, const struct device_attribute *attr) { int err = -1; struct device *dev = NULL; g_snsled_class = class_create(THIS_MODULE, SNSLED_CLASS_NAME); if(IS_ERR(g_snsled_class)) { err = PTR_ERR(g_snsled_class); printk(KERN_ALERT "Failed to create class.\n"); goto CLASS_CREATE_ERR; } dev = device_create(g_snsled_class, NULL, devt, NULL, SNSLED_DEVICE_NAME); //dev = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME); //device_create( my_class, NULL, MKDEV(hello_major, 0), "hello" "%d", 0 ); //dev = device_create(g_snsled_class, NULL, MKDEV(MYDRIVER_Major, 0), NULL, DEVICE_NAME); if(IS_ERR(dev)) { err = PTR_ERR(dev); printk(KERN_ALERT "Failed to create device.\n"); goto DEVICE_CREATE_ERR; } /*err = device_create_file(dev, attr); if(err < 0) { printk(KERN_ALERT"Failed to create attribute file."); goto DEVICE_CREATE_FILE_ERR; }*/ printk(KERN_ALERT "seems ok.\n"); //zmk@@debug return 0; DEVICE_CREATE_FILE_ERR: device_destroy(g_snsled_class, devt); DEVICE_CREATE_ERR: class_destroy(g_snsled_class); CLASS_CREATE_ERR: return err; } static int snsled_delete_devfiles(dev_t devt) { device_destroy(g_snsled_class, devt); class_destroy(g_snsled_class); //device_remove_file(dev, attr); return 0; } static int snsled_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *data) { int len =sprintf(buf, "snsled read proc.\n"); return len; } static int snsled_create_proc_file(void) { struct proc_dir_entry *entry = NULL; entry = create_proc_read_entry(SNSLED_PROC_NAME, 0, NULL, snsled_read_proc, NULL); if(entry) { return 0; } else { return -1; } } static int snsled_delete_proc_file(void) { remove_proc_entry(SNSLED_PROC_NAME, NULL); return 0; } MODULE_LICENSE("GPL"); static int snsled_init(void) { int err = -1; dev_t devt = 0; //[1] alloc node number err = vircdex_alloc_major(); if(0 > err) { printk(KERN_ALERT"alloc major failed.\n"); goto ALLOC_MAJOR_ERR; } devt = MKDEV(g_snsled_major, g_snsled_minor); //[2] device object init g_snsled_ptr = kmalloc(sizeof(struct snsled_cntx), GFP_KERNEL); if(!g_snsled_ptr) { err = -ENOMEM; printk(KERN_ALERT"kmalloc failed.\n"); goto KMALLOC_ERR; } memset(g_snsled_ptr, 0, sizeof(struct snsled_cntx)); //[3] setup device err = snsled_setup_dev(g_snsled_ptr); if(0 > err) { printk(KERN_ALERT"device setup failed.\n"); goto DEVICE_SETUP_ERR; } //[4] create files in directory "/dev/" and "/sys/" ///err = snsled_create_devfiles(devt, attr); err = snsled_create_devfiles(devt); if(0 > err) { printk(KERN_ALERT"devfiles create failed.\n"); goto DEVFILES_CREATE_ERR; } //[5] create proc file err = snsled_create_proc_file(); if(0 > err) { printk(KERN_ALERT"proc file create failed.\n"); goto PROC_FILE_CREATE_ERR; } return 0; PROC_FILE_CREATE_ERR: snsled_delete_devfiles(devt); DEVFILES_CREATE_ERR: snsled_unsetup_dev(g_snsled_ptr); DEVICE_SETUP_ERR: kfree(g_snsled_ptr); ALLOC_MAJOR_ERR: snsled_release_major(); KMALLOC_ERR: return err; } static void snsled_exit(void) { dev_t devt = MKDEV(g_snsled_major, g_snsled_minor); snsled_delete_proc_file(); snsled_delete_devfiles(devt); snsled_unsetup_dev(g_snsled_ptr); kfree(g_snsled_ptr); snsled_release_major(); } module_init(snsled_init); module_exit(snsled_exit);
二、配置Kconfig
在snsled目录中,新建Kconfig文件:
config SNSLED tristate "snsled Driver" default n #y ? help This is the sns led driver.
其中,tristate表示编译选项HELLO支持在编译内核时,hello模块支持以模块、内建和不编译三种编译方法。
三、配置Makefile
1、在snsled目录中,新建snsled文件夹对应的Makefile:
#obj-$(CONFIG_SNSLED) += snsled.o obj-y += snsled.o
上面根据 CONFIG_SNSLED的值确定是否编译,y为编译。
2、修改snsled的父目录 drivers/下的Makefile,加入:
obj-$(CONFIG_HELLO) += snsled/
这样便能在编译时编译到snsled这个文件夹。
四、配置系统的autoconfig
打开 mediatek/config/bbk73_gb/autoconfig/kconfig/project,加入:
CONFIG_SNSLED=y
在这里定义变量 CONFIG_SNSLED.
- ps:目前这里配置好像还不能snsled自动编译进去,目前的操作是直接在用到CONFIG_SNSLED的地方用y替代。
五、编译
./makeMtk bbk73_gb remake kernel bootimage
[编写hal模块]
一、新建xxx.h文件
进入"hardware/libhardware/include/hardware"目录,新建vircdev.h文件:
#ifndef ANDROID_HELLO_INTERFACE_H #define ANDROID_HELLO_INTERFACE_H #include <hardware/hardware.h> __BEGIN_DECLS /*定义模块ID*/ #define HELLO_HARDWARE_MODULE_ID "hello" /*硬件模块结构体*/ struct hello_module_t { struct hw_module_t common; }; /*硬件接口结构体*/ struct hello_device_t { struct hw_device_t common; int fd; int (*set_val)(struct hello_device_t* dev, int val); int (*get_val)(struct hello_device_t* dev, int* val); }; __END_DECLS #endif
这里按照Android硬件抽象层规范的要求,分别定义模块ID、模块结构体hello_module_t以及硬件接口结构体hello_device_t。在硬件接口结构体中,fd表示设备文件描述符,对应linux下我们经常接触到的设备文件"/dev/xxx",set_val和get_val为该HAL对上提供的函数接口。
二、新建xxx.c文件
进入到hardware/libhardware/modules目录,新建hello目录,并添加hello.c文件。 hello.c的内容较多,我们分段来看。
1、包含相关头文件和定义相关结构
#define LOG_TAG "HelloStub" #include <hardware/hardware.h> #include <hardware/hello.h> #include <fcntl.h> #include <errno.h> #include <cutils/log.h> #include <cutils/atomic.h> #define DEVICE_NAME "/dev/hello" #define MODULE_NAME "Hello" #define MODULE_AUTHOR "shyluo@gmail.com" /*设备打开和关闭接口*/ static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device); static int hello_device_close(struct hw_device_t* device); /*设备访问接口*/ static int hello_set_val(struct hello_device_t* dev, int val); static int hello_get_val(struct hello_device_t* dev, int* val); /*模块方法表*/ static struct hw_module_methods_t hello_module_methods = { open: hello_device_open }; /*模块实例变量*/ struct hello_module_t HAL_MODULE_INFO_SYM = { common: { tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id: HELLO_HARDWARE_MODULE_ID, name: MODULE_NAME, author: MODULE_AUTHOR, methods: &hello_module_methods, } };
这里,实例变量名必须为HAL_MODULE_INFO_SYM,tag也必须为HARDWARE_MODULE_TAG,这是Android硬件抽象层规范规定的。
2、定义hello_device_open函数
static int hello_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) { struct hello_device_t* dev;dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t)); if(!dev) { LOGE("Hello Stub: failed to alloc space"); return -EFAULT; } memset(dev, 0, sizeof(struct hello_device_t)); dev->common.tag = HARDWARE_DEVICE_TAG; dev->common.version = 0; dev->common.module = (hw_module_t*)module; dev->common.close = hello_device_close; dev->set_val = hello_set_val;dev->get_val = hello_get_val; if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) { LOGE("Hello Stub: failed to open /dev/hello -- %s.", strerror(errno));free(dev); return -EFAULT; } *device = &(dev->common); LOGI("Hello Stub: open /dev/hello successfully."); return 0; }
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
3、定义自定义的api函数
这里定义hello_device_close、hello_set_val和hello_get_val这三个函数:
static int hello_device_close(struct hw_device_t* device) { struct hello_device_t* hello_device = (struct hello_device_t*)device; if(hello_device) { close(hello_device->fd); free(hello_device); } return 0; } static int hello_set_val(struct hello_device_t* dev, int val) { LOGI("Hello Stub: set value %d to device.", val); write(dev->fd, &val, sizeof(val)); return 0; } static int hello_get_val(struct hello_device_t* dev, int* val) { if(!val) { LOGE("Hello Stub: error val pointer"); return -EFAULT; } read(dev->fd, val, sizeof(*val)); LOGI("Hello Stub: get value %d from device", *val); return 0; }
三、在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能够保证我们的模块总能被硬象抽象层加载到。
四、编译、重新打包Android系统镜像system.img
$:~/Android$ mmm hardware/libhardware/modules/hello
编译成功后,就可以在out/target/product/generic/system/lib/hw目录下看到hello.default.so文件了。
$:USER-NAME@MACHINE-NAME:~/Android$ make snod
重新打包后,system.img就包含我们定义的硬件抽象层模块hello.default了。
[编写jni]
虽然上一节我们在Android系统为我们自己的硬件增加了一个硬件抽象层模块,但是现在Java应用程序还不能访问到我们的硬件。我们还必须编写JNI方法和在Android的Application Frameworks层增加API接口,才能让上层Application访问我们的硬件。在这一节中,我们将首先完成jni接口的编写。
一、新建com_android_server_HelloService.cpp文件
进入到frameworks/base/services/jni目录,新建com_android_server_HelloService.cpp文件:
#define LOG_TAG "HelloService" #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include <utils/misc.h> #include <utils/Log.h> #include <hardware/hardware.h> #include <hardware/hello.h> #include <stdio.h> namespace android { /*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hello.h>*/ struct hello_device_t* hello_device = NULL; /*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val的值*/ static void hello_setVal(JNIEnv* env, jobject clazz, jint value) { int val = value; LOGI("Hello JNI: set value %d to device.", val); if(!hello_device) { LOGI("Hello JNI: device is not open."); return; } hello_device->set_val(hello_device, val); } /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val的值*/ static jint hello_getVal(JNIEnv* env, jobject clazz) { int val = 0; if(!hello_device) { LOGI("Hello JNI: device is not open."); return val; } hello_device->get_val(hello_device, &val); LOGI("Hello JNI: get value %d from device.", val); return val; } /*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/ static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) { return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device); } /*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/ static jboolean hello_init(JNIEnv* env, jclass clazz) { hello_module_t* module; LOGI("Hello JNI: initializing......"); if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) { LOGI("Hello JNI: hello Stub found."); if(hello_device_open(&(module->common), &hello_device) == 0) { LOGI("Hello JNI: hello device is open."); return 0; } LOGE("Hello JNI: failed to open hello device."); return -1; } LOGE("Hello JNI: failed to get hello stub module."); return -1; } /*JNI方法表*/ static const JNINativeMethod method_table[] = { {"init_native", "()Z", (void*)hello_init}, {"setVal_native", "(I)V", (void*)hello_setVal}, {"getVal_native", "()I", (void*)hello_getVal}, }; /*注册JNI方法*/ int register_android_server_HelloService(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table)); } };
注意文件的命名方法,com_android_server前缀表示的是包名,表示硬件服务HelloService是放在frameworks/base/services/java目录下的com/android/server目录的,即存在一个命名为com.android.server.HelloService的类。这里,我们暂时略去HelloService类的描述,在下一篇文章中,我们将回到HelloService类来。简单地说,HelloService是一个提供Java接口的硬件访问服务类。
在这个cpp文件中,我们主要是做了以下事情:
1、包括头文件
(尤其是在hal层所定义的头文件)
#define LOG_TAG "HelloService" #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include <utils/misc.h> #include <utils/Log.h> #include <hardware/hardware.h> #include <hardware/hello.h> #include <stdio.h>
2、编写jni接口
通过对hal中函数的调用,编写jni接口(这里只是简单地进行了一层包装):
- 注意,linux driver -- hal -- jni, jni与linux driver并无直接关系,即jni的函数接口与linux driver不一定完全一一对应,很简单的一个例子便是在linux driver中可能只有一个ioctl函数,可是在hal层却通过对ioctl的调用实现了get,set,exchange等多个功能.
/*通过硬件抽象层定义的硬件访问接口设置硬件寄存器val的值*/ static void hello_setVal(JNIEnv* env, jobject clazz, jint value) { int val = value; LOGI("Hello JNI: set value %d to device.", val); if(!hello_device) { LOGI("Hello JNI: device is not open."); return; } hello_device->set_val(hello_device, val); } /*通过硬件抽象层定义的硬件访问接口读取硬件寄存器val的值*/ static jint hello_getVal(JNIEnv* env, jobject clazz) { int val = 0; if(!hello_device) { LOGI("Hello JNI: device is not open."); return val; } hello_device->get_val(hello_device, &val); LOGI("Hello JNI: get value %d from device.", val); return val; }
3、定义jni加载函数,注册jni方法表
/*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/ static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) { return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device); } /*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/ static jboolean hello_init(JNIEnv* env, jclass clazz) { hello_module_t* module; LOGI("Hello JNI: initializing......"); if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) { LOGI("Hello JNI: hello Stub found."); if(hello_device_open(&(module->common), &hello_device) == 0) { LOGI("Hello JNI: hello device is open."); return 0; } LOGE("Hello JNI: failed to open hello device."); return -1; } LOGE("Hello JNI: failed to get hello stub module."); return -1; } /*JNI方法表*/ static const JNINativeMethod method_table[] = { {"init_native", "()Z", (void*)hello_init}, {"setVal_native", "(I)V", (void*)hello_setVal}, {"getVal_native", "()I", (void*)hello_getVal}, }; /*注册JNI方法*/ int register_android_server_HelloService(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table)); }
其中,上面示例中的jni加载函数hello_init是通过hal中定义的hello_device_open函数实现的。在hello_init函数中,通过Android硬件抽象层提供的hw_get_module方法来加载模块ID为HELLO_HARDWARE_MODULE_ID的硬件抽象层模块,其中,HELLO_HARDWARE_MODULE_ID是在<hardware/hello.h>中定义的。Android硬件抽象层会根据HELLO_HARDWARE_MODULE_ID的值在Android系统的/system/lib/hw目录中找到相应的模块,然后加载起来,并且返回hw_module_t接口给调用者使用。在jniRegisterNativeMethods函数中,第二个参数的值必须对应HelloService所在的包的路径,即com.android.server.HelloService。
二、修改onload.cpp,使系统启动时自动加载JNI方法调用表
修改frameworks/base/services/jni/onload.cpp:
1、在namespace android增加register_android_server_HelloService函数声明:
namespace android { //...... int register_android_server_HelloService(JNIEnv *env); };
2、在JNI_onLoad增加register_android_server_HelloService函数调用:
extern "C" jint JNI_onLoad(JavaVM* vm, void* reserved) { //...... register_android_server_HelloService(env); //...... }
这样,在Android系统初始化时,就会自动加载该JNI方法调用表。
三、修改Android.mk文件,添加编译路径
打开frameworks/base/services/jni/Android.mk,在LOCAL_SRC_FILES变量中增加一行:
LOCAL_SRC_FILES:= \ com_android_server_AlarmManagerService.cpp \ com_android_server_BatteryService.cpp \ com_android_server_InputManager.cpp \ com_android_server_LightsService.cpp \ com_android_server_PowerManagerService.cpp \ com_android_server_SystemServer.cpp \ com_android_server_UsbService.cpp \ com_android_server_VibratorService.cpp \ com_android_server_location_GpsLocationProvider.cpp \ com_android_server_HelloService.cpp / onload.cpp
四、编译和重新生成system.img
$:mmm frameworks/base/services/jni $:make snod
这样,重新打包的system.img镜像文件就包含我们刚才编写的JNI方法了
[编写Framework接口]
在Android系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序提供服务。因此,调用这些硬件服务的应用程序与这些硬件服务之间的通信需要通过代理来进行。
一、定义通信接口
1、新增接口文件
进入到frameworks/base/core/java/android/os目录,新增IHelloService.aidl接口定义文件:
package android.os; interface IHelloService { void setVal(int val); int getVal(); }
IHelloService接口主要提供了设备和获取硬件寄存器val的值的功能,分别通过setVal和getVal两个函数来实现。
2、添加编译路径
返回到frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增加IHelloService.aidl源文件:
LOCAL_SRC_FILES += / //...... core/java/android/os/IVibratorService.aidl / core/java/android/os/IHelloService.aidl / core/java/android/service/urlrenderer/IUrlRendererService.aidl / //.....
3、编译接口文件
$:mmm frameworks/base
这样,就会根据IHelloService.aidl生成相应的IHelloService.Stub接口。
二、建立java文件,编写Framework接口
进入到frameworks/base/services/java/com/android/server目录,新增HelloService.java文件:
package com.android.server; import android.content.Context; import android.os.IHelloService; import android.util.Slog; public class HelloService extends IHelloService.Stub { private static final String TAG = "HelloService"; HelloService() { init_native(); } public void setVal(int val) { setVal_native(val); } public int getVal() { return getVal_native(); } private static native boolean init_native(); private static native void setVal_native(int val); private static native int getVal_native(); };
三、在ServerThread::run函数中增加加载代码
修改同目录的SystemServer.java文件:
@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); } //start:增加加载代码 try { Slog.i(TAG, "Hello Service"); ServiceManager.addService("hello", new HelloService()); } catch (Throwable e) { Slog.e(TAG, "Failure starting Hello Service", e); } //end //...... }
四、编译、重新打包system.img
$:mmm frameworks/base/services/java $:make snod
这样,重新打包后的system.img系统镜像文件就在Application Frameworks层中包含了我们自定义的硬件服务了,并且会在系统启动的时候会自动加载HelloService,这样应用程序就可以通过Java接口来访问Hello硬件服务了。
[App访问]
//... import android.os.IHelloService; //... private IHelloService helloService = null; //... @Override public void onCreate(Bundle savedInstanceState) { //... helloService = IHelloService.Stub.asInterface( ServiceManager.getService("hello")); //... } //... int val = helloService.getVal(); //... helloService.setVal(val); //...