树莓派BootLoader

made by Rk

本文由浙江大学《嵌入式系统》课程提供强力支持。

感谢翁恺老师 @翁恺BA5AG

/*************************************************************/


实验内容:

David Welch的GitHub的 bootloader05给出了一个非常简单的RPi bootloader,他的代码链接在内存的0x00020000位置,一直在监听串口是 否有XMODEM协议的文件下载,如果有就开始接收数据,并复制到0x00008000位置,传输完成后跳转到 0x00008000去执行。
TA写了一个Python脚本,按照下面的命令调用脚本可以下载并执行用户程序


python xmodem-loader.py -p com3 -baud 115200 output.bin
你的任务是修改bootloader和python脚本实现如下功能:


调用命令 python xmodem-loader.py -p com3 -baud 115200 启动脚本并且与板卡建立串口连接,之后可以发送下面的命令。
load *.bin 下载程序*.bin
go 执行已下载的程序
peek addr 以一个字为单位读取内存中addr位置的数据(addr是4字节对齐,十六进行的形式,长度为8,例如 0x00008000),并以十六进制的形式输出
poke addr data 以一个字为单位修改内存中addr位置的数据为data(addr是4字节对齐,十六进行的形式,长 度为8, data也是十六进行的形式,长度为8)
verify *.bin 验证已下载的程序和*.bin是否完全相同。

背景

在国外大神的README中介绍了相关背景:

This repo serves as a collection of low level examples.  No operating
system, embedded or low level embedded or deeply embedded or bare metal,
whatever your term is for this.
介绍了教程来源

I am in no way shape or form associated with the raspberry pi organization
nor broadcom.  I just happen to own one (some) and am sharing my
experiences.  The raspberry pi is about education, and I feel low
level education is just as important as Python programming.
介绍了作者对于树莓派的想法

From what we know so far there is a gpu on chip which:

1) boots off of an on chip rom of some sort
2) reads the sd card and looks for additional gpu specific boot files
bootcode.bin and start.elf in the root dir of the first partition
(fat32 formatted, loader.bin no longer used/required)
3) in the same dir it looks for config.txt which you can do things like
change the arm speed from the default 700MHz, change the address where
to load kernel.img, and many others
4) it reads kernel.img the arm boot binary file and copies it to memory
5) releases reset on the arm such that it runs from the address where
the kernel.img data was written

树莓派通过板上的GPU加载启动信息,

1、读取SD卡第一个分区根目录里面的bootcode二进制文件以及start.elf。

2、在同一个目录下加载系统配置文件,比如配置CPU主频等,然后切换到读取kernel.img的地址。

3、读取kelnel后加载ARM启动二进制文件然后拷贝到内存,发送重置信号,使得树莓派可以从kernel.img的写入数据区开始加载命令继续执行。


The memory is split between the GPU and the ARM, I believe the default
is to split the memory in half.  And there are ways to change that
split (to give the ARM more).  Not going to worry about that here.


内存被分为GPU和ARM使用两部分,目测对半分。


From the ARMs perspective the kernel.img file is loaded, by default,
to address 0x8000.  (there are ways to change that, not going to worry
about that right now).
从ARM的角度来看,kernel镜像从地址0x8000开始加载。

Hardware and programming information:


You will want to go here
http://elinux.org/RPi_Hardware
And get the datasheet for the part
http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf
(might be an old link, find the one on the wiki page)
And the schematic for the board
http://www.raspberrypi.org/wp-content/uploads/2012/04/Raspberry-Pi-Schematics-R1.0.pdf
(might be an old link, find the one on the wiki page)


Early in the BCM2835 document you see a memory map.  I am going to
operate based on the middle map, this is how the ARM comes up.  The
left side is the system which we dont have direct access to in that
form, the gpu probably, not the ARM.  The ARM comes up with a memory
space that is basically 0x40000000 bytes in size as it mentions in
the middle chart.  The bottom of this picture shows total system
sdram (memory) and somewhere between zero and the top of ram is a
split between sdram for the ARM on the bottom and a chunk of that
for the VC SDRAM, basically memory for the gpu and memory shared
between the ARM and GPU to allow the ARM to ask the GPU to draw stuff
on the video screen.  256MBytes is 0x10000000, and 512MBytes is
0x20000000.
 Some models of raspberry pi have 256MB, newer models have
