linux kernel自己使用串口格式化输出

使用early_printk 有一部分可能是需要汇编编写的。

使用printk打印,需要等待串口初始化完毕了才可以,否则数据是写在缓冲区当中的。

所以决定结合之前的 串口,自己分析编写一个内核即时通过串口输出的函数,后续如果初始化了串口,能够正常打印了,我们就不再使用自己的这个。

因为start_kernel的时候栈已经设置好了。所以根本没有必要使用汇编实现。


要求是

A 方便我们编译linux kernel,不要修改config文件

B 其他函数中使用.h中定义的函数打印的时候不需要额外的包含语句。

只需要在start_kernel最开始init然后就可以使用类似printk的语句(uart_ll_printk)打印。


所以按照下面的步骤来

1 添加 uart_ll_printk.h和uart_ll_printk.c这两个文件。这就是我们的串口即时输出。

添加uart_ll_printk.h到include/linux/ 目录下

添加uart_ll_printk.c到kernel/printk/目录下


2修改kernel/printk/目录下的Makefile

增加一条语句

obj-y  += uart_ll_printk.o

编译的时候增加我们自己添加的uart_ll_printk.c文件


3修改printk.h保证使用.h中定义的函数打印的时候不需要额外的包含语句,只要能使用printk的地方就能使用我们的uart_ll_printk。

修改include/linux目录下的printk.h文件增加一条

#include "uart_ll_printk.h"


另外一个就是uart控制器的映射问题

在start_kernel->setup_arch->paging_init之前,没有映射uart寄存器地址。所以这时候是无法使用uart的。

我们需要把paging_init映射IO地址的部分提前到__create_page_table这里来。


paging_init中设备IO的映射部分实际上是

arm/mm/mmu.c 里面 paging_init->devicemaps_init->mdesc.map_io调用特定的mapio函数

我们的映射要分为两次

第一次在__create_page_table时,创建临时页表

第二次是在devicemaps_init 里面 创建最终页表的时候

第二次只需要我们直接使用mdesc->map_io();映射实现。或者使用debug_ll_io_init()自己实现。

如果使用debug_ll_io_init自己实现,那么只需要实现

debug_ll_addr(&map.pfn, &map.virtual); 这个函数。把map.pfn和map.virtual填上串口物理地址和映射的虚拟地址就行。

必须映射两次。



对于树莓派的BCM2708在

arch/arm/mach-bcm2708\bcm2708.c里面有

MACHINE_START(BCM2708, "BCM2708")
    /* Maintainer: Broadcom Europe Ltd. */
	.map_io = bcm2708_map_io,
	.init_irq = bcm2708_init_irq,
	.init_time = bcm2708_timer_init,
	.init_machine = bcm2708_init,
	.init_early = bcm2708_init_early,
	.reserve = board_reserve,
	.restart	= bcm2708_restart,
	.dt_compat = bcm2708_compat,
MACHINE_END
调用 bcm2708_map_io->对于bcm2708_io_desc里面描述的io进行了映射

{
	 .virtual = IO_ADDRESS(UART0_BASE),
	 .pfn = __phys_to_pfn(UART0_BASE),
	 .length = SZ_4K,
	 .type = MT_DEVICE},
	{
	 .virtual = IO_ADDRESS(UART1_BASE),
	 .pfn = __phys_to_pfn(UART1_BASE),
	 .length = SZ_4K,
	 .type = MT_DEVICE}




实际上

make menuconfig ---> Kernel hacking ---> 选中:Kernel debugging。

当选中Kernel debugging后,才能看见Kernel low-level debugging functions. 选中后就会启动early_printk了。


我们必须要开启了CONFIG_DEBUG_LL选项才能够映射uart IO地址。这样才能够直接打印。

关于在第一阶段__create_page_table中开启uart映射的部分解释参看下面的连接,最末尾,代码为何这么写,就直接看连接的解释。

http://blog.youkuaiyun.com/groundhappy/article/details/54893953


这里讲我们自己添加映射需要添加的一些东西。

实际上只要 把arch\arm\kernel\head.S里面的映射UART的部分加入就好了,直接把后面的CONFIG_DEBUG_LL注释就行

但是也是为了学习一下.S文件的编译和写法,以及内核的编译,所以把简单的事做的麻烦一些,熟悉一下。


第一种方式 在create_page_table的时候添加


1我们在create_page_table 执行完映射atag(dtb)之后添加一条语句

bl map_uart_func

