Android系统开发之——4、访问硬件

814 阅读4分钟

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进行了再次封装

image.png

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