实现 Android 字符设备驱动

一、开发环境

  • LineageOS 23.2 (Android 16)
  • Ubuntu 24.04.4 LTS
  • 红米9 (lancelot)已解锁 BL 并刷如 LineageOS 23.2

二、实现字符设备驱动

这里以模拟LED为例,实现一个字符设备驱动,用于控制LED的亮灭。

  1. 首先,在 YourSource/kernel/xiaomi/mt6768/drivers/char 目录下创建一个目录 myled,用于存放字符设备驱动的源文件。

  2. 创建驱动文件 YourSource/kernel/xiaomi/mt6768/drivers/char/myled/myled.c

    c 复制代码
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/uaccess.h>
    #include <linux/miscdevice.h>
    
    #define LED_ON  1
    #define LED_OFF 0
    
    static int led_fd = -1;
    static int led_state = LED_OFF;
    
    static ssize_t myled_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
        printk(KERN_ALERT"############# myled_write ########\n");
        char led_on;
        if (count != 1) return -EINVAL;
        if (copy_from_user(&led_on, buf, 1)) return -EFAULT;
    
        if (led_on == LED_ON && led_state != LED_ON) {
            // 确保LED硬件处于关闭状态再打开
            // ... 执行打开LED的操作 ...
        } else if (led_on == LED_OFF && led_state != LED_OFF) {
            // 确保LED硬件处于打开状态再关闭
            // ... 执行关闭LED的操作 ...
        } else {
            // 如果请求的LED状态与当前状态相同,则无需操作
            return 0;
        }
        led_state = led_on ? LED_ON : LED_OFF;
        return 1;
    }
    
    static ssize_t myled_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
    {
        printk(KERN_ALERT"############# myled_read ########\n");
        // 实现读逻辑
        return 0;
    }
    
    static const struct file_operations myled_fops = {
        .owner = THIS_MODULE,
        .read = myled_read,
        .write = myled_write,
    };
    
    static struct miscdevice myled_misc = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "myled",
        .fops = &myled_fops,
        .mode = 0666,   // 根据安全需求调整,这里设置无效,需要单独配置 SELinux 和 UAC 权限
    };
    
    static int __init led_init(void) {
        // 用 misc_register() 而非 register_chrdev(),因为前者会自动处理设备号分配并触发热插拔事件,简化设备节点的创建
        // led_fd = register_chrdev(0, "myleddev", &myled_fops);
        // return 0;
        return misc_register(&myled_misc);
    }
    
    static void __exit led_exit(void) {
        // unregister_chrdev(led_fd, "myleddev");
        misc_deregister(&myled_misc);
    }
    
    module_init(led_init);
    module_exit(led_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("LED");
    MODULE_DESCRIPTION("A simple LED driver for Android.");
  3. 创建配置文件 YourSource/kernel/xiaomi/mt6768/drivers/char/myled/Kconfig

    c 复制代码
    config MY_LED
        bool "My Led Test"
        default y
        help
        This is a led demo to test kernel experiment On Android .
  4. 创建 Makefile 文件 YourSource/kernel/xiaomi/mt6768/drivers/char/myled/Makefile

    c 复制代码
    # obj-m 会编译成 myled.o 文件, 这里使用 obj-y 直接编译到 boot.img 中
    obj-y += myled.o

三、将驱动配置到 kernelconfig 中,配置好后才能编译到 boot.img 中

  1. 在 YourSource/kernel/xiaomi/mt6768/drivers/char/Kconfig 中添加以下内容:

    bash 复制代码
    source "drivers/char/myled/Kconfig"
  2. 在 YourSource/kernel/xiaomi/mt6768/drivers/char/Makefile 中添加以下内容:

    bash 复制代码
    obj-y				+= myled/

四、配置 SELinux 权限

  1. 在 YourSource/device/xiaomi/mt6768-common/sepolicy/vendor/file.te 中添加以下内容:

    c 复制代码
    # myled,为led驱动定义一个设备类型,以方便SELinux权限管理
    type myled_device, dev_type;
  2. 在 YourSource/device/xiaomi/mt6768-common/sepolicy/vendor/file_contexts 中添加以下内容:

    c 复制代码
    # myled
    /dev/myled  	u:object_r:myled_device:s0

    因为 myled 挂载在 /dev 下面,所以在 file_contexts 中添加上面的权限即可,如果是在 /sys 下面,需要使用 genfscon sysfs 来添加权限(请自行搜索 genfscon sysfs、genfscon debugfs 和 genfscon devtmpfs 的使用方法)。

  3. 在 YourSource/device/xiaomi/mt6768-common/sepolicy/vendor/system_app.te 中添加以下内容:

    c 复制代码
    # myled
    allow system_app myled_device:chr_file { getattr open read write ioctl };

五、配置 UAC 权限

  1. 在 YourSource/device/xiaomi/mt6768-common/rootdir/etc/ueventd.mtk.rc 中添加以下内容:

    bash 复制代码
    /dev/myled           0660   system     system

六、编译并刷入镜像

  1. 驱动在 boot 分区,需要编译 boot.img 文件
bash 复制代码
# 编译 boot.img 文件
mka bootimage
# 进入 fastboot 模式
adb reboot bootloader
# 刷入 boot.img 文件
fastboot flash boot boot.img
  1. SELinux 配置在 vendor 分区,需要编译 vendor.img 文件
bash 复制代码
# 编译 vendor.img 文件
mka vendorimage
# android 10 以上需要进入 fastbootd 模式
adb reboot fastboot
# 刷入 vendor.img 文件
fastboot flash vendor vendor.img

七、测试驱动

验证驱动是否刷入成功,执行下面的命令后,显示如下则说明正确配置并刷入了。

shell 复制代码
(base) test@test:~$ adb shell ls -l /dev/myled
crw-rw---- 1 system system 10,  61 2026-09-28 10:55 /dev/myled
(base) test@test:~$ adb shell ls -Z /dev/myled
u:object_r:myled_device:s0 /dev/myled

因为 LED 驱动在 system 域,所以需要创建个系统级 app 来测试驱动。由于 Android 15 及以上版本,在 app 中添加 sharedUserId="android.uid.system" 并不能实现系统级权限,所以还是在下一篇中介绍怎么处理吧。

八、总结

  1. 驱动在 boot 分区,需要编译 boot.img 文件
  2. SELinux 配置在 vendor 分区,需要编译 vendor.img 文件,而 Android 10 以上需要进入 fastbootd 模式
相关推荐
Starry-sky(jing)5 小时前
Vidda ADB 开启:R Pro 65 找不到开发者选项的处理与连接验收
android·adb·远程控制·电视·vidda
雨白5 小时前
从原理到实践:使用 Kotlin KSP 仿写 ButterKnifeLite
android·kotlin
疯华整猫6 小时前
把二十七个 init 从首帧前面搬走:用 Trae 把启动优化钉进 CI
android·性能优化·ai编程
传奇开心果7 小时前
【探秘 Jetpack Compose 框架深水区】 第8课:rememberSaveable 的边界在哪里?——Bundle、Saver 与什么值得跨进程存活
android·kotlin
Frankie_stand8 小时前
测试自动化实战指南:从脚本化、自动判据到 CI 集成的完整落地路径
android·ci/cd·自动化
恋猫de小郭9 小时前
Flutter 大小核优化又被回滚,然后又开新提案?
android·前端·flutter
Kapaseker9 小时前
领导问我 Kotlin 的 Nothing 是什么?
android·kotlin
韩曙亮10 小时前
【错误记录】安卓 adb 调试 5037 端口占用排查 ( adb 的三层结构 | 找出本机占用 5037 的进程 )
android·数据库·adb·安卓·错误记录
三少爷的鞋11 小时前
不改变异步本质,只改变代码组织:Telegram 项目的 Kotlin 协程实践
android
好的佩奇19 小时前
蓝牙之广播与扫描
android·flutter·蓝牙