iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 28
0
Software Development

系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!系列 第 28

Day 28: 高手不輕易透露的技巧(2/2) - Flash Driver & Target Burner

0. 前言

終於倒數第三天啦!! 不過今天還是要來繼續奮戰一下!!

今天呢! 要來介紹一個我覺得蠻有趣的Flash燒錄方式:"Target Burner"
名子是我亂取的

這個實作的方式,主要是我在研究「GitHub: riscv/riscv-openocd」時候,所看到一個蠻有趣的應用!
內容接續上篇「Day 27: 高手不輕易透露的技巧 - Flash Programming」,講述Sifive在"GitHub: riscv/riscv-openocd"中實作的"fespi" Flash Driver,以及裡面write flash中所使用到的"Target Burner"背後的原理和實作細節!

對了,再提醒一下!
我沒有要廣告任何Sifive相關的東西的意思
謝謝!
  
  
  

1. N25Q128 SPI Flash Instructions

在開始剖析fespi的內容之前,我們先來看看"N25Q128"的Commmand Table!

請參考「N25Q128 with Subsector Erase on Boot Sectors」的內容!

這邊節錄一些待會會用到的指令:

  • 0x9F(RDID): Read Identification,讀取Flash的Identification,內容包含Manufacturer、Device等資訊
  • 0x06(WREN): Write Enable,在Program/Erase之前,都必須先使用這個Instruction,讓Flash進入可寫入的狀態
  • 0x02(PP), offset(3 Bytes), data(1~256 Bytes): Program Page,將後面資料寫入Flash中offset的位置,至多一次寫入256 Bytes
  • 0xD8(SE), offset(3 Bytes): Sector Erase,清除該Sector的所有內容(還原成1)
  • 0x05(RDSR): Read Status Register,讀取Flash中的Status Register,詳細內容下面會介紹!

再過來介紹Status Register的內容:

https://ithelp.ithome.com.tw/upload/images/20180115/20107327ByAItQaEtM.png
--引用自N25Q128 with Subsector Erase on Boot Sectors

目前只會使用到其中的兩個bits,基本定義如下:

  • bit 0: WIP(Write in progress),判斷目前的Write(Program/Erase/...)是否還在執行中
  • bit 1: WEL(Write enable latch),判斷目前的Flash是否可進行Write(Program/Erase/...)

剩下的內容就留給有興趣的讀者去研究吧,大概183頁而已!!!!!
  
  
  

2. Sifive SPI Controller

再來介紹一下,E300中所使用到的SPI Controller!
還記的這篇Lab嗎!? 「Day 22: [Lab] 深入淺出 RISC-V 源碼剖析 (Final) - 執行 & Log分析」,在這邊,我們使用Xilinx Arty開發版,配合SiFive E51 FPGA Eval Kit v1p0
突然發現那篇忘了放連結XDD

裡面有包含一個SPI Controller,因為待會fespi Flash Driver都會跟他有關,因此在這邊簡單介紹一下,常用到的Registers!
有錯請在留言處提出,他們的IP我也不太熟*

先來看看Registers Table:

https://ithelp.ithome.com.tw/upload/images/20180115/20107327HtALyCMTEg.png
--引用自Freedom E300 Platform Reference Manual

底下介紹幾個有用到的Register!
  
  

2.1 0x18 csmode(Chip Select Mode) Register

https://ithelp.ithome.com.tw/upload/images/20180115/20107327QqbkThWmc7.png
--引用自Freedom E300 Platform Reference Manual

主要是讓SPI Controller可以選擇正確的Flash,拉起對應的CS pin,
包含以下幾種Mode:

  • 0x0: AUTO,自動的在傳輸過程中,Assert/Deassert CS pin!
  • 0x2: HOLD,保持CS Pin在Assert的狀態
  • 0x3: OFF,關閉SPI Controler控制CS的功能
      
      

2.2 0x40 fmt(Frame Format) Register

https://ithelp.ithome.com.tw/upload/images/20180115/201073273kDbCP4xsu.png
--引用自Freedom E300 Platform Reference Manual

這個Register主要控制FIFO傳輸的方式,定義如下:

  • proto: SPI傳輸模式,分成以下幾種
    • 0x0: Single,普通SPI傳輸模式,利用DQ0(MOSI)做TX,DQ1(MISO)做RX
    • 0x1: Dual,同時利用DQ0、DQ1做傳輸
    • 0x2: Quad,同時利用DQ0、DQ1、DQ2、DQ3做傳輸
  • endian: 控制傳輸位元的起始
    • 0x0: 從MSB開始
    • 0x1: 從LSB開始
  • dir: 傳輸方向
    • 0x0: RX
    • 0x1: TX
  • len: 傳輸的長度,預設是8-bits
      
      

2.3 0x48 txdata(Tx FIFO)、0x4c rxdata(Rx FIFO)

https://ithelp.ithome.com.tw/upload/images/20180115/20107327vteBfQ1oVB.png
--引用自Freedom E300 Platform Reference Manual

https://ithelp.ithome.com.tw/upload/images/20180115/20107327aZRNldxV7m.png
--引用自Freedom E300 Platform Reference Manual

這兩個比較類似,放在一起!
主要是傳輸/接收的資料的FIFO,裡面還包含指示目前狀態的Flag:

  • full: 當拉起來的時候,表示TX尚未完成,txdata將不接受任何寫入
  • empty: 當拉起來的時候,表示RX尚未完成,rxdata的內容為垃圾

另外提醒一下,實作中所使用的名稱,與文件上不相同,
對照的時候需要注意一下(src/flash/nor/fespi.c)::

#define FESPI_REG_TXFIFO          0x48
#define FESPI_REG_RXFIFO          0x4c

  
  

2.4 0x50 txmark(Tx FIFO watermark)、0x54 rxmark(Rx FIFO watermark)

https://ithelp.ithome.com.tw/upload/images/20180115/20107327xPhomPCJeB.png
--引用自Freedom E300 Platform Reference Manual

https://ithelp.ithome.com.tw/upload/images/20180115/20107327HbUv95GKIm.png
--引用自Freedom E300 Platform Reference Manual

當Tx的剩餘FIFO內容小於$txmark所設定的時候,自動將Interrupt拉起!
至於$rxmark在實作中沒有用到,放在這邊給大家參考一下而已XD!

另外提醒一下,實作中所使用的名稱,與文件上不相同,
對照的時候需要注意一下(src/flash/nor/fespi.c)::

#define FESPI_REG_TXCTRL          0x50
#define FESPI_REG_RXCTRL          0x54

  
  

2.5 0x60 fctrl(Flash Interface Control) Register

https://ithelp.ithome.com.tw/upload/images/20180115/20107327i4AlFPjej7.png
--引用自Freedom E300 Platform Reference Manual

在Sifive的設計當中,主要分成兩種操作模式:

  • 0x0: SW Mode,或是可以稱為Programmed-I/O mode,由軟體這邊控制Flash的操作!
  • 0x1: HW Mode,或是可以稱為Memory-mapped(direct-mapped),對Flash的讀取,將會由SPI Controller自動發送READ(0x03) Instruction
      
      

2.6 0x70 ie(Interrupt Enable)、0x74 ip(Interrupt Pending)

https://ithelp.ithome.com.tw/upload/images/20180115/20107327ySbOiiCDdd.png
--引用自Freedom E300 Platform Reference Manual

