如何以JNI方式实现安卓APP控制GPIO?

本文档提供了在 Android 10 设备上通过应用程序(App)控制通用输入输出(GPIO)的详细指南。这涵盖了从创建 gpio驱动到App 配置 以及 SELinux 策略以允许特定访问的所有必要步骤。

1. 驱动实现

添加创建gpio控制驱动bsp\kernel\kernel4.14\drivers\gpio\gpio_led.c,并添加好对应的Makfile编译

#include <linux/init.h>

#include <linux/slab.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/ioctl.h>

#include <linux/uaccess.h>

#include <linux/string.h>

#include <linux/wait.h>

#include <linux/types.h>

#include <linux/proc_fs.h>

#include <linux/of.h>

#include <linux/of_gpio.h>

#include <linux/gpio.h>

#include <linux/delay.h>

#include <linux/platform_device.h>

#include <linux/err.h>

#include <linux/gpio/consumer.h>

#include <linux/io.h>

#include <linux/miscdevice.h>

#include <linux/irq.h>

#include <linux/of_irq.h>

#include <linux/kernel.h>

#include <linux/dmi.h>

#include <linux/firmware.h>

#include <linux/gpio/consumer.h>

#include <linux/input.h>

#include <linux/input/mt.h>

#include <linux/module.h>

#include <linux/delay.h>

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <linux/slab.h>

#include <linux/acpi.h>

#include <linux/of.h>

#include <asm/unaligned.h>



#define GPIO_HIGH _IO('L', 0)

#define GPIO_LOW _IO('L', 1)



#define LED_ON 1

#define LED_OFF 0

#define SIMPIE_LED_MAX 4



//============================== Upper interface value ==============================//

// 驱动模块名称定义

#define MODULE_NAME "gpio_led"          // 驱动模块的名字

#define MISC_NAME "gpio_led_device"     // 用于注册为“misc”设备的名字



// 模块函数接口定义,供上层应用调用的接口。通过MM_DEV_MAGIC区分不同系统接口,通过_IO()加上自己的编号作为接口number。

#define MM_DEV_MAGIC 'N'



// LED 控制命令

#define RFID_IO1 _IO(MM_DEV_MAGIC, 93)

#define RFID_IO2 _IO(MM_DEV_MAGIC, 130)

#define RFID_IO3 _IO(MM_DEV_MAGIC, 121)

#define RFID_LED _IO(MM_DEV_MAGIC, 138)



static int major;

static struct class *cls;



// GPIO 描述数组

struct gpio_desc *led_gpio[SIMPIE_LED_MAX];



// cat命令将调用该函数

static ssize_t gpio_value_show(struct device *dev, struct device_attribute *attr, char *buf)

{

    return sprintf(buf, "%d\n", gpiod_get_value(led_gpio[0]));

}



// echo命令将调用该函数

static ssize_t gpio_value_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len)

{

    pr_err("[vanxoak]%c\n", buf[0]);

    if ('0' == buf[0])

    {

        gpiod_direction_output(led_gpio[0], 0);

        pr_err("[vanxoak]: _%s_ :gpio off\n", __func__);

    }

    else if ('1' == buf[0])

    {

        gpiod_direction_output(led_gpio[0], 1);

        pr_err("[vanxoak]: _%s_ :gpio on\n", __func__);

    }

    else

        pr_err("I only support 0 or 1 to ctrl gpio on or off\n");

    pr_err("[vanxoak]gpio_value_store\n");

    return len;

}



// 定义一个名为gpio_led的设备属性

static DEVICE_ATTR(gpio_led, 0664, gpio_value_show, gpio_value_store);



// 提供给上层控制的接口

long gpio_led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

{

    switch (cmd)

    {

    case RFID_LED:

        gpiod_direction_output(led_gpio[0], arg);

        break;

    case RFID_IO1:

        gpiod_direction_output(led_gpio[1], arg);

        break;

    case RFID_IO2:

        gpiod_direction_output(led_gpio[2], arg);

        break;

    case RFID_IO3:

        gpiod_direction_output(led_gpio[3], arg);

        break;



    default:

        pr_err("[vanxoak] %s default: break\n", __func__);

        break;

    }

    return 0;

}



struct file_operations gpio_led_ops = {

    .owner = THIS_MODULE,

    .unlocked_ioctl = gpio_led_ioctl,

};



// LED灯初始化

static int simpie_led_init(struct platform_device *pdev)

