您的位置:首页 > 运维架构 > Linux

移植linux到龙芯3210笔记4

2011-04-20 21:30 134 查看
跑过trap_init之后,跑进console_init就停止了。问题出在console_init里面。从字面上看是控制台初始化。而我们现在的控制是串口0。所以这部分的问题应该出在串口上。之前可以打印信息是因为用了prom_printf,这个函数定义在./arch/mips/arc/console.c文件里:

void prom_printf(char *fmt, ...)

{

va_list args;

char ppbuf[1024];

char *bptr;

va_start(args, fmt);

vsprintf(ppbuf, fmt, args);

bptr = ppbuf;

while (*bptr != 0) {

if (*bptr == '/n')

prom_putchar('/r');

prom_putchar(*bptr++);

}

va_end(args);

}

从这段代码中可以得到两个重要信息:第一个,打印的字符串最长为1024个字节;第二,打印的实际执行者是prom_putchar(),这个执行者与平台相关,定义在./arch/mips/mips-boards/soc3210-boards/soc-soc/prom.c中。prom_putchar的实际执行者是putDebugChar,定义在./arch/mips/mips-boards/soc3210-boards/soc-soc/dbg_io.c中。可以看到实际的硬件操作了。
像《Linux Mips Porting Guide》所说的,prom_printf是eary_printk。
实际的printk的初始化应该在console_init中。

修改控制台串口的参数
进入console_init函数之后:

void __init console_init(void)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
#ifdef CONFIG_EARLY_PRINTK
disable_early_printk();
#endif
call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}
}


发现,好像初始化串口的代码找不到,只有一段怪怪的代码:

call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}


上网查了一些资料,了解到__con_initcall_start实际上是一个地址值,在./arch/mips/kernel/vmlinux.lds.S中指定了。打开vmlinux.lds.S文件,看到此部分代码:

__con_initcall_start = .;
.con_initcall.init : { *(.con_initcall.init) }
__con_initcall_end = .;


显然,__con_initcall_start这个地址值,实际上保存了 .con_initcall.init这个符号,这个值在include/linux/init.h里定义了。查看此部分代码:

#define console_initcall(fn) /
static initcall_t __initcall_##fn /
__attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn


显然,通过宏函数console_initcall来指定一个函数指针,而这个函数指针保存在.con_initcall.init这个段中。
也就是说,(*call)();的实际执行者是console_initcall指定的函数。所以要找到这个指定的函数,这个函数一定在串口驱动里面。
打开./drivers/serial/8250.c,我们可以找到:

console_initcall(serial8250_console_init);
也就是调用serial8250_console_init来进行初始化串口的。

static int __init serial8250_console_init(void)
{
serial8250_isa_init_ports();
register_console(&serial8250_console);
return 0;
}

serial8250_isa_init_ports

这个函数主要是获取一些硬件信息,如串口的IO基地址呀之类的:

for (i = 0, up = serial8250_ports;
i < ARRAY_SIZE(old_serial_port) && i < nr_uarts;
i++, up++) {
up->port.iobase   = old_serial_port[i].port;
up->port.irq      = irq_canonicalize(old_serial_port[i].irq);
up->port.uartclk  = old_serial_port[i].baud_base * 16;
up->port.flags    = old_serial_port[i].flags;
up->port.hub6     = old_serial_port[i].hub6;
up->port.membase  = old_serial_port[i].iomem_base;
up->port.iotype   = old_serial_port[i].io_type;
up->port.regshift = old_serial_port[i].iomem_reg_shift;
if (share_irqs)
up->port.flags |= UPF_SHARE_IRQ;
}
从这段代码里看到,我们的硬件基本信息是来自于一个叫做old_serial_port的数组。
如果有多个串口要进行初始化,那么串口的数量决定于old_serial_port的长度和nr_uarts这个变量。
先来看nr_uarts这个变量:

static unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS;


也就是说这个变量值取决于CONFIG_SERIAL_8250_RUNTIME_UARTS这个宏,这个宏是CONFIG为前缀的,应该是在配置内核里决定的,所以可以到./include/linux/autoconf.h中去找,就在这个文件里定义的。龙芯3210支持两路UART,所以这个值设置大于或者等于2就可以了。
接下来把重点放在old_serial_port这个数组上。

static const struct old_serial_port old_serial_port[] = {
SERIAL_PORT_DFNS /* defined in asm/serial.h */
};


这个数组的成员是由宏SERIAL_PORT_DFNS指定,所以在./include/asm-mips/serial.h中找到这个宏定义:

#define SERIAL_PORT_DFNS                                /
DDB5477_SERIAL_PORT_DEFNS                       /
EV96100_SERIAL_PORT_DEFNS                       /
IP32_SERIAL_PORT_DEFNS                          /
ITE_SERIAL_PORT_DEFNS                           /
IVR_SERIAL_PORT_DEFNS                           /
JAZZ_SERIAL_PORT_DEFNS                          /
STD_SERIAL_PORT_DEFNS                           /
MOMENCO_OCELOT_G_SERIAL_PORT_DEFNS              /
MOMENCO_OCELOT_C_SERIAL_PORT_DEFNS              /
MOMENCO_OCELOT_SERIAL_PORT_DEFNS                /
MOMENCO_OCELOT_3_SERIAL_PORT_DEFNS


可以了解到,这个宏里根据每个平台的不同,定义了串口硬件相关的值。所以在这里,我们加入龙芯3210的串口定义:

#define SERIAL_PORT_DFNS                                /
SOC32101_PORT_DENFS                             /
DDB5477_SERIAL_PORT_DEFNS                       /
EV96100_SERIAL_PORT_DEFNS                       /
IP32_SERIAL_PORT_DEFNS                          /
ITE_SERIAL_PORT_DEFNS                           /
IVR_SERIAL_PORT_DEFNS                           /
JAZZ_SERIAL_PORT_DEFNS                          /
STD_SERIAL_PORT_DEFNS                           /
MOMENCO_OCELOT_G_SERIAL_PORT_DEFNS              /
MOMENCO_OCELOT_C_SERIAL_PORT_DEFNS              /
MOMENCO_OCELOT_SERIAL_PORT_DEFNS                /
MOMENCO_OCELOT_3_SERIAL_PORT_DEFNS


注意到,加入到最前面,因为放到后面的话,nr_uarts的值,也就是CONFIG_SERIAL_8250_RUNTIME_UARTS在配置内核时要设置比较大的值,否则是不能获取到SOC32101_PORT_DENFS这个参数的,所以放在最前面就行了。

然后再定义SOC32101_PORT_DENFS的值:

#ifdef CONFIG_SOC_SOC
#include <asm/soc-soc/soc_soc.h>
#include <asm/soc-soc/soc_soc_int.h>
#ifdef BASE_BAUD
#undef BASE_BAUD
#endif
#define BASE_BAUD (44000000/16)
#define SOC32101_PORT_DENFS /
{ 0, BASE_BAUD, SOC_SOC_UART0_BASE, /
SOC_SOC_UART0_IRQ, STD_COM_FLAGS }, /
{ 0, BASE_BAUD, SOC_SOC_UART1_BASE, /
SOC_SOC_UART1_IRQ, STD_COM_FLAGS },
#else
#define SOC32101_PORT_DENFS
#endif
register_console(&serial8250_console);
如果说上一个函数是获取信息,那么这个函数就是实际操作。主要是设置实际的硬件参数,如波特率呀停止位等的设置。
首先是分析从bootloader传入来的命令参数,然后把这些关于串口配置的参数传入到serial8250_console_setup。
再次编译,运行。

发现,还是在console_init里停住了。
一路用打印,查找,调试,终于发现程序在第一次写串口的寄存器操作时死机了。

->serial8250_console_setup
->uart_set_options
->serial8250_set_termios
->serial_dl_write
->_serial_dl_write
->outb

outb函数出问题了,也就是对IO操作的时候死机了。系统对IO的操作outb:

#define __BUILD_IOPORT_SINGLE(pfx, bwlq, type, p, slow)                 /
/
static inline void pfx##out##bwlq##p(type val, unsigned long port)      /
{                                                                       /
volatile type *__addr;                                          /
type __val;                                                     /
/
__addr = (void *)__swizzle_addr_##bwlq(mips_io_port_base + port); /
/
__val = pfx##ioswab##bwlq(__addr, val);                         /
/
/* Really, we want this to be atomic */                         /
BUILD_BUG_ON(sizeof(type) > sizeof(unsigned long));             /
/
*__addr = __val;                                                /
slow;                                                           /
}


实则上调用这个函数,不过怎么调用到这个函数,现在还没弄清楚。
回到我们的问题,这个函数中会把地址从port转为实际的操作的地址__addr,这里有个基地址mips_io_port_base,这个地址在setup_arch里进行了初始化:

->setup_arch
->arch_mem_init
->plat_mem_setup
->set_io_port_base(PTR_PAD(0xbf000000));

发现 mips_io_port_base = 0xbf000000,而UART0的基地址是0x1f004080,那么实际的操作地址__addr是两者相加,那么变成了0xde004080了???这个是一个错误的地址,怪不得会死机了。

可以猜得出,本意是相或,这样就可以转为正确的地址了,也可以set_io_port_base传入参数改为0xa0000000。这样应该就没问题了。
编译运行,OK,过了,但是出现乱码!!!

初步分析原因:
串口打印出现乱码一般情况下就是波特率设置有问题,但查看设置参数,传入的波特率确实是115200,那就是设置串口时钟分频有问题了。
找到计算串口时钟的代码:

quot = (port->uartclk + (8 * baud)) / (16 * baud);
没有问题,对照旧的可用内核,公式也是这样。
后来通过对照,发现去掉:

serial_outp(up, UART_LCR, cval | UART_LCR_DLAB);
就可以了。
这个操作是设置串口的线控制器的,其中UART_LCR_DLAB是使偏移地址为0和1的寄存器为分频寄存器的,默认情况下是数据寄存器。
照看3210手册,确定应该是有这个操作的,但是又非要去掉才正常。搞不明白,可能是芯片自身的问题。
到此为此,内核可以跑到命令行了,也就是控制台,基本完成了内核的移植,文件系统是用ramdisk。
对于驱动,还要调试。

PS:内核基本上可以跑起来了,在这个调试的过程中,对内核的启动有了比较清晰的认识,遇到问题最終还是得查看代码。首先是确定问题出现在地方,然后再去分析为什么出现这个问题,然后再去解决。在确定代码可能出现的地方,这次用的是比较笨的办法,就是通过在很多地方插入prom_printf。当然也不能盲目的插入,可以先看代码,觉得可能出现问题的地方,或者搞得不太懂的地方再printf,另外也可以用二分法来确定位置,这个就更死板了。完成了基本的移植工作,但是还有很多地方还是没有完全搞清楚,例如cache和TLB的一些设置了,还得继续深入学习。

--龙芯嵌入式系列开发板更多信息请关注 http://shop107479358.taobao.com

--龙芯1B开发板: http://item.taobao.com/item.htm?spm=a1z10.1.w4004-4678790104.8.WBYZuT&id=36562593290
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: