X-012-KERNEL-serial early console的移植
作者:wowo 发布于:2016-10-2 22:50 分类:X Project
1. 前言
对Linux kernel工程师来说,最依赖的工具非printk莫属(不多解释,大家都懂)。因此,在Linux kernel移植的初期阶段,如果能够尽快地实现printk功能,将会为后续的工作带来极大的帮助。
在众多可用作printk输出的终端里面(串口、屏幕、USB、网络、等等),串口终端(也即串口驱动)无疑是实现起来最简单一种,因此也是嵌入式linux开发过程中(特别是早期阶段)最普遍使用的。
但是,受限于Linux TTY框架的复杂性[1],长久以来,在Kernel移植的初期阶段(各种功能都不ready,缺乏有效的调试手段),快速的实现serial driver也是一个不小的挑战。不过,随着serial subsystem中的early console功能的出现,这种状况得到了极大的改善。
本文将借助“X Project” kernel的开发过程,介绍serial early console功能的移植过程。
注1:博客中“Linux TTY子系统”的分析正在缓慢推进,不过还未涉及console、serial subsystem、earlycon等模块。因此我一直在纠结先写理论(分析),还是先写实践(移植说明)。结论是先写本文,理由是:虽然earlycon功能涉及到TTY子系统的复杂知识,如console driver、tty driver、serial driver等等,但从移植的角度看,我们可以什么都不懂。这种优雅的抽象和封装,是优秀软件(如Linux kernel)所必需具备的特性,也是我们软件人需要竭力追求的神圣目标。
2. 移植步骤
2.1 串口驱动
early console是linux serial subsystem的一个子功能,因此需要依附于具体平台的serial driver之下(放心,本文不涉及任何串口驱动的知识)。以“X Project” 所使用的bubblegum-96平台为例,我们需要在“drivers/tty/serial/”下新建一个serial driver,并修改kernel serial subsystem的Kconfig和Makefile文件,将其添加到kernel的编译系统中, 步骤如下:
1)新建serial driver
touch drivers/tty/serial/owl-serial.c
其中“owl”是bubblegum-96平台所使用SOC的代号,大家可以根据实际情况,使用和自己平台匹配的名称。
2)修改drivers/tty/serial/Kconfig和drivers/tty/serial/Makefile,将新建的serial driver加入到编译框架中
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 13d4ed6..bbf4b69 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1624,6 +1624,14 @@ config SERIAL_MVEBU_CONSOLE
and warnings and which allows logins in single user mode)
Otherwise, say 'N'.+config SERIAL_OWL
+ tristate "Actions OWL serial port support"
+ depends on ARCH_OWL
+ select SERIAL_CORE
+ select SERIAL_CORE_CONSOLE
+ help
+ If you have a machine based on an Actions OWL CPU you
+ can enable its onboard serial ports by enabling this option.
endmenuconfig SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 8c261ad..3f75d73 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -91,6 +91,7 @@ obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o
obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o
obj-$(CONFIG_SERIAL_STM32) += stm32-usart.o
obj-$(CONFIG_SERIAL_MVEBU_UART) += mvebu-uart.o
+obj-$(CONFIG_SERIAL_OWL) += owl-serial.o
我们为新建的serial driver指定了“SERIAL_OWL”配置项,该配置项依赖ARCH_OWL[2]。与此同时,我们需要选中“SERIAL_CORE”和“SERIAL_CORE_CONSOLE”两个配置项,以支持earlycon功能。
3)配置kernel,开启TTY、serial等功能,并使能我们新加入的serial driver
cd ~/work/xprj/build
make kernel-config#选中如下的配置项
Device Drivers --->
Character devices --->
[*] Enable TTY
Serial drivers --->
[*] Actions OWL serial port support
完成后make kernel重新编译即可。
2.2 early console的移植
实现early console的移植过程非常简单,包括:
1)实现一个early console的setup接口,并调用serial core提供的注册接口(EARLYCON_DECLARE)将其注册到kernel中,如下:
int __init earlycon_owl_setup(struct earlycon_device *device, const char *opt)
{
/* TODO */
uart5_base = early_ioremap(UART5_BASE, 4);device->con->write = earlycon_owl_write;
return 0;
}
EARLYCON_DECLARE(owl_serial, earlycon_owl_setup);
其中“owl_serial”是early console的名称,earlycon_owl_setup是开始使用之前的初始化接口,需要在这个初始化接口中做两件事情:
进行一些必要的初始化,如例子中的寄存器map;
为printk输出制定一个write接口----earlycon_owl_write。
注2:“EARLYCON_DECLARE”是实现early console的一种方法,另外我们也可以使用device tree的方式,为了不增加复杂度,本文就不提及相关的实现了。
注3:为了简单,early console和u-boot[3]使用相同的串口,并且使用相同的配置,因此不需要额外的初始化操作。
注4:虽然配置不变,我们还是需要访问串口寄存器输出字符串,因此需要map相应的IO地址,由于early console需要在很早的时候使用,此时mm还没有初始化,因此我们可以用early_ioremap[4]进行map。
2)earlycon_owl_write的实现
console输出需要字符串处理,可以借用serial core的标准接口----uart_console_write:
static void earlycon_owl_write(struct console *con, const char *s, unsigned n)
{
/* TODO */
uart_console_write(NULL, s, n, owl_serial_putc);
}
我们需要做的就是提供一个字符输出的API----owl_serial_putc,具体可参考代码以及“X-004-UBOOT-串口驱动移植(Bubblegum-96平台)[3]”中有关的描述。
3)移植完成后重新编译kernel即可
3. 使用说明
移植完成后,可以通过kernel的命令行参数,告诉kernel在启动的时候使用该early console,参数的格式如下:
earlycon=owl_serial
其中“earlycon”是关键字,“owl_serial”是early console的名字。
命令行参数可以通过u-boot传入,为了测试方便,可以暂时加到kernel的配置项中[5],如下:
#
# Boot options
#
-CONFIG_CMDLINE=""
+CONFIG_CMDLINE="earlycon=owl_serial"
以上改动具体可参考下面的patch:
https://github.com/wowotechX/linux/commit/1285ab5e6e5c5bb263a1d715fe04cca2d217bd98
4. 测试和调试
移植完成后,按照“README.bubblegum96[6]”中的步骤,编译并运行kernel,发现printk信息没有如约出现,没关系,兵来将挡,水来土掩,开始调试了。
4.1 使用点LED的方式,定位问题所在位置
该方法在u-boot移植的初期,也是用过,应该是轻车熟路了[7]。不过在kernel中有点稍微的不一样,我们需要将GPIO有关的寄存器map成虚拟地址才能使用,代码如下(只为暂时调试使用,因而没有上传到代码仓库):
1)在init/main.c中添加LED debug有关的函数
+void __init xprj_earlyyyy_debug(void)
+{
+ static void __iomem *gpioa_outen = NULL;
+ static void __iomem *gpioa_outdat = NULL;
+
+ if (gpioa_outen == NULL) {
+ gpioa_outen = early_ioremap(0xe01b0000, 4);
+ gpioa_outdat = early_ioremap(0xe01b0008, 4);
+ }
+
+ writel(0xffffffff, gpioa_outen);
+ writel(0xffffffff, gpioa_outdat);
+}
注5:为了可以尽早(在kernel内存管理模块初始化之前)使用,我们使用early_ioremap获取寄存器的虚拟地址[4]。
注6:为了简单,这里粗暴的把GPIOA的所有GPIO都输出高电平了。
2)根据earlycon的初始化逻辑,在earlycon初始化的路径上,调用debug API,点亮LED。第一个地方是setup_earlycon(earlycon的具体流程,会在下一篇文章中分析):
+extern void __init xprj_earlyyyy_debug(void);
int __init setup_earlycon(char *buf)
{
const struct earlycon_id *match;
+#if 1
+ xprj_earlyyyy_debug();
+ while (1);
+#endif
+
无法点亮!
接着往前跟踪,放到param_setup_earlycon中:
@@ -204,6 +210,11 @@ static int __init param_setup_earlycon(char *buf)
{
int err;
+#if 1
+ xprj_earlyyyy_debug();
+ while (1);
+#endif
+
还是无法点亮!
怀疑earlycon的代码压根没有执行,检查drivers/tty/serial/Makefile,发现由CONFIG_SERIAL_EARLYCON控制:
obj-$(CONFIG_SERIAL_EARLYCON) += earlycon.o
仿照drivers/tty/serial/Kconfig中其它serial driver,在OWL_SERIAL中select该配置项,如下:
@@ -1629,6 +1629,7 @@ config SERIAL_OWL
depends on ARCH_OWL
select SERIAL_CORE
select SERIAL_CORE_CONSOLE
+ select SERIAL_EARLYCON
help
If you have a machine based on an Actions OWL CPU you
can enable its onboard serial ports by enabling this option.
配置kernel后,再次编译测试,得到如下的执行过程:
param_setup_earlycon OK
setup_earlycon OK
earlycon_owl_setup OK
earlycon_owl_write FAIL
看来初始化OK了,没有人调用console的write接口?
注7:上面debug的过程中,下面的代码会导致编译错误:
extern void __init xprj_earlyyyy_debug(void);
static void earlycon_owl_write(struct console *con, const char *s, unsigned n)
{
#if 1
xprj_earlyyyy_debug();
while (1);
#endif
错误信息为:
WARNING: modpost: Found 2 section mismatch(es).
To see full details build your kernel with:
make CONFIG_DEBUG_SECTION_MISMATCH=y'
FATAL: modpost: Section mismatches detected.
Set CONFIG_SECTION_MISMATCH_WARN_ONLY=y to allow them.
make[3]: *** [vmlinux.o] Error 1
make[2]: *** [vmlinux] Error 2
原因是我们在非__init的section中(earlycon_owl_write)调用了__init section(xprj_earlyyyy_debug)的代码,按照提示说的,打开CONFIG_SECTION_MISMATCH_WARN_ONLY配置项即可编译通过:
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Make section mismatch errors non-fatal
4.2 使能printk的配置项
依稀记得linux kernel的printk有配置项可以控制使能与否,检查kernel的相关代码,果然如此。重新配置kernel使能该配置项:
General setup --->
[*] Enable support for printk
再次运行,串口有输出了:
Starting kernel ...
flushing dcache successfully.
Booting Linux on physical CPU 0xrFailed to find device node for bpDBPDentry cacInode-cache hash table entries: software IO TLB [mem 0x79cb4000-Memory: 1993624K/2097152K availa0ffSsaK
觉得奇奇怪怪的?没有换行?没有格式化输出?
4.3 检查earlycon驱动
再仔细检查一下drivers/tty/serial/owl-serial.c中owl_serial_putc的实现,有个地方写错了(从u-boot抄过来的时候大意了),修改一下:
static void owl_serial_putc(struct uart_port *port, int ch)
{
- if (readl(uart5_base + UART_STAT) & UART_STAT_TFFU)
- return;
+ /* wait for TX FIFO untill it is not full */
+ while (readl(uart5_base + UART_STAT) & UART_STAT_TFFU)
+ ;
writel(ch, uart5_base + UART_TXDAT);
}
终于OKAY了:
Booting Linux on physical CPU 0x0
Linux version 4.6.0-rc5+ (pengo@ubuntu) (gcc version 4.8.3 20131202 (prerelease) (crosstool-NG linaro-1.13.1-4.8-2013.12 - Linaro GCC 2013.11) ) #8 SMP Mon Oct 3 23:36:39 PDT 2016
Boot CPU: AArch64 Processor [410fd032]
earlycon: owl_serial0 at I/O port 0x0 (options '')
bootconsole [owl_serial0] enabled
Failed to find device node for boot cpu
missing boot CPU MPIDR, not enabling secondaries
percpu: Embedded 14 pages/cpu @ffffffc07ffdd000 s28032 r0 d29312 u57344
Detected VIPT I-cache on CPU0
Built 1 zonelists in Zone order, mobility grouping on. Total pages: 516096
Kernel command line: earlycon=owl_serial
PID hash table entries: 4096 (order: 3, 32768 bytes)
Dentry cache hash table entries: 262144 (order: 9, 2097152 bytes)
Inode-cache hash table entries: 131072 (order: 8, 1048576 bytes)
software IO TLB [mem 0x79cb4000-0x7dcb4000] (64MB) mapped at [ffffffc079cb4000-ffffffc07dcb3fff]
Memory: 1993624K/2097152K available (1028K kernel code, 78K rwdata, 120K rodata, 116K init,
201K bss, 103528K reserved, 0K cma-reserved)
Virtual kernel memory layout:
modules : 0xffffff8000000000 - 0xffffff8008000000 ( 128 MB)
vmalloc : 0xffffff8008000000 - 0xffffffbdbfff0000 ( 246 GB)
.text : 0xffffff8008080000 - 0xffffff8008181000 ( 1028 KB)
.rodata : 0xffffff8008181000 - 0xffffff80081a0000 ( 124 KB)
.init : 0xffffff80081a0000 - 0xffffff80081bd000 ( 116 KB)
.data : 0xffffff80081bd000 - 0xffffff80081d0800 ( 78 KB)
fixed : 0xffffffbffe7fd000 - 0xffffffbffec00000 ( 4108 KB)
PCI I/O : 0xffffffbffee00000 - 0xffffffbfffe00000 ( 16 MB)
memory : 0xffffffc000000000 - 0xffffffc080000000 ( 2048 MB)
SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
Hierarchical RCU implementation.
Build-time adjustment of leaf fanout to 64.
RCU restricting CPUs from NR_CPUS=64 to nr_cpu_ids=1.
RCU: Adjusting geometry for rcu_fanout_leaf=64, nr_cpu_ids=1
NR_IRQS:64 nr_irqs:64 0
Kernel panic - not syncing: No interrupt controller found.
---[ end Kernel panic - not syncing: No interrupt controller found.
终于从蛮荒地世界中走出来了,庆祝吧~~!!
注8:上述过程可参考如下patch:
https://github.com/wowotechX/linux/commit/1e18b4d2a5df61e5f495dbc071fe2ad2740d7061
5. 参考文档
[1] Linux TTY framework(2)_软件架构
[2] X-009-KERNEL-Linux kernel的移植(Bubblegum-96平台)
[3] X-004-UBOOT-串口驱动移植(Bubblegum-96平台)
[5] Linux kernel内核配置解析(5)_Boot options(基于ARM64架构)
[6] README.bubblegum96,https://github.com/wowotechX/doc/blob/master/README.bubblegum96
原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。
标签: Linux Kernel console earlycon printk

评论:
2018-11-04 15:04
您好!我想请问一下,我的打印Log每次从Starting kernel....开始好像都只有250个左右字符,每次打印一些我就把它注释掉,就可以看见后面打印的字符了,我在earlycon_xxx_setup()函数里也添加了打印,有Log出来,说明执行到了这里,我怀疑是缓存buffer的问题,但是不知道该怎么查,请问您有这方面的相关经历吗?谢谢您!
2016-10-09 14:17
注3:为了简单,early console和u-boot[3]使用相同的串口,并且使用相同的配置,因此不需要额外的初始化操作。
这句话意思是不是说只要uboot阶段能成功驱动串口打印出信息,Linux内核初始化早期阶段如果想做early_serial_console功能的话,就不用再初始化配置串口了,直接操作串口发送数据就行了。因为这个时候,串口波特率设置,控制寄存器设置都是沿用uboot阶段的配置。换句话说,就是uboot阶段对SOC各个外设控制器(SPI,I2C,UART,lcd,sdio...还有它们的clock tree设置),在Linux kernel初始化阶段不会自动清零,除非手动再次执行外设相关驱动(比如在do_basic_setup里面执行各个外设驱动init函数),再次去配置寄存器。
2016-10-09 10:21
移植步骤写得很详细,学习到了。:D有个问题请教下
----注2:“EARLYCON_DECLARE”是实现early console的一种方法,另外我们也可以使用device tree的方式,为了不增加复杂度,本文就不提及相关的实现了。----
这个有点困惑,我觉得EARLYCON_DECLARE应该是必须的,我的理解是这个宏是把earlycon_id放在一个init段里面,这个device tree个人感觉应该做不到。我觉得是earlycon的串口指定可以用earlycon=owl_serial来指定也可以用device tree里面的“linux,stdout-path”的属性来指定。
2016-10-09 13:38
static int __init s5pv210_early_console_setup(struct earlycon_device *device,
const char *opt)
{
device->port.private_data = &s5pv210_early_console_data;
return samsung_early_console_setup(device, opt);
}
OF_EARLYCON_DECLARE(s5pv210, "samsung,s5pv210-uart",
s5pv210_early_console_setup);
到最后都会将earlycon信息注册到__earlycon_table。
其实使用of_的方式,隐含了的思路是:串口驱动是现成的,可以摇身一变把它变成earlycon。当然,这种方法是标准做法,最后一定要变成这样。
而本文所展示的,是最简单的做法:在串口驱动还没有的情况下,怎么用最简单的方法实现earlycon。
2016-10-08 17:51
你的early_ioremap 比较不错,有了它,应该在start_kernel里面很早位置处(比如在page_init之前,是否可以?),就可以操作控制外设了。能大概讲下它和fixmap结合起来时,到底具体是怎么实现的啊。
page_init里面会调用具体机器的map_io,建立device 的静态映射。之后也可以通过IO_ADDRESS直接访问外设寄存器,我好奇的是你的early_ioremap如果依赖fixmap的话,并且也能在setup_arch-> page_init这些函数之前被成功被调用发挥作用的话,究竟实现思路是什么。
2016-10-08 18:12
对arm64平台来说,利用early_ioremap+earlycon,在setup_arch时候就能用了。
至于early_ioremap的原理,请参考这篇文章:
http://www.wowotech.net/memory_management/fixmap.html
2016-10-09 09:14
这种提前建立好静态映射的方法,和你的文章中的early_ioremap起的作用效果是否相同?都是映射建立好后,就可以随心所欲操作外设了?
还有这两种方法都是要在setup_arch阶段最后建立页表时才能发挥作用,如果要在更靠前面的地方打印信息的话,是否就不能不借助CONFIG_DEBUG_LL对应的那个汇编代码串口收发指令来工作了啊?
2016-10-09 09:50
1. early_ioremap是一个比较新的功能,我检查了手上的两份kernel:
一份是3.10,只有arm64平台提供了该功能(arch/arm64/include/asm/fixmap.h);
另一份是x project所使用的kernel(版本是基于4.6.0-rc5的一个分支),arm和arm64都支持了(arch/arm/include/asm/fixmap.h)。
2. early_ioremap什么时候可以使用
(这里的描述基于4.6.0-rc5。)
early_ioremap在start_kernel-->setup_arch-->early_fixmap_init/early_ioremap_init之后,就可以使用了。
至于你提到的start_kernel-->setup_arch-->paging_init-->devicemaps_init,首先,它没有early_ioremap早,其次,好像只有arm平台才有(也就是说将要被抛弃了)。具体能否使用,我也没有研究过。
3. printk可用的时机
其实在"start_kernel-->setup_arch-->parse_early_param之后就可以使用了。
再往前,我觉得,没有打印的必要,因为都是arch无关的标准代码,不会出问题。之所以需要打印,是因为我们执行了arch相关的代码,有出问题的可能。
当然,如果一定要打印,只能用一些非标的东西,例如CONFIG_DEBUG_LL。
2016-10-08 17:32
还有我的这个版本内核early_prink的实现,最终通过CONFIG_DEBUG_LL对应的debug.S 里面的几个汇编函数来实现串口寄存器级别的发送字符操作过程,你文章中的early printk的实现应该和CONFIG_DEBUG_LL没有任何关系。
是不是高版本的Linux内核 早期串口输出字符已经完全脱离了CONFIG_DEBUG_LL?而且高版本的也有arm平台的early_ioremap的实现,可以让早期串口输出可以放在start_kernel里面的任何位置?
功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2018-11-04 16:12