這邊主要是控制Interrupt的Enabled和指示目前狀態!
當Tx的剩餘FIFO內容小於$txmark所設定的時候,如果$ie中的txwm有Enabled的話,
則$ip的txwm會被自動拉起來,表示目前Tx FIFO已空!
同理Rx也是類似的概念!
  
  
  

3. Flash Driver

以上終於 報告 介紹完畢,開始正式進入今天的主題內容--
fespi(Freedom E SPI controller) Flash Driver

上篇有提到,每個Bank都需要搭配自己的一套Flash Driver,幾本上Flash Driver應該包含的內容,如下所示,請參考(src/flash/nor/driver.h):

struct flash_driver {
    const char *name;
    const char *usage;
    const struct command_registration *commands;
    __FLASH_BANK_COMMAND((*flash_bank_command));
    int (*erase)(struct flash_bank *bank, int first, int last);
    int (*protect)(struct flash_bank *bank, int set, int first, int last);
    int (*write)(struct flash_bank *bank,
            const uint8_t *buffer, uint32_t offset, uint32_t count);
    int (*read)(struct flash_bank *bank,
            uint8_t *buffer, uint32_t offset, uint32_t count);
    int (*probe)(struct flash_bank *bank);
    int (*erase_check)(struct flash_bank *bank);
    int (*protect_check)(struct flash_bank *bank);
    int (*info)(struct flash_bank *bank, char *buf, int buf_size);
    int (*auto_probe)(struct flash_bank *bank);
};

詳細內容如下:

  • name: 提供一個好用的名稱
  • commmands: 如果有針對這個Flash Driver提供特別的Commmands,可在這邊註冊!
  • flash_bank_command: 主要在初始化過程中,去處理Config中的設定,並初始化內部的資料
  • erase: 以Sector為單位,將Flash指定的那些Sectors做Erase
  • protect: 以Sector為單位,將Flash做unlock
  • write: 就是將buffer的內容寫入指定的位置
  • probe: 初始化Sectors用,查詢每個Sector的狀態
  • info: 將Flash相關的資料轉成String
  • auto_probe: 提供上層呼叫使用(例如: GDB),在每次Flash進行Program前,GDB都會要求呼叫這個函式!

底下的章節中,將會剖析fespi在其中幾個比較重要的函式的實作內容!
至於沒說明的部分,剩下留給讀者自行研究啦!!
  
  

3.1 flash_bank_command (fespi_flash_bank_command)

如果讀者有仔細觀察過「Day 22: [Lab] 深入淺出 RISC-V 源碼剖析 (Final) - 執行 & Log分析」這篇所使用的Config,就不難發下裡面偷偷的使用到了一項設定:

flash bank spi0 fespi 0x40000000 0 0 0 $_TARGETNAME 0x20004000  #譯註: 定義Flash區塊

這個Config就會讓OpenOCD定義一個全新的Bank,叫做"spi0",然後必須使用"fespi"這套Flash Driver!

讓我們看一下fespi_flash_bank_command()中的實作,
請參考(src/flash/nor/fespi.c):

FLASH_BANK_COMMAND_HANDLER(fespi_flash_bank_command)
{
    struct fespi_flash_bank *fespi_info;

    LOG_DEBUG("%s", __func__);

    if (CMD_ARGC < 6)
        return ERROR_COMMAND_SYNTAX_ERROR;

    fespi_info = malloc(sizeof(struct fespi_flash_bank));   ///譯註: 建立內部資料結構
    if (fespi_info == NULL) {
        LOG_ERROR("not enough memory");
        return ERROR_FAIL;
    }

    bank->driver_priv = fespi_info;
    fespi_info->probed = 0;
    fespi_info->ctrl_base = 0;

    ///譯註: 如果Config中有接上專用的參數
    if (CMD_ARGC >= 7) {
        int temp;
        COMMAND_PARSE_NUMBER(int, CMD_ARGV[6], temp);
        fespi_info->ctrl_base = (uint32_t) temp;
        LOG_DEBUG("ASSUMING FESPI device at ctrl_base = 0x%x", fespi_info->ctrl_base);
    }

    return ERROR_OK;
}

從這邊不難發現,Config最後面所接的"0x20004000",其實就是SPI Contorller的位置!
在這邊,簡單的準備好內部資料結構,然後把Controller的Base給存放起來!
  
  

3.2 auto_probe (fespi_auto_probe)、probe (fespi_probe)

這兩個處理過程比較類似! 應該說是幾本上一樣XD
主要負責初始化這個Bank中內部的Setors資料內容!

先來看一下fespi_auto_probe()的內容,請參考(src/flash/nor/fespi.c):

static int fespi_auto_probe(struct flash_bank *bank)
{
    struct fespi_flash_bank *fespi_info = bank->driver_priv;
    if (fespi_info->probed)
        return ERROR_OK;
    return fespi_probe(bank);
}

沒錯,就是直接呼叫fespi_probe()來做初始化,
不過要記得! 初始化只需要一次即可!

來看看fespi_probe()的內容吧,請參考(src/flash/nor/fespi.c):

static int fespi_probe(struct flash_bank *bank)
{
    struct target *target = bank->target;
    struct fespi_flash_bank *fespi_info = bank->driver_priv;
    uint32_t ctrl_base;
    struct flash_sector *sectors;
    uint32_t id = 0; /* silence uninitialized warning */
    const struct fespi_target *target_device;
    int retval;

    if (fespi_info->probed)
        free(bank->sectors);
    fespi_info->probed = 0;

    ///譯註 Step 1: 尋找SPI Controller Base
    if (fespi_info->ctrl_base == 0) {
        for (target_device = target_devices ; target_device->name ; ++target_device)
            if (target_device->tap_idcode == target->tap->idcode)
                break;

        if (!target_device->name) {
            LOG_ERROR("Device ID 0x%" PRIx32 " is not known as FESPI capable",
                    target->tap->idcode);
            return ERROR_FAIL;
        }

        fespi_info->ctrl_base = target_device->ctrl_base;

        LOG_DEBUG("Valid FESPI on device %s at address 0x%" PRIx32,
                target_device->name, bank->base);

    } else {
      LOG_DEBUG("Assuming FESPI as specified at address 0x%x with ctrl at 0x%x",
            fespi_info->ctrl_base,
            bank->base);
    }
    ctrl_base = fespi_info->ctrl_base;

    ///譯註 Step 2: 尋找Flash ID
    /* read and decode flash ID; returns in SW mode */
    FESPI_WRITE_REG(FESPI_REG_TXCTRL, FESPI_TXWM(1));
    fespi_set_dir(bank, FESPI_DIR_TX);

    retval = fespi_read_flash_id(bank, &id);

    FESPI_ENABLE_HW_MODE();
    if (retval != ERROR_OK)
        return retval;

    fespi_info->dev = NULL;
    for (const struct flash_device *p = flash_devices; p->name ; p++)
        if (p->device_id == id) {
            fespi_info->dev = p;
            break;
        }

    if (!fespi_info->dev) {
        LOG_ERROR("Unknown flash device (ID 0x%08" PRIx32 ")", id);
        return ERROR_FAIL;
    }

    LOG_INFO("Found flash device \'%s\' (ID 0x%08" PRIx32 ")",
            fespi_info->dev->name, fespi_info->dev->device_id);

    /* Set correct size value */
    bank->size = fespi_info->dev->size_in_bytes;

    ///譯註 Step 3: 初始化Sectors
    /* create and fill sectors array */
    bank->num_sectors =
        fespi_info->dev->size_in_bytes / fespi_info->dev->sectorsize;
    sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
    if (sectors == NULL) {
        LOG_ERROR("not enough memory");
        return ERROR_FAIL;
    }

    for (int sector = 0; sector < bank->num_sectors; sector++) {
        sectors[sector].offset = sector * fespi_info->dev->sectorsize;
        sectors[sector].size = fespi_info->dev->sectorsize;
        sectors[sector].is_erased = -1;
        sectors[sector].is_protected = 1;
    }

    bank->sectors = sectors;
    fespi_info->probed = 1;
    return ERROR_OK;
}

