Pixel 6Pro Android驱动模块编译

Android 驱动编译背景

Android 内核驱动是Android中负责硬件的连接和操控,确保硬件与操作系统的交互,这些驱动是内核的拓展组件,通过提供统一的驱动操作接口供用户层使用。

Android内核驱动主要分为两种类型:Android专用驱动和Android使用的设备驱动(基于Linux)。其中,Android专用驱动包括如Ashmem(匿名共享内存驱动)、Logger(轻量级的日志驱动)、Binder驱动(基于OpenBinder驱动,为Android平台提供IPC进程间通信的支持)等。而设备驱动则主要基于Linux的设备驱动模型,负责管理各种硬件设备,如USB驱动、物理内存驱动等。

在Android系统中,一个传统的驱动架构通常包括kernel驱动、HAL(硬件抽象层)驱动、JNI(Java Native Interface)层、service层等。程序运行时的调用关系通常是从apk(应用程序包)开始,通过service层、JNI层、HAL层,最终到达kernel驱动层。

在开发 Android 驱动程序的过程中,如何对开发好的驱动程序进行编译。

对于Linux驱动有两种编译和运行方式:

  • 将驱动编译进Linux 内核中,当Linux 内核启动的时候就会自动运行驱动程序。
  • 将驱动编译成模块(Linux 中模块拓展名为.ko), 在 Linux 内核启动以后使用相应命令加载驱动模块。
    • 内核模块是Linux 内核向外部提供的一个插口
    • 内核模块是具有独立功能的程序,可以被单独编译,但不能单独运行,在运行时被链接到内核作为内核一部分在内核空间运行。
    • 内核模块便于 驱动和文件系统的二次开发

所以想要驱动在Android 系统中运行,有两种编译方式:

  • 驱动编译到内核源码树中,直接编译到内核中。
  • 驱动编译到内核模块,或者随着内核源码树一起编译成ko 文件。

这里将介绍如何把字符设备驱动源程序在内核源码中编译成 .ko 文件,然后加载到pixel 6P 中。主要分为两部分

  • 第一步:将字符设备驱动源代码放到内核 /common/driver/char/ 目录中
  • 第二步:编辑Kconfig 和 Makefile 文件,Kconfig 文件 是在编译前执行配置命令 make menuconfi 时用到的;Makefile 在执行编译命令make 用到的。

驱动源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
android_hello.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

static int major = 300; # 主设备号
static int minor = 0;
static dev_t devno;
static struct cdev cdev;

static int hello_open(struct inode *inode, struct file *filep)
{
printk("hello open\n");
return 0;
}
static struct file_operations hello_ops=
{
.open = hello_open,
};
static int hello_init(void)
{
int ret;

printk("hello_init\n");
printk("ohhhhhhhhh HelloWorld driver!");
devno = MKDEV(major, minor); # 定义设备号宏
ret = register_chrdev_region(devno, 1, "HelloWorld"); 注册字符设备
if(ret < 0)
{
printk("register_chrdev_region faile\n");
return ret;
}
cdev_init(&cdev, &hello_ops);
ret = cdev_add(&cdev, devno, 1);
if(ret < 0)
{
printk("cdev_add faile \n");
return ret;
}
return 0;
}
static void hello_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
printk("hello_exit\n");
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

编译过程

这里我下载的是common-android13-5.10的通用内核源码,这里在编译驱动之前要将这个common-android13-5.10 先编译出来,然后将boot.img 刷入到pixel6P 中,避免内核版本不一致,导致后面编译出来的驱动加载不起来,另外,单独对 pixel 6P boot 分区刷编译的内核镜像要注意的是,编译出来的内核镜像版本不能低于pixel 6P 原来的内核版本,否则会成砖,要重新刷入工厂镜像。

因为我测试过common-android13-5.10编译出来的boot.img 刷到pixel6P中,然后驱动可以执行,但是再pixel 5 中,都是aarch64 架构的,就无法加载了,显示ELF格式错误。

