龙芯俱乐部开源社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 575|回复: 3

智龙裸机串口打印Hellow World

[复制链接]

5

主题

16

帖子

1253

积分

金牌会员

Rank: 6Rank: 6

积分
1253
发表于 2019-7-18 02:22:26 | 显示全部楼层 |阅读模式
本帖最后由 ahau 于 2019-7-21 17:25 编辑

一直有一个心愿,就是要弄清楚Hello World到底是怎么打印出来的。虽然我们精通各种语言Hello World的写法,但是我们是否知道Hello World背后揪净做了什么?如果你对这个也感兴趣,希望本贴能帮到你。 这次写的有些匆忙,有些地方颇有疑惑,希望大家能一起讨论讨论。

正宗的Hello World应该是打印到屏幕上的,我们先从简单的开始,通过串口将Hello World打印到控制台上。也就是说我们需要一个终端仿真程序比如Secure CRT。先来看下效果:

printing.gif


除了这个,我们还需要一个USB转串口线,注意色那根线不用接,其它三根线从左到右依次为绿黑。

USB转串口线接法

USB转串口线接法


插入USB转串口线后你会发现电脑并没有发现有串口接入。 这有2种可能:①你的电脑缺少串口驱动。②VMWare弹出了一个确认框阻止了,此时点确认即可。

如果我没记错的话,应该先插USB转串口线再接电源线,别忘了要给智龙小板供电哦。

=== 这是分割线 ===

如果你已经看过上一篇帖子 智龙裸机点亮LED ,相信你对1C的启动流程和编译链接流程已经有所了解。接着上一贴,我们需要在start.S里添加如下:
    /* 初始化时钟计时器,防止产生计数器中断 */
    mtc0    zero, CP0_COUNT
    mtc0    zero, CP0_COMPARE

    /* 设置kseg0区不经过cache,cache需要初始化才能使用(其实这句没什么用,我们直接用的kseg1) */
    li      t0, CONF_CM_UNCACHED // mipsregs.h中定义的,值为2
    mtc0    t0, CP0_CONFIG

    /* Initialize $gp */
    /*这一句大有来头,跳一下就知道当前内存的地址值了,注意gp在u-boot.lds里有定义
    其偏移值永远为0x7ff0,咱没搞懂这个偏移值啥意思,参考这里:https://www.cr0.org/paper/mips.elf.external.resolution.txt
    */
    bal     1f
    nop
    .word   _gp
1:
    lw      gp, 0(ra)


    /* initialize spi */
    li      t0, 0xbfe80000      //地址0xbfe80000为SPI0的寄存器基地址
    li      t1, 0x17            // div 4, fast_read + burst_en + memory_en double I/O 模式 部分SPI flash可能不支持
    sb      t1, 0x4(t0)            // 设置寄存器sfc_param
    li      t1, 0x05
    sb      t1, 0x6(t0)         // 设置寄存器sfc_timing

    la      t9, lowlevel_init
    j       t9
    nop

                                                                                                                                                                       (再也不用代码块了,坑死人)



bal     1f
    nop
    .word   _gp
1:
    lw      gp, 0(ra)


别小看这几行代码,涉及到的知识点非常多×3,多到让你怀疑人生!


`bal 1f` f表示forward,向前跳转,也就是跳转到 `1w gp, 0(ra)`这里。
`nop`前一贴说过了,由于delay slot的原因,跳转指令后面要紧跟一个nop指令。
`.word _gp`,这个_gp哪儿来的,这个是在u-boot.lds里定义的一个symbol。我们可以直接在代码里引用链接脚本里定义的symbol。
注意代码里只能使用这个symbol指向的地址,不能使用地址里存的值,因为在链接脚本里定义的symbol根本没有给它分配存储空间,它只有地址而已。 为什么这里可以访问到_gp? 我们知道代码里定义的变量,在编译的时候会产生一个符号表,符号表的每个entry就是变量的地址。编译器在解析`.word _gp`这一句的时候发现_gp既不是寄存器又不是汇编助记符,就会去符号表里查有没有_gp这个symbol,显然是有的,虽然没在代码里定义它,但是我们在链接脚本里定义它了,因为它是一个symbol,所以在符号表里能查到这个符号。
.data : {
                *(.data*)
        }

        . = .;
        _gp = ALIGN(16) + 0x7ff0;

        .got : {
                *(.got)
        }