看起來複雜,讓我們一一來剖析他!
首先是Step 1,尋找SPI Controller Base,這邊不難發現,如果剛剛Config已經指定的話(以上述例子: 0x20004000),那這邊就會跳過,反之,如果沒有指定的話,這邊採用idcode轉Controller Base的方式,請參考(src/flash/nor/fespi.c):

static const struct fespi_target target_devices[] = {
    /* name,   tap_idcode, ctrl_base */
    { "Freedom E300 SPI Flash",  0x10e31913 , 0x10014000 },
    { NULL,    0,           0          }
};

不過他只支援一種對應方式XDD

再來是Step 2,尋找Flash ID!
終於進入我們第一筆SPI Tx/Rx的流程當中!
首先先設定$txmark(忘記的話往上捲),請參考(src/flash/nor/fespi.c):

#define FESPI_WRITE_REG(a, v)                   \
{                               \
    int __r;                        \
                                \
    __r = target_write_u32(target, ctrl_base + (a), (v));   \
    if (__r != ERROR_OK) {                  \
        LOG_ERROR("FESPI_WRITE_REG error");     \
        return __r;                 \
    }                           \
}

FESPI_WRITE_REG(FESPI_REG_TXCTRL, FESPI_TXWM(1));

這邊其實就是呼叫OpenOC的target_write_u32(),然後對應的位置就是SPI Controller的Base再加上0x50($txmark的Address),然後把對應的值(這邊是0x1)寫入!
然後是fespi_set_dir(bank, FESPI_DIR_TX);,請參考(src/flash/nor/fespi.c):

static int fespi_set_dir(struct flash_bank *bank, bool dir)
{
    struct target *target = bank->target;
    struct fespi_flash_bank *fespi_info = bank->driver_priv;
    uint32_t ctrl_base = fespi_info->ctrl_base;

    FESPI_WRITE_REG(FESPI_REG_FMT,
            (FESPI_READ_REG(FESPI_REG_FMT) & ~(FESPI_FMT_DIR(0xFFFFFFFF))) |
            FESPI_FMT_DIR(dir));

    return ERROR_OK;

}

一樣是呼叫FESPI_WRITE_REG,然後去設定$fmt中的dir為Tx!
最後呼叫fespi_read_flash_id()來讀取ID,請參考(src/flash/nor/fespi.c):

static int fespi_read_flash_id(struct flash_bank *bank, uint32_t *id)
{
    struct target *target = bank->target;
    struct fespi_flash_bank *fespi_info = bank->driver_priv;
    uint32_t ctrl_base = fespi_info->ctrl_base;
    int retval;

    if (target->state != TARGET_HALTED) {
        LOG_ERROR("Target not halted");
        return ERROR_TARGET_NOT_HALTED;
    }

    fespi_txwm_wait(bank);

    /* Disable Hardware accesses*/
    FESPI_DISABLE_HW_MODE();

    /* poll WIP */
    retval = fespi_wip(bank, FESPI_PROBE_TIMEOUT);
    if (retval != ERROR_OK)
        return retval;

    fespi_set_dir(bank, FESPI_DIR_RX);

    /* Send SPI command "read ID" */
    FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_HOLD);

    fespi_tx(bank, SPIFLASH_READ_ID);
    /* Send dummy bytes to actually read the ID.*/
    fespi_tx(bank, 0);
    fespi_tx(bank, 0);
    fespi_tx(bank, 0);

    /* read ID from Receive Register */
    *id = 0;
    if (fespi_rx(bank, NULL) != ERROR_OK)
        return ERROR_FAIL;
    uint8_t rx;
    if (fespi_rx(bank, &rx) != ERROR_OK)
        return ERROR_FAIL;
    *id = rx;
    if (fespi_rx(bank, &rx) != ERROR_OK)
        return ERROR_FAIL;
    *id |= (rx << 8);
    if (fespi_rx(bank, &rx) != ERROR_OK)
        return ERROR_FAIL;
    *id |= (rx << 16);

    FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_AUTO);

    fespi_set_dir(bank, FESPI_DIR_TX);

    return ERROR_OK;
}

這邊的流程可以簡化如下:

  1. fespi_txwm_wait(): 等待Tx FIFO可以寫入
  2. FESPI_DISABLE_HW_MODE(): 關閉HW Mode,改採SW Mode
  3. fespi_wip(): 等待Flash可以進行操作時
  4. fespi_set_dir(): 將dir設為Rx
  5. fespi_tx(bank, SPIFLASH_READ_ID): 發送0x9F(RDID) Instruction
  6. fespi_rx(bank, NULL): 從Rx FIFO中讀回ID

讓我們看看實際執行的成果:

Info : 6533 4570 fespi.c:1094 fespi_probe(): Found flash device 'micron n25q128' (ID 0x0018ba20)

OK! 看起來正確,跟文件「N25Q128 with Subsector Erase on Boot Sectors」的內容相同!

最後回到fespi_probe()的Step 3,初始化Sectors!

    /* create and fill sectors array */
    bank->num_sectors =
        fespi_info->dev->size_in_bytes / fespi_info->dev->sectorsize;
    sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
    if (sectors == NULL) {
        LOG_ERROR("not enough memory");
        return ERROR_FAIL;
    }

    for (int sector = 0; sector < bank->num_sectors; sector++) {
        sectors[sector].offset = sector * fespi_info->dev->sectorsize;
        sectors[sector].size = fespi_info->dev->sectorsize;
        sectors[sector].is_erased = -1;
        sectors[sector].is_protected = 1;
    }

    bank->sectors = sectors;
    fespi_info->probed = 1;

這個Flash中的Sector固定為64KB,因此可以用簡單的for loop計算出每個Sector的offset,然後順便設定size、is_erase(設定成Unknown)和is_protected(設定成Protected/Locked)!

最後已經初始化完畢的話,記得把probed設成1:

    fespi_info->probed = 1;

搞定! 收工!
  
  

3.3 erase (fespi_erase)

有了前面fespi_read_flash_id()的剖析後,讓我們來研究如何將Flash進行Erase,
請參考(src/flash/nor/fespi.c):

static int fespi_erase(struct flash_bank *bank, int first, int last)
{
    ....前面判斷 & 初始化省略XD

    for (sector = first; sector <= last; sector++) {
        retval = fespi_erase_sector(bank, sector);
        if (retval != ERROR_OK)
            break;
        keep_alive();
    }

    ....後面不重要
}


為了省篇幅,我們直接看到最重要的部分!
簡單的來說,就是針對一個個Sector去呼叫fespi_erase_sector()來清除該Sector!
所以直接看fespi_erase_sector()的內容,請參考(src/flash/nor/fespi.c):

static int fespi_erase_sector(struct flash_bank *bank, int sector)
{
    struct target *target = bank->target;
    struct fespi_flash_bank *fespi_info = bank->driver_priv;
    uint32_t ctrl_base = fespi_info->ctrl_base;
    int retval;

    retval = fespi_tx(bank, SPIFLASH_WRITE_ENABLE);
    if (retval != ERROR_OK)
        return retval;
    retval = fespi_txwm_wait(bank);
    if (retval != ERROR_OK)
        return retval;

    FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_HOLD);
    retval = fespi_tx(bank, fespi_info->dev->erase_cmd);
    if (retval != ERROR_OK)
        return retval;
    sector = bank->sectors[sector].offset;
    retval = fespi_tx(bank, sector >> 16);
    if (retval != ERROR_OK)
        return retval;
    retval = fespi_tx(bank, sector >> 8);
    if (retval != ERROR_OK)
        return retval;
    retval = fespi_tx(bank, sector);
    if (retval != ERROR_OK)
        return retval;
    retval = fespi_txwm_wait(bank);
    if (retval != ERROR_OK)
        return retval;
    FESPI_WRITE_REG(FESPI_REG_CSMODE, FESPI_CSMODE_AUTO);

    retval = fespi_wip(bank, FESPI_MAX_TIMEOUT);
    if (retval != ERROR_OK)
        return retval;

    return ERROR_OK;
}

好,太複雜了,簡化如下:

  1. fespi_tx(bank, SPIFLASH_WRITE_ENABLE): 傳送0x06(WREN),讓Flash進入可寫狀態
  2. fespi_txwm_wait(): 等待Tx FIFO可以寫入
  3. fespi_tx(bank, fespi_info->dev->erase_cmd): 傳送0xD8(SE),將後面Address的這個Sector清除(Erase)掉
  4. fespi_tx(bank, sector >> 16)、fespi_tx(bank, sector >> 8)、fespi_tx(bank, sector): 傳送這個Sector的Address(Offset)
  5. fespi_txwm_wait(bank): 等待Tx FIFO可以寫入
  6. fespi_wip(bank, FESPI_MAX_TIMEOUT): 等待剛剛的Erase指令完成!

以上就是Erase的內容,非常簡單吧!!
  
  

3.4 write (fespi_write)

嘿嘿嘿!! 終於到今天的重頭戲啦!!!
既然講到Flash燒綠,怎可能不提到Write(Program)的實作!

當初在實驗這個Sifive提供的這個Target時,就意外發現他的燒綠速度非比尋常!
在剖析整套程式碼後,才發現這個有趣的設計,就讓我們來看看後面隱藏的秘密以及相關實作的內容吧!
  

3.4.1 Target Burner原理

仔細觀察一下剛剛所提到的"Read ID"和"Erase Sector"的流程,
不難發現它類似下面這張圖:

https://ithelp.ithome.com.tw/upload/images/20180115/20107327WedQe7apel.png

你會發現,每做一次的Tx/Rx就是一道、一道的USB + JTAG + SPI傳輸,
尤其是PC到Adapter的USB傳輸,中間的速度非常的慢!

換個角度想,如果沒辦法避免大量的USB+JTAG+SPI傳輸,
Burner本身的速度就會非常的緩慢,慢到使用者不能接受!
那如果假設今天有一個方法,可以一次搬移一堆資料到Target端,由Target端負責SPI Controller的處理,讓他慢慢讀寫完SPI Flash後,再回報成果!
我們是不是就能夠加速這個處理流程,例如下圖的方式:

https://ithelp.ithome.com.tw/upload/images/20180115/20107327BUI36ajrLh.png

先一次將資料搬到Target上的RAM/ILM/DLM/....等可以讀、寫、執行指令的地方,
然後透過一個預先設計好放在上面的"小程式",負責將一筆筆的資料透過SPI Controller寫入到SPI Flash上,是不是就能加速啊!!

這個流程,就稱為"Target Burner"?! 我承認是自己亂命名的XD
以上就是整個背後的原理,接下來剖析如何實現這個功能!
  

3.4.2 Burner Program

那既然上面提到這個預先設計好放在上面的"小程式",
就要來剖析一下他擁有的功能啦!!

我們先來看看內容:

static const uint8_t algorithm_bin[] = {
     0x6f, 0x00, 0xc0, 0x01, 0x73, 0x00, 0x10, 0x00, 0x6f, 0x00, 0xc0, 0x02,
     0x6f, 0x00, 0x00, 0x05, 0x6f, 0x00, 0xc0, 0x05, 0x6f, 0x00, 0x00, 0x07,
     0x6f, 0x00, 0x00, 0x0a, 0x83, 0xc2, 0x05, 0x00, 0x93, 0x85, 0x15, 0x00,
     0x17, 0x03, 0x00, 0x00, 0x13, 0x03, 0xc3, 0xfd, 0xb3, 0x82, 0x62, 0x00,
     0x67, 0x80, 0x02, 0x00, 0x03, 0xc3, 0x05, 0x00, 0x93, 0x85, 0x15, 0x00,
     0x83, 0x22, 0x85, 0x04, 0xe3, 0xce, 0x02, 0xfe, 0x83, 0xc2, 0x05, 0x00,
     0x23, 0x24, 0x55, 0x04, 0x93, 0x85, 0x15, 0x00, 0x13, 0x03, 0xf3, 0xff,
     0xe3, 0x44, 0x60, 0xfe, 0x6f, 0xf0, 0x5f, 0xfc, 0x83, 0x22, 0x45, 0x07,
     0x93, 0xf2, 0x12, 0x00, 0xe3, 0x8c, 0x02, 0xfe, 0x6f, 0xf0, 0x5f, 0xfb,
     0x83, 0xc2, 0x05, 0x00, 0xb3, 0x82, 0xa2, 0x00, 0x03, 0xc3, 0x15, 0x00,
     0x93, 0x85, 0x25, 0x00, 0x23, 0xa0, 0x62, 0x00, 0x6f, 0xf0, 0xdf, 0xf9,
     0x13, 0x06, 0x50, 0x00, 0xef, 0x00, 0x80, 0x01, 0x13, 0x06, 0x00, 0x00,
     0xef, 0x00, 0x00, 0x01, 0x93, 0x72, 0x16, 0x00, 0xe3, 0x9a, 0x02, 0xfe,
     0x6f, 0xf0, 0x1f, 0xf8, 0x83, 0x22, 0x85, 0x04, 0xe3, 0xce, 0x02, 0xfe,
     0x23, 0x24, 0xc5, 0x04, 0x03, 0x26, 0xc5, 0x04, 0xe3, 0x4e, 0x06, 0xfe,
     0x67, 0x80, 0x00, 0x00, 0x83, 0x22, 0x05, 0x04, 0x13, 0x03, 0x70, 0xff,
     0xb3, 0xf2, 0x62, 0x00, 0x03, 0xc3, 0x05, 0x00, 0x93, 0x85, 0x15, 0x00,
     0xb3, 0xe2, 0x62, 0x00, 0x23, 0x20, 0x55, 0x04, 0x6f, 0xf0, 0x9f, 0xf4
};

好,結束!
開玩笑地,這個內容沒啥人可以直接看懂XD!
換成以下方式,請參考(src/flash/nor/fespi.c):

        .global _start