1
2
3
4
5
6
7
8
Pixel 5
redfin:/data/local/tmp # insmod android_hello.ko
insmod: failed to load android_hello.ko: Exec format error
1|redfin:/data/local/tmp # file android_hello.ko
android_hello.ko: ELF relocatable, 64-bit LSB arm64, static, BuildID=5d50bec4bbc8b8d26924f0630567fb9a4b0e347c, not stripped
redfin:/data/local/tmp # uname -a
Linux localhost 4.19.252-g005154455a52-ab9276409 #1 SMP PREEMPT Wed Nov 9 11:59:07 UTC 2022 aarch64 Toybox
redfin:/data/local/tmp #

在pixel6P中

1
2
3
4
5
6
raven:/data/local/tmp # file android_hello.ko
android_hello.ko: ELF relocatable, 64-bit LSB arm64, static, BuildID=5d50bec4bbc8b8d26924f0630567fb9a4b0e347c, not stripped
raven:/data/local/tmp # insmod android_hello.ko
insmod: failed to load android_hello.ko: File exists
1|raven:/data/local/tmp # uname -a
Linux localhost 5.10.205-android13-4-00002-g6bc28fdfeec3-dirty #1 SMP PREEMPT Tue Mar 12 21:27:16 UTC 2024 aarch64 Toybox

排除内核源码版本的问题后,将代码放到对应的 内核源码目录内,

然后在 /drivers/char/Kconfig 中添加配置。

1
2
3
4
5
6
7
Kconfig
config ANDROID_HELLO
tristate "MY testAndroid_hello"
default y
help
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
endmenu

如果驱动程序单独放在一个文件夹内,那么要在该文件夹内单独编写Kconfig, 然后在上一级目录的Kconfig中加入Kconfig 的路径,便于menuconfig 找得到你的Kconfig。
类似于如下所示

然后在/drivers/char/Makefile 中添加你要编译的对象。

1
2
3
Makefile
obj-m += android_hello.o
或者obj-$(CONFIG_ANDROID_HELLO) += android_hello.o

然后执行在 /common-android13-5.10/common 中执行 make menuconfig 命令,

修改 “MY testAndroid_hello” 为 M , 因为 M 表示程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成KO 文件,Y 表示直接编译内核。

在char driver 中可以看到我们定义的内核模块

然后 /common-android13-5.10/ 下执行

BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
开始编译

可以看到 交叉编译链 将 android_hello.o 编译为 module。

编译到这一块会中断,显示如下的 “ERROR: modules list out of data”

虽然中断了,但不影响ko编译出来

如果想解决整个ERROR, 在build.sh 文件中可以看到整个判断

可以删除“exit 1”,或者只需要

cp /root/aosp/common-android13-5.10/out/android13-5.10/common/modules.order common/android/gki_aarch64_modules
再次编译就可以成功通过编译了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
部分编译log
+ set +x
========================================================
Checking the list of modules:
========================================================
Comparing the KMI and the symbol lists:
+ gki_modules_list=/root/aosp/common-android13-5.10/common/android/gki_system_dlkm_modules
++ sed 's/\.ko$//' /root/aosp/common-android13-5.10/common/android/gki_system_dlkm_modules
++ tr '\n' ' '
+ KMI_STRICT_MODE_OBJECTS='vmlinux '
+ /root/aosp/common-android13-5.10/build/abi/compare_to_symbol_list /root/aosp/common-android13-5.10/out/android13-5.10/common/Module.symvers /root/aosp/common-android13-5.10/out/android13-5.10/common/abi_symbollist.raw
+ set +x
========================================================
Installing kernel modules into staging directory
INSTALL drivers/char/android_hello.ko # 编译出来的驱动
DEPMOD 5.10.205-android13-4-00002-g6bc28fdfeec3-dirty
========================================================
Generating gki_certification_tools.tar.gz
========================================================
Generating test_mappings.zip
========================================================

adb push 到手机上

cat /proc/devices 查看

总结

编译驱动多看看Android 内核源码中驱动编译的流程,这里我没有刷面具,而是直接在编译内核镜像的时候,把KernelSU 编译到内核中,这样可以不用单独刷面具拿root。后面有时间再用交叉编译链来单独编译android 能用的ko 驱动模块。


Pixel 6Pro Android驱动模块编译
https://tig3rhu.github.io/2024/03/28/25__Pixel6P_Android驱动模块编译/
Author
Tig3rHu
Posted on
March 28, 2024
Licensed under