512MB total ram which is split between the GPU and the ARM.  Assume
the ARM gets at least half of this.  Peripherals (uart, gpio, etc)
are mapped into arm address space at 0x20000000.
 When you see
0x7Exxxxxx in the manual replace that with 0x20xxxxxx as your ARM
physical address.  Experimentally I have seen the memory repeats every
0x40000000, read 0x40008000 and you see the data from 0x8000.  I
wouldnt rely on this, just an observation (likely ignoring the upper

address bits in the memory controller).

第一次编译运行

在Mac系统上的编译一开始遇到一些问题,需要修改loader文件为以下内容:
MEMORY
{
    ram : ORIGIN = 0x8000, LENGTH = 0x1000000
}

SECTIONS
{
    .text : { *(.text*) *(.rodata.str1.4) *(.rodata) } > ram
    .bss : { *(.bss*) } > ram
}

修改bootloader05.c文件,原理是:
将原有的3个控制状态和128个数据读写状态修改为7个控制状态以及128个数据读写状态。
其中新增的状态有:
1、GO状态,跳转到Base Address读取并且执行代码;
2、Verify状态,根据error address的不同进行检查,error address初始化为0,一旦在verify过程中检查到已读到的数据与新的bin文件不一致,记录当前错误地址,并且回到状态0;
3、Peek状态,根据后面指令输入的address,通过GET(address)函数(定义在vector.s中)得到该内存地址上存的值,打印输出;
4、Poke状态,根据指令后面的地址以及新的数据,通过PUT(address,data value)函数(定义在vector.s中)修改内存地址,并且打印输出修改地址和修改后的值(通过GET得到)
5、LOAD状态,也就是加载二进制文件。

源代码中检查block块有两次检验,个人感觉第二次可以去掉,于是尝试了一下,事实证明也是可以的。
代码以及注释如下:

//-------------------------------------------------------------------------
//-------------------------------------------------------------------------

// The raspberry pi firmware at the time this was written defaults
// loading at address 0x8000.  Although this bootloader could easily
// load at 0x0000, it loads at 0x8000 so that the same binaries built
// for the SD card work with this bootloader.  Change the ARMBASE
// below to use a different location.

#define ARMBASE 0x8000
#define true 1

#define LOAD    0x00
#define GO      0x01
#define PEEK    0x02
#define POKE    0x03
#define VERIFY  0x04

extern void PUT32 ( unsigned int, unsigned int );
extern void PUT16 ( unsigned int, unsigned int );
extern void PUT8 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern unsigned int GET8 ( unsigned int );
extern unsigned int GETPC ( void );
extern void BRANCHTO ( unsigned int );
extern void dummy ( unsigned int );

extern void uart_init ( void );
extern unsigned int uart_lcr ( void );
extern void uart_flush ( void );
extern void uart_send ( unsigned int );
extern unsigned int uart_recv ( void );
extern void hexstring ( unsigned int );
extern void hexstrings ( unsigned int );
extern void timer_init ( void );
extern unsigned int timer_tick ( void );

extern void timer_init ( void );
extern unsigned int timer_tick ( void );

void print_pi(char* s) {
    int i = 0;
    while(s[i] != '\0') {
        uart_send(s[i++]);
    }
    uart_send(0x0D);    //send carriage return
    uart_send(0x0A);    //send new line
}