_start:
command_table:
        j       main            // 0
        ebreak                  // 4
        j       tx              // 8
        j       txwm_wait       // 12
        j       write_reg       // 16
        j       wip_wait        // 20
        j       set_dir         // 24

// Execute the program.
main:
        lbu     t0, 0(a1)
        addi    a1, a1, 1
        la      t1, command_table
        add     t0, t0, t1
        jr      t0

// Read 1 byte the contains the number of bytes to transmit. Then read those
// bytes from the program and transmit them one by one.
tx:
        lbu     t1, 0(a1)       // read number of bytes to transmit
        addi    a1, a1, 1
1:      lw      t0, FESPI_REG_TXFIFO(a0)        // wait for FIFO clear
        bltz    t0, 1b
        lbu     t0, 0(a1)       // Load byte to write
        sw      t0, FESPI_REG_TXFIFO(a0)
        addi    a1, a1, 1
        addi    t1, t1, -1
        bgtz    t1, 1b
        j       main

// Wait until TXWM is set.
txwm_wait:
1:      lw      t0, FESPI_REG_IP(a0)
        andi    t0, t0, FESPI_IP_TXWM
        beqz    t0, 1b
        j       main

// Read 1 byte that contains the offset of the register to write, and 1 byte
// that contains the data to write.
write_reg:
        lbu     t0, 0(a1)       // read register to write
        add     t0, t0, a0
        lbu     t1, 1(a1)       // read value to write
        addi    a1, a1, 2
        sw      t1, 0(t0)
        j       main

wip_wait:
        li      a2, SPIFLASH_READ_STATUS
        jal     txrx_byte
        // discard first result
1:      li      a2, 0
        jal     txrx_byte
        andi    t0, a2, SPIFLASH_BSY_BIT
        bnez    t0, 1b
        j       main

txrx_byte:  // transmit the byte in a2, receive a bit into a2
        lw      t0, FESPI_REG_TXFIFO(a0)        // wait for FIFO clear
        bltz    t0, txrx_byte
        sw      a2, FESPI_REG_TXFIFO(a0)
1:      lw      a2, FESPI_REG_RXFIFO(a0)
        bltz    a2, 1b
        ret

set_dir:
        lw      t0, FESPI_REG_FMT(a0)
        li      t1, ~(FESPI_FMT_DIR(0xFFFFFFFF))
        and     t0, t0, t1
        lbu     t1, 0(a1)       // read value to OR in
        addi    a1, a1, 1
        or      t0, t0, t1
        sw      t0, FESPI_REG_FMT(a0)
        j       main

有沒有比較好懂一點,基本上他提供以下7種功能,分別用進入點的位置(command_table)表示:

  • 0x0 j main: 跳到main的位置,基本上這邊就負責讀取下一個Byte的資料,並判斷接下來要處理的動作,然後跳到對應處理的位置去
  • 0x4 ebreak: 當程式執行完畢後,最後會跳到這邊,然後使用ebreak,讓Target停下來,等待OpenOCD的後續處理!
  • 0x8 j tx: 就是剛剛呼叫的fespi_tx內容,後面接上1-bytes的長度,然後再接上至多256-bytes的資料!
  • 0xc j txwm_wait: 就是剛剛呼叫fespi_txwm_wait()的功能,負責讓程式停下來,直到SPI Controller的Tx FIFO可以寫入
  • 0x10 j write_reg: 這邊的Register指的是SPI Controller裡面的那些Registers!
  • 0x14 j wip_wait: 類似剛剛fespi_wip(),等待SPI Flash執行完畢
  • 0x18 j set_dir: 由於會頻繁用到,這邊將fespi_set_dir()內容獨立

可以把這個小程式當作是一個小小小的"Assembler"!
只要我們負責把對應的指令&資料設定好,寫入Target上某塊空間後,再呼叫這個小程式來執行對應的內容,和寫入對應的資料!

這邊給個小提示:
$a0 裡面的內容是SPI Controller的Base Address
$a1 裡面的內容是指向這個指令&資料目前執行到的位置
  

3.4.3 fespi_write實作

基本內容如下,請參考(src/flash/nor/fespi.c):

static int fespi_write(struct flash_bank *bank, const uint8_t *buffer,
        uint32_t offset, uint32_t count)
{
    struct target *target = bank->target;
    struct fespi_flash_bank *fespi_info = bank->driver_priv;
    uint32_t ctrl_base = fespi_info->ctrl_base;
    uint32_t cur_count, page_size, page_offset;
    int sector;
    int retval = ERROR_OK;

    LOG_DEBUG("%s: offset=0x%08" PRIx32 " count=0x%08" PRIx32,
            __func__, offset, count);

    if (target->state != TARGET_HALTED) {
        LOG_ERROR("Target not halted");
        return ERROR_TARGET_NOT_HALTED;
    }

    if (offset + count > fespi_info->dev->size_in_bytes) {
        LOG_WARNING("Write past end of flash. Extra data discarded.");
        count = fespi_info->dev->size_in_bytes - offset;
    }

    /* Check sector protection */
    for (sector = 0; sector < bank->num_sectors; sector++) {
        /* Start offset in or before this sector? */
        /* End offset in or behind this sector? */
        if ((offset <
                    (bank->sectors[sector].offset + bank->sectors[sector].size))
                && ((offset + count - 1) >= bank->sectors[sector].offset)
                && bank->sectors[sector].is_protected) {
            LOG_ERROR("Flash sector %d protected", sector);
            return ERROR_FAIL;
        }
    }

    struct working_area *algorithm_wa;
    if (target_alloc_working_area(target, sizeof(algorithm_bin),
                &algorithm_wa) != ERROR_OK) {
        LOG_WARNING("Couldn't allocate %zd-byte working area.",
                sizeof(algorithm_bin));
        algorithm_wa = NULL;
    } else {
        retval = target_write_buffer(target, algorithm_wa->address,
                sizeof(algorithm_bin), algorithm_bin);
        if (retval != ERROR_OK) {
            LOG_ERROR("Failed to write code to 0x%" TARGET_PRIxADDR ": %d",
                    algorithm_wa->address, retval);
            target_free_working_area(target, algorithm_wa);
            algorithm_wa = NULL;
        }
    }

    struct working_area *data_wa = NULL;
    unsigned data_wa_size = 2 * count;
    while (1) {
        if (data_wa_size < 128) {
            LOG_WARNING("Couldn't allocate data working area.");
            target_free_working_area(target, algorithm_wa);
            algorithm_wa = NULL;
        }
        if (target_alloc_working_area_try(target, data_wa_size, &data_wa) ==
                ERROR_OK) {
            break;
        }

        data_wa_size /= 2;
    }

    page_size = fespi_info->dev->pagesize;

    fespi_txwm_wait(bank);

    /* Disable Hardware accesses*/
    FESPI_DISABLE_HW_MODE();

    /* poll WIP */
    retval = fespi_wip(bank, FESPI_PROBE_TIMEOUT);
    if (retval != ERROR_OK)
        return retval;

    struct algorithm_steps *as = as_new(count / 4);

    /* unaligned buffer head */
    if (count > 0 && (offset & 3) != 0) {
        cur_count = 4 - (offset & 3);
        if (cur_count > count)
            cur_count = count;
        if (algorithm_wa)
            retval = steps_add_buffer_write(as, buffer, offset, cur_count);
        else
            retval = slow_fespi_write_buffer(bank, buffer, offset, cur_count);
        if (retval != ERROR_OK)
            goto err;
        offset += cur_count;
        buffer += cur_count;
        count -= cur_count;
    }

    page_offset = offset % page_size;
    /* central part, aligned words */
    while (count >= 4) {
        /* clip block at page boundary */
        if (page_offset + count > page_size)
            cur_count = page_size - page_offset;
        else
            cur_count = count & ~3;

        if (algorithm_wa)
            retval = steps_add_buffer_write(as, buffer, offset, cur_count);
        else
            retval = slow_fespi_write_buffer(bank, buffer, offset, cur_count);
        if (retval != ERROR_OK)
            goto err;

        page_offset = 0;
        buffer += cur_count;
        offset += cur_count;
        count -= cur_count;
    }

    /* buffer tail */
    if (count > 0) {
        if (algorithm_wa)
            retval = steps_add_buffer_write(as, buffer, offset, count);
        else
            retval = slow_fespi_write_buffer(bank, buffer, offset, count);
        if (retval != ERROR_OK)
            goto err;
    }

    if (algorithm_wa)
        retval = steps_execute(as, bank, algorithm_wa, data_wa);

err:
    if (algorithm_wa) {
        target_free_working_area(target, data_wa);
        target_free_working_area(target, algorithm_wa);
    }

    as_delete(as);

    /* Switch to HW mode before return to prompt */
    FESPI_ENABLE_HW_MODE();
    return retval;
}

