移植linux到龙芯3210笔记4
2011-04-20 21:30
134 查看
跑过trap_init之后,跑进console_init就停止了。问题出在console_init里面。从字面上看是控制台初始化。而我们现在的控制是串口0。所以这部分的问题应该出在串口上。之前可以打印信息是因为用了prom_printf,这个函数定义在./arch/mips/arc/console.c文件里:
从这段代码中可以得到两个重要信息:第一个,打印的字符串最长为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函数之后:
发现,好像初始化串口的代码找不到,只有一段怪怪的代码:
上网查了一些资料,了解到__con_initcall_start实际上是一个地址值,在./arch/mips/kernel/vmlinux.lds.S中指定了。打开vmlinux.lds.S文件,看到此部分代码:
显然,__con_initcall_start这个地址值,实际上保存了 .con_initcall.init这个符号,这个值在include/linux/init.h里定义了。查看此部分代码:
显然,通过宏函数console_initcall来指定一个函数指针,而这个函数指针保存在.con_initcall.init这个段中。
也就是说,(*call)();的实际执行者是console_initcall指定的函数。所以要找到这个指定的函数,这个函数一定在串口驱动里面。
打开./drivers/serial/8250.c,我们可以找到:
serial8250_isa_init_ports
这个函数主要是获取一些硬件信息,如串口的IO基地址呀之类的:
如果有多个串口要进行初始化,那么串口的数量决定于old_serial_port的长度和nr_uarts这个变量。
先来看nr_uarts这个变量:
也就是说这个变量值取决于CONFIG_SERIAL_8250_RUNTIME_UARTS这个宏,这个宏是CONFIG为前缀的,应该是在配置内核里决定的,所以可以到./include/linux/autoconf.h中去找,就在这个文件里定义的。龙芯3210支持两路UART,所以这个值设置大于或者等于2就可以了。
接下来把重点放在old_serial_port这个数组上。
这个数组的成员是由宏SERIAL_PORT_DFNS指定,所以在./include/asm-mips/serial.h中找到这个宏定义:
可以了解到,这个宏里根据每个平台的不同,定义了串口硬件相关的值。所以在这里,我们加入龙芯3210的串口定义:
注意到,加入到最前面,因为放到后面的话,nr_uarts的值,也就是CONFIG_SERIAL_8250_RUNTIME_UARTS在配置内核时要设置比较大的值,否则是不能获取到SOC32101_PORT_DENFS这个参数的,所以放在最前面就行了。
然后再定义SOC32101_PORT_DENFS的值:
首先是分析从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:
实则上调用这个函数,不过怎么调用到这个函数,现在还没弄清楚。
回到我们的问题,这个函数中会把地址从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,那就是设置串口时钟分频有问题了。
找到计算串口时钟的代码:
后来通过对照,发现去掉:
这个操作是设置串口的线控制器的,其中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
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
相关文章推荐
- 移植linux到龙芯3210笔记1
- 移植linux到龙芯3210笔记2
- 移植linux到龙芯3210笔记3
- Zynq-Linux移植学习笔记之11-qspi驱动配置
- 【TINY4412】LINUX移植笔记:(9)USB驱动
- 【TINY4412】LINUX移植笔记:(21)常见问题
- Zynq-Linux移植学习笔记之11-qspi驱动配置与测试接口
- Zynq-Linux移植学习笔记之二-知识点
- Linux上USB移植错误解决笔记
- Zynq-Linux移植学习笔记之20-Zynq linux can驱动开发
- libmxml (Mini-XML) arm-linux 移植笔记
- 【TINY4412】LINUX移植笔记:(10)USB OTG
- 我的arm_linux移植笔记
- Zynq-Linux移植学习笔记之17-Zynq下linuxPL部分Flash
- Linux学习内核移植相关笔记第4部分
- 基于S3C2440的Linux SPI驱动移植笔记
- Zynq-Linux移植学习笔记之15-用户APP直接访问PL物理地址
- 给linux移植Ethercat笔记
- QT6410移植linux-2.6.39 笔记(一)-搭建开发板环境
- 基于ATMEL AT91RM9200的嵌入式Linux移植笔记 (2)