//------------------------------------------------------------------------
unsigned char xstring[256];
//------------------------------------------------------------------------
int notmain ( void ) {
    unsigned int ra;
    //unsigned int rb;
    unsigned int rx;
    unsigned int addr;
    unsigned int block;
    unsigned int state;

    unsigned int crc;
    unsigned int error_addr;

    uart_init();            //init serial
    hexstring(0x12345678);  //translate to hex value and send
    hexstring(GETPC());
    hexstring(ARMBASE);
    print_pi("This is Raspberry Pi!");
    uart_send(0x04);
    timer_init();

//SOH 0x01
//ACK 0x06
//NAK 0x15
//EOT 0x04

//block numbers start with 1

//132 byte packet
//starts with SOH
//block number byte
//255-block number
//128 bytes of data
//checksum byte (whole packet)
//a single EOT instead of SOH when done, send an ACK on it too
    
    addr=ARMBASE;	//base RAM address
    error_addr = 0; //record the address of error
    block=1;        //the 
    state=0;		//initial state
    crc=0;          //check sum
    rx=timer_tick();

    while(true)
    {
        ra=timer_tick();
        if((ra-rx)>=4000000)
        {
            uart_send(0x15);		//send long time no-response signal
            rx+=4000000;
        }

        if((uart_lcr()&0x01)==0)
        {
            continue;               //test if input string begin with 0x01
        }

        xstring[state]=uart_recv();
        rx=timer_tick();

        switch(state)
        {
            case 0:     //initial state, decide which action to take
            {
                if(xstring[state]==0x01)
                {
                    crc=xstring[state];
                    state++;
                }
                else if (xstring[state] == 0x04)    //End Of transmission
                {                                   //decide the action
                    uart_send(0x06);
                    if (xstring[1] == LOAD)
                    {
                        print_pi("This is LOAD command!");
                        uart_send(0x04);
                        uart_flush();
                    }
                    else if (xstring[1] == VERIFY)
                    {
                        if (error_addr == 0)
                        {
                            print_pi("Verify successful!");
                            uart_send(0x04);
                        }
                        else
                        {
                            print_pi("Verify error");
                            print_pi("Error Adress:");
                            hexstring(error_addr);
                            print_pi("Error Value:");
                            hexstring(GET32(error_addr));
                            uart_send(0x04);
                        }
                        uart_flush();
                    }
                    addr = ARMBASE;
                    error_addr = 0;
                    block = 1;
                    state = 0;
                    crc = 0;
                }
                else
                {
                    state=0;
                    uart_send(0x15);
                    print_pi("Init Error!");
                    uart_send(0x04);
                    uart_flush();
                }
                break;
            }
            case 1:                             //decide the action
            {
                if (xstring[1] > VERIFY)
                {
                    state = 0;
                    uart_send(0x15);
                    print_pi("Command error!");
                    uart_send(0x04);
                    uart_flush();
                }
                else if (xstring[1] == GO)
                {
                    state = 0;
                    uart_send(0x06);
                    print_pi("Branch to the base address!");
                    uart_send(0x04);
                    uart_flush();
                    BRANCHTO(ARMBASE);
                }
                else if (xstring[1] == PEEK)
                {
                    state = 133;
                }
                else if (xstring[1] == POKE)
                {
                    state = 133;
                }
                else
                {
                    state++;
                }
                break;
            }
            case 2:
            {
                if(xstring[state] == block)//if the data has the right block number
                {
                    crc += xstring[state];
                    state++;
                }
                else
                {
                    state = 0;
                    uart_send(0x15);
                    print_pi("Data block error!");
                    uart_send(0x04);
                    uart_flush();
                }
                break;
            }
            
            case 132:   //receive and verify progress
            {
                crc &= 0xFF;
                if(xstring[state]==crc)
                {
                    if (xstring[1] == LOAD)
                    {
                        for(ra=0; ra<128; ra++)
                        {
                            PUT8(addr++,xstring[ra+4]);
                        }
                        uart_send(0x06);
                    }
                    else
                    {
                        //Verify progress
                        for (ra=0; ra<128; ra++,addr++)
                        {
                            if (xstring[ra + 4] != (GET8(addr) & 0xff))
                            {
                                error_addr = addr;  //get the error address
                                break;
                            }
                        }
                        uart_send(0x06);
                    }
                    block=(block+1) & 0xFF; //if the data flow has not stopped
                }
                else
                {
                    uart_send(0x15);
                    print_pi("Check sum error!");
                    uart_send(0x04);
                    uart_flush();
                }
                state=0;
                break;
            }
            case 136:
            {
                if (xstring[1] == PEEK)
                {
                    unsigned int peek_addr = 0;
                    for (ra = 0; ra < 4; ra++)
                    {   //generate the address
                        peek_addr = peek_addr << 8 | xstring[ra + 133];
                    }
                    uart_send(0x06);
                    print_pi("Peek command value:");
                    hexstring(GET32(peek_addr));
                    uart_send(0x04);
                    uart_flush();
                    state = 0;
                }
                else
                {
                    state++;
                }
                break;
            }
            case 140:
            {
                if (xstring[1] == POKE)
                {
                    unsigned int poke_addr = 0x00000000;
                    unsigned int poke_data = 0;
                    for (ra = 0; ra < 4; ra++)
                    {
                        poke_addr = poke_addr << 8 | xstring[ra + 133];
                        poke_data = poke_data << 8 | xstring[ra + 137];
                    }
                    uart_send(0x06);
                    print_pi("Poke command:");
                    PUT32(poke_addr, poke_data);
                    print_pi("Poke address:");
                    hexstring(poke_addr);           //get the Poke address
                    print_pi("Poke value:");
                    hexstring(GET32(poke_addr));   //get the value after edit action
                    uart_send(0x04);
                    uart_flush();
                    state = 0;
                }
                else
                {
                    state = 0;
                }
                break;
            }
            default:
            {
                crc+=xstring[state];
                state++;
                break;
            }
        }
    }
    return(0);
}

