一、编写LED灯的Linux驱动程序代码
之所以使用存在HAL层,是为了保护对硬件驱动过程的逻辑与原理;
所以,残留在Linux驱动层的代码,只保留了基本的读写操作,而不含有关键的逻辑思维;
1. leds_hal_define.h (包含对读写寄存器的宏定义)
#define S3C6410_LEDS_HAL_WRITE_GPMPUD 1 #define S3C6410_LEDS_HAL_WRITE_GPMCON 2 #define S3C6410_LEDS_HAL_WRITE_GPMDAT 3 #define S3C6410_LEDS_HAL_READ_GPMPUD 4 #define S3C6410_LEDS_HAL_READ_GPMCON 5 #define S3C6410_LEDS_HAL_READ_GPMDAT 6
2. s3c6410_leds_hal.h (包含必须的头文件及设备的定义)
#include <linux/fs.h> #include <linux/cdev.h> #include <linux/pci.h> #include <linux/uaccess.h> #include <mach/map.h> #include <mach/regs-gpio.h> #include <mach/gpio-bank-m.h> #define DEVICE_NAME "s3c6410_leds_hal" #define DEVICE_COUNT 1 #define S3C6410_LEDS_MAJOR 0 #define S3C6410_LEDS_MINOR 234
3. s3c6410_leds_hal.c (包含对设备文件读写操作)
#include "s3c6410_leds_hal.h" #include "leds_hal_define.h" static unsigned char mem[5]; /* 第1字节保存寄存器类型,后边4字节保存寄存器的值 */ static int major = S3C6410_LEDS_MAJOR; static int minor = S3C6410_LEDS_MINOR; static dev_t dev_number; static struct class *leds_class = NULL; //字节转换int类型 static int bytes_to_int(unsigned char buf[], int start){ int n=0; n = ((int)buf[start]<<24 | (int)buf[start+1]<<16 | (int)buf[start+2]<<8 |(int)buf[start+3]); return n; } //将int转换成bytes static void int_to_bytes(int n, unsigned char buf[], int start){ buf[start] = n >> 24; buf[start+1] = n >> 16; buf[start+2] = n >> 8; buf[start+3] = n; } //设备的写操作 static ssize_t s3c6410_leds_hal_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){ if (copy_from_user(mem, buf, 5)){ return -EFAULT; } else{ int gpm_type = mem[0]; switch ( gpm_type ) { case S3C6410_LEDS_HAL_WRITE_GPMCON: iowrite32(bytes_to_int(mem,1),S3C64XX_GPMCON); break; case S3C6410_LEDS_HAL_WRITE_GPMPUD: iowrite32(bytes_to_int(mem,1),S3C64XX_GPMPUD); break; case S3C6410_LEDS_HAL_WRITE_GPMDAT: iowrite32(bytes_to_int(mem,1),S3C64XX_GPMDAT); break; default: printk(DEVICE_NAME "\tnot the write type.\n"); break; } } return 5; } //设备的读操作 static ssize_t s3c6410_leds_hal_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){ int gpm_type = mem[0]; int gpm_value = 0; switch ( gpm_type ) { case S3C6410_LEDS_HAL_READ_GPMCON: gpm_value=ioread32(S3C64XX_GPMCON); break; case S3C6410_LEDS_HAL_READ_GPMPUD: gpm_value=ioread32(S3C64XX_GPMPUD); break; case S3C6410_LEDS_HAL_READ_GPMDAT: gpm_value=ioread32(S3C64XX_GPMDAT); break; default: printk(DEVICE_NAME "\tnot the read type.\n"); break; } int_to_bytes(gpm_value, mem, 1); /* 转换成byte数组 */ if (copy_to_user(buf, (void*)mem, 5)){ /* 把数据复制到用户空间 */ return -EFAULT; } return 5; /* 返回已读数据长度 */ } static struct file_operations dev_fops= { .owner = THIS_MODULE, .read = s3c6410_leds_hal_read, .write = s3c6410_leds_hal_write }; static struct cdev leds_cdev; //创建设备文件 static int leds_create_device(void){ int ret = 0; int err = 0; cdev_init(&leds_cdev, &dev_fops); /* 初始化cdev成员,与dev_fops连接 */ leds_cdev.owner = THIS_MODULE; if (major>0){ /* 若大于0,选择手动分配 */ dev_number = MKDEV(major, minor); err = register_chrdev_region(dev_number, DEVICE_COUNT,DEVICE_NAME); if (err<0){ printk(KERN_WARNING "register_chrdev_region() failed\n"); return err; } } else { /* 若主设备号小等于0,使用自动分配设备号 */ err = alloc_chrdev_region(&leds_cdev.dev, 10, DEVICE_COUNT,DEVICE_NAME); if (err<0){ printk(KERN_WARNING "alloc_chrdev_region() failed\n"); return err; } major = MAJOR(leds_cdev.dev); /* 及时保存主设备号 */ minor = MINOR(leds_cdev.dev); /* 及时保存次设备号*/ dev_number = leds_cdev.dev; /* 保存设备号信息*/ } ret = cdev_add(&leds_cdev, dev_number, DEVICE_COUNT); /* 添加驱动设备 */ leds_class = class_create(THIS_MODULE, DEVICE_NAME); /* 创建类型*/ device_create(leds_class, NULL, dev_number, NULL, DEVICE_NAME); /* 创建设备 */ return ret; } //驱动初始化函数 static int __init leds_init(void){ int ret; ret = leds_create_device(); printk(DEVICE_NAME "\tintialized\n"); return ret; } //驱动卸载函数 static void __exit leds_destroy_device(void){ device_destroy(leds_class, dev_number); if (leds_class){ class_destroy(leds_class); } unregister_chrdev_region(dev_number, DEVICE_COUNT); return; } module_init(leds_create_device); module_exit(leds_destroy_device); MODULE_AUTHOR("linkscue"); MODULE_DESCRIPTION("leds driver witten via hal"); MODULE_ALIAS("leds driver hal"); MODULE_LICENSE("GPL");
4. Makefile
obj-m := s3c6410_leds_hal.o PWD := $(shell pwd) #CROSS_COMPILE ?= arm-none-linux-gnueabi- #CC := $(CROSS_COMPILE)gcc #CFLAGS += -static KPATH := /media/Source/Forlinx/android2.3_kernel_v1.01 all: make -C $(KPATH) M=$(PWD) modules clean: rm -rf *.o *.ko *.mod.c Module.* modules.*
在编写好了Linux驱动程序代码之后,把make生成的s3c6410_leds_hal.ko上传至OK6410a并使用insmod安装,继续下一步。
二、编写LED灯的Android HAL层代码
这一层使用的协议并不是GPL,不被强制地开放源代码,这一层,包含了对LED控制过程中的所有的逻辑思维;
在本例子当中,这LED灯的初始化寄存器操作的逻辑,打开或者关闭LED灯时应当向寄存器写入什么数据等等。
1. leds_hal.h (包含必须的头文件及宏定义)
#include <hardware/hardware.h> #include <fcntl.h> #include <cutils/log.h> struct led_module_t{ struct hw_module_t hw_module; }; struct led_control_device_t{ struct hw_device_t hw_device; int (*set_on)(struct led_control_device_t *dev, int32_t led); int (*set_off)(struct led_control_device_t *dev, int32_t led); }; #define LED_HARDWARE_MODULE_ID "led_hal" /*----------------------------------------------------------------------------- * 编写HAL模块需要的3个非常重要的结构体: * hw_module_t * hw_device_t * hw_module_methods_t * 位置:hardware/libhardware/include/hardware/hardware.h *-----------------------------------------------------------------------------*/
2. leds_hal.c (包含对灯控制的所有逻辑思想与操作)
#include "leds_hal.h" #include "leds_hal_define.h" int dev_file = 0; /*----------------------------------------------------------------------------- * 控制LED开关的通用函数 * led表示灯的序号,从0开始 * on_off表示开(1)、关(0) *-----------------------------------------------------------------------------*/ int led_on_off(struct led_control_device_t *dev, int32_t led, int32_t on_off){ if (led>0 && led<3){ if (on_off == 1){ LOGI("LED stub: set %d on", led); } else { LOGI("LED stub: set %d off", led); } unsigned char buf[5]; buf[0] = S3C6410_LEDS_HAL_READ_GPMDAT; /* 准备要读取 */ write(dev_file, buf, 5); read(dev_file, buf, 5); buf[0] = S3C6410_LEDS_HAL_WRITE_GPMDAT; /* 准备要写入 */ switch ( led ) { case 0: if (on_off == 1){ buf[4]&=0xFE; /* 1111,1110,GPMDAT最后一位是0 */ } else if (on_off == 0){ buf[4]|=0x1; /* 0000,0001,GPMDAT最后一位是1 */ } break; case 2: if (on_off == 1){ buf[4]&=0xFD; /* 1111,1101 */ } else if (on_off == 0){ buf[4]|=0x2; /* 0000,0010 */ } break; case 3: if (on_off == 1){ buf[4]&=0xFB; /* 1111,1011 */ } else if (on_off == 0){ buf[4]|=0x4; /* 0000,0100 */ } break; case 4: if (on_off == 1){ buf[4]&=0xF7; /* 1111,0111 */ } else if (on_off == 0){ buf[4]|=0x8; /* 0000,1000 */ } break; default: break; } write(dev_file, buf, 5); } else { LOGI("LED Stub: set led %d on error,no this led", led); } return 0; } /*----------------------------------------------------------------------------- * 打开指定的LED灯 *-----------------------------------------------------------------------------*/ int led_on(struct led_control_device_t *dev, int32_t led){ return led_on_off(dev, led, 1); } /*----------------------------------------------------------------------------- * 关闭指定的LED灯 *-----------------------------------------------------------------------------*/ int led_off(struct led_control_device_t *dev, int32_t led){ return led_on_off(dev, led, 0); } /*----------------------------------------------------------------------------- * 关闭设备函数,HAL模块的事件函数之一 *-----------------------------------------------------------------------------*/ int led_device_close(struct hw_device_t *device){ //强行转换成 led_control_device_t struct led_control_device_t *ctx = (struct led_control_device_t*)device; if (ctx){ free(ctx); /* 释放设备 */ } close(dev_file); return 0; } /*----------------------------------------------------------------------------- * 初始化GPMCON,GPMPUD寄存器 *-----------------------------------------------------------------------------*/ static void leds_init_gpm(){ int tmp = 0; unsigned char buf[5]; //初始化寄存器GPMDAT buf[0] = S3C6410_LEDS_HAL_READ_GPMCON; write(dev_file, buf, 5); read(dev_file, buf, 5); buf[3] |= 0x11; /* 0001,0001 */ buf[4] |= 0x11; /* 0001,0001 */ buf[0] = S3C6410_LEDS_HAL_WRITE_GPMCON; write(dev_file, buf, 5); //初始化寄存器GPMPUD buf[0] = S3C6410_LEDS_HAL_READ_GPMPUD; write(dev_file, buf, 5); read(dev_file, buf, 5); buf[4] |= 0xAA; buf[0] = S3C6410_LEDS_HAL_WRITE_GPMPUD; write(dev_file, buf, 5); } /*----------------------------------------------------------------------------- * 打开设备的函数 *-----------------------------------------------------------------------------*/ 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; dev = (struct led_control_device_t *)malloc(sizeof(*dev)); memset(dev, 0, sizeof(*dev)); /* 清理内存空间 */ dev->hw_device.tag = HARDWARE_DEVICE_TAG; /* HAL设备的标志 */ dev->hw_device.version = 0; /* HAL设备的版本号 */ dev->hw_device.module = (struct hw_module_t *)module; /* HAL模块的hw_module_t结构体 */ dev->hw_device.close = led_device_close; /* 关闭设备的函数 */ dev->set_on = led_on; /* HAL的接口函数 */ dev->set_off = led_off; /* HAL的接口函数 */ *device = (hw_device_t *)dev; /* 可替换为*device = &dev->hw_device */ dev_file = open("/dev/s3c6410_leds_hal", O_RDWR); if (dev_file < 0){ LOGI("LED Stub: open /dev/s3c6410_leds_hal fail."); } else { LOGI("LED Stub: open /dev/s3c6410_leds_hal success ."); } leds_init_gpm; /* 初始化寄存器 */ return 0; } /*----------------------------------------------------------------------------- * 初始化hw_module_method_t结构体的open成员变量 *-----------------------------------------------------------------------------*/ static struct hw_module_methods_t led_module_methods = { open: led_device_open }; /*----------------------------------------------------------------------------- * 初始化描述HAL模块信息的结构体,此结构体变量名必须是HAL_MODULE_INFO_SYM *-----------------------------------------------------------------------------*/ struct led_module_t HAL_MODULE_INFO_SYM = { hw_module: { tag:HARDWARE_MODULE_TAG, /* 初始化模块标志 */ version_major:1, /* 模块的主版本号 */ version_minor:0, /* 模块的次版本情 */ id:LED_HARDWARE_MODULE_ID, /* 初始化模块的ID,通过ID找到此模块 */ name:"Sample LED HAL Stub", /* 初始化模块的名称 */ author:"linkscue", /* 初始化模块的作者 */ methods:&led_module_methods, /* 初始化模块的open函数指针 */ } };
3. 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 := leds_hal.c LOCAL_MODULE :=led_hal.default LOCAL_MODULE_TAGS := eng include $(BUILD_SHARED_LIBRARY)
在Android源代码目录下,创建一个 hardware/leds_hal(名字和路径可以随意,当然也可以放在device目录上对应的设备上);
把源代码 Android.mk leds_hal.c leds_hal_define.h leds_hal.h 复制至 hardware/leds_hal 目录下(可通过ln -s链接);
在配置好编译环境之后,通过 mmm hardware/leds_hal 生成HAL共享库文件 led_hal.default.so(命名与头文件的ID有关);
三、编写调用HAL模块的Service
此模块中一个非常重要的函数是 hw_get_module ,它通过在leds_hal.h中定义的LED_HARDWARE_MODULE_ID找到LED的HAL模块,并取得led_module_t结构体。通过 led_module_t.hw_module.methods.open 函数初始化LED驱动,并通过 open 函数返回 led_control_device_t 结构体,在此结构体中含有我们需要的对LED操作的set_on和set_off函数指针。这一段由于使用了C++和JNI(NDK)编程,阅读起来相对有点晦涩难懂,不过编程量相对也比较少,只需要两文件即可。
1. LedHalService.cpp (主要的源代码文件)
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <assert.h> #include <jni.h> #include <leds_hal.h> struct led_control_device_t *led_hal_device = NULL; /*----------------------------------------------------------------------------- * JNI函数,打开指定的LED *-----------------------------------------------------------------------------*/ static jboolean led_setOn(JNIEnv *env, jobject thiz, jint led){ LOGI("Led HAL JNI: led_setOn() is invoked."); if (led_hal_device == NULL){ LOGI("LED HAL JNI: led_hal_device was not fetched correcttly."); return -1; } else { return led_hal_device->set_on(led_hal_device, led); } } /*----------------------------------------------------------------------------- * JNI函数,关闭指定的LED *-----------------------------------------------------------------------------*/ static jboolean led_setOff(JNIEnv *env, jobject thiz, jint led){ LOGI("Led HAL JNI: led_setOff() is invoked."); if (led_hal_device == NULL){ LOGI("LED HAL JNI: led_hal_device was not fetched correcttly."); return -1; } else { return led_hal_device->set_off(led_hal_device, led); } } static inline int led_control_open(const struct hw_module_t *module, struct led_control_device_t **device){ /* * 调用led_module_t.hw_module.methods.open进行一系列的初始化工作 * open函数会获取led_control_device_t结构体 */ return module->methods->open(module, LED_HARDWARE_MODULE_ID, (struct hw_device_t**) device); } //JNI函数,此方法通过LED_HARDWARE_MODULE_ID找到HAL模块 static jboolean led_init(JNIEnv *env, jclass clazz){ led_module_t *module; LOGE("************start find hal************"); LOGE("LED_HARDWARE_MODULE_ID"); if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t **) &module) == 0){ LOGI("LedService JNI: LED Stub found."); if (led_control_open(&module->hw_module, &led_hal_device) == 0){ LOGI("LedService JNI: Got LED operations."); return 0; } } LOGE("LedService JNI: Got LED operations failed."); return -1; } //定义JNI函数的映射 static const JNINativeMethod methods[] = { { "_init", "()Z", (void*) led_init }, { "_seton", "(I)Z" ,(void *) led_setOn }, { "_setoff", "(I)Z", (void *) led_setOff }, }; /*----------------------------------------------------------------------------- * Java数据类型在JNI中的描述符: * boolean Z * byte B * char C * short S * int I * long J * float F * double D *-----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------- * Java复杂类型在JNI的描述符: * String "Ljava/lang/String;" * int[] "[I" * Object[] "[Ljava/lang/Object;" * 小技巧: * [ 表示数组,比如byte[]在JNI中是"[B" * V 表示未返回任何值即void,比如void method(int a)的返回值是"(I)V" *-----------------------------------------------------------------------------*/ //将JNI程序库与Java类绑定 int register_led_hal_jni(JNIEnv *env){ //必须由此类调用当前的JNI程序库 static const char *const kClassName = "mobile/android/leds/hal/service/LedHalService"; jclass clazz; clazz = env->FindClass(kClassName); /* 获取LedHalService类的jclass对象 */ if (clazz == NULL){ LOGE("Can't find class %s", kClassName); return -1; } /* * 将此函数注册至LedHalService类当中的一个方法 * sizeof(methods) / sizeof(methods[0]) 用于计算methods的长度 */ if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) != JNI_OK){ LOGE("Failed register methods for %s \n", kClassName); return -1; } return 0; } //自动调用JNI_OnLoad函数,用于初始化JNI模块 jint JNI_OnLoad(JavaVM *vm, void *reserved){ JNIEnv *env = NULL; jint result = -1; /* * JNI_VERSION_1_4表明只能在JSE1.4版本以上才能使用 */ if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK){ LOGE("GetEnv failed!"); return result; } register_led_hal_jni(env); return JNI_VERSION_1_4; }
2. Android.mk
#Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_MODULE := led_hal_jni #指定Service程序库的存放路径(指编译出来的.so存放的地方) LOCAL_MODULE_PATH := /home/scue/work/androidexplorer/led_hal/leds_hal/leds_hal_jni LOCAL_SRC_FILES := \ LedHalService.cpp #指定共享库的位置 LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ libcutils \ libhardware \ libhardware_legacy \ libnativehelper \ libsystem_server \ libutils \ libui \ libsurfaceflinger_client #指定头文件的位置 LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ hardware/leds_hal #指定预链接模式 LOCAL_PRELINK_MODULE := false include $(BUILD_SHARED_LIBRARY)
把 Android.mk LedHalService.cpp放至同一目录(/home/scue/work/androidexplorer/led_hal/leds_hal/leds_hal_jni);
配置好Android编译环境之后,使用命令 mmm -B /home/scue/work/androidexplorer/led_hal/leds_hal/leds_hal_jni 编译;
mmm 表示编译指定目录的中的内容
-B 表示强制编译其中的内容(即使源代码.cpp文件没有任何改变)
小技巧:
可以不用把/home/scue/work/androidexplorer/led_hal/leds_hal/leds_hal_jni移至Android源代码目录下亦可编译
(未完待续)..
注:本文大部分代码摘取自某本书的源程序代码。