看到就昏倒了,不愧是大魔王!
基本上我們可以把流程簡化成以下內容:

  1. 寫入預先準備好的"小程式"
  2. 如果可以寫入成功,表示Target端應該支援Target Burner
  3. 準備好要寫入的資料,一次一個Page為單位
  4. 將準備好的資料轉成"小程式"可以執行的"程式碼"
  5. 寫入準備好的"程式碼"
  6. 執行小程式,並等待結果!

底下開始一一剖析,首先是1,寫入預先準備好的"小程式":

    struct working_area *algorithm_wa;
    if (target_alloc_working_area(target, sizeof(algorithm_bin),
                &algorithm_wa) != ERROR_OK) {
        LOG_WARNING("Couldn't allocate %zd-byte working area.",
                sizeof(algorithm_bin));
        algorithm_wa = NULL;
    } else {
        retval = target_write_buffer(target, algorithm_wa->address,
                sizeof(algorithm_bin), algorithm_bin);
        if (retval != ERROR_OK) {
            LOG_ERROR("Failed to write code to 0x%" TARGET_PRIxADDR ": %d",
                    algorithm_wa->address, retval);
            target_free_working_area(target, algorithm_wa);
            algorithm_wa = NULL;
        }
    }

基本上就是利用OpenOCD提供的target_alloc_working_area()!
還記得在「Day 22: [Lab] 深入淺出 RISC-V 源碼剖析 (Final) - 執行 & Log分析」的Config中有以下這段嗎!?

$_TARGETNAME configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1

這行就是定義Target端上面,有一個小小的空間在0x80000000的位置,大小為10000Bytes
然後使用前記得先Backup!

接下來要預留我們小小程式碼放置的位置:

    struct working_area *data_wa = NULL;
    unsigned data_wa_size = 2 * count;
    while (1) {
        if (data_wa_size < 128) {
            LOG_WARNING("Couldn't allocate data working area.");
            target_free_working_area(target, algorithm_wa);
            algorithm_wa = NULL;
        }
        if (target_alloc_working_area_try(target, data_wa_size, &data_wa) ==
                ERROR_OK) {
            break;
        }

        data_wa_size /= 2;
    }

由於我們會安插一些小程式所需執行的指令、長度等等資料在其中,所以這邊準備"2x寫入資料"大小的空間!

在過來是3,準備好要寫入的資料,一次一個Page為單位:

    struct algorithm_steps *as = as_new(count / 4);

    page_offset = offset % page_size;
    /* central part, aligned words */
    while (count >= 4) {
        /* clip block at page boundary */
        if (page_offset + count > page_size)
            cur_count = page_size - page_offset;
        else
            cur_count = count & ~3;

        if (algorithm_wa)
            retval = steps_add_buffer_write(as, buffer, offset, cur_count);
        else
            retval = slow_fespi_write_buffer(bank, buffer, offset, cur_count);
        if (retval != ERROR_OK)
            goto err;

        page_offset = 0;
        buffer += cur_count;
        offset += cur_count;
        count -= cur_count;
    }

這邊一次以一個Page為單位處理,如果發現剛剛的"小程式"沒有寫入成功,那就用傳統"slow_fespi_write_buffer()"的方式來處理,有興趣的讀者自行觀賞XD!

讓我們看看steps_add_buffer_write()的內容,請參考(src/flash/nor/fespi.c):

/* This should write something less than or equal to a page.*/
static int steps_add_buffer_write(struct algorithm_steps *as,
        const uint8_t *buffer, uint32_t chip_offset, uint32_t len)
{
    as_add_tx1(as, SPIFLASH_WRITE_ENABLE);
    as_add_txwm_wait(as);
    as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD);

    uint8_t setup[] = {
        SPIFLASH_PAGE_PROGRAM,
        chip_offset >> 16,
        chip_offset >> 8,
        chip_offset,
    };
    as_add_tx(as, sizeof(setup), setup);

    as_add_tx(as, len, buffer);
    as_add_txwm_wait(as);
    as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO);

    /* fespi_wip() */
    as_add_set_dir(as, FESPI_DIR_RX);
    as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD);
    as_add_wip_wait(as);
    as_add_write_reg(as, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO);
    as_add_set_dir(as, FESPI_DIR_TX);

    return ERROR_OK;
}

這邊就是負責準備我們這個小程式所需要執行的"程式碼",基本上可以簡化成以下內容:

  • step[0] = {STEP_TX(0x8), 0x1, SPIFLASH_WRITE_ENABLE}
  • step[1] = {STEP_TXWM_WAIT(0xc)}
  • step[2] = {STEP_WRITE_REG(0x16), FESPI_REG_CSMODE, FESPI_CSMODE_HOLD}
  • step[3] = {STEP_TX(0x8), 0x4, setup的內容,共4-bytes} ==> 執行Flash的PP指令,並帶入位置,準備寫入資料
  • step[4] = {STEP_TX(0x8), 255, 255-bytes的資料}
  • step[5] = {STEP_TX(0x8), 1, 最後一筆資料}
  • step[6] = {STEP_TXWM_WAIT(0xc)}
  • ....

就是前面接剛剛command_table所支援的指令(那些指令恰巧是小程式中,對應處理函式的進入點),
後面再帶要處理資料得長度、資料等等東西!

準備完畢後,就進入4,將準備好的資料轉成"小程式"可以執行的"程式碼",
請參考(src/flash/nor/fespi.c):

