实现 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 模式
相关推荐
alexhilton3 小时前
Room Fts4:全文搜索替代 SQL LIKE
android·kotlin·android jetpack
hai_android8 小时前
把 Android App 打包成系统应用(System App)
android·kotlin·android jetpack
JMchen9 小时前
【Jetpack 内核实战 01】开篇:Jetpack 组件全家桶核心逻辑与专栏完整规划
android·kotlin·android jetpack
骑着蜗牛撵大象32711 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
hunterandroid11 小时前
Retrofit 网络请求的异常处理与重试策略实战
android
sejinan12 小时前
极致轻量与秒级起播:NativeWasmTv 开源硬核技术解析与升级版体验
android·开源·webview·tv
小羊没烦恼!12 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
mmsx12 小时前
Android 静默失败实录:五个没有日志的线上事故
android·kotlin
hai_android13 小时前
从零实现一个支持跨进程(IPC)的自定义 Toast
android·kotlin·android jetpack
龙腾-虎跃13 小时前
基于 SIP 协议的自研电话终端:FreeSWITCH 语音与视频通信实战
android·音视频·freeswitch·sip协议终端·wertc