.word也是一个汇编伪指令,就是在当前位置存放一个值,这个值就是_gp(ALIGN(16) + 0x7ff0;)。
你在谷歌的时候会发现一般我们会这么用.word:
`myvar: .word 123`,意思就是`myvar=123`(定义一个变量,变量的名字叫myvar,值为123 。 这里并没有在.word前面加一个符号,很奇怪对不对?不定义符号,就不知道地址,不知道地址怎么把里面存的值取出来?

这里有一个小技巧。
`bal 1f`之后 PC立马指向了 `nop`这一行,即`PC=PC+4`。由于`$ra = PC+4`, 所以此时`$ra = PC + 8`。
就是跳转之后, $ra 实际上应该是 PC + 8。参考这里: https://chortle.ccsu.edu/AssemblyTutorial/Chapter-26/ass26_4.html

lw      gp, 0(ra)
这句话的意思是把 $ra 地址里存的值取出来赋值给 $gp 寄存器。  $ra 就是 `.word _gp`这一行的地址,这就是不使用标号也能知道地址了。巧妙之处就是利用$ra拿到地址,然后把$ra里存的值拿出来。 $ra地址处存的值就是_gp的值,这个值只是个地址而已。

_gp的值为什么是ALIGN(16) + 0x7ff0;?

$gp = base(.got) + OFFSET_GP_GOT。 OFFSET_GP_GOT 的值永远为 0x7ff0。从上面的链接脚本可以看出, _gp是在.got段上面定义的。
ALIGN(16)正是.got段的首地址,加上偏移就能得到正确的gp值了。 注意gp地址必须是对齐的,这里为什么是ALIGN(16)而不是ALIGN(4)或者ALIGN(8)?猜想跟偏移的值有关,如果不是16字节对齐,加上0x7ff0后就不是整数了,你说是不是?


这样我们就设置好$gp寄存器了。

不设置$gp就不能解析全局符号,也就调用不了其他汇编文件中定义的函数,就是跳不到lowlevel_init这里了。参考这里:http://blog.chinaunix.net/uid-22590270-id-3243750.html

la      t9, lowlevel_init
    j       t9
    nop

把地址放到t9里,然后就可以跳转过去了。
好,我们来新建 lowlevel_init.S,然后在这个文件里初始化串口,并打印字符串。


为了顺便学习一下u-boot,我们跟u-boot一样,在board/ls1x下新建lowlevel_init.S文件。
点亮LED那一贴里,我直接把头文件放在源码顶层目录(chapter03)下了,这样不好。现在我们改过来,跟u-boot一样,放在include目录下。所以,我们的编译命令要稍作调整,原命令里我们是通过`-I/mnt/hgfs/CentOS_Share/chapter03`指定头文件的搜索目录的,现在要改成`-I/mnt/hgfs/CentOS_Share/chapter04/include`。


哦,这样看起来很Low,因为目录写死了。 之前还有2个地方的路径写死了:
-isystem /opt/gcc-4.3-ls232/bin/../lib/gcc/mipsel-linux/4.3.0/include
-L /opt/gcc-4.3-ls232/bin/../lib/gcc/mipsel-linux/4.3.0



我们都改成自动获取正确的路径,Makefile改成:



  1. CC         = mipsel-linux-gcc
  2. gccincdir  := $(shell $(CC) -print-file-name=include)
  3. PLATFORM_LIBGCC := -L $(shell dirname `$(CC) -print-libgcc-file-name`) -lgcc

  4. SRCTREE   := $(CURDIR)
  5. export CC gccincdir PLATFORM_LIBGCC SRCTREE

  6. u-boot.bin: start.o ls1x
  7.         
  8.         @echo "linking u-boot..."
  9.         mipsel-linux-ld -G 0 -static -n -nostdlib -EL -m elf32ltsmip -T u-boot.lds --gc-sections -pie -Bstatic -Ttext 0xbfc00000 start.o board/ls1x/lowlevel_init.o $(PLATFORM_LIBGCC) -Map u-boot.map -o u-boot
  10.         @echo "generating u-boot.bin..."
  11.         mipsel-linux-objcopy --remove-section=.dynsym --gap-fill=0xff -O binary u-boot u-boot.bin
  12.         @echo "done!"

  13. start.o:
  14.         @echo "compiling start.S..."
  15.         mipsel-linux-gcc -D__ASSEMBLY__ -g -Os -I$(SRCTREE)/include -ffreestanding -nostdinc -isystem $(gccincdir) -pipe  -G 0 -mabicalls -fpic -EL -msoft-float -march=mips32 -mabi=32 -o start.o start.S -c

  16. ls1x:
  17.         make -C board/ls1x

  18. .PHONY : clean

  19. clean:
  20.         @find $(CURDIR) -type f \
  21.                 \( -name 'core' -o -name '*.bak' -o -name '*~' -o -name '*.su' \
  22.                 -o -name '*.o'        -o -name '*.a' -o -name '*.exe' \
  23.                 -o -name '*.cfgtmp' \) -print \
  24.                 | xargs rm -f
复制代码


乍一看很多,其实就多了几条shell命令,自动获取当前文件夹和编译器所在的路径。自动寻找并删除*.o文件。
为了熟悉Makefile,顺便了解u-boot, 我们在 board/ls1x下也加了一个Makefile文件。


  1. lowlevel_init.o:
  2.         @echo "compiling lowlevel_init.S..."
  3.         mipsel-linux-gcc -D__ASSEMBLY__ -g -Os -I$(SRCTREE)/include -ffreestanding -nostdinc -isystem $(gccincdir) -pipe  -G 0 -mabicalls -fpic -EL -msoft-float -march=mips32 -mabi=32 -o $@ lowlevel_init.S -c
复制代码


在顶层目录的Makfile里使用make -C board/ls1x就可以执行子目录下的Makefile了。

嗯,这样看起来就比较有逼格了。

lowlevel_init.S:

#include <asm/asm.h>
#include <configs/ls1c300a_openloongson.h>
#include <asm/regdef.h>
#include <asm/arch/regs-clk.h>

#include "ns16550.h"

#define PRINTSTR(x) \
    .rdata;98: .asciz x; .text; la a0, 98b; bal stringserial; nop

    .globl  lowlevel_init
lowlevel_init:
    move  s0, ra
    /* initialize pll */

    ...

    li        a0,   0
    bal       initserial
    nop

loop_print:

    PRINTSTR("hello world!\r\n");
    DELAY(30000)
    b loop_print
    nop

    /* serial port configuration */
LEAF(initserial)
    ...
    ...
END(initserial)

stringserial:
    .set      noreorder
    move      a2, ra
    move      a1, a0
    lbu       a0, 0(a1)
1:
    beqz      a0, 2f
    nop      
    bal       tgt_putchar
    addiu     a1, 1
    b         1b
    lbu       a0, 0(a1)

2:
    j         a2
    nop
    .set reorder


tgt_putchar:
    la        v0, UART_BASE_ADDR
1:
    lbu       v1, NSREG(NS16550_LSR)(v0)
    and       v1, LSR_TXRDY
    beqz      v1, 1b
    nop

    sb        a0, NSREG(NS16550_DATA)(v0)
    move      v1, v0
    la        v0, UART_BASE_ADDR
    bne       v0, v1, 1b
    nop
    j         ra
    nop

我们要把lowlevel_init声明为global的。
关于头文件,我已经尽最大努力引入最少的头文件了。

注意原来u-boot是通过#include <config.h>来间接引入ls1c300a_openloongson.h这个头文件的
config.h是构建u-boot的时候动态生成的,里面就定义了一些宏而已,我们没必要引入,所以直接引入ls1c300a_openloongson.h这个头文件

注意ls1c300a_openloongson.h这个头文件的顺序,如果放在最下面
会报 lowlevel_init.S:127: Error: expression out of range 的错误。

大概看了一下,应该是有些宏必须先定义,这些宏就在ls1c300a_openloongson.h里。


引入这些头文件都是有作用的,一些宏定义和LEAF都是在这些头文件里定义的。 这个LEAF暂时还没有仔细研究。还有串口到底怎么初始化的也没有细说,这节内容有点多,有些地方我还没来得及细细琢磨,以后再补上吧。 其实代码并不多,大家稍微花点时间研究一下就好了。
有了心得,别忘了在这里跟大家分享一下哦。

源码在这里,如果你不想搭环境,只想看一下效果,.bin文件我也打包进来了,直接烧写就可以看效果了。

chapter04.zip (27.75 KB, 下载次数: 3)

0

主题

19

帖子

2094

积分

金牌会员

Rank: 6Rank: 6

积分
2094
发表于 2019-7-18 14:23:52 | 显示全部楼层
谢谢,很有意思。

5

主题

16

帖子

1253

积分

金牌会员

Rank: 6Rank: 6

积分
1253
 楼主| 发表于 2019-7-21 17:26:48 | 显示全部楼层
VG2018 发表于 2019-7-18 14:23
谢谢,很有意思。

设置gp的地方我之前说错了,已修改过来,希望没有误导你

55

主题

374

帖子

43万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
437709
发表于 2019-7-24 04:42:06 | 显示全部楼层
ahau 发表于 2019-7-21 17:26
设置gp的地方我之前说错了,已修改过来,希望没有误导你

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|龙芯俱乐部开源社区  

GMT+8, 2020-2-26 08:09 , Processed in 0.202724 second(s), 42 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表