/* Return size of compiled program. */
static unsigned as_compile(struct algorithm_steps *as, uint8_t *target,
        unsigned target_size)
{
    unsigned offset = 0;
    bool finish_early = false;
    for (unsigned s = 0; s < as->used && !finish_early; s++) {
        unsigned bytes_left = target_size - offset;
        switch (as->steps[s][0]) {
            case STEP_NOP:
                break;
            case STEP_TX:
                {
                    unsigned size = as->steps[s][1];
                    if (size + 3 > bytes_left) {
                        finish_early = true;
                        break;
                    }
                    memcpy(target + offset, as->steps[s], size + 2);
                    offset += size + 2;
                    break;
                }
            case STEP_WRITE_REG:
                if (4 > bytes_left) {
                    finish_early = true;
                    break;
                }
                memcpy(target + offset, as->steps[s], 3);
                offset += 3;
                break;
            case STEP_SET_DIR:
                if (3 > bytes_left) {
                    finish_early = true;
                    break;
                }
                memcpy(target + offset, as->steps[s], 2);
                offset += 2;
                break;
            case STEP_TXWM_WAIT:
            case STEP_WIP_WAIT:
                if (2 > bytes_left) {
                    finish_early = true;
                    break;
                }
                memcpy(target + offset, as->steps[s], 1);
                offset += 1;
                break;
            default:
                assert(0);
        }
        if (!finish_early)
            as->steps[s][0] = STEP_NOP;
    }
    assert(offset + 1 <= target_size);
    target[offset++] = STEP_EXIT;

    LOG_DEBUG("%d-byte program:", offset);
    for (unsigned i = 0; i < offset;) {
        char buf[80];
        for (unsigned x = 0; i < offset && x < 16; x++, i++)
            sprintf(buf + x*3, "%02x ", target[i]);
        LOG_DEBUG("%s", buf);
    }

    return offset;
}

基本上這邊東西比較簡單,就是把我們剛剛準備好的那堆"step[]"轉成一個一維的陣列,
變成類似下面這個東西:

Debug: 30254 14838 fespi.c:692 as_compile(): 5511-byte program:
Debug: 30255 14838 fespi.c:697 as_compile(): 08 01 06 0c 10 18 02 08 04 02 40 00 00 08 ff 97
Debug: 30256 14838 fespi.c:697 as_compile(): 11 c0 3f 93 81 01 81 17 41 c0 3f 13 01 81 ff 17
Debug: 30257 14838 fespi.c:697 as_compile(): 15 00 00 13 05 85 29 97 05 c0 3f 93 85 85 fe 17
Debug: 30258 14838 fespi.c:697 as_compile(): 06 c0 3f 13 06 06 04 63 fa c5 00 83 22 05 00 23
Debug: 30259 14838 fespi.c:697 as_compile(): a0 55 00 11 05 91 05 e3 ea c5 fe 17 05 c0 3f 13
Debug: 30260 14839 fespi.c:697 as_compile(): 05 45 02 97 05 c0 3f 93 85 45 7a 63 77 b5 00 23
Debug: 30261 14839 fespi.c:697 as_compile(): 20 05 00 11 05 e3 6d b5 fe 17 05 00 00 13 05 25
Debug: 30262 14839 fespi.c:697 as_compile(): 6f ef 00 20 6a ef 00 20 72 97 00 00 00 41 11 06
Debug: 30263 14839 fespi.c:697 as_compile(): e4 01 45 81 45 ef 00 a0 00 6f 00 e0 69 01 a0 41
Debug: 30264 14839 fespi.c:697 as_compile(): 11 22 e0 37 24 00 20 18 44 06 e4 13 77 f7 f8 18
Debug: 30265 14839 fespi.c:697 as_compile(): c4 18 48 9d 46 13 77 f7 f8 18 c8 58 40 71 46 13
Debug: 30266 14839 fespi.c:697 as_compile(): 67 07 07 58 c0 58 40 b7 05 00 0c 61 9b 58 c0 18
Debug: 30267 14839 fespi.c:697 as_compile(): 44 17 05 c0 3f 13 05 e5 71 13 67 77 00 18 c4 58
Debug: 30268 14839 fespi.c:697 as_compile(): 44 13 67 47 00 58 c4 58 44 71 9b 58 c4 18 44 13
Debug: 30269 14839 fespi.c:697 as_compile(): 67 07 10 23 24 e4 00 ef 00 00 2e ef 00 80 1a 93
Debug: 30270 14839 fespi.c:697 as_compile(): 07 c4 00 13 07 00 10 2f a0 e7 20 f5 bf 00 00 11
Debug: 30271 14839 fespi.c:697 as_compile(): 71 06 e4 0a e8 0e ec 12 f0 16 f4 1a f8 1e 08 01
Debug: 30272 14839 fespi.c:697 as_compile(): fc 0c 10 18 00 18 00 10 18 02 14 10 18 00 18 08
Debug: 30273 14839 fespi.c:697 as_compile(): 08 01 06 0c 10 18 02 08 04 02 40 01 00 08 ff a2
Debug: 30274 14839 fespi.c:697 as_compile(): e0 a6 e4 aa e8 ae ec b2 f0 b6 f4 ba f8 be fc 42
Debug: 30275 14839 fespi.c:697 as_compile(): e1 46 e5 4a e9 4e ed 52 f1 56 f5 5a f9 5e fd e2
Debug: 30276 14839 fespi.c:697 as_compile(): e1 e6 e5 ea e9 ee ed f2 f1 f6 f5 fa f9 fe fd 73
Debug: 30277 14839 fespi.c:697 as_compile(): 25 20 34 f3 25 10 34 0a 86 ef 00 e0 3b 73 10 15
Debug: 30278 14839 fespi.c:697 as_compile(): 34 b7 22 00 00 9b 82 02 80 73 a0 02 30 a2 60 42
Debug: 30279 14839 fespi.c:697 as_compile(): 61 e2 61 02 72 a2 72 42 73 e2 73 06 64 a6 64 46
Debug: 30280 14839 fespi.c:697 as_compile(): 65 e6 65 06 76 a6 76 46 77 e6 77 0a 68 aa 68 4a
Debug: 30281 14839 fespi.c:697 as_compile(): 69 ea 69 0a 7a aa 7a 4a 7b ea 7b 0e 6c ae 6c 4e
Debug: 30282 14839 fespi.c:697 as_compile(): 6d ee 6d 0e 7e ae 7e 4e 7f ee 7f 11 61 73 00 20
Debug: 30283 14839 fespi.c:697 as_compile(): 30 01 a0 00 00 00 00 67 80 00 00 b7 27 00 20 d8

.......後面很多

這個一維的陣列就是我們小程式,所需要執行的程式碼XDD,
可以參考下圖:

https://ithelp.ithome.com.tw/upload/images/20180115/20107327h0V5LyCSlx.jpg

最後是5,寫入準備好的"程式碼":

        int retval = target_write_buffer(target, data_wa->address, bytes,
                data_buf);
        if (retval != ERROR_OK) {
            LOG_ERROR("Failed to write data to 0x%" TARGET_PRIxADDR ": %d",
                    data_wa->address, retval);
            return retval;
        }

然後是6,執行小程式,並等待結果:

        retval = target_run_algorithm(target, 0, NULL, 2, reg_params,
                algorithm_wa->address, algorithm_wa->address + 4,
                10000, NULL);
        if (retval != ERROR_OK) {
            LOG_ERROR("Failed to execute algorithm at 0x%" TARGET_PRIxADDR ": %d",
                    algorithm_wa->address, retval);
            return retval;
        }

注意這邊,程式最後應該會停在小程式"0x4 ebreak"的位置,等待OpenOCD做進一步的處理!

  
  
  

99. 結語

怎麼樣啊!! 是不是很有趣啊!!! 再次把程式碼塞好塞滿
不過要特別提醒一下,不同廠牌的SPI NOR Flash有可能會有不同的Command,
在使用上的時候,務必小心!

