四月 5, 2016

Android内核编译过程全解

Written by

之前编译过锤子的坚果手机内核,摸索了很长时间,遗憾的是没有把一些填坑的细节记录下来,免不了有些细节还得摸索一遍。这次要编译三星的Galaxy Note 5内核,加上已经有了一次成功经历,所以想把它系统化的记录下来,供自己或其他需要的人参考。

我这里主要讲内核的编译,至于关联到的一些其他工具的安装或配置,这里就不展开了,碰到这样的问题请移步问问Google。

1.  获取CPU信息

要为某手机编译内核,首先要了解手机所用的CPU,不同厂商生产的CPU,对应的linux内核是不一样的。

查看CPU信息的一种方法是利用adb,“adb shell cat /roc/cpuinfo”可以得到cpu架构和生产厂商。另外,利用“adb shell cat /proc/version”还可以得到手机中正在使用的内核信息。以下是我的Galaxy note 5的信息:将手机与PC通过usb线相连,首先查看adb是否已经可以访问、然后获取cpu信息、最后获取linux内核信息:

C:\Users\yuanhui>adb devices
List of devices attached
0715e7e408981f38 device

C:\Users\yuanhui>adb shell cat /proc/cpuinfo
Processor : AArch64 Processor rev 2 (aarch64)
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7
Features : fp asimd aes pmull sha1 sha2 crc32
CPU implementer : 0x41
CPU architecture: AArch64
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 2

Hardware : SAMSUNG Exynos7420

C:\Users\yuanhui>adb shell cat /proc/version
Linux version 3.10.61-6137732 (dpi@SWDC3312) (gcc version 4.9 20140514 (prerelea
se) (GCC) ) #1 SMP PREEMPT Fri Feb 5 13:33:23 KST 2016
另外,通过官网也可以获得比较详细的硬件信息及参数:

QQ截图20160401115714

2. 下载源代码

得到CPU信息后,就可以到Android网站去下载linux内核源码了,下载地址说明:

http://source.android.com/source/building-kernels.html#figuring-out-which-kernel-to-build

这里需要特别注意,三星的源码树有两个,一个是kernel/exynos,一个是kernel/samsung,Galaxy note 5 用的是exynos芯片,所以一定要下载exynos的源码。

QQ截图20160401115941

所以,git命令为:

$ git clone https://android.googlesource.com/kernel/exynos

这里稍微注意一下,由于国内googlesource.com被墙了,只能通过VPN才能下载。

我为了方便,所有下载及编译都是在Ubuntu里完成的:

hyh@ubuntu:~$ git clone https://android.googlesource.com/kernel/exynos
Cloning into ‘exynos’…
remote: Sending approximately 733.06 MiB …
remote: Counting objects: 9, done
remote: Finding sources: 100% (9/9)
Receiving objects: 100% (3159494/3159494), 733.07 MiB | 631.00 KiB/s, done.
remote: Total 3159494 (delta 2631328), reused 3159494 (delta 2631328)
Resolving deltas: 100% (2631328/2631328), done.
Checking connectivity… done.
hyh@ubuntu:~$

这样,在我的home下就生成了一个exynos目录,源码就在这个文件夹里。

为了后面使用方便,把目录改成了linux-kernel-exynos。

到里面看看都有些什么分支:

hyh@ubuntu:~$ cd linux-kernel-exynos/
hyh@ubuntu:~/linux-kernel-exynos$ git branch -r
origin/HEAD -> origin/master
origin/android-exynos-3.4
origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release
origin/android-exynos-manta-3.4-adf
origin/android-exynos-manta-3.4-jb-mr1
origin/android-exynos-manta-3.4-jb-mr1-fr
origin/android-exynos-manta-3.4-jb-mr1.1
origin/android-exynos-manta-3.4-jb-mr2
origin/android-exynos-manta-3.4-kitkat-mr0
origin/android-exynos-manta-3.4-kitkat-mr1
origin/android-exynos-manta-3.4-kitkat-mr2
origin/android-exynos-manta-3.4-lollipop-mr1
origin/android-exynos-manta-3.4-lollipop-release
origin/master
hyh@ubuntu:~/linux-kernel-exynos$

之前看到手机用的是3.10的内核,那就把3.10的分支checkout出来:

