Linux最小系统移植之早期打印CONFIG_DEBUG_LL
一、几个关键宏定义
CONFIG_DEBUG_LL、 CONFIG_DEBUG_LL_INCLUDE
容我慢慢道来, 首先要使能早期打印, menuconfig必须选中CONFIG_DEBUG_LL, 我们再慢慢梳理其他所以宏及代码
/* linux-3.10.65/arch/arm/kernel/Makefile */
obj-$(CONFIG_DEBUG_LL) += debug.o
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
我们选中“Kernel low-level debugging functions (read help!)” 在linux-3.10.65/arch/arm/Kconfig.debug 中就是DEBUG_LL
在这个选项中发现还有个依赖的子菜单“Kernel low-level debugging port”, 里面有一堆宏定义如AT91_DEBUG_LL_DBGU0、AT91_DEBUG_LL_DBGU1、DEBUG_BCM2835、DEBUG_ICEDCC,
实际上前面3个又依赖其他宏定义所以在menuconfig中只看到几个子选项如下:
这几个子选项用来干嘛呢? 一是代码文件debug.S(obj-$(CONFIG_DEBUG_LL) += debug.o) 会根据子宏定义走不同的分支; 二是这个代码里会引用宏CONFIG_DEBUG_LL_INCLUDE,
这个宏在“linux-3.10.65/arch/arm/Kconfig.debug”中定义如下:
看到没? 子选项定义的DEBUG_BCM2835 在DEBUG_LL_INCLUDE中被依赖了, 也就是我们移植一个新平台, 需要在子选项定义新的宏, 然后在这添加依赖这个新宏对应的文件, 我们现在就这么做:
/* linux-3.10.65/arch/arm/Kconfig.debug */
choice
prompt "Kernel low-level debugging port"
depends on DEBUG_LL + config VEDIC_DEBUG_LL
+ bool "I just add a test macro" config AT91_DEBUG_LL_DBGU0
bool "Kernel low-level debugging on rm9200, 9260/9g20, 9261/9g10 and 9rl"
depends on HAVE_AT91_DBGU0 config DEBUG_LL_INCLUDE
string
+ default "debug/vedic.S" if VEDIC_DEBUG_LL
default "debug/bcm2835.S" if DEBUG_BCM2835
default "debug/cns3xxx.S" if DEBUG_CNS3XXX
接下来我们在menuconfig选中VEDIC_DEBUG_LL宏和添加新文件vedic.S, 如下:
/* linux-3.10.65/.config */
+CONFIG_DEBUG_LL=y
+CONFIG_VEDIC_DEBUG_LL=y
+CONFIG_DEBUG_LL_INCLUDE="debug/vedic.S"
那这个vedic.S该怎么写呢? 我们看同目录下(linux-3.10.65/arch//arm/include/debug/)bcm2835.S文件写了什么:
/*
* Debugging macro include header
*
* Copyright (C) 2010 Broadcom
* Copyright (C) 1994-1999 Russell King
* Moved from linux/arch/arm/kernel/debug.S by Ben Dooks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/ #define BCM2835_DEBUG_PHYS 0x20201000
#define BCM2835_DEBUG_VIRT 0xf0201000 .macro addruart, rp, rv, tmp
ldr \rp, =BCM2835_DEBUG_PHYS
ldr \rv, =BCM2835_DEBUG_VIRT
.endm #include <asm/hardware/debug-pl01x.S>
没错, 就是提供一个函数而已 -> addruart(rp, rv, tmp) , 其实就是返回参数 -- 串口物理地址和串口虚拟地址, 为什么要提供虚拟地址呢? 因为在kernel C语言的第一个入口start_kernel()时, 汇编期间已经开启了MMU, CPU取的都是
虚拟地址; 该函数只是返回地址而已, 如果是开启MMU,返回虚拟地址还不够, 还要事前构建好页表, 不然根据虚拟地址也找不到物理地址。 至于具体在哪里构建页表, 待会说, 根据我目前的硬件平台, 提供文件代码如下:
/* linux-3.10.65/arch/arm/include/debug/vedic.S
* phy_addr is fixed of hardware, but virt_addr? Why 0xF5368000
*/ #define VEDIC_DEBUG_PHYS 0x70400000
#define VEDIC_DEBUG_VIRT 0xF5368000 .macro addruart, rp, rv, tmp
ldr \rp, =VEDIC_DEBUG_PHYS
ldr \rv, =VEDIC_DEBUG_VIRT
.endm
二、源码分析
为方便待会分批解释功能我先贴出debug.S源码:
/*
* linux/arch/arm/kernel/debug.S
*
* Copyright (C) 1994-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 32-bit debugging code
*/
#include <linux/linkage.h>
#include <asm/assembler.h> .text /*
* Some debugging routines (useful if you've got MM problems and
* printk isn't working). For DEBUGGING ONLY!!! Do not leave
* references to these in a production kernel!
*/ #if !defined(CONFIG_DEBUG_SEMIHOSTING)
#include CONFIG_DEBUG_LL_INCLUDE
#endif #ifdef CONFIG_MMU
.macro addruart_current, rx, tmp1, tmp2
addruart \tmp1, \tmp2, \rx
mrc p15, , \rx, c1, c0
tst \rx, #
moveq \rx, \tmp1
movne \rx, \tmp2
.endm #else /* !CONFIG_MMU */
.macro addruart_current, rx, tmp1, tmp2
addruart \rx, \tmp1
.endm #endif /* CONFIG_MMU */ /*
* Useful debugging routines
*/
ENTRY(printhex8)
mov r1, #
b printhex
ENDPROC(printhex8) ENTRY(printhex4)
mov r1, #
b printhex
ENDPROC(printhex4) ENTRY(printhex2)
mov r1, #
printhex: adr r2, hexbuf
add r3, r2, r1
mov r1, #
strb r1, [r3]
: and r1, r0, #
mov r0, r0, lsr #
cmp r1, #
addlt r1, r1, #''
addge r1, r1, #'a' -
strb r1, [r3, #-]!
teq r3, r2
bne 1b
mov r0, r2
b printascii
ENDPROC(printhex2) hexbuf: .space .ltorg #ifndef CONFIG_DEBUG_SEMIHOSTING ENTRY(printascii)
addruart_current r3, r1, r2
b 2f
: waituart r2, r3
senduart r1, r3
busyuart r2, r3
teq r1, #'\n'
moveq r1, #'\r'
beq 1b
: teq r0, #
ldrneb r1, [r0], #
teqne r1, #
bne 1b
mov pc, lr
ENDPROC(printascii) ENTRY(printch)
addruart_current r3, r1, r2
mov r1, r0
mov r0, #
b 1b
ENDPROC(printch) #ifdef CONFIG_MMU
ENTRY(debug_ll_addr)
addruart r2, r3, ip
str r2, [r0]
str r3, [r1]
mov pc, lr
ENDPROC(debug_ll_addr)
#endif #else ENTRY(printascii)
mov r1, r0
mov r0, #0x04 @ SYS_WRITE0
ARM( svc #0x123456 )
THUMB( svc #0xab )
mov pc, lr
ENDPROC(printascii) ENTRY(printch)
adr r1, hexbuf
strb r0, [r1]
mov r0, #0x03 @ SYS_WRITEC
ARM( svc #0x123456 )
THUMB( svc #0xab )
mov pc, lr
ENDPROC(printch) ENTRY(debug_ll_addr)
mov r2, #
str r2, [r0]
str r2, [r1]
mov pc, lr
ENDPROC(debug_ll_addr) #endif
linux-3.10.65/arch/arm/kernel/debug.S
具体分析如下:
1.
#if !defined(CONFIG_DEBUG_SEMIHOSTING)
#include CONFIG_DEBUG_LL_INCLUDE
#endif ---> 因为没有定义CONFIG_DEBUG_SEMIHOSTING, 所以包含了CONFIG_DEBUG_LL_INCLUDE=linux-3.10./arch/arm/include/debug/vedic.S,
也即提供addruart()功能 .
#ifdef CONFIG_MMU
.macro addruart_current, rx, tmp1, tmp2
addruart \tmp1, \tmp2, \rx
mrc p15, , \rx, c1, c0
tst \rx, #
moveq \rx, \tmp1
movne \rx, \tmp2
.endm #else /* !CONFIG_MMU */
.macro addruart_current, rx, tmp1, tmp2
addruart \rx, \tmp1
.endm #endif /* CONFIG_MMU */ ---> 注意参数的位置!addruart()第一个参数是返回物理地址, 第二个是返回虚拟地址, 第三个不用
addruart_current(rx, tmp1, tmp2) rx是返回串口地址,至于是物理地址还是虚拟地址做了判断, 如果没有开MMU, 当然rx是物理地址,所以直接
将rx作为addruart()第一个参数, 如果使能MMU可以直接返回虚拟地址, 但开发者可能为保险起见, 判断p15协处理器硬件是否真的开启MMU,
如果开启就返回虚拟地址rx=tmp2, 不然返回物理地址rx=tmp1 .
ENTRY(printascii)
addruart_current r3, r1, r2
b 2f /* 先跳转到2,把要打印的值放置r1 */
: waituart r2, r3
senduart r1, r3
busyuart r2, r3
teq r1, #'\n'
moveq r1, #'\r'
beq 1b
: teq r0, #
ldrneb r1, [r0], # /* r0存放的是要打印字符内存地址, 该指令是加载r0地址上的值,取一个byte到r1, 同时r0偏移1个byte */
teqne r1, #
bne 1b
mov pc, lr
ENDPROC(printascii) ENTRY(printch)
addruart_current r3, r1, r2
mov r1, r0
mov r0, #
b 1b
ENDPROC(printch) ---> 这段汇编提供两个函数功能, 打印字符串printascii(), 和打印字符printch(), 这里需要再提供waituart() senduart() busyuart()
所以我们要在vedic.S提供这三个函数 .
#ifdef CONFIG_MMU
ENTRY(debug_ll_addr)
addruart r2, r3, ip
str r2, [r0]
str r3, [r1]
mov pc, lr
ENDPROC(debug_ll_addr)
#endif ---> 这个函数其实就是将串口物理地址赋值给参数0, 虚拟地址赋值给参数1, 其C定义的含义为:
#ifdef CONFIG_DEBUG_LL
extern void debug_ll_addr(unsigned long *paddr, unsigned long *vaddr);
extern void debug_ll_io_init(void);
#else
static inline void debug_ll_io_init(void) {}
#endif
可以理解debug_ll_addr()等效于addruart(), 那谁用这个功能呢? 在linux-3.10./arch/arm/mmu.c: #ifdef CONFIG_DEBUG_LL
void __init debug_ll_io_init(void)
{
struct map_desc map; debug_ll_addr(&map.pfn, &map.virtual);
if (!map.pfn || !map.virtual)
return;
map.pfn = __phys_to_pfn(map.pfn);
map.virtual &= PAGE_MASK;
map.length = PAGE_SIZE;
map.type = MT_DEVICE;
create_mapping(&map, false);
}
#endif
没错, debug_ll_io_init()就是构建串口虚拟地址和物理地址页表, 一般放在 machine_desc.map_io:
static void __init zynq_map_io(void)
{
debug_ll_io_init();
zynq_scu_map_io();
} MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
.smp = smp_ops(zynq_smp_ops),
.map_io = zynq_map_io,
.init_irq = irqchip_init,
.init_machine = zynq_init_machine,
.init_time = zynq_timer_init,
.dt_compat = zynq_dt_match,
.restart = zynq_system_reset,
MACHINE_END 这个在:
start_kernel()
-> setup_arch()
-> paging_init()
-> devicemaps_init()
->mdesc->map_io() 从这里可以看出, 要使用printascii(), printch()功能必须在setup_arch()执行之后,因为之前页表都没有建立访问虚拟地址压根找不到物理地址
很明显跑到start_kernel()时已经开启MMU了,我就想在start_kernel()时就立马打印log出来, 当操作虚拟地址时由于页表没建立串口的映射导致系统Oops
这是非常不好的用户体验!串口虽然是device, 应该归属devicemaps_init(), 但由于它的特殊性, 我们希望这块映射越早越好, 因此稍微新的内核版本
都把这块映射放置在汇编阶段:
linux-3.10./arch/arm/kernel/head.s
__create_page_tables:
#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.
*/
addruart r7, r3, r0 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
#ifdef CONFIG_ARM_LPAE
mov r7, # << ( - ) @ XN
#ifdef CONFIG_CPU_ENDIAN_BE8
str r7, [r0], #
str r3, [r0], #
#else
str r3, [r0], #
str r7, [r0], #
#endif
#else
orr r3, r3, #PMD_SECT_XN
str r3, [r0], #
#endif #else /* CONFIG_DEBUG_ICEDCC || CONFIG_DEBUG_SEMIHOSTING */
/* we don't need any serial debugging mappings */
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
#endif 其他平台串口需要特殊映射的.....
#endif Now,汇编阶段映射好页表, 然后跳转到start_kernel()就可以立马使用printascii(), 同时mdesc->map_io也不需要调用debug_ll_io_init()
/* linux-3.10.65/arch/arm/include/debug/vedic.S
* phy_addr is uart base addr which fixed by hardware, but virt_addr? Why 0xF5368000
*/ #define VEDIC_DEBUG_PHYS 0x70400000
#define VEDIC_DEBUG_VIRT 0xF5368000 .macro addruart, rp, rv, tmp
ldr \rp, =VEDIC_DEBUG_PHYS
ldr \rv, =VEDIC_DEBUG_VIRT
.endm .macro senduart,rd,rx
and \rd,\rd,#0xFF
str \rd, [\rx, #0x00] /* tx_reg is offset 0x00 */
.endm .macro waituart,rd,rx
: ldr \rd, [\rx, #0x0C] /* fifo_reg is offset 0x0C */
mov \rd,\rd,lsr #
and \rd,\rd,#0x7F
teq \rd, #0x00
bne 1b
.endm .macro busyuart,rd,rx
: ldr \rd, [\rx, #0x0C]
mov \rd,\rd,lsr #
and \rd,\rd,#0x7F
teq \rd, #0x00
bne 2b
.endm
完整的vedic.S源码
三、测试验证
/* linux-3.10.65/include/generated/autoconf.h */
#define CONFIG_DEBUG_LL_INCLUDE "debug/vedic.S"
四、压缩镜像 zImage 使能打印
上面的调试都是解压后Image的log, 如果固件是压缩zImage呢? 如果想打印log? 我们先看一下log所在文件:
/* linux-3.10.65/arch/arm/boot/compressed/misc.c */ static void putstr(const char *ptr)
{
char c; while ((c = *ptr++) != '\0') {
if (c == '\n')
putc('\r');
putc(c);
} flush();
} void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
{
int ret; output_data = (unsigned char *)output_start;
free_mem_ptr = free_mem_ptr_p;
free_mem_end_ptr = free_mem_ptr_end_p;
__machine_arch_type = arch_id; arch_decomp_setup(); putstr("Uncompressing Linux...");
ret = do_decompress(input_data, input_data_end - input_data,
output_data, error);
if (ret)
error("decompressor returned an error");
else
putstr(" done, booting the kernel.\n");
}
看样子最终靠putc()实现, misc.c开头包含一个宏“CONFIG_UNCOMPRESS_INCLUDE”, 定义在:
/* linux-3.10.65/arch/arm/Kconfig.debug */ /* Here I delete ARCH_MULTIPLATFORM since my menuconfig does not declare
config DEBUG_UNCOMPRESS
bool
default y if ARCH_MULTIPLATFORM && DEBUG_LL && \
!DEBUG_OMAP2PLUS_UART && \
!DEBUG_TEGRA_UART config UNCOMPRESS_INCLUDE
string
default "debug/uncompress.h" if ARCH_MULTIPLATFORM
default "mach/uncompress.h"
*/ config DEBUG_UNCOMPRESS
bool
default y if DEBUG_LL && \
!DEBUG_OMAP2PLUS_UART && \
!DEBUG_TEGRA_UART config UNCOMPRESS_INCLUDE
string
default "debug/uncompress.h" 所以:
CONFIG_DEBUG_UNCOMPRESS=y
CONFIG_UNCOMPRESS_INCLUDE=linux-3.10./arch/arm/include/debug/uncompress.h
#ifdef CONFIG_DEBUG_UNCOMPRESS
extern void putc(int c);
#else
static inline void putc(int c) {}
#endif
static inline void flush(void) {}
static inline void arch_decomp_setup(void) {}
uncompress.h源码
/* linux-3.10.65/arch/arm/boot/compressed/Makefile */
ifeq ($(CONFIG_DEBUG_UNCOMPRESS),y)
OBJS += debug.o 而linux-3.10./arch/arm/boot/compressed/debug.S:
#include <linux/linkage.h>
#include <asm/assembler.h> #ifndef CONFIG_DEBUG_SEMIHOSTING #include CONFIG_DEBUG_LL_INCLUDE ENTRY(putc)
addruart r1, r2, r3
waituart r3, r1
senduart r0, r1
busyuart r3, r1
mov pc, lr
ENDPROC(putc) #else ENTRY(putc)
adr r1, 1f
ldmia r1, {r2, r3}
add r2, r2, r1
ldr r1, [r2, r3]
strb r0, [r1]
mov r0, #0x03 @ SYS_WRITEC
ARM( svc #0x123456 )
THUMB( svc #0xab )
mov pc, lr
.align
: .word _GLOBAL_OFFSET_TABLE_ - .
.word semi_writec_buf(GOT)
ENDPROC(putc) .bss
.global semi_writec_buf
.type semi_writec_buf, %object
semi_writec_buf:
.space
.size semi_writec_buf, #endif
debug.S源码
核心思想就是选中DEBUG_UNCOMPRESS, 使其编译linux-3.10.65/arch/arm/boot/compressed/debug.S, 里面会提供putc()实现,
然后用户可以在misc.c删掉“#include CONFIG_UNCOMPRESS_INCLUDE”, 添加申明代码“extern void putc(int c);”, 或者选中
UNCOMPRESS_INCLUDE=linux-3.10.65/arch/arm/include/debug/uncompress.h 帮我们申明
我的开机log如下:
五、其他
说起早期打印不得不提CONFIG_EARLY_PRINTK, 这个可以理解为对printch()进一步的封装, 同时归属于console框架, 具备缓存数据等后期console的所有功能,与printascii() printch()最大不同在于eraly_printk()可以打印格式参数%d, %p等, 方便调试
具体请参考另一篇博文: Linux最小系统移植之早期打印CONFIG_EARLY_PRINTK
Linux最小系统移植之早期打印CONFIG_DEBUG_LL的更多相关文章
- Linux最小系统移植之早期打印CONFIG_EARLY_PRINTK
请先参考先前博文: Linux最小系统移植之早期打印CONFIG_DEBUG_LL , 因为eraly_printk其实就是对printch()封装的 一. 必要选项(在上面链接选中的前提下再新增 ...
- [嵌入式开发入门]4412开发板从零建立Linux最小系统
本文转自iTOP-4412开发板实战教程书籍 http://www.topeetboard.com iTOP-4412开发板不仅可以运行Android,还可以运行简单的Linux最小文件系统. 最小L ...
- Linux 最小系统制作
Linux 最小系统制作 一.制作工具Busybox 在制作文件系统的时候,我们需要使用“Busybox 工具”,即为附件压缩包“busybox-1.21.1.tar.bz2”.“BusyBox 工具 ...
- ZYNQ学习之路1. Linux最小系统构建
https://blog.csdn.net/u010580016/article/details/80430138?utm_source=blogxgwz1 开发环境:window10, vivado ...
- 在Linux最小系统上编译运行第一个helloworld程序
一.安装和使用SSH软件 1.安装SSH 软件 1)SSH 软件压缩包可以在网盘下载,下载后解压,进入解压出来的文件夹,如下图. 2)单击上图中的“SSHSecureShellClient-3.2.9 ...
- Linux 最小系统挂载U盘(SD、TF卡)并执行程序
一.在Ubuntu下编译C文件 使用指令"arm-none-linux-gnueabi-gcc-4.4.1 -o HelloWorld HelloWorld.c -static"编 ...
- 虚机Linux最小系统下安装图形界面,与yum配置
出于未知原因,想装一下. 因为有光盘,所以就从光盘安装就可以了. 首先是配置yum下的下载地址: 找到yum的地址,然后打开文件. 然后建立该文件的/media/cdrom路径.将光盘挂载到该路径下. ...
- 用BusyBox制作Linux最小系统
1.下载busybox-1.30.1 地址:https://busybox.net/downloads/busybox-1.30.1.tar.bz2 2.解压:tar xvf busybox-1.30 ...
- 浅谈Android系统移植、Linux设备驱动
一.Android系统架构 第一层:Linux内核 包括驱动程序,管理内存.进程.电源等资源的程序 第二层:C/C++代码库 包括Linux的.so文件以及嵌入到APK程序中的NDK代码 第三层:An ...
随机推荐
- BZOJ_3083_遥远的国度_树链剖分+线段树
BZOJ_3083_遥远的国度_树链剖分 Description 描述 zcwwzdjn在追杀十分sb的zhx,而zhx逃入了一个遥远的国度.当zcwwzdjn准备进入遥远的国度继续追杀时,守护神Ra ...
- maven+springmvc的配置
1. 首先创建1个mavenweb项目 如果没有的话最好是去官网下载一个最新版本的eclipse 里面什么都有 maven/gradle 啥的 2. 选择路径 没啥影响 就是一个路径 默认就行 ...
- awk的递归
想来惭愧,之前写的一篇文章<用awk写递归>里多少是传递里错误的信息.虽然那篇文章目的上是为了给出一种思路,但实际上awk是可以支持函数局部变量的. awk对于局部变量的支持比起大多数过程 ...
- layer的删除询问框的使用
删除是个很需要谨慎的操作 我们需要进行确认 对了删除一般使用ajax操作 因为如果同url请求 处理 再返回 会有空白页 1.js自带的样式 <button type="button& ...
- laravel 分页和共多少条 加参数的分页链接
<div class="pagers "> <span class="fs pager">共 {{$trades->total() ...
- SA SD SE 区别
[SA(System Analysis)系统分析师] 通过一系列分析手法把User想要的结果,以各种文件方式表达出来. 此过程着重于工作流程和处理逻辑. 规划系统功能和模块. 定出初步的数据库内容及系 ...
- 解决linux netcore https请求使用自签名证书忽略安全检查方法
当前系统环境:centos7 x64. dotnet 2.0. 不管是 ServicePointManager.ServerCertificateValidationCallback = (a, b, ...
- Postman----Presets(预先设置)的使用
使用场景: 当我们在进行接口测试post请求时,headers是必填项,我们将一个A接口的headers编写后测试完成,再次进行B接口的测试,需重新编写headers,为了简单操作,我们就用到了Pre ...
- cmd 【已解决】windows连接手机,运行adb devices提示“unauthorized”
报错截图如下: 问题原因:电脑连接手机.手机未授权 解决方式: 设置----开发者选项-----打开USB调试,出现如下弹框,点击"确定"即可解决问题.
- 集群IPtables转发与防火墙
子网集群通过接入公网的服务器Iptables转发上网 1. 对iptables进行初始化工作 清空filter表 iptables -F 清空nat表 iptables -t nat -F 默认禁止所 ...