根據測試的結果,約莫有10x ~ 30x速度的進步!
當然使用這套方法的前提是Target端必須要有一個可以跑小小程式的地方(可讀可寫可執行)!

在鐵人賽最後的兩篇中,我們將探討更上層的GDB,看看GDB和OpenOCD之前的關係,
以及簡單的介紹GDB Remote Protocol!
  
  
  

參考資料

  1. GitHub: riscv/riscv-openocd
  2. N25Q128 with Subsector Erase on Boot Sectors
  3. Freedom E300 Platform Reference Manual
  4. Day 22: [Lab] 深入淺出 RISC-V 源碼剖析 (Final) - 執行 & Log分析

上一篇
Day 27: 高手不輕易透露的技巧(1/2) - Flash Programming
下一篇
Day 29: 深藏不露的GDB - Remote Serial Protocol的秘密
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
pine_stone
iT邦新手 5 級 ‧ 2021-05-09 01:46:20

https://ithelp.ithome.com.tw/upload/images/20210509/20137330S1L6awV0jK.jpg
这段spi flash 的code 在被烧录到flash 之前,是存放在 riscv 芯片内部的哪个地址空间? (0x8000_0000, 0x9000_0000, 0x4000_0000 ?) 如何选择地址空间呢?
https://ithelp.ithome.com.tw/upload/images/20210509/20137330JRC0LYX4y3.jpg
这段是spi flash 的烧写 程序, 它被存放在riscv 芯片中的哪个地址空间呢? (0x8000_0000, 0x9000_0000, 0x4000_0000 ?) 如何选择地址空间呢?

看更多先前的回應...收起先前的回應...

0x8000_0000

由OpenOCD的config決定的,可以參考這一行

$_TARGETNAME configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1

對應E51的DTIM區

你好, 首先感谢你的回答。
目前,我正在设计一个自己的risc-v的cpu, 基本的功能都是可以跑通的, jtag 也可以正常工作。目前想使用jtag 下载软件 程序到spi flash中去。 spi flash 的controller 也是写好了,能够正常的工作(我自己写的软件driver 可以正常的读写spi flash)使用的也是sifive 公司的 spi controller 架构(register 地址 和sifive 定义的相同)。

当我编写一段软件 app (比如 hello world) 可以使用
$_TARGETNAME configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1
正常的下载到芯片内部的程序存储空间(dtim)中, 并且通过jtag 接口看到相应的 assemble code. 地址确实是0x8000_0000 开始的,运行也没有问题。

我目前的问题是:
1)
.global _start
_start:
command_table:
j main // 0
ebreak // 4
j tx // 8
j txwm_wait // 12
j write_reg // 16
j wip_wait // 20
j set_dir // 24

// Execute the program.
main:
lbu t0, 0(a1)
addi a1, a1, 1
la t1, command_table
add t0, t0, t1
jr t0

这段代码被存放在dtim 的什么位置了, 怎么没有找到? (是否需要在软件中的lds 文件 或者是 startup.s 文件中需要相关的设置 才可以导入这段代码到 dtim 中 ?
2) -work-area-size 10000 是存放问题1 中的代码吗? 地址是 0x8000_0000 -- 0x8000_0000 + 10000 这个位置吗。
3) 最后软件生成的hello.elf (举例) 存放在什么位置呢? 是芯片内部的data memory 中吗, 地址又是怎么确定的呢。 在jtag 的cfg 文件中,没有找到相关的设定。

谢谢了

(1)(2)这段代码被存放在dtim 的什么位置了, 怎么没有找到?

這段代碼會放在"$TARGETNAME configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1"中指定的位置,也就是0x80000000 -- 0x8000000 + 10000這個區間沒錯,不過只有在需要時(也就是burn flash)才會被加載到那個區間中,當使用完畢後,OpenOCD會將原本區間中的資料還原回去!

(3) Elf(也就是你的程序)其存放位置由lds文件決定,至於放置的位置,有些是整個加載到DRAM,有些是將指令加載到ILM,另外將資料(變數...等等)放到DLM中,端看設計/需求而定

你好,感谢你的回答。
问题1,2 我听明白了, (需要实践一下, 看看是否工作)
问题3 可能是我没有说清楚.
https://ithelp.ithome.com.tw/upload/images/20210512/20137330orVj4K1hty.jpg
上图中所说的code (这个code ,我想应该是包括 elf 的 所有内容, 同时又增加了写入flash 的格式) 存放在什么位置呢?需要修改lds 文件吗?

下面code 是我目前的lds 文件:
OUTPUT_FORMAT( "elf32-littleriscv" )
OUTPUT_ARCH( "riscv" )
OUTPUT( "asm_temp.bin" )
/* STARTUP( "startup.o" ) */

ENTRY(_start)

_STACK_SIZE = 0x800;
_HEAP_SIZE = 0x1000;

MEMORY
{
rom (rx) : ORIGIN = 0x80000000, LENGTH = 32K
ram (wx!r) : ORIGIN = 0x90000000, LENGTH = 16K
}

SECTIONS
{
/* ================================= . = 0x80000000; ===================================== /
.init :
{
KEEP (
(SORT_NONE(.init)))
} >rom AT>rom

.text  :
{ 
    *(.text .text.*)
} >rom AT>rom 

.rodata : 
{
    *(.srodata .srodata.*)
    *(.rodata .rodata.*)
    *(.rdata .rdata.*)
} >rom AT>rom 

/* save at rom , will be use to load to 0x9000_00000 .data .sdata etc*/
.lalign         :
{
. = ALIGN(8);
PROVIDE( _data_lma = . );
} >rom AT>rom     

/* ================================= . = 0x90000000; ===================================== */

/* start address of .data .sdata ,  data come from rom address */
.dalign         :
{
	. = ALIGN(8);
PROVIDE( _data_start = . );
} >ram AT>rom 

.data : 
{ 
    *(.sdata .sdata.*)

    . = ALIGN(8);
    *(.data .data.*)
} >ram AT>rom

. = ALIGN(8);
PROVIDE( _data_end = . );

.bss : 
{ 
. = ALIGN(8);
    PROVIDE( __bss_start = . );
    *(.sbss*)
    *(.bss .bss.*)
    *(COMMON)
    . = ALIGN(4);

. = ALIGN(8);
    __bss_end = .;
    PROVIDE( __bss_end = . );
} >ram AT>ram 

. = ALIGN(8);
PROVIDE( _end = . );
PROVIDE( end = . );

.heap ORIGIN(ram) + LENGTH(ram) - _STACK_SIZE - _HEAP_SIZE :
{ 
    _heap_begin = .;
    __heap_start = .;
    PROVIDE( __heap_start = . );
    . = . + _HEAP_SIZE;
    . = ALIGN(16);
    __heap_end = .;
    PROVIDE( __heap_end = . );

} >ram AT>ram 


.stack ORIGIN(ram) + LENGTH(ram) - _STACK_SIZE :
{
    . = _STACK_SIZE;
    PROVIDE( _sp = . );
} >ram AT>ram 

}

上图中所说的code (这个code ,我想应该是包括 elf 的 所有内容, 同时又增加了写入flash 的格式) 存放在什么位置呢?

algorithm_bin會在需要時被加載到0x80000000中

需要修改lds 文件吗?

不需要

我要留言

立即登入留言