hyh@ubuntu:~/linux-kernel-exynos$ git checkout origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release
Checking out files: 100% (45351/45351), done.
Note: checking out ‘origin/android-exynos-koi-3.10-marshmallow-mr1-wear-release’.

You are in ‘detached HEAD’ state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 0f9bded… Merge “Fix that wrong eint information is displayed” into android-exynos-koi-3.10
hyh@ubuntu:~/linux-kernel-exynos$

这时候才能看到目录下有了源代码(之前目录里面实际有个800多兆的.git文件夹,隐藏了,看不到而已)。

3. 配置/定制内核

通常情况下,各个厂商都会针对自己的手机做大量定制,如果编译内核时选择生成相应的配置文件,则我们可以从手机中直接得到。能拿到这个内核配置文件编译内核就会顺利很多。

C:\Users\yuanhui>adb pull /proc/config.gz

大部分情况都能拿到这个config文件,然后解压出其中的.config文件放到linux-kernel-exynos目录下,直接调用make ARCH=arm menuconfig即可定制内核。

但是很不幸,三星的这款手机没有此文件:

C:\Users\yuanhui>adb pull /proc/config.gz
remote object ‘/proc/config.gz’ does not exist

好吧,没有也没关系,直接make ARCH=arm menuconfig,通常情况也是能顺利编译的……

我这里为了把CP210x的驱动编译进去,从Silicon官网下载了Android内核编译CP210x驱动的文档,照着把CP210x驱动编译选项勾选好,保存退出,在linux-kernel-exynos目录下生成了一个.config的文件:

hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm menuconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/lxdialog/checklist.o
HOSTCC scripts/kconfig/lxdialog/inputbox.o
HOSTCC scripts/kconfig/lxdialog/menubox.o
HOSTCC scripts/kconfig/lxdialog/textbox.o
HOSTCC scripts/kconfig/lxdialog/util.o
HOSTCC scripts/kconfig/lxdialog/yesno.o
HOSTCC scripts/kconfig/mconf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCC scripts/kconfig/zconf.tab.o
In file included from scripts/kconfig/zconf.tab.c:2503:0:
scripts/kconfig/menu.c: In function ‘get_symbol_str’:
scripts/kconfig/menu.c:567:18: warning: ‘jump’ may be used uninitialized in this function [-Wmaybe-uninitialized]
jump->offset = r->len – 1;
^
scripts/kconfig/menu.c:528:19: note: ‘jump’ was declared here
struct jump_key *jump;
^
HOSTLD scripts/kconfig/mconf
scripts/kconfig/mconf Kconfig
#
# using defaults found in /boot/config-4.2.0-23-generic
#
/boot/config-4.2.0-23-generic:928:warning: symbol value ‘m’ invalid for BRIDGE_NETFILTER
/boot/config-4.2.0-23-generic:2668:warning: symbol value ‘m’ invalid for STMMAC_PLATFORM
/boot/config-4.2.0-23-generic:3834:warning: symbol value ‘m’ invalid for GPIO_UCB1400
/boot/config-4.2.0-23-generic:4336:warning: symbol value ‘m’ invalid for MFD_WM8994
/boot/config-4.2.0-23-generic:4343:warning: symbol value ‘m’ invalid for REGULATOR_88PM8607
/boot/config-4.2.0-23-generic:4365:warning: symbol value ‘m’ invalid for REGULATOR_LP872X
/boot/config-4.2.0-23-generic:4367:warning: symbol value ‘m’ invalid for REGULATOR_LP8788
/boot/config-4.2.0-23-generic:4410:warning: symbol value ‘m’ invalid for REGULATOR_TWL4030
/boot/config-4.2.0-23-generic:5453:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_REALTEK
/boot/config-4.2.0-23-generic:5454:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_ANALOG
/boot/config-4.2.0-23-generic:5455:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_SIGMATEL
/boot/config-4.2.0-23-generic:5456:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_VIA
/boot/config-4.2.0-23-generic:5457:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_HDMI
/boot/config-4.2.0-23-generic:5458:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CIRRUS
/boot/config-4.2.0-23-generic:5459:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CONEXANT
/boot/config-4.2.0-23-generic:5460:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CA0110
/boot/config-4.2.0-23-generic:5461:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CA0132
/boot/config-4.2.0-23-generic:5463:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_CMEDIA
/boot/config-4.2.0-23-generic:5464:warning: symbol value ‘m’ invalid for SND_HDA_CODEC_SI3054
/boot/config-4.2.0-23-generic:5465:warning: symbol value ‘m’ invalid for SND_HDA_GENERIC
/boot/config-4.2.0-23-generic:6539:warning: symbol value ‘m’ invalid for COMEDI_PCI_DRIVERS
/boot/config-4.2.0-23-generic:6594:warning: symbol value ‘m’ invalid for COMEDI_PCMCIA_DRIVERS
/boot/config-4.2.0-23-generic:6602:warning: symbol value ‘m’ invalid for COMEDI_USB_DRIVERS
/boot/config-4.2.0-23-generic:7036:warning: symbol value ‘m’ invalid for LP8788_ADC
/boot/config-4.2.0-23-generic:8176:warning: symbol value ‘m’ invalid for KVM
configuration written to .config

