#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include #include#include#include#include#include#include#include#include#include#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 设备的交互publicclassNativeClass { 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); } } // 声明本地方法 publicnativeintcontrolGPIO(int cmd, long arg); }
1.2.2实现 Native 方法在app/src/main目录下创建一个cpp文件夹(如果你的项目是用Kotlin编写的,这个步骤仍然适用,因为JNI是用C/C++实现的)将你的libjni_gpiocontrol.cpp文件放到这个cpp目录中注意事项:确保本地方法签名正确,Java方法签名和本地(C/C++)方法实现之间必须完全匹配#include#include#include#include#include#include#include#include#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 JNICALLJava_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.txtcmake_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 方法以控制 GPIOpublic 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_deviceallow 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.tecp system/sepolicy/private/coredomain.te system/sepolicy/prebuilts/api/29.0/private/coredomain.te
3.测试与部署测试 App:在具有所需硬件支持的 Android 10 设备上测试 App确保 App 能成功加载 native 库,并能通过 JNI 调用控制 GPIOSELinux 策略测试:验证 SELinux 策略更改是否允许 App 无障碍地访问 GPIO 设备问题排查:如果遇到访问被拒绝的情况,请检查 SELinux 审计日志以确定是否需要进一步调整策略3.1注意事项安全性:在修改 SELinux 策略以增加访问权限时,务必小心谨慎,避免引入安全漏洞设备兼容性:确保你的实现考虑到了不同设备可能存在的硬件和配置差异文档和维护:适当记录你的设计和实现过程,包括 JNI 接口、native 代码和 SELinux 策略更改,以便于未来的审计和维护通过遵循以上步骤,你可以在遵守 Android 安全模型的同时,实现 App 对 GPIO 的有效控制(图片来源网络,侵删)
0 评论