手机版
你好,游客 登录 注册
背景:
阅读新闻

移植Linux到龙芯3210笔记

[日期:2011-04-20] 来源:Linux社区  作者:lqx4_3 [字体: ]

跑过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的一些设置了,还得继续深入学习。

linux
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款