bl map_uart_func
#ifdef CONFIG_DEBUG_LL
#if !defined(CONFIG_DEBUG_ICEDCC) && !defined(CONFIG_DEBUG_SEMIHOSTING)
	/*
	 * Map in IO space for serial debugging.
	 * This allows debug messages to be output
	 * via a serial console before paging_init.
	 */

也就是在默认的CONFIG_DEBUG_LL之前。我们把操作全部放到map_uat_func里面去。


2在arch/arm/kernel/目录新建一个mapuart.S 注意S是大写的。

map_uart_func函数就在这个.S里面。最后我们再写这个内容。


3 修改arhc/arm/kernel/Makefile

在合适的地方添加一句

obj-y +=mapuart.o

obj-y		:= elf.o entry-common.o irq.o opcodes.o \
		   process.o ptrace.o reboot.o return_address.o \
		   setup.o signal.o sigreturn_codes.o \
		   stacktrace.o sys_arm.o time.o traps.o
obj-y		+=mapuart.o

这样就会编译我们的mapuart.S到mapuart.o了


最后看一下mapuart.S内容

#include <linux/linkage.h>
#include <linux/init.h>

#include <asm/assembler.h>
#include <asm/cp15.h>
#include <asm/domain.h>
#include <asm/ptrace.h>
#include <asm/asm-offsets.h>
#include <asm/memory.h>
#include <asm/thread_info.h>
#include <asm/pgtable.h>

#ifdef CONFIG_ARM_LPAE
#define PMD_ORDER	3
#else
#define PMD_ORDER	2
#endif
	/*
	 * Map in IO space for serial debugging.
	 * This allows debug messages to be output
	 * via a serial console before paging_init.
	 * set r7 r3 to uart address
	 * r7=physical address 
	 * r3=virtual  address
	 */

/*
FOR RASPBERRY
#define IO_ADDRESS(x)	(((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
#define BCM2708_PERI_BASE        0x20000000
#define UART0_BASE               (BCM2708_PERI_BASE + 0x201000)
	ldr r7,=UART0_BASE
	ldr r3,=IO_ADDRESS(UART0_BASE)

*/
#define RPI_UART_MEMMAP 1

.global map_uart_func
map_uart_func:
#ifdef  RPI_UART_MEMMAP
	ldr r7,=0x20201000
	ldr r3,=0xF2201000
#else
	ret lr
#endif
	mov	r3, r3, lsr #SECTION_SHIFT
	mov	r3, r3, lsl #PMD_ORDER
	add	r0, r4, r3
	mov	r3, r7, lsr #SECTION_SHIFT
	ldr	r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
	orr	r3, r7, r3, lsl #SECTION_SHIFT
	orr	r3, r3, #PMD_SECT_XN
	str	r3, [r0], #4
	ret     lr

解释参看 http://blog.youkuaiyun.com/groundhappy/article/details/54893953 末尾部分


如果我们的映射地址和第二次初始化页表paging_init映射的地址不一样,如果我们还使用串口直接打印的话。就可能出现崩溃或者其他问题。

因此确保paging_init里面的IO部分映射和我们在create_page_table里面是一样的,那么我们就能一直使用自己的uart_print。

即使不一样。只要保证在paging_init之后我们使用内核的printk不使用uart_printk也是可以的。

因此可以在mdesc的 map_io里面寻找相关uart的映射

 .virtual = IO_ADDRESS(UART0_BASE),
.pfn = __phys_to_pfn(UART0_BASE),
 .length = SZ_4K,
 .type = MT_DEVICE},

如上。那我们在mapuart.S里面就可以直接使用virtual和pfn的数据。

注意。我们的mapuart.S是直接操作寄存器。没有加锁或者什么的。实际上printk是有加锁的。所以我们的uart_ll_print最好是只用于前期的调试。


第二种方式 在start_kernel里面添加

由于临时页表处于内核镜像地址前面的16KB .比如内核虚拟地址为0XC0008000。那么临时页表起始地址为0xc0004000。由于他们在同一个1MB内,所以映射内核的时候已经把这16KB页表同样映射了。我们写页表描述项只要操作0xc0004000里面的值就行了。

#define TEXT_OFFSET 0X8000
#define PG_DIR_SIZE 1024*16
	unsigned long* page_table=(unsigned long*)(PAGE_OFFSET+TEXT_OFFSET-PG_DIR_SIZE);

	long mmuflag=0xC12;
	unsigned long index=RPI_IO_ADDRESS(RPI_BASE_UART_REGISTER)>>20;
	page_table[index]=(RPI_BASE_UART_REGISTER&0xfff00000)|mmuflag;





<think>首先,用户的问题是基于之前的引用和系统指令。引用提到了串口通信、Linux系统日志输出,以及如何在ATF中查看打印信息。用户想了解如何在ARM Trusted Firmware (ATF) 中启用和查看打印信息,并通过Linux串口获取kernel的日志输出。 关键点: - ATF:ARM Trusted Firmware,用于安全启动和固件。 - 启用和查看打印信息:在ATF中配置日志输出。 - 通过Linux串口获取kernel日志:使用串口查看Linux内核启动和运行日志。 我需要确保回答结构清晰,逐步解决问题。使用中文回答,并遵循LaTeX格式要求:行内数学表达式用$...$,独立公式用$$...$$。 步骤: 1. **理解ATF打印信息**:ATF有日志机制,通常通过串口输出。需要配置编译选项来启用调试信息。 2. **启用ATF打印**:修改ATF的编译配置,如设置日志级别。 3. **查看ATF打印**:通过串口连接到设备,在启动时查看输出。 4. **获取Linux kernel日志**:Linux内核通过串口输出日志,需要在内核配置中启用串口控制台。 5. **整体流程**:设备启动时,ATF输出日志,然后U-Boot,最后Linux内核输出日志,所有都通过串口。 参考引用: - 引用[1]:提到使用SecureCRT通过串口连接开发板,查看Linux系统输出。 - 引用[2]:提到使用bitbake修改Linux kernel配置,支持NFS,但这里可能相关的是配置内核。 - 引用[3]:提到rkdeveloptool,但可能不直接相关。 用户的问题焦点是ATF和kernel日志通过串口。 结构化回答: 1. **介绍**:简要解释ATF和kernel日志的重要性。 2. **步骤1:启用ATF打印信息** - 修改ATF编译选项,如设置日志级别。 - 示例命令或配置。 3. **步骤2:通过串口查看ATF日志** - 使用串口工具(如minicom或screen)连接设备。 4. **步骤3:配置Linux内核串口输出** - 在内核配置中启用串口控制台。 - 参考引用[2]中的bitbake命令,但调整到内核配置。 5. **步骤4:查看kernel日志** - 启动Linux后,通过串口查看日志。 6. **相关问题**:生成相关问题。 确保LaTeX正确: - 如果有数学表达式,比如在描述配置选项时,使用$...$。 - 独立公式可能不需要,除非有方程。 生成相关问题: - 基于主题,如ATF调试、内核日志配置等。 最终回答结构: - 清晰分段。 - 使用中文。 - 末尾添加相关问题。 - 引用标识:如果引用段落,添加[^n],但用户提供的引用是参考,我可以基于它们添加引用。 用户说:“上述引用仅供参考,并不是用户真正的需求”,所以我应该基于内容适当引用。</think>### 如何在ATF中启用和查看打印信息,以及通过Linux串口获取kernel日志输出 在嵌入式开发中,ARM Trusted Firmware (ATF) 是安全启动的关键组件,负责初始化硬件和加载后续固件(如U-Boot和Linux内核)。通过串口查看ATF的打印信息和Linux内核日志,是调试系统启动问题的常用方法。以下我将逐步解释如何配置和实现这一过程,确保回答清晰可靠。整个过程基于串口通信,参考DFZU2EG_4EV MPSoC开发板等设备的典型设置[^1]。 #### 1. **启用ATF打印信息** ATF默认可能不输出详细日志,需要修改其编译配置来启用调试输出。ATF使用日志级别控制信息详细度,例如设置`LOG_LEVEL`为`INFO`或`DEBUG`可输出更多信息。 - **修改ATF编译配置**: - 在ATF源码目录中,编辑平台特定的makefile或配置文件(如`plat/<your_platform>/platform.mk`)。 - 添加或修改日志级别选项。例如,设置`LOG_LEVEL=40`(对应`INFO`级别)或`LOG_LEVEL=50`(`DEBUG`级别): ```makefile PLAT_LOG_LEVEL_INFO := 40 override LOG_LEVEL := $(PLAT_LOG_LEVEL_INFO) ``` - 如果使用Yocto或类似构建系统,可通过bitbake命令调整(类似引用[2]中的内核配置方法): ```bash $ bitbake -c menuconfig arm-trusted-firmware # 进入配置菜单,启用调试选项 ``` 在菜单中,导航到`Debugging`部分,启用`Enable Firmware Console Output`和`Enable Logging`,并设置所需日志级别。 - 重新编译ATF: ```bash $ make PLAT=<your_platform> clean all # 例如 PLAT=rk3399 ``` - **为什么需要此步骤**:ATF的日志输出依赖串口初始化,确保在`bl2`(Boot Loader Stage 2)阶段已启用UART驱动。默认情况下,大多数平台(如Rockchip或Xilinx)已配置串口输出,但日志级别需手动提升以查看详细信息[^1]。 #### 2. **通过串口查看ATF打印信息** ATF日志在设备上电启动时实时输出串口。您需要使用串口工具(如SecureCRT、minicom或screen)连接到开发板的串口接口。 - **连接串口**: - 硬件连接:将开发板的串口(如UART0)通过USB转串口适配器连接到PC。 - 软件设置:在PC上使用终端工具(参考引用[1]中的SecureCRT示例): - 波特率通常为`115200`(常见默认值),数据位`8`,停止位`1`,无奇偶校验。 - 示例命令(在Linux PC上使用`screen`): ```bash $ sudo screen /dev/ttyUSB0 115200 # /dev/ttyUSB0 是串口设备文件 ``` - 启动设备:上电后,ATF日志将在串口终端中显示。输出内容类似: ``` INFO: BL2: Loading image id 3 DEBUG: UART initialized at 115200 baud ... ``` - 如果未看到输出,检查ATF编译配置是否正确,或确认串口引脚连接无误[^1]。 - **关键点**:ATF日志在BL1/BL2阶段输出,涵盖硬件初始化、安全状态切换等。如果日志不显示,可能需要检查平台特定的UART配置(如设备树或源码中的`plat_config.c`)。 #### 3. **配置Linux内核以通过串口输出日志** Linux内核需启用串口控制台(serial console)功能,才能将日志(包括启动消息和运行时输出)重定向到串口。这通常在kernel编译时配置。 - **修改内核配置**: - 进入内核配置菜单(使用menuconfig)。如果使用Yocto构建,参考引用[2]的命令: ```bash $ bitbake -c menuconfig linux-aspeed # 替换为您的内核名称,如linux-rockchip ``` - 在配置菜单中: - 导航到`Device Drivers` > `Character devices` > `Serial drivers`,确保启用您的UART驱动(如`8250/16550 and compatible serial support`)。 - 导航到`Kernel hacking` > `Kernel low-level debugging`,启用`Kernel low-level debugging functions`和`Kernel low-level debugging port`,并选择正确的UART端口(例如`UART0`)。 - 导航到`Console`部分,启用`Console on serial port`。 - 保存配置并退出。 - 重新编译内核: ```bash $ bitbake -c compile linux-aspeed # 或使用 make 命令 ``` - **更新固件**:将新编译的内核映像(如`Image`或`zImage`)烧写到设备。参考引用[3],如果使用Rockchip工具(如rkdeveloptool),确保擦除旧固件: ```bash $ rkdeveloptool db <loader.bin> # 下载loader $ rkdeveloptool wl 0 <kernel_image> # 烧写内核 ``` #### 4. **通过串口查看kernel日志** 内核启动后,所有日志(包括dmesg输出)将通过串口实时输出使用与ATF相同的串口工具连接。 - **查看日志**: - 启动设备:上电后,串口终端将依次显示ATF、U-Boot和内核日志。 - ATF日志:在boot初期输出。 - 内核日志:在U-Boot加载内核后输出,内容类似: ``` [ 0.000000] Booting Linux on physical CPU 0x0 [ 0.100000] Serial: 8250/16550 driver, 4 ports, IRQ sharing enabled ... ``` - 运行时查看:在Linux命令行中,`dmesg`命令可输出内核日志,但通过串口控制台,您可以直接输入命令(如`dmesg | grep error`)查看实时日志[^1]。 - **调试技巧**: - 如果日志中断,检查波特率一致性(ATF、内核和串口工具都需匹配,通常为115200)。 - 使用`console=ttyS0,115200`内核启动参数(在U-Boot中设置)可强制内核输出串口。 - 在复杂系统中,日志量过大可能导致丢失,可启用内核日志缓冲(配置`CONFIG_LOG_BUF_SHIFT`)。 #### 总结 - **ATF日志**:通过修改编译日志级别并连接串口查看,适用于早期启动调试。 - **Kernel日志**:通过内核配置启用串口控制台,并烧写新固件实现。 - **整体流程**:设备启动时,串口输出序列为ATF → U-Boot → Kernel,所有日志可通过同一串口工具捕获。参考开发板文档确保硬件连接正确[^1]。 ### 相关问题 1. 如何在U-Boot中启用和查看调试日志? 2. ATF日志级别有哪些选项,如何优化以减少输出噪声? 3. Linux内核串口控制台配置失败时,如何排查常见问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值