{

    int ret = 0;

    int i;



    // 申请gpio设备

    led_gpio[0] = devm_gpiod_get(&pdev->dev, "led0", GPIOD_OUT_LOW);

    led_gpio[1] = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW);

    led_gpio[2] = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW);

    led_gpio[3] = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW);



    for (i = 0; i < SIMPIE_LED_MAX; i++)

    {

        if (IS_ERR(led_gpio[i]))

        {

            ret = PTR_ERR(led_gpio[i]);

            return ret;

        }

        // 输出初始电平

        ret = gpiod_direction_output(led_gpio[i], LED_OFF);

    }



    device_create_file(&pdev->dev, &dev_attr_gpio_led);

    return ret;

}



// 驱动入口

static int gpio_led_probe(struct platform_device *pdev)

{

    int ret = 0;

    pr_err("[vanxoak]gpio_led_probe start...\n");



    // LED灯gpio初始化及输出配置

    ret = simpie_led_init(pdev);



    pr_err("[vanxoak]gpio_led_probe end...\n");



    return 0;

}



// 绑定设备

static struct of_device_id gpio_led_match_table[] = {

    {.compatible = "yz,gpio-led"},

    {}};



static int gpio_led_remove(struct platform_device *pdev)

{

    pr_err("[vanxoak]gpio_led_remove...\n");

    return 0;

}



static struct platform_driver gpio_led_driver = {

    .driver = {

        .name = MODULE_NAME,

        .owner = THIS_MODULE,

        .of_match_table = gpio_led_match_table,

    },

    .probe = gpio_led_probe,

    .remove = gpio_led_remove,

};



// gpio初始化入口

static int gpio_led_init(void)

{

    struct device *mydev;



    pr_err("[vanxoak]gpio_led_init start...\n");

    platform_driver_register(&gpio_led_driver);



    major = register_chrdev(0, "gpiotest", &gpio_led_ops);



    // 创建gpio_led_class设备

    cls = class_create(THIS_MODULE, "gpio_led_class");



    // 在gpio_led_class设备目录下创建一个gpio_led_device属性文件

    mydev = device_create(cls, 0, MKDEV(major, 0), NULL, MISC_NAME);

    if (sysfs_create_file(&(mydev->kobj), &dev_attr_gpio_led.attr))

    {

        return -1;

    }



    return 0;

}



static void gpio_led_exit(void)

{

    pr_err("[vanxoak]gpio_led_exit...\n");

    platform_driver_unregister(&gpio_led_driver);



    device_destroy(cls, MKDEV(major, 0));

    class_destroy(cls);

    unregister_chrdev(major, "gpiotest");

}



module_init(gpio_led_init);

module_exit(gpio_led_exit);



MODULE_DESCRIPTION("Device_create Driver");

MODULE_LICENSE("GPL");

设备树配置 

gpio_led: yz,gpio-led {

status = "disabled";

compatible = "yz,gpio-led";

led0-gpio = <&ap_gpio 138 GPIO_ACTIVE_HIGH>;

led1-gpio = <&ap_gpio 93 GPIO_ACTIVE_HIGH>;

led2-gpio = <&ap_gpio 130 GPIO_ACTIVE_HIGH>;

led3-gpio = <&ap_gpio 121 GPIO_ACTIVE_HIGH>;

};

配置好上面gpio驱动后重新编译更新kernel 可以在/dev目录下找到对应的设备文件

"/dev/gpio_led_device",通过读写设备文件就可以操作gpio了。

1.2 创建 Native 库

1.2.1设置 JNI 方法

在 App 中定义 JNI 方法以实现与 GPIO 设备的交互。

public class NativeClass {

    static {
        try {
            System.loadLibrary("jni_gpiocontrol");
            Log.d("NativeClass", "Native library loaded successfully.");
        } catch (UnsatisfiedLinkError e) {
            Log.e("NativeClass", "Failed to load native library: " + e.getMessage());
            // throw new RuntimeException("Failed to load native library", e);
        }
    }
    // 声明本地方法
    public native int controlGPIO(int cmd, long arg);
}
1.2.2 实现 Native 方法

在app/src/main目录下创建一个cpp文件夹(如果你的项目是用Kotlin编写的,这个步骤仍然适用,因为JNI是用C/C++实现的)。将你的libjni_gpiocontrol.cpp文件放到这个cpp目录中。

注意事项:确保本地方法签名正确,Java方法签名和本地(C/C++)方法实现之间必须完全匹配。

#include <jni.h>

#include <fcntl.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include <stdio.h>

#include <android/log.h>

#include <errno.h>

#include <string.h>



#define MM_DEV_MAGIC 'N'

#define RFID_LED _IO(MM_DEV_MAGIC, 138)

#define RFID_IO1 _IO(MM_DEV_MAGIC, 93)

#define RFID_IO2 _IO(MM_DEV_MAGIC, 130)