//-------------------------------------------------------------------------
//-------------------------------------------------------------------------


//-------------------------------------------------------------------------
//
// Copyright (c) 2012 David Welch [email protected]
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//-------------------------------------------------------------------------

在vector.s中增加GET8:
.globl GET8
GET8:
    ldrb r0,[r0]
    bx lr

修改py文件对串口数据进行操作:
import sys, getopt
import serial
import time

def open(aport='/dev/tty.usbserial', abaudrate=115200) :#此处修改为本机串口地址,可以参见博文 http://blog.csdn.net/rk2900/article/details/8632713
  return serial.Serial(
      port=aport,
      baudrate=abaudrate,     # baudrate
      bytesize=8,             # number of databits
      parity=serial.PARITY_NONE,
      stopbits=1,
      xonxoff=0,              # enable software flow control
      rtscts=0,               # disable RTS/CTS flow control
      timeout=None               # set a timeout value, None for waiting forever
  )

def printLog(sp):
  temp = sp.read()
  while ord(temp) != 0x04:
      write(temp)
      temp = sp.read()

if __name__ == "__main__":

  # Import Psyco if available
  try:
      import psyco
      psyco.full()
      print "Using Psyco..."
  except ImportError:
      pass

  conf = {
      'port': '/dev/tty.usbserial',#此处修改为本机串口地址
      'baud': 115200,
  }

  try:
      opts, args = getopt.getopt(sys.argv[1:], "hqVewvrp:b:a:l:")
  except getopt.GetoptError, err:
      print str(err)
      sys.exit(2)

  for o, a in opts:
      if o == '-p':
          conf['port'] = a
      elif o == '-b':
          conf['baud'] = eval(a)
      else:
          assert False, "option error!"

  sp = open(conf['port'], conf['baud'])

