使用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.csdn.net/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.csdn.net/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;