#define RFID_IO3 _IO(MM_DEV_MAGIC, 121)



#define DEVICE_PATH "/dev/gpio_led_device"

#define LOG_TAG "GPIOControl"





extern "C" JNIEXPORT jint JNICALL

Java_com_example_gpio_NativeClass_controlGPIO(JNIEnv *env, jobject obj, jint cmd, jlong arg) {



    int device_fd;

    long ioctl_result;

    unsigned int ioctl_cmd = cmd;



    // Open the device file

    device_fd = open(DEVICE_PATH, O_RDWR);

    if (device_fd < 0) {

        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Could not open device: %s", strerror(errno));

        return -1;

    }



    // Translate cmd to appropriate ioctl command based on input

    switch (cmd) {

        case 138:

            ioctl_cmd = RFID_LED;

            break;

        case 93:

            ioctl_cmd = RFID_IO1;

            break;

        case 130:

            ioctl_cmd = RFID_IO2;

            break;

        case 121:

            ioctl_cmd = RFID_IO3;

            break;

        default:

            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Invalid command");

            close(device_fd);

            return -1;

    }



    // Send an ioctl to the device

    ioctl_result = ioctl(device_fd, ioctl_cmd, arg);

    if (ioctl_result < 0) {

        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to call ioctl: %s", strerror(errno));

        close(device_fd);

        return -1;

    }



    // Close the device

    close(device_fd);



    return 0;

}

1.2.3 编译 Native 库

使用 CMake 或 ndk-build 工具编译你的 native 代码为共享库(.so 文件)。

添加app\src\main\cpp\CMakeLists.txt 

cmake_minimum_required(VERSION 3.4.1)

project("gpiotest")

add_library(jni_gpiocontrol SHARED libjni_gpiocontrol.cpp)

find_library( log-lib log )

target_link_libraries(jni_gpiocontrol

                       ${log-lib} ) 
1.2.4 调用 Native 方法

通过 JNI 接口在 App 中调用实现的 native 方法以控制 GPIO。

public class MainActivity extends AppCompatActivity {

    private NativeClass nativeClass;



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);



        nativeClass = new NativeClass();



        // 示例:打开LED

        int result = nativeClass.controlGPIO(138, 1);

        // 根据result处理结果

    }

}

2. SELinux 配置

由于直接访问硬件设备在 Android 中受到 SELinux 策略的限制,需要修改 SELinux 策略以允许 App 访问 GPIO 设备文件。

定义设备类型:为 GPIO 设备定义一个新的 SELinux 类型(如 gpio_led_device_t)。

在SDK_dir/device/sprd/sharkle/common/sepolicy/device.te 添加

# 定义新的设备类型

type gpio_led_device_t, dev_type;

分配文件上下文:为 GPIO 设备文件分配新定义的 SELinux 类型。

SDK_dir/device/sprd/sharkle/common/sepolicy/file_contexts中添加

/dev/gpio_led_device u:object_r:gpio_led_device_t:s0

授予权限:在 SELinux 策略中添加规则,允许 App 访问 GPIO 设备。

SDK_dir/device/sprd/sharkle/common/sepolicy/system_app.te

# 允许 system_app 访问 gpio_led_device

allow system_app gpio_led_device_t:chr_file { read write };

重新编译 SELinux 策略:对更改的 SELinux 策略进行编译,并将其部署到设备上。这一步骤的目的是将自定义的安全策略更改应用到Android构建系统的预设SELinux策略中,确保在编译系统镜像时,这些更改会被包含进去。

cp system/sepolicy/public/app.te system/sepolicy/prebuilts/api/29.0/public/app.te

cp system/sepolicy/private/coredomain.te system/sepolicy/prebuilts/api/29.0/private/coredomain.te

3. 测试与部署

测试 App:在具有所需硬件支持的 Android 10 设备上测试 App。确保 App 能成功加载 native 库,并能通过 JNI 调用控制 GPIO。

SELinux 策略测试:验证 SELinux 策略更改是否允许 App 无障碍地访问 GPIO 设备。

问题排查:如果遇到访问被拒绝的情况,请检查 SELinux 审计日志以确定是否需要进一步调整策略。

3.1注意事项

安全性:在修改 SELinux 策略以增加访问权限时,务必小心谨慎,避免引入安全漏洞。

设备兼容性:确保你的实现考虑到了不同设备可能存在的硬件和配置差异。

文档和维护:适当记录你的设计和实现过程,包括 JNI 接口、native 代码和 SELinux 策略更改,以便于未来的审计和维护。

通过遵循以上步骤,你可以在遵守 Android 安全模型的同时,实现 App 对 GPIO 的有效控制。

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值