# print args[0]
# print conf['port']
# print conf['baud']

  write=sys.stdout.write

  isLoaded = False

  while True:
      print ''
      cmd = raw_input('>> ').split(' ');
      sp.flushInput()

      if cmd[0] == 'go':
          if not isLoaded:
              confirm = raw_input("No file loaded, sure to go? [Y/N]")
              if confirm == '' or confirm[0] == 'N' or confirm[0] == 'n':
                  continue

          success = False
          while success == False:
              sp.write(chr(0x01))
              sp.write(chr(0x01))
              sp.flush()

              temp=sp.read()

              if ord(temp)==0x06:
                  success = True
              else:
                  print ord(temp)

                  printLog(sp)

      elif cmd[0] == 'peek':
          if len(cmd) < 2:
              print "Incorrect command, should be 'peek addr'"
              continue

          addr = int(cmd[1], 16) & 0xffffffff

          success = False
          while success == False:
              sp.write(chr(0x01))
              sp.write(chr(0x02))

              for i in range(0,4):
                  sp.write(chr(addr >> 24 & 0xff))
                  addr = addr << 8

              sp.flush()

              temp=sp.read()

              if ord(temp)==0x06:
                  success = True
              else:
                  print ord(temp)

                  printLog(sp)

      elif cmd[0] == 'poke':
          if len(cmd) < 3:
              print "Incorrect command, should be 'poke addr data'"
              continue

          addr = int(cmd[1], 16) & 0xffffffff
          data = int(cmd[2], 16) & 0xffffffff

          success = False
          while success == False:
              sp.write(chr(0x01))
              sp.write(chr(0x03))

              for i in range(0,4):
                  sp.write(chr(addr >> 24 & 0xff))
                  addr = addr << 8
              for i in range(0,4):
                  sp.write(chr(data >> 24 & 0xff))
                  data = data << 8

              sp.flush()

              temp=sp.read()

              if ord(temp)==0x06:
                  success = True
              else:
                  print ord(temp)

                  printLog(sp)

      elif cmd[0] == 'load' or cmd[0] == 'verify':
          if len(cmd) < 2:
              print "Please input the filename"
              continue

          try:
              data = map(lambda c: ord(c), file(cmd[1],"rb").read())
          except:
              print "File path error"
              continue

          temp = sp.read()
          buf = ""

          dataLength = len(data)
          blockNum = (dataLength-1)/128+1
          print "The size of the image is ",dataLength,"!"
          print "Total block number is ",blockNum,"!"
          print "Download start,",blockNum,"block(s) in total!"

          for i in range(1,blockNum+1):
              success = False
              while success == False:
                  sp.write(chr(0x01))
                  if cmd[0] == 'load':
                      sp.write(chr(0x00))
                  else:
                      sp.write(chr(0x04))
                  sp.write(chr(i&0xFF))
                  sp.write(chr(0xFF-i&0xFF))
                  crc = 0x01+0xFF

                  for j in range(0,128):
                      if len(data)>(i-1)*128+j:
                          sp.write(chr(data[(i-1)*128+j]))
                          crc += data[(i-1)*128+j]
                      else:
                          sp.write(chr(0xff))
                          crc += 0xff

                  crc &= 0xff
                  sp.write(chr(crc))
                  sp.flush()

                  temp=sp.read()
                  sp.flushInput()

                  if ord(temp)==0x06:
                      success = True
                      print "Block",i,"has finished!"
                  else:
                      print ord(temp)
                      print "Error,send again!"

                      printLog(sp)

          sp.write(chr(0x04))
          sp.flush()
          temp=sp.read()

          if ord(temp)==0x06:
              if (cmd[0] == 'load'):
                  isLoaded = True
              print "Download has finished!\n"
      elif cmd[0] == 'q':
          sys.exit(0)
      else:
          print "Invalid command!"


      printLog(sp)


  # while True:
  #   write(sp.read())

  sp.close()

实验结果

LOAD指令:


输入GO指令,此处我加载的是外国人搞的blinker二进制文件,效果就是树莓派上ACT的小灯闪烁。
效果如图:


进行PEEK与POKE操作
此处POKE将修改后的内存值打印出来是需要通过GET操作得到后来该内存地址处的值,而非打印POKE操作输入的参数。


POKE操作之前进行Verify:



POKE操作之后进行verify:



参考资料:

百科关于ASCII码:http://baike.baidu.com/view/15482.htm

华官的博客:http://blog.csdn.net/logicworldzju/article/details/8923596

达达的博客:http://www.neohe.tk/raspberrypi-diy-bootloader/

外国人的GitHub:https://github.com/dwelch67/raspberrypi

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章