对于Android系统移植,主要是信息中framework的移植,而且都会涉及到硬件。关于硬件相关,资料目前不算小,最先比较详细介绍的是Jollen,其他资料也大部分基于他的分析而写出了一些自己的理解,他的博客地址是http://www.jollen.org/blog/2009/。
以下是自己的学习笔记及理解,以为备忘。
本文的主要内容如下:
写在前面:关于分层
一、 Stub编写
二、 JNI编写
三、 framework的java编写
四、 应用程序编写
五、 linux硬件驱动编写
尽量把每一部分内家写得短些,太长看了会比较累。
------------------------------------------------------------------------------
写在前面:关于分层
一个系统的分层,可以有比较清晰的层次感,基本上我们的世界都是在一个分层的理念中,生物圈,管理、行政体系、人类需求……都可以看到分层的影子,还有一些逻辑上的概念,如tcp/ip分层。
有些时候,我们称之为等级。
分层不能太多层,否则鞭长莫及。也不能不太小,否则层面太宽,失去分层之要义。所谓适当的、有效的、健壮的有分层,就是我们所常说的“中庸”,当然此中庸,非彼中庸了。
anroid系统的分层,有一张图很清楚地显示了,网上比较多这张图,此处略去。apps->framework(apps service, manager)->jni->stub->linux kernel/driver。
对于整个系统来说,这个分层的确有点太多层了,不花点时间,会顾此失彼,而且涉及到的知识面比较广。
对于效率来说,分层太多,效率肯定会打折扣,何况还应用程序还是用java写的,java比较高级的语言了。目前来看,android系统对硬件要求也比较高了,似乎这些折扣,可以通过硬件的方式来补偿了。硬件,硬件,发展也是会有瓶颈的吧??
一、Stub编写
1、Stub的编写
Stub是与linux驱动打交道,也就是直接调用了open,close,ioctl,read,write等函数。实际上等价于我们直接在linux上写一些应用程序时所调用硬件驱动功能。只是在android里叫做stub而已。
首先熟悉一下,两个结构体:hw_module_t 和hw_device_t。
此两结构体定义在 hardware/libhardware/include/hardware/hardware.h
typedef struct hw_module_t { /** tag must be initialized to HARDWARE_MODULE_TAG */ uint32_t tag; /** major version number for the module */ uint16_t version_major; /** minor version number of the module */ uint16_t version_minor; /** Identifier of module */ const char *id; /** Name of this module */ const char *name; /** Author/owner/implementor of the module */ const char *author; /** Modules methods */ struct hw_module_methods_t* methods; /** module's dso */ void* dso; /** padding to 128 bytes, reserved for future use */ uint32_t reserved[32-7]; } hw_module_t; typedef struct hw_module_methods_t { /** Open a specific device */ int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device); } hw_module_methods_t; /** * Every device data structure must begin with hw_device_t * followed by module specific public methods and attributes. */ typedef struct hw_device_t { /** tag must be initialized to HARDWARE_DEVICE_TAG */ uint32_t tag; /** version number for hw_device_t */ uint32_t version; /** reference to the module this device belongs to */ struct hw_module_t* module; /** padding reserved for future use */ uint32_t reserved[12]; /** Close this device */ int (*close)(struct hw_device_t* device); } hw_device_t;
写Stub代码,就是填充这两个结体体。
在访问硬件设备时,是通过文件标识来操作,因此实际上在hw_device_t还应该添一个文件标识变fd,有两种方式可以实现:1)把hw_device_t中reseverd变量来放文件标识,这是被系统保留,应该说不太安全。2)自定义一个结构体,把hw_device_t 当作此结构体的成员变量。如下操作:
struct gpio_device_t {
struct hw_device_t hwdev;
/* attributes */
int fd; //the file description of the device in /dev/xxx
}
这样在open设备的时候,就可以把返回的文件标识值,传给fd了。
下面就是简单写Stub的流程:
1、 在vendor目录下建一个hal目录,再分别创建framework和modules目录。对应于framework代码和c/c++模块代码,在moueles创建gpio目录。gpio比较通用,led也是属于gpio范畴。
2、创建gpio.h 如下
#include <hardware/hardware.h> #include <fcntl.h> #include <errno.h> #include <cutils/log.h> #include <cutils/atomic.h> struct gpio_module_t { struct hw_module_t hwmod; }; struct gpio_device_t { struct hw_device_t hwdev; /* attributes */ int fd; //the file description of the device in /dev/xxx /* supporting control APIs go here */ int (*SetGPIO)(struct gpio_device_t *dev, int32_t gpio); int (*ClrGPIO)(struct gpio_device_t *dev, int32_t gpio); }; #define GPIO_HARDWARE_MODULE_ID "gpio"
头文件:
#include <hardware/hardware.h> //上两个基本结构体定义处
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
自己定义的两个结构体
struct gpio_module_t {
struct hw_module_t hwmod;
};
struct gpio_device_t {
struct hw_device_t hwdev;
int fd; //the file description of the device in /dev/xxx
int (*SetGPIO)(struct gpio_device_t *dev, int32_t gpio);
int (*ClrGPIO)(struct gpio_device_t *dev, int32_t gpio);
};
定义一个模块id,#define GPIO_HARDWARE_MODULE_ID "gpio"
保存到gpio目录下。
3 编写gpio.c
#include "gpio.h" #define LOG_TAG "GPIO" int gpio_device_close(struct hw_device_t* device) { struct gpio_device_t* gdev = (struct gpio_device_t*)device; if (gdev) { free(gdev); } return 0; } int gpio_set(struct gpio_device_t *dev, int32_t gpio) { LOGI("gpio : %d set .", gpio); if (dev->fd >= 0) { } return 0; } int gpio_clr(struct gpio_device_t *dev, int32_t gpio) { LOGI("gpio : %d clr.", gpio); if (dev->fd >= 0) { } return 0; } static int gpio_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) { struct gpio_device_t *dev; dev = (struct gpio_device_t *)malloc(sizeof(struct gpio_device_t)); memset(dev, 0, sizeof(struct gpio_device_t)); dev->hwdev.tag = HARDWARE_DEVICE_TAG; dev->hwdev.version = 0; dev->hwdev.module = module; dev->hwdev.close = gpio_device_close; dev->SetGPIO = gpio_set; dev->ClrGPIO = gpio_clr; dev->fd = open("/dev/gpio0", O_RDWR); *device = &dev->hwdev; if (dev->fd < 0) { free(dev); dev = NULL; return -1; } return 0; } static struct hw_module_methods_t gpio_module_methods = { open: gpio_device_open }; const struct gpio_module_t HAL_MODULE_INFO_SYM = { hwmod: { tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id: GPIO_HARDWARE_MODULE_ID, name: "gpio Stub", author: "guim", methods: &gpio_module_methods, } };
int gpio_device_close(struct hw_device_t* device) //关闭gpio设备
int gpio_set(struct gpio_device_t *dev, int32_t gpio)//gpio置1
int gpio_clr(struct gpio_device_t *dev, int32_t gpio)//gpio置0
static int gpio_device_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device) //打开gpio设备,这个函数是一定要实现,因为之后要传给hw_module_methods_t中的open函数指针:
static struct hw_module_methods_t gpio_module_methods =
{
open: gpio_device_open
};
在此函数中,要填充gpio_device_t一些接口,供上层调用:
dev->hwdev.tag = HARDWARE_DEVICE_TAG; //默认都为此值
dev->hwdev.version = 0; //应该可以随便写,根据自己定义
dev->hwdev.module = module;
dev->hwdev.close = gpio_device_close;
dev->SetGPIO = gpio_set;
dev->ClrGPIO = gpio_clr;
dev->fd = open("/dev/gpio0", O_RDWR); //打开gpio设备
//注册接口,HAL_MODULE_INFO_SYM
const struct gpio_module_t HAL_MODULE_INFO_SYM = //固定为HAL_MODULE_INFO_SYM,不可修改,不知为什么,照做。
{
hwmod:
{
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: GPIO_HARDWARE_MODULE_ID,
name: "gpio Stub",
author: "guim",
methods: &gpio_module_methods,
}
};
4 编写Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := gpio.c
#模块名为gpio.default
LOCAL_MODULE := gpio.default
include $(BUILD_SHARED_LIBRARY)
其中LOCAL_PRELINK_MODULE要置为false,否则编译时会出错。
5 关于模块名的命名
在jni调用stub方法时,是用到hw_get_module函数来查找这个库。hw_get_module函数实现如下(位置hardware/libhardware/Hardware.c ):
在查找模块的时候,默认是查找system/lib/hwh目录下的,也就是上面编译好的gpio.default.so是放在此目录下
#define HAL_LIBRARY_PATH "/system/lib/hw"
//模块的键值,在查找库的时候,要根据模块的id,即GPIO_HARDWARE_MODULE_ID来查找模块文件
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
int hw_get_module(const char *id, const struct hw_module_t **module)
{
int status;
int i;
const struct hw_module_t *hmi = NULL;
char prop[PATH_MAX];
char path[PATH_MAX];
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {
if (i < HAL_VARIANT_KEYS_COUNT) {
if (property_get(variant_keys, prop, NULL) == 0) {
continue;
}
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH, id, prop);
} else {
snprintf(path, sizeof(path), "%s/%s.default.so",
HAL_LIBRARY_PATH, id);
}
if (access(path, R_OK)) {
continue;
}
/* we found a library matching this id/variant */
break;
}
status = -ENOENT;
if (i < HAL_VARIANT_KEYS_COUNT+1) {
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
status = load(id, path, module);
}
return status;
}
property_get(variant_keys, prop, NULL) 会从variant_keys数组中去获取相应变量所对应的值,然后返回给 prop :
数组中的变量对应的值,如下:
"ro.product.board=$TARGET_BOOTLOADER_BOARD_NAME"
"ro.board.platform=$TARGET_BOARD_PLATFORM"
TARGET_BOOTLOADER_BOARD_NAME会根据具体的设定而确定,当然要还要看在编译android系统的时所选的target了,此处默认选择genic,TARGET_BOOTLOADER_BOARD_NAME和TARGET_BOARD_PLATFORM都为空,因此,此处就找不到对应的模块文件,如果找到会传给prop值,然后根据snprintf(path, sizeof(path), "%s/%s.%s.so", HAL_LIBRARY_PATH, id, prop);得到模块的绝对文件名。否则则用snprintf(path, sizeof(path), "%s/%s.default.so", HAL_LIBRARY_PATH, id);设定模块的全路径名,即/system/lib/hw/xxx.default.so。因此,上面生成模块名是定义为gpio.default.so, 此处xxx即为gpio。如果都没有找到模块文件,那么上层调用时就会提示出错,可以通过LOG信息来跟踪。
此部分详情,将由JNI的编写具体讨论。