RK3568 Android 15驱动开发实战:从环境搭建到HAL集成

发布时间:2026/7/29 2:24:05
RK3568 Android 15驱动开发实战:从环境搭建到HAL集成 在嵌入式开发领域RK3568 作为一款性能均衡、接口丰富的通用型 SoC被广泛应用于智能终端、工业控制和边缘计算设备。随着 Android 15 的发布开发者面临新的系统特性和驱动适配需求。正点原子推出的 RK3568 开发板为学习和实战提供了稳定的硬件平台而驱动开发作为连接硬件与操作系统的核心环节其规范性和可调试性直接决定了项目的成败。本文将以正点原子 RK3568 开发板为硬件基础基于 Android 15 系统完整演示一个可实操的驱动开发流程。内容涵盖环境搭建、内核配置、驱动模块编写、编译调试、HAL 层对接以及常见问题排查目标是让读者掌握从零开始为 RK3568 开发定制外设驱动的能力并理解 Android 系统下驱动开发的完整链路。1. RK3568 与 Android 15 驱动开发环境准备1.1 硬件与软件基础要求在开始驱动开发前需确保具备以下基础环境硬件平台正点原子 RK3568 开发板至少配备 4GB RAM、32GB eMMC、Type-C 数据线、12V 电源适配器、串口调试工具如 USB 转 TTL 模块。主机环境Ubuntu 20.04 LTS 或 22.04 LTS推荐 16GB 内存、200GB 可用磁盘空间。工具链RK3568 官方提供的 GCC 交叉编译工具链aarch64-linux-gnu-。源码准备Android 15 系统源码、RK3568 内核源码版本需与 Android 系统匹配。以下为环境依赖的详细清单组件版本/型号获取方式备注开发板正点原子 RK3568官方渠道确认板载外设接口如 GPIO、I2C、SPI主机系统Ubuntu 20.04/22.04官网下载需安装必备软件包git、curl、tar、make工具链aarch64-linux-gnu-gcc 10.3RK 官网或 Linaro注意与内核编译配置一致内核源码kernel-5.10Rockchip GitHub 仓库对应 Android 15 的基线版本Android 源码android-15.0.0_r1AOSP 镜像站可仅下载设备相关部分1.2 开发环境配置步骤1. 安装基础依赖包在 Ubuntu 终端中执行以下命令安装编译所需的软件包sudo apt update sudo apt install -y git curl wget make gcc g libssl-dev libncurses-dev \ bison flex libelf-dev bc device-tree-compiler lz4 liblz4-tool2. 配置交叉编译工具链下载并解压工具链将其路径加入环境变量# 下载工具链以 Linaro 为例 wget https://releases.linaro.org/components/toolchain/binaries/10.3-2021.07/aarch64-linux-gnu/gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu.tar.xz tar -xf gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu.tar.xz sudo mv gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu /opt/ # 永久添加环境变量 echo export PATH/opt/gcc-linaro-10.3.1-2021.07-x86_64_aarch64-linux-gnu/bin:$PATH ~/.bashrc source ~/.bashrc # 验证工具链 aarch64-linux-gnu-gcc --version3. 获取内核源码并切换分支使用 git 克隆 Rockchip 内核仓库并切换到与 Android 15 匹配的分支git clone https://github.com/rockchip-linux/kernel.git cd kernel git checkout origin/kernel-5.10 -b android15-rk3568注意内核版本与 Android 系统的兼容性至关重要。若使用不匹配的分支可能导致编译错误或运行时异常。2. RK3568 内核配置与驱动框架理解2.1 内核配置与编译验证RK3568 的内核配置需针对具体硬件进行定制。正点原子开发板的设备树文件通常已由厂商提供但开发者仍需掌握如何调整配置以适应外设需求。执行内核配置菜单# 进入内核源码目录 cd kernel # 使用默认配置正点原子提供 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- rockchip_defconfig # 启动图形化配置 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- menuconfig在 menuconfig 中重点关注以下选项Device Drivers→Character devices→ 启用动态设备号分配CONFIG_DYNAMIC_DEVNODevice Drivers→GPIO Support→ 启用 Rockchip GPIO 驱动CONFIG_GPIO_ROCKCHIPFile systems→ 启用用于模块加载的CONFIG_MODULE_FORCE_LOADKernel hacking→ 启用CONFIG_DEBUG_KERNEL和CONFIG_DEBUG_DRIVER以便调试编译内核与模块# 编译内核镜像 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc) # 编译设备树二进制文件 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- dtbs # 编译外部模块准备驱动开发环境 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- modules_prepare编译成功后会在arch/arm64/boot/下生成Image内核镜像在arch/arm64/boot/dts/rockchip/下生成rk3568-evb.dtb等设备树文件。2.2 Linux 驱动框架核心概念在编写具体驱动前需理解 Linux 内核驱动的几个基础框架1. 字符设备驱动最基础的驱动类型适用于 GPIO、串口等逐字节读写的外设。核心结构体包括struct file_operations定义驱动支持的操作open、read、write、ioctl 等struct cdev表示一个字符设备dev_t设备号主设备号次设备号2. 平台设备与驱动用于描述 SoC 内部集成的外设如 I2C 控制器、SPI 总线。通过设备树Device Tree描述硬件资源驱动通过of_match_table匹配设备。3. 设备树Device TreeRK3568 使用设备树替代传统的板级文件board file描述硬件连接关系。设备树源文件.dts编译后生成二进制文件.dtb由内核解析。一个典型的 GPIO LED 设备树节点示例如下// 在 rk3568-evb.dtsi 中添加 leds { compatible gpio-leds; user_led: led-0 { label user_led; gpios gpio0 10 GPIO_ACTIVE_HIGH; // GPIO0_A10 linux,default-trigger heartbeat; default-state off; }; };设备树修改后需重新编译 dtb 文件并更新到开发板的 boot 分区。3. 实战编写一个简单的字符设备驱动3.1 驱动模块基础代码结构以下以一个最简单的虚拟字符设备为例演示完整的驱动编写流程。该设备通过/dev/hello_dev节点提供读写接口。创建驱动源码文件hello_driver.c#include linux/module.h #include linux/fs.h #include linux/cdev.h #include linux/device.h #include linux/uaccess.h #include linux/slab.h #define DEVICE_NAME hello_dev #define CLASS_NAME hello_class static int major_num; static struct class *hello_class NULL; static struct cdev hello_cdev; static char *buffer NULL; static size_t buffer_size 1024; // 设备打开函数 static int hello_open(struct inode *inode, struct file *file) { printk(KERN_INFO Hello driver: device opened\n); return 0; } // 设备释放函数 static int hello_release(struct inode *inode, struct file *file) { printk(KERN_INFO Hello driver: device closed\n); return 0; } // 设备读取函数 static ssize_t hello_read(struct file *file, char __user *user_buf, size_t count, loff_t *offset) { size_t len min(count, buffer_size); if (copy_to_user(user_buf, buffer, len)) { return -EFAULT; } printk(KERN_INFO Hello driver: read %zu bytes\n, len); return len; } // 设备写入函数 static ssize_t hello_write(struct file *file, const char __user *user_buf, size_t count, loff_t *offset) { size_t len min(count, buffer_size); if (copy_from_user(buffer, user_buf, len)) { return -EFAULT; } printk(KERN_INFO Hello driver: wrote %zu bytes\n, len); return len; } // 文件操作结构体 static struct file_operations hello_fops { .owner THIS_MODULE, .open hello_open, .release hello_release, .read hello_read, .write hello_write, }; // 模块初始化函数 static int __init hello_init(void) { dev_t dev_num; // 动态申请设备号 if (alloc_chrdev_region(dev_num, 0, 1, DEVICE_NAME) 0) { printk(KERN_ALERT Failed to allocate device number\n); return -1; } major_num MAJOR(dev_num); // 创建字符设备 cdev_init(hello_cdev, hello_fops); if (cdev_add(hello_cdev, dev_num, 1) 0) { unregister_chrdev_region(dev_num, 1); printk(KERN_ALERT Failed to add cdev\n); return -1; } // 创建设备类 hello_class class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(hello_class)) { cdev_del(hello_cdev); unregister_chrdev_region(dev_num, 1); printk(KERN_ALERT Failed to create class\n); return PTR_ERR(hello_class); } // 在 /dev 下创建设备节点 device_create(hello_class, NULL, dev_num, NULL, DEVICE_NAME); // 分配缓冲区 buffer kzalloc(buffer_size, GFP_KERNEL); if (!buffer) { device_destroy(hello_class, dev_num); class_destroy(hello_class); cdev_del(hello_cdev); unregister_chrdev_region(dev_num, 1); printk(KERN_ALERT Failed to allocate buffer\n); return -ENOMEM; } printk(KERN_INFO Hello driver loaded with major number %d\n, major_num); return 0; } // 模块清理函数 static void __exit hello_exit(void) { dev_t dev_num MKDEV(major_num, 0); device_destroy(hello_class, dev_num); class_destroy(hello_class); cdev_del(hello_cdev); unregister_chrdev_region(dev_num, 1); kfree(buffer); printk(KERN_INFO Hello driver unloaded\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(A simple hello world character device driver);3.2 编译驱动模块的 Makefile 配置为驱动代码编写对应的 Makefile# 指定内核源码路径根据实际路径修改 KDIR : /path/to/your/kernel # 指定架构和交叉编译工具链 ARCH : arm64 CROSS_COMPILE : aarch64-linux-gnu- # 模块目标名称 obj-m hello_driver.o all: $(MAKE) -C $(KDIR) M$(PWD) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) modules clean: $(MAKE) -C $(KDIR) M$(PWD) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) clean .PHONY: all clean编译驱动模块make成功编译后将生成hello_driver.ko文件这就是可加载到 RK3568 内核的驱动模块。3.3 在开发板上测试驱动模块1. 将模块文件传输到开发板使用 ADB 或 SCP 将编译好的模块文件传输到开发板adb push hello_driver.ko /data/2. 加载驱动模块在开发板的终端中执行# 切换到模块所在目录 cd /data # 加载模块 insmod hello_driver.ko # 检查模块是否加载成功 lsmod | grep hello_driver # 查看内核日志确认初始化信息 dmesg | tail -103. 测试设备节点功能驱动加载后会在/dev下生成hello_dev设备节点# 检查设备节点是否存在 ls -l /dev/hello_dev # 测试写入功能 echo test message /dev/hello_dev # 测试读取功能 cat /dev/hello_dev # 查看操作日志 dmesg | tail -54. 卸载驱动模块测试完成后卸载模块rmmod hello_driver4. Android 15 系统下的驱动集成与 HAL 适配4.1 将驱动集成到 Android 系统镜像在原型验证通过后需要将驱动正式集成到 Android 系统构建流程中使其随系统启动自动加载。1. 将驱动源码放入内核目录在内核源码的drivers/char/目录下创建hello_driver/文件夹将hello_driver.c和Kconfig、Makefile放入其中。配置 Kconfig 文件# drivers/char/hello_driver/Kconfig config HELLO_DRIVER tristate Simple Hello World Driver default n help This is a simple hello world character device driver for demonstration.配置 Makefile 文件# drivers/char/hello_driver/Makefile obj-$(CONFIG_HELLO_DRIVER) hello_driver.o2. 修改上层 Kconfig 和 Makefile在drivers/char/Kconfig中添加source drivers/char/hello_driver/Kconfig在drivers/char/Makefile中添加obj-$(CONFIG_HELLO_DRIVER) hello_driver/3. 配置内核并重新编译在menuconfig中启用该驱动make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- menuconfig导航到Device Drivers → Character devices → Simple Hello World Driver选择M编译为模块或*编译进内核。重新编译内核和模块make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc) make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- modules4.2 为驱动编写 Android HAL 层在 Android 系统中应用层通常不直接访问内核驱动而是通过 HALHardware Abstraction Layer进行交互。以下为 hello_driver 创建一个简单的 HAL 服务。1. 创建 HAL 接口定义文件在hardware/interfaces/hello/1.0/目录下创建IHello.halpackage android.hardware.hello1.0; interface IHello { write(string data) generates (bool success); read() generates (string data); };2. 生成 HAL 代码框架使用hidl-gen工具生成代码框架hidl-gen -o hardware/interfaces/hello/1.0/default/ \ -Lc-impl -randroid.hardware:hardware/interfaces \ -randroid.hidl:system/libhidl/transport android.hardware.hello1.0 hidl-gen -o hardware/interfaces/hello/1.0/default/ \ -Landroidbp-impl -randroid.hardware:hardware/interfaces \ -randroid.hidl:system/libhidl/transport android.hardware.hello1.03. 实现 HAL 服务在生成的Hello.cpp中实现具体的读写逻辑#include hidl/LegacySupport.h #include hidl/HidlSupport.h #include Hello.h #include fcntl.h #include unistd.h #include string using android::hardware::hello::V1_0::IHello; using android::hardware::Return; using android::hardware::Void; struct Hello : public IHello { Returnbool write(const hidl_string data) override { int fd open(/dev/hello_dev, O_WRONLY); if (fd 0) return false; ssize_t ret ::write(fd, data.c_str(), data.size()); close(fd); return (ret (ssize_t)data.size()); } Returnhidl_string read() override { int fd open(/dev/hello_dev, O_RDONLY); if (fd 0) return hidl_string(); char buffer[1024]; ssize_t ret ::read(fd, buffer, sizeof(buffer)-1); close(fd); if (ret 0) { buffer[ret] \0; return hidl_string(buffer); } return hidl_string(); } }; int main() { return android::hardware::configureRpcThreadpool(1, true) | android::hardware::registerAsService(new Hello()); }4. 配置 SELinux 策略在device/rockchip/rk3568/sepolicy/中添加策略文件允许 HAL 服务访问设备节点# hello_driver.te type hello_device, dev_type; type hello_service, system_api_service_type, service_manager_type; allow hello_service hello_device:chr_file { open read write };4.3 集成到 Android 构建系统1. 修改设备配置在设备 Makefile 中添加# device/rockchip/rk3568/device.mk PRODUCT_PACKAGES \ android.hardware.hello1.0-service2. 编译并刷写系统重新编译 Android 系统并刷写到开发板source build/envsetup.sh lunch rk3568-userdebug make -j$(nproc) # 刷写系统 fastboot flash boot out/target/product/rk3568/boot.img fastboot flash system out/target/product/rk3568/system.img fastboot reboot5. 驱动开发中的常见问题与排查方法5.1 编译阶段问题排查问题 1交叉编译工具链不匹配现象编译时出现未定义的架构相关错误。排查检查工具链版本与内核配置是否一致。解决使用内核源码目录中scripts/diffconfig对比配置差异或使用厂商推荐的工具链。问题 2内核头文件缺失现象编译驱动时找不到头文件。排查确认KDIR路径正确且已执行make modules_prepare。解决完整编译一次内核确保头文件生成。5.2 运行时问题排查问题 1模块加载失败现象insmod返回 Invalid module format 或 Unknown symbol。排查步骤检查内核版本是否匹配uname -r与编译时内核版本一致查看详细错误信息dmesg | tail -20检查符号依赖modinfo hello_driver.ko问题 2设备节点权限不足现象应用层无法打开设备节点。排查检查/dev/hello_dev的权限和所属组。解决在 init 脚本中设置正确权限或配置 udev 规则。5.3 调试技巧与工具使用1. 内核日志分级查看使用dmesg查看内核日志可通过级别过滤重要信息# 只显示错误和警告信息 dmesg -l err,warn # 实时监控新日志 dmesg -w2. 使用 printk 调试在驱动代码中合理添加 printk 语句注意日志级别printk(KERN_DEBUG Debug: variable value %d\n, var); // 调试信息 printk(KERN_INFO Info: device opened\n); // 普通信息 printk(KERN_WARNING Warning: unusual condition\n); // 警告 printk(KERN_ERR Error: operation failed\n); // 错误3. 使用 strace 跟踪系统调用在应用层调试时使用 strace 跟踪对设备节点的访问strace -e open,read,write,ioctl cat /dev/hello_dev6. 生产环境驱动开发最佳实践6.1 代码质量与维护性1. 错误处理要完整每个可能失败的操作都要有错误处理确保资源正确释放int example_probe(struct platform_device *pdev) { struct resource *res; void __iomem *base; res platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(pdev-dev, Failed to get memory resource\n); return -ENODEV; } base devm_ioremap_resource(pdev-dev, res); if (IS_ERR(base)) { dev_err(pdev-dev, Failed to map memory\n); return PTR_ERR(base); } // ... 其他初始化代码 return 0; }2. 使用 devm_ 资源管理函数自动管理资源生命周期避免内存泄漏// 传统方式需要手动释放 ptr kmalloc(size, GFP_KERNEL); // ... 使用 ptr kfree(ptr); // 推荐方式自动释放 ptr devm_kzalloc(pdev-dev, size, GFP_KERNEL);6.2 性能与稳定性考量1. 避免在中断上下文中进行耗时操作将耗时任务推送到工作队列或线程中执行static struct work_struct my_work; static void my_work_handler(struct work_struct *work) { // 耗时操作 msleep(100); printk(KERN_INFO Work completed\n); } // 在中断处理函数中调度工作 irqreturn_t my_interrupt_handler(int irq, void *dev_id) { schedule_work(my_work); return IRQ_HANDLED; }2. 合理使用互斥锁保护共享资源确保多线程访问时的数据一致性static DEFINE_MUTEX(my_lock); static int shared_data; static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int ret; mutex_lock(my_lock); // 读写共享数据 ret copy_to_user(buf, shared_data, sizeof(shared_data)); mutex_unlock(my_lock); return ret; }6.3 电源管理适配为驱动添加电源管理支持确保系统休眠时正确处理设备状态static int my_suspend(struct device *dev) { struct my_device *my_dev dev_get_drvdata(dev); // 保存设备状态 my_dev-saved_reg readl(my_dev-base REG_OFFSET); // 关闭设备时钟或电源 clk_disable(my_dev-clk); return 0; } static int my_resume(struct device *dev) { struct my_device *my_dev dev_get_drvdata(dev); // 恢复时钟和电源 clk_enable(my_dev-clk); // 恢复设备状态 writel(my_dev-saved_reg, my_dev-base REG_OFFSET); return 0; } static const struct dev_pm_ops my_pm_ops { .suspend my_suspend, .resume my_resume, };RK3568 的 Android 15 驱动开发需要平衡功能实现、系统稳定性和长期维护成本。从最简单的字符设备开始逐步掌握平台设备、中断处理、DMA 传输等复杂机制最终能够为复杂外设编写完整的驱动栈。在实际项目中建议先通过模块化开发验证基本功能再集成到系统镜像并建立完整的测试和调试流程。