Android底层基于Linux内核,对于Android App来说,我们是无法直接访问到硬件的(读写硬件设备的寄存器),只能调用Linux内核的硬件驱动,来实现操作硬件的功能。
这里以点亮LED为例,逐步讲解Android应用如何访问到硬件设备的。
本文基于ROC-3399-PC开发板,系统版本为Android8.1。
一、添加LED硬件驱动
想要App访问LED硬件,首先需要编写驱动程序,让内核提供相应的硬件设备节点,framework通过读写该节点,来完成控制LED的效果。
1.1、编写LED驱动程序
我们创建一个名为led的字符设备
在kernel/drivers/char/目录下,创建led目录。
然后编写led驱动程序。
led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
static int led_gpios[] = {
EXYNOS4212_GPM4(0),
EXYNOS4212_GPM4(1),
EXYNOS4212_GPM4(2),
EXYNOS4212_GPM4(3),
};
int led_open(struct inode *inode, struct file *file) {
/* 配置GPIO为输出引脚 */
int i;
for (i = 0; i < 4; i++) {
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
}
return 0;
}
static long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
/* 根据传入的参数设置GPIO的电平 */
if (cmd != 0 && cmd != 1)
return -EINVAL;
if (arg < 0 || arg > 3)
return -EINVAL;
gpio_set_value(led_gpios[arg], !cmd);
return 0;
}
static struct file_operations leds_ops = {
.owner = THIS_MODULE,
.open = led_open,
.unlocked_ioctl = led_ioctl
};
static int major;
static struct class *led_class;
static struct class_device *led_class_dev;
int __init leds_init(void) {
/* 注册一个字符设备,动态分配主设备号 */
major = register_chrdev(0, "leds", &leds_ops);
/* 创建/sys/class/leds */
led_class = class_create(THIS_MODULE, "leds");
/* 在类下面创建一个设备,让udev和mdev自动创建设备节点,dev/leds */
led_class_dev = device_create(led_class, NULL, MKDEV(major, 0), NULL, "leds");
return 0;
}
void __exit leds_exit(void) {
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "leds");
}
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pujh");
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/init.h>
#define NAME "LED"
static int led_open(struct inode *inode, struct file *file)
{
return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.unlocked_ioctl = led_ioctl,
};
static int __init led_init(void)
{
printk(KERN_DEBUG NAME "initializing\n");
return 0;
}
static void __exit led_exit(void)
{
printk(KERN_DEBUG NAME "cleanup\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_AUTHOR("pujunhui <2441598769@qq.com>");
MODULE_LICENSE("GPL");
1.2、修改内核配置
kernel/drivers/char/led/Makefile
obj-$(CONFIG_LED) += led.o
kernel/drivers/char/led/Kconfig
config LED
tristate "ROC-3399-PC Led Driver"
default m
help
This is the led driver for ROC-3399-PC.
这里默认将驱动以module的形式编译,及动态加载ko驱动。
将led的Kconfig添加到字符设备驱动的Kconfig文件中。
kernel/drivers/char/Kconfig
#
# Character device configuration
#
menu "Character devices"
......
# 添加下面内容
source "drivers/char/led/Kconfig"
endmenu
1.3、编译驱动
由于我们驱动是自己编写的,难免会出现一下错误,所以一般在开发阶段,都采用模块的方式进行编译和运行。
在编译模块之前,需要先将kernel完整编译。
make ARCH=arm64 firefly_defconfig
make -j8 ARCH=arm64 rk3399-roc-pc.img
然后执行以下命令,即可在led源码目录生成led.ko驱动文件。
make -j8 ARCH=arm64 M=./drivers/char/led/ modules
1.4、加载驱动
将led.ko通过adb push到/sdcard目录中。
然后执行
su insmod /sdcard/led.ko
然后就可以在sys/class/leds目录和dev/leds的字符设备文件。
二、编写App
我们可以通过Android Studio快速创建 Native C++ 工程,这里就不详细讲解每个步骤了。主要代码如下:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/start_marquee"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动跑马灯" />
<Button
android:id="@+id/stop_marquee"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止跑马灯" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private boolean mLedIsOpened = false;
private final Timer mTimer = new Timer();
private TimerTask mTimerTask = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start_marquee).setOnClickListener(this);
findViewById(R.id.stop_marquee).setOnClickListener(this);
mLedIsOpened = LedUtil.ledOpen();
if (mLedIsOpened) {
Toast.makeText(this, "Led设备打开成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Led设备打开失败", Toast.LENGTH_SHORT).show();
finish();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mTimer.cancel();
LedUtil.ledClose();
mLedIsOpened = false;
Toast.makeText(this, "Led设备设备已关闭", Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
final int id = v.getId();
if (id == R.id.start_marquee) {
if (mTimerTask == null) {
mTimerTask = new MarqueeTask();
mTimer.schedule(mTimerTask, 0, 500);
}
} else if (id == R.id.stop_marquee) {
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
}
private static class MarqueeTask extends TimerTask {
private int mTimeCount = 0;
@Override
public void run() {
if (mTimeCount >= 8) {
mTimeCount = 0;
}
int witch = mTimeCount % 4;
int light = mTimeCount / 4;
LedUtil.ledCtrl(witch, light == 1);
mTimeCount++;
}
}
}
LedUtil.java
public class LedUtil {
static {
System.loadLibrary("led_ctrl");
}
public static native boolean ledOpen();
public static native void ledClose();
public static native void ledCtrl(int which, boolean light);
}
led_ctrl.cpp
#include <jni.h>
#include <android/log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define TAG "pujh"
#define LOG_D(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
static int led_fd = -1;
static jboolean led_open(JNIEnv *env, jclass clazz) {
LOG_D("led open");
led_fd = open("/dev/leds", O_RDWR);
if (led_fd >= 0) {
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
static void led_close(JNIEnv *env, jclass clazz) {
LOG_D("led close");
if (led_fd >= 0) {
close(led_fd);
led_fd = -1;
}
}
static void led_ctrl(JNIEnv *env, jclass clazz, int witch, jboolean light) {
LOG_D("led ctrl witch = %d, open = %d", witch, light);
if (led_fd < 0) {
return;
}
ioctl(led_fd, light, witch);
}
static const JNINativeMethod jniNativeMethod[] = {
{"ledOpen", "()Z", (void *) (led_open)},
{"ledClose", "()V", (void *) (led_close)},
{"ledCtrl", "(IZ)V", (void *) (led_ctrl)},
};
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *jniEnv;
// 1.获取JNIEnv
jint ret = vm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);
if (ret != JNI_OK) {
return -1;
}
// 2.找到需要动态动态注册的Jni类
jclass jniClass = jniEnv->FindClass("com/pujh/leds/LedUtil");
// 3.动态注册
jniEnv->RegisterNatives(jniClass, jniNativeMethod, 3);
return JNI_VERSION_1_6;
}
这里涉及到方法签名,可以参考以下文章:blog.csdn.net/anye_bbk/ar…
编译运行,Android Studio能够自动将led_ctrl.cpp编译为libled_ctrl.so,并将其打包到apk中。当然我们也可通过以下命令手动编译:
arm-linux-gcc -fPIC -shared led_ctrl.c -o libled_ctrl.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -I ./android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/include/ -nostdlib ./android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/libc.so ./android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/liblog.so
然后将该so文件放入项目工程中的libs/armeabi-v7a目录下,并配置app/build.gradle。
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
这样Android Studio也能够将给so文件打包进apk中。
运行程序,点击启动跑马灯”,核心板上4个LED以次点亮和熄灭。
三、硬件访问服务
框架
1、loadLibrary 加载C库
2、JNI_Onload 注册本地方法
3、将硬件服务添加到SystemServer中
4、app活动service,执行service的方法
public final class SystemServer {
/**
* The main entry point from zygote.
*/
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
Looper.prepareMainLooper();
// Initialize native services.
System.loadLibrary("android_servers");
nativeInit();
}
frameworks/base/services/core/jni/onload.cpp
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
register_android_server_location_GpsLocationProvider(env);
register_android_server_location_FlpHardwareProvider(env);
register_android_server_connectivity_Vpn(env);
register_android_server_AssetAtlasService(env);
register_android_server_ConsumerIrService(env);
register_android_server_BatteryStatsService(env);
register_android_server_hdmi_HdmiCecController(env);
register_android_server_tv_TvInputHal(env);
register_android_server_PersistentDataBlockService(env);
register_android_server_fingerprint_FingerprintService(env);
register_android_server_Watchdog(env);
return JNI_VERSION_1_4;
}
怎么实现硬件访问服务 1、JNI和HAL 在frameworks/base/services/core/jni中创建com_android_server_LedService.cpp文件,由于注册jni方法
2、修改frameworks/base/services/core/jni/onload.cpp,调用注册函数
3、创建frameworks/base/services/core/java/com/android/server/LedService.java,用于调用本地方法操作硬件。
4、修改frameworks/base/services/java/com/android/server/SystemServer.java,创建LedService对象,并将其添加到serviceManager中。
5、app调用
frameworks/base/core/java/android/os/ILedService.aidl
package android.os;
/** {@hide} */
interface ILedService
{
int ledCtrl(int which, boolean light);
}
frameworks/base/Android.mk
LOCAL_SRC_FILES += \
core/java/android/os/ILedService.aidl \
编译后,将在out目录中生成ILedService.java文件。
public final class SystemServer {
private void startOtherServices() {
LedService ledService = null;
Slog.i(TAG, "Led Service");
ledService = new LedService(context);
ServiceManager.addService("led", ledService);
}
}
mmm frameworks/base show commands > log.txt 2>&1
app引用时,需要保护out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/class.jar
编写HAL文件
Linux是GPL协议,也就是如果用户使用Linux内核,就必须公开其源代码。而Android是Apache协议,它允许用户不用公开自己的源代码。
JNI向上通过本地函数,向下加载hal文件,并调用hal文件的函数,hal负责访问驱动程序执行硬件函数。用到dlopen函数,Android对dlopen进行了再次封装
hw_get_module("led") 1、模块名=>文件名
1、添加LED内核驱动
1、编写LED驱动,linux-3.0.86\drivers\char\leds_4412.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
static int led_gpios[] = {
EXYNOS4212_GPM4(0),
EXYNOS4212_GPM4(1),
EXYNOS4212_GPM4(2),
EXYNOS4212_GPM4(3),
};
int led_open(struct inode * inode, struct file * file){
/* 配置GPIO为输出引脚 */
int i;
for(i = 0; i < 4; i++){
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
}
return 0;
}
static long led_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
/* 根据传入的参数设置GPIO的电平 */
if(cmd != 0 && cmd != 1)
return -EINVAL;
if(arg < 0 || arg >3)
return -EINVAL;
gpio_set_value(led_gpios[arg], !cmd);
return 0;
}
static struct file_operations leds_ops = {
.owner = THIS_MODULE,
.open = led_open,
.unlocked_ioctl = led_ioctl
};
static int major;
static struct class *led_class;
static struct class_device *led_class_dev;
int __init leds_init(void)
{
major = register_chrdev(0,"leds",&leds_ops);
/* 为了让系统udev,mdev给我们创建设备节点 */
/* 创建类,在类下创建设备:/sys */
led_class = class_create(THIS_MODULE,"leds");
led_class_dev = device_create(led_class, NULL, MKDEV(major, 0), NULL, "leds"); /* dev/leds */
return 0;
}
void __exit leds_exit(void)
{
device_destroy(led_class,MKDEV(major, 0));
unregister_chrdev(major, "leds");
}
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.idste.cn");
2、linux-3.0.86\drivers\char\Makefile中添加
obj-y += leds_4412.o
并注释原始led驱动
# obj-$(CONFIG_TINY4412_LEDS) += tiny4412_leds.o
3、在内核源码根目录执行make menuconfig
进入Device Drivers->LED Support,去掉LED Class Support,否则添加的led设备将无法创建class
此时,可通过编写jni文件,直接通过open、close、ioctl控制Led
2、增加服务
1、添加aidl文件
android-5.0.2\frameworks\base\core\java\android\os\ILedService.aidl
package android.os;
/** {@hide} */
interface ILedService
{
int ledOn(int which);
int ledOff(int which);
}
修改android-5.0.2\frameworks\base\Android.mk +204
core/java/android/os/IVibratorService.aidl \
core/java/android/os/ILedService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
使用mmm android-5.0.2\frameworks\base将aidl文件编译成ILedService.java文件
2、添加服务类
android-5.0.2\frameworks\base\services\core\java\com\android\server\LedService.java
package com.android.server;
import android.os.ILedService;
public class LedService extends ILedService.Stub{
private static final String TAG = "LedService";
/* call native c function to access hardware */
public LedService(){
native_ledOpen();
}
public int ledOn(int which) throws android.os.RemoteException {
return native_ledCtrl(which, 1);
}
public int ledOff(int which) throws android.os.RemoteException {
return native_ledCtrl(which, 0);
}
public native int native_ledOpen();
public native void native_ledClose();
public native int native_ledCtrl(int which, int status);
}
注册服务类
修改android-5.0.2\frameworks\base\services\java\com\android\server\SystemServer +485
private void startOtherServices() {
//.............
ServiceManager.addService("vibrator", vibrator);
Slog.i(TAG, "Led Service");
ServiceManager.addService("led", new LedService());
Slog.i(TAG, "Consumer IR Service");
//.............
}
4、实现服务类native方法
android-5.0.2\frameworks\base\services\core\jni\com_android_server_LedService.cpp
#define TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware_legacy/vibrator.h>
#include <stdio.h>
namespace android{
static int fd;
static jint ledOpen(JNIEnv *env, jobject obj) {
fd = open("/dev/leds", O_RDWR);
ALOGI("led opend fd = %d", fd);
if(fd >= 0)
return 0;
else
return -1;
}
static void ledClose(JNIEnv *env, jobject obj) {
close(fd);
}
static jint ledCtrl(JNIEnv *env, jobject obj, jint which, jint status) {
int ret = ioctl(fd, status, which);
ALOGI("set led status which=%d status=%d, ret = ", which, status, ret);
return ret;
}
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"native_ledOpen", "()I", (void *) ledOpen},
{"native_ledClose", "()V", (void *) ledClose},
{"native_ledCtrl", "(II)I", (void *) ledCtrl}
};
int register_android_server_LedService(JNIEnv *env){
return jniRegisterNativeMethods(env, "com/android/server/LedService",
sMethods, NELEM(sMethods));
}
};
注册JNI文件
修改android-5.0.2\frameworks\base\services\core\jni\Android.mk
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
$(LOCAL_REL_DIR)/com_android_server_LedService.cpp \
$(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
修改android-5.0.2\frameworks\base\services\core\jni\onload.cpp:
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
//.......
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_LedService(JNIEnv* env);
};
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
//.......
register_android_server_Watchdog(env);
register_android_server_LedService(env);
return JNI_VERSION_1_4;
}
3、使用HAL
hal的好处:
- 1、容易修改:驱动改变,只需替换对应的so文件,而不需要替换整个android_servers.so文件
- 2、出于保密:不必开源自己的驱动文件
getprop ro.hardware.led