前言:
以一个GPIO控制的GPIO LED为例,描述Android系统中,如何完成一个最简单的从软件控制硬件的示例:
l 如何完成一个最简单的驱动程序控制某个GPIO引脚
l 如何在Android系统中建立这个驱动程序对应的HAL
l 如何使上层应用程序通过HAL来控制驱动程序
1 总体结构
modkoid工程提供了一个LedTest示例程序,是台湾的Jollen用于培训的。
原始工程下载方法:
#svn checkout http://mokoid.googlecode.com/svn/trunk/mokoid-read-only
本文所使用的代码基于硬件(s5pc100开发板)做了部分修改。
http://download.csdn.net/source/3542132
2 HAL在Android系统中的位置
3 驱动程序
本部分共两个文件。一个是led_drv.ko,这是驱动程序;另一个是:main,这个实际上main.c生成的测试程序。它可以通过ioctl来控制驱动程序,测试驱动程序是否达到目标。
3.1 驱动程序初始化和退出
static int simple_major = 250;//默认的设备号码,如果为0则尝试自动分配
……
/*
* Set up the cdev structure for a device.
*/
static void simple_setup_cdev(struct cdev *dev, int minor,
struct file_operations *fops)//自编的函数,注册字符设备
{
int err, devno = MKDEV(simple_major, minor);//建立设备号
cdev_init(dev, fops);//初始化设备结构体struct cdev *dev
dev->owner = THIS_MODULE;
dev->ops = fops;//关联fops
err = cdev_add (dev, devno, 1);//注册一个字符设备
/* Fail gracefully if need be */
if (err)//注册失败处理
printk (KERN_NOTICE "Error %d adding simple%d", err, minor);
}
/*
* Our various sub-devices.
*/
/* Device 0 uses remap_pfn_range */
static struct file_operations simple_remap_ops = { //定义设备的fops
.owner = THIS_MODULE,
.open = simple_open,
.release = simple_release,
.read = simple_read,
.write = simple_write,
.ioctl = simple_ioctl,
};
/*
* We export two simple devices. There's no need for us to maintain any
* special housekeeping info, so we just deal with raw cdevs.
*/
static struct cdev SimpleDevs;
/*
* Module housekeeping.
*/
static struct class *my_class;
static int simple_init(void)
{
int result;
dev_t dev = MKDEV(simple_major, 0);//将设备号转化为dev_t的结构
/* Figure out our device number. */
if (simple_major)
result = register_chrdev_region(dev, 1, "simple");//尝试申请主设备号
else {
result = alloc_chrdev_region(&dev, 0, 1, "simple");//请求自动分配主设备号,起始值是0,总共分配1个,设备名simple
simple_major = MAJOR(dev);//将分配成功的设备号保存在simple_major变量中
}
if (result < 0) {//分配主设备号失败
printk(KERN_WARNING "simple: unable to get major %d\n", simple_major);
return result;
}
if (simple_major == 0)//将返回值记录为主设备号。需要么?
simple_major = result;
/* Now set up two cdevs. */
simple_setup_cdev(&SimpleDevs, 0, &simple_remap_ops);//调用自编的函数注册字符设备,有Bug没有返回注册是否成功。
printk("simple device installed, with major %d\n", simple_major);//Bug:打印前应该检查注册是否成功?
my_class= class_create(THIS_MODULE, "simple");//建立一个叫simple的内核class,目的是下一步创建设备节点文件
device_create(my_class, NULL, MKDEV(simple_major, 0),
NULL, "led");//创建设备节点文件
return 0;
}
static void simple_cleanup(void)
{
cdev_del(&SimpleDevs);//删除字符设备
unregister_chrdev_region(MKDEV(simple_major, 0), 1);//注销主设备号
device_destroy(my_class,MKDEV(simple_major,0));//删除设备节点
printk("simple device uninstalled\n");
}
module_init(simple_init);
module_exit(simple_cleanup);
3.2 驱动程序Open and release 函数
//寄存器地址,见CPU手册70页
#define pGPG3CON 0xE03001C0
#define pGPG3DAT 0xE03001C4
//寄存器操作指针
static void *vGPG3CON , *vGPG3DAT;
#define GPG3CON (*(volatile unsigned int *) vGPG3CON)
#define GPG3DAT (*(volatile unsigned int *) vGPG3DAT)
…
static int simple_major = 250;//默认的主设备号
module_param(simple_major, int, 0);//向内核申明一个参数,可以在insmod的时候传递给驱动程序
MODULE_AUTHOR("farsight");
MODULE_LICENSE("Dual BSD/GPL");
/*
* Open the device; in fact, there's nothing to do here.
*/
int simple_open (struct inode *inode, struct file *filp)
{
vGPG3CON=ioremap(pGPG3CON,0x10);//io remap地址pGPG3CON到变量
vGPG3DAT=vGPG3CON+0x04;//计算vGPG3DAT寄存器的地址
GPG3CON=0x1111;//使用宏设定寄存器初始值
GPG3DAT=0xff; //使用宏设定寄存器初始值
return 0;
}
ssize_t simple_read(struct file *file, char __user *buff, size_t count, loff_t *offp)
{
return 0;
}
ssize_t simple_write(struct file *file, const char __user *buff, size_t count, loff_t *offp)
{
return 0;
}
……
static int simple_release(struct inode *node, struct file *file)
{
return 0;
}
3.3 驱动程序ioctl函数
…
//ioctl命令值,LED ON ,LED OFF
#define LED_ON 0x4800
#define LED_OFF 0x4801
…
void led_off( void )
{
GPG3DAT=GPG3DAT|(1<<2);//通过宏设定寄存器的值,置1
//printk("stop led\n");
}
void led_on( void )
{
GPG3DAT=GPG3DAT&(~(1<<2)); //通过宏设定寄存器的值,置0
//printk("start led\n");
}
static int simple_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
switch ( cmd )
{
case LED_ON://判断命令
{
led_on();//执行命令
break;
}
case LED_OFF:
{
led_off();
break;
}
default:
{
break;
}
}
return 0;
}
3.4 驱动测试程序main.c
/*
* main.c : test demo driver
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#define LED_ON 0x4800
#define LED_OFF 0x4801
int main()
{
int i = 0;
int dev_fd;
dev_fd = open("/dev/simple",O_RDWR | O_NONBLOCK);//打开设备文件
if ( dev_fd == -1 ) {
printf("Cann't open file /dev/simple\n");
exit(1);
}
while(1)
{
ioctl(dev_fd,LED_ON,0);//发送ioctl命令LED_ON
// printf("on\n");
sleep(1);
ioctl(dev_fd,LED_OFF,0); //发送ioctl命令LED_OFF
// printf("off\n");
sleep(1);
}
return 0;
}
4 HAL
4.1 Hardware Stub (运行在用户空间,直接操作设备文件,对上提供操作接口)
本层生成一个so文件,作为Android HAL层的Stub。按照Android HAL的要求,此文件必须放在固定的目录下面,并且具有特定的文件名。
规则如下:
# HAL module implemenation, not prelinked and stored in
# hw/<OVERLAY_HARDWARE_MODULE_ID>.<ro.product.board>.so
本例中:#define LED_HARDWARE_MODULE_ID "led"
所以生成:system/lib/hw/led.default.so
注:hardware\libled目录下的文件没用。只用hardware\modules目录下的文件用用。
4.1.1 头文件
#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
/********************************************************/
struct led_module_t {//定义了一个继承自hw_module_t的结构,记录本stub的基本信息和入口
struct hw_module_t common;
};
struct led_control_device_t {//定义一个继承自hw_device_t的结构记录本stub操作设备时需要包括的接口
struct hw_device_t common;
/* attributes */
int fd;//文件句柄
//下面是操作接口
/* supporting control APIs go here */
int (*set_on)(struct led_control_device_t *dev, int32_t led);
int (*set_off)(struct led_control_device_t *dev, int32_t led);
};
/**********************************************/
struct led_control_context_t {//定义一个继承自device结构的上下文结构
struct led_control_device_t device;
};
#define LED_HARDWARE_MODULE_ID "led"//定义HAL 的模块ID
4.1.2 C文件
4.1.2.1 入口定义
//定一个hw_module_methods_t结构体,关联入口函数
static struct hw_module_methods_t led_module_methods = {
open: led_device_open
};
//定义Stub入口
//注意必须使用:
//1。hw_module_t继承类
//2。必须使用HAL_MODULE_INFO_SYM这个名字
const struct led_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: LED_HARDWARE_MODULE_ID,//模块ID,上层的Service通过这个ID应用当前Stub
name: "Sample LED Stub",
author: "The Mokoid Open Source Project",
methods: &led_module_methods,//入口函数管理结构体
}
/* supporting APIs go here */
};
4.1.2.2 Open & Close函数定义
static int led_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)
{
struct led_control_device_t *dev;
//建立hw_device_t 继承类的dev变量,并初始化
dev = (struct led_control_device_t *)malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = module;
dev->common.close = led_device_close;//关联关闭接口
dev->set_on = led_on;//关联操作接口
dev->set_off = led_off; //关联操作接口
*device = &dev->common;//common是hw_device_t结构体
if((fd=open("/dev/led",O_RDWR))==-1)//将fd初始化为设备文件
{
LOGE("LED open error");
}
else//这里没有失败后的处理,只是记录Log,这是Bug
LOGI("open ok");
success:
return 0;
}
int led_device_close(struct hw_device_t* device)
{
struct led_control_device_t* ctx = (struct led_control_device_t*)device;//强制转化,得到设备指针
if (ctx) {
free(ctx);//释放
}
close(fd);
return 0;
}
4.1.2.3 操作接口定义
#define GPG3DAT2_ON 0x4800
#define GPG3DAT2_OFF 0x4801
…
int led_on(struct led_control_device_t *dev, int32_t led)
{
//led参数可以用来控制打开哪个LED,但是没用到
LOGI("LED Stub: set %d on.", led);
ioctl(fd,GPG3DAT2_ON,NULL);//向设备驱动发送请求
return 0;
}
int led_off(struct led_control_device_t *dev, int32_t led)
{
//led参数可以用来控制打开哪个LED,但是没用到
LOGI("LED Stub: set %d off.", led);
ioctl(fd,GPG3DAT2_OFF,NULL); //向设备驱动发送请求
return 0;
}
4.2 Framework
本层共包括两个java包,一个是com.mokoid.server.LedService,另一个是mokiod.hardware.LedManager。这两个包编译在同一个jar文件中:mokoid.jar
另外还有一个/system/lib/libmokoid_runtime.so文件供JNI接口使用。详见下面jni一节。
这样上层的应用程序就可以通过调用LedServer或者LedManager来实现调用HAL的Stub了。
注意:这个jar文件需要通过xml描述文件注册到Android系统中。否则上层的应用程序会找不到需要的服务。
即:拷贝frameworks\base\service\com.mokoid.server.xml到目标系统的system/etc/permissions/目录下
4.2.1 Make file (frameworks\base\Android.mk)
……
LOCAL_SRC_FILES := \
$(call all-subdir-java-files) #编译子目录下的所有Java文件
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE := mokoid #编译结果为mokoid.jar
# AIDL
LOCAL_SRC_FILES += \
core/java/mokoid/hardware/ILedService.aidl #接口定义文件
……
4.2.2 Service
本层负责通过JNI接口将C层的接口映射到Java层。其中C部分的内容以libmokoid_runtime.so文件存在,java部分以com.mokoid.server.LedServer的形式存在(编译在mokiod.jar文件中,见本章开始处Makefile的说明)
4.2.2.1 jni (com_mokoid_server_LedService.cpp)
4.2.2.1.1 JNI_OnLoad 入口函数
在Android中,JNI部分采用JNI_OnLoad作为入口的方式实现,即:所有的C实现的so文件以JNI_OnLoad为入口。
/*
*
* This is called by the VM when the shared library is first loaded.
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
LOGI("JNI_OnLoad LED");
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {//获取当前的VM的环境,保存在env变量中,稍候通过这个变量
LOGE("ERROR: GetEnv failed\n");
goto fail;
}
assert(env != NULL);
if (registerMethods(env) != 0) {//自己写的函数,向当前JAVA环境中注册接口
LOGE("ERROR: PlatformLibrary native registration failed\n");
goto fail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
fail:
return result;
}
4.2.2.1.2 registerMethods 注册函数
static int registerMethods(JNIEnv* env) {
static const char* const kClassName =
"com/mokoid/server/LedService";
jclass clazz;
/* look up the class */
clazz = env->FindClass(kClassName);//查找被注册的类
if (clazz == NULL) {
LOGE("Can't find class %s\n", kClassName);
return -1;
}
/* register all the methods */
if (env->RegisterNatives(clazz, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)//向类中注册本SO中Native的接口,接口定义在gMethods数组中
{
LOGE("Failed registering methods for %s\n", kClassName);
return -1;
}
/* fill out the rest of the ID cache */
return 0;
}
gMethods的定义如下:
/*
* Array of methods.
*
* Each entry has three fields: the name of the method, the method
* signature, and a pointer to the native implementation.
*/
static const JNINativeMethod gMethods[] = {
{ "_init", "()Z", (void *)mokoid_init },
{ "_set_on", "(I)Z", (void *)mokoid_setOn },
{ "_set_off", "(I)Z", (void *)mokoid_setOff },
};
/*
*JNINativeMethod是jni层注册的方法,Framework层可以使用这些方法
*_init 、_set_on、_set_off是在Framework中调用的方法名称,函数的类型及返回值如下:
*()Z 无参数 返回值为bool型
* (I)Z 整型参数 返回值为bool型
*/
4.2.2.1.3 mokoid_init 初始化函数
在本层的java文件frameworks\base\service\java\com\mokoid\server\ LedService.java中,构造函数public LedService()会通过_init接口调用到本函数static jboolean mokoid_init(JNIEnv *env, jclass clazz),来完成本模块的初始化。
struct led_control_device_t *sLedDevice = NULL;
……
static jboolean mokoid_init(JNIEnv *env, jclass clazz)
{
led_module_t* module;
LOGI("jni init-----------------------.");
if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {//调用Android HAL标准函数hw_get_module,通过LED_HARDWARE_MODULE_ID获取LED Stub的句柄,句柄保存在module变量中
LOGI("LedService JNI: LED Stub found.");
if (led_control_open(&module->common, &sLedDevice) == 0) {//通过自定义函数调用stub中的open接口,并将stub中的devices handle保存到变量sLedDevice中。
LOGI("LedService JNI: Got Stub operations.");
return 0;
}
}
LOGE("LedService JNI: Get Stub operations failed.");
return -1;
}
//自定义函数如下
static inline int led_control_open(const struct hw_module_t* module,
struct led_control_device_t** device) {
return module->methods->open(module,
LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);//通过标准的Open接口,调用stub中的open函数
}
4.2.2.1.4 mokoid_setOn / Off 操作接口函数
static jboolean mokoid_setOn(JNIEnv* env, jobject thiz, jint led)
{
LOGI("LedService JNI: mokoid_setOn() is invoked.");
if (sLedDevice == NULL) {
LOGI("LedService JNI: sLedDevice was not fetched correctly.");
return -1;
} else {
return sLedDevice->set_on(sLedDevice, led);//通过set_on函数指针,调用stub中的led_on函数
}
}
static jboolean mokoid_setOff(JNIEnv* env, jobject thiz, jint led)
{
LOGI("LedService JNI: mokoid_setOff() is invoked.");
if (sLedDevice == NULL) {
LOGI("LedService JNI: sLedDevice was not fetched correctly.");
return -1;
} else {
return sLedDevice->set_off(sLedDevice, led); //通过set_off函数指针,调用stub中的led_off函数
}
}
4.2.2.2 java (向下调用runtime so来调用Stub,向上暴露Java接口)
public final class LedService extends ILedService.Stub {
static {
System.load("/system/lib/libmokoid_runtime.so");//加载runtime so文件
}
public LedService() {
Log.i("LedService", "Go to get LED Stub...");
_init();//构造函数,通过_init指针调用JNI的CPP文件中的mokoid_init函数
}
/*
* Mokoid LED native methods.
*/
public boolean setOn(int led) {
Log.i("MokoidPlatform", "LED On");
return _set_on(led);//调用JNI层的mokiod_setOn
}
public boolean setOff(int led) {
Log.i("MokoidPlatform", "LED Off");
return _set_off(led);//调用JNI层的mokiod_setOff
}
//申明native层的接口
private static native boolean _init();
private static native boolean _set_on(int led);
private static native boolean _set_off(int led);
}
4.2.3 Core (LedManager)
本层是对上一节中的LedService另一种形式的封装。
我没有开发过Android上的Java应用程序,不知道为什么要这样做。也许是因为权限原因?非Root权限不能直接访问Service?
此层分为两部分:
ILedService.aidl 接口定义
LedManager.java 实现LedManager
4.2.3.1 ILedService.aidl
package mokoid.hardware;
//定义ILedService接口
interface ILedService
{
boolean setOn(int led);
boolean setOff(int led);
}
4.2.3.2 LedManager
/**
* Class that lets you access the Mokoid LedService.
*/
public class LedManager
{
private static final String TAG = "LedManager";
private ILedService mLedService;//申明ILedService接口的变量
public LedManager() {
mLedService = ILedService.Stub.asInterface(
ServiceManager.getService("led")); //从ServiceManage中获取一个LedService的实例
if (mLedService != null) { //获取失败
Log.i(TAG, "The LedManager object is ready.");
}
}
public boolean LedOn(int n) {
boolean result = false;
try {
result = mLedService.setOn(n); //调用Service中的setOn
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in LedManager.LedOn:", e);
}
return result;
}
public boolean LedOff(int n) {
boolean result = false;
try {
result = mLedService.setOff(n); //调用Service中的setOff
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in LedManager.LedOff:", e);
}
return result;
}
}
5 应用程序
本理中共有两个应用程序示例。一个是LedClient,直接调用LedService来实现控制硬件;另一个是LedTest,通过 LedManager来访问LedService实现控制硬件。
由于我没有开发过Android Java应用程序,所以不太清楚两种方式的异同。
5.1 LedClient
package com.mokoid.LedClient;
import com.mokoid.server.LedService;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class LedClient extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Call an API on the library.
LedService ls = new LedService();//建立LedService
ls.setOn(1);//操作硬件
ls.setOff(2); //操作硬件
TextView tv = new TextView(this);
tv.setText("LED 1 is on. LED 2 is off.");//显示文字
setContentView(tv);
}
}
5.2 LedTest
5.2.1 Xml
<activity android:name=".LedTest">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".LedSystemServer"
android:process=".LedSystemServer" >
<intent-filter>
<action android:name="com.mokoid.systemserver"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
从XML中可以看到申明了一个应用程序和一个名为com.mokoid.systemserver的server。
5.2.2 LedSystemServer
public class LedSystemServer extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void onStart(Intent intent, int startId) {
Log.i("LedSystemServer", "Start LedService...");
/* Please also see SystemServer.java for your interests. */
LedService ls = new LedService();//建立LedService
try {
ServiceManager.addService("led", ls);//向ServiceManager增加一个Service
} catch (RuntimeException e) {
Log.e("LedSystemServer", "Start LedService failed.");
}
}
}
5.2.3 LedTest
public class LedTest extends Activity implements View.OnClickListener {
private LedManager mLedManager = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Start LedService in a seperated process.
startService(new Intent("com.mokoid.systemserver"));//启动了自己写的com.mokiod.systemserver
Button btn = new Button(this);//建立一个按钮
btn.setText("Click to turn LED 1 On");
btn.setOnClickListener(this);
setContentView(btn);
}
public void onClick(View v) {
// Get LedManager.
if (mLedManager == null) {
Log.i("LedTest", "Creat a new LedManager object.");
mLedManager = new LedManager();//建立LedManager,此时LedManager的构造函数会从ServiceManager获取LedService的一个实例
}
if (mLedManager != null) {
Log.i("LedTest", "Got LedManager object.");
}
/** Call methods in LedService via proxy object
* which is provided by LedManager.
*/
mLedManager.LedOn(1);//通过mLedManager的接口调用LedService
TextView tv = new TextView(this);
tv.setText("LED 1 is On.");
setContentView(tv);
}
}
6 备注
6.1 insmod的时候如何传递参数
例如:insmod hello.ko count=0 string="hello"
注:在程序中通过如下宏来申明一个接受参数的变量
module_param(simple_major, int, 0);//向内核申明一个参数,可以在insmod的时候传递给驱动程序