感谢支持
我们一直在努力

移植Linux到龙芯3210笔记

移植的基本思路是:先编译后调试


首先修改配置文件,如:Makefile 、 Kconfig等。这样就可以进行内核的配置,然后可以编译。


编译完之后,开始从代码上一步步的进行调试。


修改配置文件:


./arch/mips/Kconfig


这个构架相关的配置文件实则上就是主配置文件,作用是在make menuconfig的时候定义好一些宏,这些宏的作用就是为了加入平台相关的代码而作的一些内核配置选择。


首先作一个基本的认识:


menu/endmenu  表示主菜单中菜单选项的开始与结束,也就是说中间部分是在这个菜单选项被打开后显示出来的。


choice/endchoice 表示菜单里的某个子菜单,属于某个子菜单是通过depends on A为指定(假设是A),也就是说这部分内容是依赖于A。


config 表示是一个条目,例如,config A。那么配置完之后,会在auto.config文件中生成一个CONFIG_A的宏。


select  表示使能这个条目,或者说这个条目依赖于这个config。


depends on 表示这个config依赖于这个条目。



1)在menu “”Machine selection”添加:



config SOC32101_BOARDS


bool “Support for SOC CAS SOC3210 1 generic boards”


 select SYS_HAS_CPU_SOC32101


 select DMA_NONCOHERENT


 select BOOT_ELF32


 select BOARD_SCACHE


 select HW_HAS_PCI


 select ISA


 select IRQ_CPU


 select GENERIC_IOMAP


 select SYS_SUPPORTS_32BIT_KERNEL


 select SYS_SUPPORTS_64BIT_KERNEL


 select SYS_SUPPORTS_LITTLE_ENDIAN


 select SYS_SUPPORTS_HIGHMEM


 help


     ICT experimental board, which uses ict fpga north bridge


     and soc32101 CPU.


choice


 prompt “subsystem type”


 depends on SOC32101_BOARDS


 


  config SOC_SOC


       bool “soc32101 gc soc support”


endchoice


2)在menu “CPU selection”中添加


choice


    prompt “CPU type”


     default CPU_SOC32101      #修改默认CPU


    config CPU_SOC32101       #添加CPU


         bool “SOC32101”


            depends on SYS_HAS_CPU_SOC32101


            select CPU_SUPPORTS_32BIT_KERNEL


  … …


endchoice


PS:通过上面两步的配置,我们得到了三个宏:CONFIG_SOC32101_BOARDS、CONFIG_SOC_SOC、CONFIG_CPU_SOC32101,它们在./include/config/auto.conf里定义。

有了前面的配置,也就是说,得到了三个宏:


CONFIG_SOC32101_BOARDS、CONFIG_SOC_SOC、CONFIG_CPU_SOC32101


那么:


修改或者增加一些与CPU相关的代码就可以用CONFIG_CPU_SOC32101来判断,例如cache、tlb等。


修改或者增加一些与板级相关的代码就可以用CONFIG_SOC_SOC或者CONFIG_SOC32101_BOARDS来判断。


修改Makefile


1、./Makefile(顶级makefile)


我们修改这个Makefile的原因是通过它来选择我们的CPU构架,指定我们的编译工具


首先修改两个宏,SUBARCH是选定CPU构架的,CROSS_COMPILE选定交叉工具链:


修改宏SUBARCH = mips


修改宏CROSS_COMPILE ?= mipsel-linux-


2、./arch/mips/Makefile(CPU构架相关makefile)


我们修改这个Makefile的原因是加入与我们平台相关的代码到内核源码中,并作一些平台相关的修改。


主要的修改任务:


1)、加入3210的平台相关代码到内核工程中


2)、指定链接的起始地址


3)、指定编译器的编译选项



所以添加:


core-$(CONFIG_SOC_SOC) += arch/mips/mips-boards/soc32101- boards/soc-soc/


load-$(CONFIG_SOC_SOC) += 0xffffffff80200000


cflags-$(CONFIG_SOC_SOC) += -Iinclude/asm-mips/mach-longmeng


cflags-$(CONFIG_CPU_SOC32101) += -march=r4600 -Wa,–trap


3、./arch/mips/kernel/Makefile(内核相关makefile)


这个修改主要是针对CPU的,关于CPU方面的FPU操作和上下文切换操作等。


添加:


    obj-$(CONFIG_CPU_SOC32101) += r4k_fpu.o r4k_switch.o


4、arch/mips/mm/Makefile(MM相关的,即内存管理)


这部分要加入的模块了是针对CPU的,主要是加入CPU在内存管理方面的操作,主要是Cache和TLB。


添加:


     obj-$(CONFIG_CPU_SOC32101) += c-r4k.o cex-gen.o pg-r4k.o tlb-r4k.o


这部分内容是根据实际情况进行修改,因为龙芯3210在cache和tlb的操作上都有自己特定的指令,所以这部分代码需要修改。


5、arch/mips/lib-32/Makefile(TLB相关的)


添加:


   obj-$(CONFIG_CPU_SOC32101) += dump_tlb.o


6、arch/mips/pci/Makefile (PCI相关的)


添加:


    obj-$(CONFIG_SOC_SOC)           += fixup-soc-soc.o ops-soc-soc.o


可见fixup-soc-soc.c 和 ops-soc-soc.c文件将要加入到这个目录下进行编译


PS:以上的修改,主要是加入一些平台相关的代码到内核工程中,这样, 这些平台相关的代码就会被编译。接下来的工作就是把这些平台相关的代码加入到这些目录下。

根据前面第2页对Makefile的配置,接下来就应该加入这些平台相关的代码到相应的目录下。


所以这次的主要任务是:增加代碼或修改代碼


把平台相关的代碼,加入内核工程中:


<!– @page { margin: 2cm } P { margin-bottom: 0.21cm } –>


(1)将soc3210-board目录拷贝到arch/mips目录下,这里拷贝到arch/mips/mips-boards/。


这个目录将在arch/mips/kernel/Makefile中添加进去,这样集成到内核里。


(2)将mach-longmeng目录拷贝到include/asm-mips/目录下,这部分代码也将在arch/mips/kernel/Makefile中添加进去,作为头文件路径。这部分代码是loongmeng平台的特殊性,对SOC32101不知道相不相关。


(3)PCI操作相关的两个文件:fixup-soc-soc.c和ops-soc-soc.c拷贝到./arch/mips/pci/下。


(4)把平台相关的头文件目录soc-soc/加入到./include/asm-mips/下面,其中soc_soc.h保存了外设资源的地址。


在内核工程中的代碼上添加或修改平台相关的代碼:

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

赞(0) 打赏
转载请注明出处:服务器评测 » 移植Linux到龙芯3210笔记
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