コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2021年7月6日 >>> 2021年7月6日
link もっと後

2021年7月6日

OpenOCDとHiFive UnleashedのSPI Flashその2 - OpenOCDのSPI Flashドライバの仕組み(書き込み編、通常版)

目次: OpenOCD

SPI Flashにどのように書き込みするか見ていきます。OpenOCDのSPI Flash書き込みは大きく2つの方法に分けられます。ヘルパープログラムを使う方法と、SPIを直接制御する方法です。順に紹介します。

ヘルパープログラム

後述しますがJTAGからSPIインタフェースを制御すればSPI Flashの書き換えは可能です。しかし速度が出ません。そのためOpenOCDはヘルパープログラムを使った高速な書き込みを実装しています。

SiFive SoC向けのSPI Flashインタフェースのコードを見ると、下記のような謎の #includeが出てきます。参照先のファイルにはバイナリが羅列されています。

ヘルパープログラムのバイナリを保持する配列の定義

// openocd/src/flash/nor/fespi.c

static const uint8_t algorithm_bin[] = {
#include "../../../contrib/loaders/flash/fespi/fespi.inc"
};


// openocd/contrib/loaders/flash/fespi/fespi.inc

/* Autogenerated with ../../../../src/helper/bin2char.sh */
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,

...

このファイルの元となるコード(アセンブラです)は下記のディレクトリにあります。RISC-V向けのクロスコンパイラを用意して、このディレクトリでmakeを実行するとfespi.incを自分で作成することも可能です。

ヘルパープログラムのソースコードの位置
$ ls openocd/contrib/loaders/flash/fespi/

Makefile  fespi.S  fespi.inc

Makefileではfespi.Sをコンパイルしてバイナリをfespi.incに変換します。バイナリからfespi.incへの変換にはsrc/helper/bin2char.shというヘルパースクリプトを使います。

SPI Flashへの書き込み

前置きが長くなりましたが、フラッシュの書き込みルーチンは下記のとおりです。

OpenOCDのSPI Flash書き込みドライバ(SiFive SPI用)

// openocd/src/flash/nor/fespi.c

static int fespi_write(struct flash_bank *bank, const uint8_t *buffer,
		uint32_t offset, uint32_t count)
{

...

	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);    //★ヘルパープログラム本体algorithm_binをターゲットのメモリに書く
		if (retval != ERROR_OK) {
			LOG_ERROR("Failed to write code to " TARGET_ADDR_FMT ": %d",
					algorithm_wa->address, retval);
			target_free_working_area(target, algorithm_wa);
			algorithm_wa = NULL;
		}
	}

...

	page_offset = offset % page_size;
	/* central part, aligned words */
	while (count > 0) {
		/* clip block at page boundary */
		if (page_offset + count > page_size)
			cur_count = page_size - page_offset;
		else
			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);    //★JTAGからSPIを直接操作して書き込むモード(遅い)
		if (retval != ERROR_OK)
			goto err;

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

	//★ヘルパープログラムに書き込むデータを全部渡し、フラッシュに書き込みしてもらう
	if (algorithm_wa)
		retval = steps_execute(as, bank, algorithm_wa, data_wa);

...


static int steps_execute(struct algorithm_steps *as,
		struct flash_bank *bank, struct working_area *algorithm_wa,
		struct working_area *data_wa)
{

...

	while (!as_empty(as)) {
		keep_alive();
		uint8_t *data_buf = malloc(data_wa->size);
		unsigned bytes = as_compile(as, data_buf, data_wa->size);
		retval = target_write_buffer(target, data_wa->address, bytes,
				data_buf);
		free(data_buf);
		if (retval != ERROR_OK) {
			LOG_ERROR("Failed to write data to " TARGET_ADDR_FMT ": %d",
					data_wa->address, retval);
			goto exit;
		}

		retval = target_run_algorithm(target, 0, NULL, 2, reg_params,
				algorithm_wa->address, algorithm_wa->address + 4,
				10000, NULL);    //★ヘルパープログラムでflash write

...


// openocd/src/target/target.c

int target_run_algorithm(struct target *target,
		int num_mem_params, struct mem_param *mem_params,
		int num_reg_params, struct reg_param *reg_param,
		uint32_t entry_point, uint32_t exit_point,
		int timeout_ms, void *arch_info)
{
	int retval = ERROR_FAIL;

	if (!target_was_examined(target)) {
		LOG_ERROR("Target not examined yet");
		goto done;
	}
	if (!target->type->run_algorithm) {
		LOG_ERROR("Target type '%s' does not support %s",
				target_type_name(target), __func__);
		goto done;
	}

	target->running_alg = true;
	retval = target->type->run_algorithm(target,
			num_mem_params, mem_params,
			num_reg_params, reg_param,
			entry_point, exit_point, timeout_ms, arch_info);    //★riscv_run_algorithmへ


// openocd/src/target/riscv/riscv.c

/* Algorithm must end with a software breakpoint instruction. */
static int riscv_run_algorithm(struct target *target, int num_mem_params,
		struct mem_param *mem_params, int num_reg_params,
		struct reg_param *reg_params, target_addr_t entry_point,
		target_addr_t exit_point, int timeout_ms, void *arch_info)
{

...

	/* Save registers */
	struct reg *reg_pc = register_get_by_name(target->reg_cache, "pc", true);
	if (!reg_pc || reg_pc->type->get(reg_pc) != ERROR_OK)
		return ERROR_FAIL;
	uint64_t saved_pc = buf_get_u64(reg_pc->value, 0, reg_pc->size);    //★復帰するときに使う
	LOG_DEBUG("saved_pc=0x%" PRIx64, saved_pc);

...

	/* Run algorithm */
	LOG_DEBUG("resume at 0x%" TARGET_PRIxADDR, entry_point);
	if (riscv_resume(target, 0, entry_point, 0, 0, true) != ERROR_OK)    //★ヘルパープログラムを起動する
		return ERROR_FAIL;

...

かなり複雑で全部説明するのは難しいですが、処理の大まかな流れとしては下記のようになっています。

  • ヘルパープログラムをターゲットのメモリに書く
  • ヘルパープログラムに渡す制御データを作成する(SPIの制御コマンド+書き込むデータ列で構成されています)
  • ヘルパープログラムの後ろに制御データを書く
  • ヘルパープログラムを実行する

あとはデータが尽きるまで3つ目と4つ目の処理を実行し続けます。関数でいうとsteps_execute() です。1回で書き込む量は「ページ」のサイズに依存します。ページサイズはSPI Flashデバイスによって違いますが、128バイトか256バイトが多いです。

OpenOCDが対応しているSPI Flashは下記のファイルにまとまっていて、

SPI Flashのパラメータを保持している配列の定義

// openocd/src/flash/nor/spi.c

const struct flash_device flash_devices[] = {
	/* name, read_cmd, qread_cmd, pprog_cmd, erase_cmd, chip_erase_cmd, device_id,
	 * pagesize, sectorsize, size_in_bytes */
	FLASH_ID("st m25p05",           0x03, 0x00, 0x02, 0xd8, 0xc7, 0x00102020, 0x80,  0x8000,  0x10000),
	FLASH_ID("st m25p10",           0x03, 0x00, 0x02, 0xd8, 0xc7, 0x00112020, 0x80,  0x8000,  0x20000),
	FLASH_ID("st m25p20",           0x03, 0x00, 0x02, 0xd8, 0xc7, 0x00122020, 0x100, 0x10000, 0x40000),

...

	FLASH_ID("issi is25lp128d",     0x03, 0xeb, 0x02, 0xd8, 0xc7, 0x0018609d, 0x100, 0x10000, 0x1000000),
	FLASH_ID("issi is25wp128d",     0x03, 0xeb, 0x02, 0xd8, 0xc7, 0x0018709d, 0x100, 0x10000, 0x1000000),
	FLASH_ID("issi is25lp256d",     0x13, 0xec, 0x12, 0xdc, 0xc7, 0x0019609d, 0x100, 0x10000, 0x2000000),
	FLASH_ID("issi is25wp256d",     0x13, 0xec, 0x12, 0xdc, 0xc7, 0x0019709d, 0x100, 0x10000, 0x2000000),

...

例えばHiFive Unleashedに搭載されているSPI FlashはISSI is25wp256dですから、ページサイズは0x100 = 256バイトとわかります。

この情報だけでは不安ならデータシートの8.10 PAGE PROGRAM OPERATION (PP, 02h or 4PP, 12h) を見ていただくと確実です。"The Page Program (PP/4PP) instruction allows up to 256 bytes data ..." とあります。

SPI Flashの書き込みにはもっと簡単な(ただし遅い)方法もあります。今回の処理は実用的な反面、書き込みとは直接関係ない機構(ヘルパープログラム)が登場してやや複雑でした。じっくり見るのは面倒だなと思った方は、次回の簡易版を見ていただくほうがわかりやすいかもしれません。

続きはまた今度。

編集者:すずき(2023/09/24 09:17)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2021年7月6日 >>> 2021年7月6日
link もっと後

管理用メニュー

link 記事を新規作成

<2021>
<<<07>>>
----123
45678910
11121314151617
18192021222324
25262728293031

最近のコメント5件

  • link 24年4月22日
    hdkさん (04/24 08:36)
    「うちのHHFZ4310は15年突破しまし...」
  • link 24年4月22日
    すずきさん (04/24 00:37)
    「ちゃんと数えてないですけど蛍光管が10年...」
  • link 24年4月22日
    hdkさん (04/23 20:52)
    「おお... うちのHHFZ4310より後...」
  • link 20年6月19日
    すずきさん (04/06 22:54)
    「ディレクトリを予め作成しておけば良いです...」
  • link 20年6月19日
    斎藤さん (04/06 16:25)
    「「Preferencesというメニューか...」

最近の記事3件

  • link 24年4月25日
    すずき (04/29 10:08)
    「[AVIFの変換] AVIFが読めないアプリケーションがたまにあるので、AVIF(AV1 Image File Format)...」
  • link 24年2月7日
    すずき (04/24 02:52)
    「[複数の音声ファイルのラウドネスを統一したい] PCやデジタル音楽プレーヤーで音楽を聞いていると、曲によって音量の大小が激しく...」
  • link 24年4月22日
    すずき (04/23 20:13)
    「[仕事部屋の照明が壊れた] いきなり仕事部屋のシーリングライトが消えました。蛍光管の寿命にしては去年(2022年10月19日の...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 04/29 10:08