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.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;





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值