*** End of the configuration.
*** Execute ‘make’ to start the build or try ‘make help’.

hyh@ubuntu:~/linux-kernel-exynos$

 

4. 编译内核

 

不同的CPU架构,就得选择不同架构的工具链。我们在刚开始的时候通过adb shell cat /proc/cpuinfo已经得到了CPU的家规信息,为aarch64,所以我们要选择的aarch64工具链来编译,Google Source 网站上同时提供了各个版本的编译工具:

https://android.googlesource.com/platform/prebuilts/

我们选择aarch64 gcc 4.9的版本来编译:

hyh@ubuntu:~$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9
Cloning into ‘aarch64-linux-android-4.9’…
remote: Sending approximately 180.15 MiB …
remote: Counting objects: 108, done
remote: Finding sources: 100% (108/108)
remote: Total 1641 (delta 990), reused 1641 (delta 990)
Receiving objects: 100% (1641/1641), 180.18 MiB | 620.00 KiB/s, done.
Resolving deltas: 100% (990/990), done.
Checking connectivity… done.
hyh@ubuntu:~$

把这个路径加入到$PATH中,以便编译时省去冗长的路径:

hyh@ubuntu:~/linux-kernel-exynos$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
hyh@ubuntu:~/linux-kernel-exynos$ export PATH=$PATH:~/aarch64-linux-android-4.9/bin
hyh@ubuntu:~/linux-kernel-exynos$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/hyh/aarch64-linux-android-4.9/bin

可以看到,用于编译内核的编译器路径已经添加到了$PATH中。

下面开始编译:
hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm CROSS_COMPILE=arm-linux-androideabi- zImage
CHK include/generated/uapi/linux/version.h
CHK include/generated/utsrelease.h
make[1]: ‘include/generated/mach-types.h’ is up to date.
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/arm/kernel/asm-offsets.s
In file included from include/linux/scatterlist.h:10:0,
from include/linux/dma-mapping.h:9,
from arch/arm/kernel/asm-offsets.c:15:
/home/hyh/linux-kernel-exynos/arch/arm/include/asm/io.h:30:28: fatal error: mach/exynos-ss.h: No such file or directory
#include <mach/exynos-ss.h>
^
compilation terminated.
/home/hyh/linux-kernel-exynos/./Kbuild:81: recipe for target ‘arch/arm/kernel/asm-offsets.s’ failed
make[1]: *** [arch/arm/kernel/asm-offsets.s] Error 1
Makefile:836: recipe for target ‘prepare0’ failed
make: *** [prepare0] Error 2
hyh@ubuntu:~/linux-kernel-exynos$

出错了,改个编译工具,网上下载了一个arm-eabi-4.8,放到hyh目录下,在$PATH中加入目录/home/hyh/arm-eabi-4.8/bin,编译。这次好点,编译了大部分代码,但是在编译驱动时出错了:

hyh@ubuntu:~/linux-kernel-exynos$ make ARCH=arm CROSS_COMPILE=arm-eabi- zImage
CHK include/linux/version.h
CHK include/generated/utsrelease.h
make[1]: ‘include/generated/mach-types.h’ is up to date.
CALL scripts/checksyscalls.sh
CHK include/generated/compile.h
CC drivers/mfd/ezx-pcap.o
drivers/mfd/ezx-pcap.c: In function ‘pcap_isr_work’:
drivers/mfd/ezx-pcap.c:205:2: error: implicit declaration of function ‘irq_to_gpio’ [-Werror=implicit-function-declaration]
} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
^
cc1: some warnings being treated as errors
scripts/Makefile.build:305: recipe for target ‘drivers/mfd/ezx-pcap.o’ failed
make[2]: *** [drivers/mfd/ezx-pcap.o] Error 1
scripts/Makefile.build:441: recipe for target ‘drivers/mfd’ failed
make[1]: *** [drivers/mfd] Error 2
Makefile:945: recipe for target ‘drivers’ failed
make: *** [drivers] Error 2
hyh@ubuntu:~/linux-kernel-samsung$

发现ezx-pcap.c文件中有一段如下代码:

static void pcap_isr_work(struct work_struct *work)
{
struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work);
struct pcap_platform_data *pdata = pcap->spi->dev.platform_data;
u32 msr, isr, int_sel, service;
int irq;

do {
ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);

/* We can’t service/ack irqs that are assigned to port 2 */
if (!(pdata->config & PCAP_SECOND_PORT)) {
ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
isr &= ~int_sel;
}

ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr);
ezx_pcap_write(pcap, PCAP_REG_ISR, isr);

local_irq_disable();
service = isr & ~msr;
for (irq = pcap->irq_base; service; service >>= 1, irq++) {
if (service & 1)
generic_handle_irq(irq);
}
local_irq_enable();
ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr);
} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
}

定位到申明的位置:<linux/gpio.h>,在gpio.h文件中有这样一段申明:

static inline int irq_to_gpio(unsigned irq)
{
/* irq can never have been returned from gpio_to_irq() */
WARN_ON(1);
return -EINVAL;
}

可见被加了static,外部无法访问。而且,看到注释,以及返回值,可见这个函数是无用的。分析在ezx-pcap.c文件中的这段代码,do while会执行一次这段代码,但irq_to_gpio()永远只会返回失败,所以一定只会执行一次,所以果断把 “do{” 和 “}while”  这两行代码屏蔽:

static void pcap_isr_work(struct work_struct *work)
{
struct pcap_chip *pcap = container_of(work, struct pcap_chip, isr_work);
struct pcap_platform_data *pdata = pcap->spi->dev.platform_data;
u32 msr, isr, int_sel, service;
int irq;

//do {
ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);

/* We can’t service/ack irqs that are assigned to port 2 */
if (!(pdata->config & PCAP_SECOND_PORT)) {
ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
isr &= ~int_sel;
}

ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr);
ezx_pcap_write(pcap, PCAP_REG_ISR, isr);

local_irq_disable();
service = isr & ~msr;
for (irq = pcap->irq_base; service; service >>= 1, irq++) {
if (service & 1)
generic_handle_irq(irq);
}
local_irq_enable();
ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr);
//} while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
}

再次编译,过了:

……

LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
LD .tmp_vmlinux1
KSYM .tmp_kallsyms1.S
AS .tmp_kallsyms1.o
LD .tmp_vmlinux2
KSYM .tmp_kallsyms2.S
AS .tmp_kallsyms2.o
LD vmlinux
SYSMAP System.map
SYSMAP .tmp_System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
GZIP arch/arm/boot/compressed/piggy.gzip
AS arch/arm/boot/compressed/piggy.gzip.o
SHIPPED arch/arm/boot/compressed/lib1funcs.S
AS arch/arm/boot/compressed/lib1funcs.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
hyh@ubuntu:~/linux-kernel-exynos$

5. 后记

最后一步的编译经常会出一些具体的问题,比如某个包含文件找不到、某段代码编译出错,这种错误需要仔细分析错误提示,往往能定位到问题。源码过于庞大,而实际的应用千差万别,导致自定义后的某些模块之间出现问题。所以这样的问题只能是具体问题具体分析,没用的代码甚至可以屏蔽。这恰恰也是导致部分自定义代码无法编译通过的原因。

 

Category : AndroidARMKernelLinux/UnixNDK

Tags :

发表评论

电子邮件地址不会被公开。

Proudly powered by WordPress and Sweet Tech Theme