樹莓派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

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