コグノスケ


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

link もっと前
2021年7月6日 >>> 2021年8月5日
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 この記事にコメントする



2021年7月7日

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

目次: OpenOCD

フラッシュの書き込みは、ヘルパープログラムを使うタイプ(上記で追いかけた方)と、SPIを直接制御する方があります。高速に書き込めるのはヘルパープログラムを使うタイプですが、わかりやすいのはSPIを直接制御するタイプです。そちらも見てみましょう。

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(bank, 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);

...

ここまでは前回と同じです。前回はsteps_add_buffer_write() とsteps_execute() が活躍しましたが、今回はslow_fespi_write_buffer() の方を見ます。

JTAGからSPIを直接操作して書き込むモード(簡単、遅い)

static int slow_fespi_write_buffer(struct flash_bank *bank,
		const uint8_t *buffer, uint32_t offset, uint32_t len)
{
	uint32_t ii;

	if (offset & 0xFF000000) {
		LOG_ERROR("FESPI interface does not support greater than 3B addressing, can't write to offset 0x%" PRIx32,
				offset);
		return ERROR_FAIL;
	}

	/* TODO!!! assert that len < page size */

	fespi_tx(bank, SPIFLASH_WRITE_ENABLE);
	fespi_txwm_wait(bank);

	if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD) != ERROR_OK)    //★CE# 信号をLowにする(負論理、処理の開始を示す)
		return ERROR_FAIL;

	fespi_tx(bank, SPIFLASH_PAGE_PROGRAM);    //★PPコマンド = 0x02

	fespi_tx(bank, offset >> 16);    //★アドレス
	fespi_tx(bank, offset >> 8);
	fespi_tx(bank, offset);

	for (ii = 0; ii < len; ii++)    //★データ
		fespi_tx(bank, buffer[ii]);

	fespi_txwm_wait(bank);    //★送信し終わるまで待つ

	if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO) != ERROR_OK)    //★CE# 信号をHighにする(負論理、処理の終了を示す)
		return ERROR_FAIL;

	keep_alive();

	return ERROR_OK;
}


// openocd/src/flash/nor/spi.h

/* SPI Flash Commands */
#define SPIFLASH_READ_ID		0x9F /* Read Flash Identification */
#define SPIFLASH_READ_MID		0xAF /* Read Flash Identification, multi-io */
#define SPIFLASH_READ_STATUS	0x05 /* Read Status Register */
#define SPIFLASH_WRITE_ENABLE	0x06 /* Write Enable */
#define SPIFLASH_PAGE_PROGRAM	0x02 /* Page Program */    //★コマンドはこれ

...

コマンド、データ、待機、たったこれだけです。前回はヘルパープログラムの制御などが出てきて複雑でしたが、実はSPI Flashの制御だけ見ると非常にシンプルです。

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

コメント一覧

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



2021年7月8日

OpenOCDとHiFive UnleashedのSPI Flashその4 - OpenOCDのSPI Flashドライバの仕組み(消去編)

目次: OpenOCD

いよいよ、正常に動かないSPI Flashの消去を見ます。消去は書き込みと違い大量にデータを書く必要がなく、ヘルパープログラムを使った実装はありません。前回紹介したJTAGでSPIを直接制御する方法(簡易版)と似た処理です。

消去の方法

SPI Flashの消去には3種類あります。ブロック消去に対応していないデバイスもあるようです。ややこしいことに統一された名前がないらしくて、メーカーによっては大きな消去単位をセクタと呼んでいます(Micronがそうだった)。なんじゃそりゃ、訳がわからんから統一してくれ〜。

  • 全消去
  • ブロック(セクタ)消去: いくつかのセクタをまとめたブロック単位で消去します、64KBか32KBです
  • セクタ(サブセクタ)消去: セクタ単位で消去します、デバイスによりますが4KB程度です

細かい名前は覚えても意味がない(メーカーによって違うのでどうでも良い)ので、全部消す、大サイズで消す、小サイズで消す、くらいの理解で十分だと思います。

消去の処理

前置きはこれくらいにして、消去処理のコードを見ましょう。

消去処理

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

static int fespi_erase_sector(struct flash_bank *bank, int sector)
{
	struct fespi_flash_bank *fespi_info = bank->driver_priv;
	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;

	if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_HOLD) != ERROR_OK)    //★CE# 信号をLowにする(負論理、処理の開始を示す)
		return ERROR_FAIL;
	retval = fespi_tx(bank, fespi_info->dev->erase_cmd);    //★ブロック64K消去コマンド
	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;
	if (fespi_write_reg(bank, FESPI_REG_CSMODE, FESPI_CSMODE_AUTO) != ERROR_OK)    //★CE# 信号をHighにする(負論理、処理の終了を示す)
		return ERROR_FAIL;

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

	return ERROR_OK;
}

ページ書き込みのときはコマンドが決め打ちでしたが、セクター消去ではコマンドが変数になっています。この変数が持つ値は、前回も紹介した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),

...

	//★HiFive1が搭載しているFlash
	FLASH_ID("issi is25lp032",      0x03, 0x00, 0x02, 0xd8, 0xc7, 0x0016609d, 0x100, 0x10000, 0x400000),
	FLASH_ID("issi is25lp064",      0x03, 0x00, 0x02, 0xd8, 0xc7, 0x0017609d, 0x100, 0x10000, 0x800000),
	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),
	//★HiFive Unleashedが搭載しているFlash
	FLASH_ID("issi is25wp256d",     0x13, 0xec, 0x12, 0xdc, 0xc7, 0x0019709d, 0x100, 0x10000, 0x2000000),

...

HiFive Unleashedが搭載しているSPI Flashの品番はISSI IS25WP256Dですから、erase_cmdは0xdcです。SPI Flashのデータシートを見ると0xdcはBLOCK ERASE OPERATIONの4BER64Kコマンドです。

コマンドとアドレス指定

コードを見たときにお気づきかもしれませんが、SPI Flashの書き込みや消去で渡しているアドレスは3バイトしかなく、最大で16MBまでしか扱えません。一方HiFive Unleashedが搭載しているSPI Flashの容量は256Mbit = 32MBです。

足りないですね?どうやって16MB以降の消去や書き込みを行うのでしょうか?方法は3つあるようです。

  • バンク切替: アドレスの始点を変えて3バイトアドレスコマンドを使う
  • モード切替: 3バイトアドレスコマンドに4バイトアドレスを渡せる
  • 別コマンド: 4バイトアドレスコマンドを使う

OpenOCDは3番目を期待しているようです。なぜかというと4BER64Kコマンドは4バイトアドレスを期待するコマンドだからです。


4BER64Kコマンド仕様

データシートから抜粋したコマンドの仕様は上記の通りです。アドレスを4バイト送ってくることを期待しています。しかし実装は3バイトしかアドレスを送っていません。うーん、何かおかしいですね?

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

コメント一覧

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



2021年7月9日

OpenOCDとHiFive UnleashedのSPI Flashその5 - UnleashedとOpenOCD消去の謎を追う

目次: OpenOCD

これまでに書き込み、消去の仕組みを調べました。その過程でどうしてHiFive UnleashedでSPI Flashの消去が正常に動作しないのかわかりました。アドレスの指定が間違っているからです。

SPI Flashの消去処理

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

static int fespi_erase_sector(struct flash_bank *bank, int sector)
{

...

	retval = fespi_tx(bank, fespi_info->dev->erase_cmd);    //★ブロック64K消去コマンド(4バイトアドレスを期待)
	if (retval != ERROR_OK)
		return retval;
	sector = bank->sectors[sector].offset;
	retval = fespi_tx(bank, sector >> 16);    //★アドレス(3バイトしか送っていない)
	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;

消去コマンドは0xdc(4BER64Kコマンド)で4バイトアドレスを期待していますが、今のOpenOCDの実装は3バイトアドレスを渡しています。これでは動作しません。最初に動作確認したログを覚えているでしょうか?改めて見直すと、

flash probe実行時の警告
$ riscv64-zephyr-elf-gdb

(gdb) set arch riscv:rv64

The target architecture is assumed to be riscv:rv64

(gdb) target remote :3333

Remote debugging using :3333

(gdb) monitor reset halt

JTAG tap: riscv.cpu tap/device found: 0x20000913 (mfg: 0x489 (SiFive Inc), part: 0x0000, ver: 0x2)

★★monitor xxxxはremote monitor(この場合OpenOCD)へのコマンドと解釈される

(gdb) monitor flash probe 0

Found flash device 'issi is25wp256d' (ID 0x0019709d)
device needs paging or 4-byte addresses - not implemented    ★★ん?なんだこれ
flash 'fespi' found at 0x20000000

OpenOCDが4バイトアドレスには対応していないよ、という警告を出していました。なるほど、やっと警告の意味がわかりました。

書き込みはどうして動くのか?

Unleashedの消去コマンドが動作しない理由はわかりましたが、まだいくつか不思議な点が残っています。

Unleashedで書き込みできるのはなぜ?
書き込みコマンドとして0x02(PPコマンド)を決め打ちしているためです。PPコマンドは3バイトアドレスを期待するので正常に動作します。ただし16MB以降には書き込みできないので、今の実装は十分とは言えません。
HiFive1が正常に書き換え、消去ができるのはなぜ?
搭載されているSPI Flashの型番が違うためです。HiFive1はISSI IS25LP032を搭載しています。SPI Flashのパラメータリストを見るとerase_cmd = 0xd8(BER64Kコマンド)になっていて、BER64Kコマンドは3バイトアドレスを期待するので正常に動作します。容量も小さいので3バイトアドレスで十分です。

比較しやすいようにSPI FlashのパラメータリストのうちHiFive1とHiFive Unleashedに関わるところだけ抜粋しておきます。

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),

...

	//★HiFive1が搭載しているFlash
	FLASH_ID("issi is25lp032",      0x03, 0x00, 0x02, 0xd8, 0xc7, 0x0016609d, 0x100, 0x10000, 0x400000),

...

	//★HiFive Unleashedが搭載しているFlash
	FLASH_ID("issi is25wp256d",     0x13, 0xec, 0x12, 0xdc, 0xc7, 0x0019709d, 0x100, 0x10000, 0x2000000),

...

結論としてはOpenOCDの実装を変えないとHiFive UnleashedのSPI Flashの書き込み、消去は正常に行えないことがわかりました。手順では回避不可能です。

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

コメント一覧

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




2021年8月2日

シンタックス・ハイライト

コードのキーワードハイライトに使っているhighlight.js(公式サイトへのリンク)を11.2.0にしました。導入したのは7年も前(2014年9月11日の日記参照)ですが、バージョンアップはスムーズにできました。

APIに変更があってinitHighlightingOnLoad() がdeprecatedになりました。代わりにhighlightAll() を呼べばOKです。ファイル名も微妙に変わっていました。スクリプトはhighlight.pack.jsからhighlight.min.jsに、CSSはvs.cssからvs.min.cssになっていました(Visual StudioスタイルのCSSを使う場合の設定、この他にもスタイルはたくさんあります)。

編集者:すずき(2021/08/05 13:02)

コメント一覧

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



2021年8月3日

udevでPCIデバイスのパーミッションを設定する

目次: Linux

PCIデバイスを使った実験をする際にコンフィグ空間やメモリ空間を読み書きしたいときがあります。メモリ空間の読み書き程度ならば、わざわざPCIデバイスドライバを書かなくても /sys/bus/pci/devices/*/ にあるresourceNファイルにアクセスすれば良いです。

PCIデバイスはコンフィグ空間にBAR (Base Address Register) という32bitレジスタが6個あり、メモリ空間をPC側のアドレスのどこにマッピングするかを指定します。BARがそのままresourceNに対応しています。

最近は64bitアドレスでマッピングするデバイスも多いですが、その際は連続するBARを2つ使ってマッピング先のアドレスを指定します。実際に見たほうが早いと思うので、私の使っているマシンのグラフィックカードNVIDIA GeForce GT 1030を例にしましょう。

lspciでBARを確認
# lspci | grep VGA

08:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1)

# lspci -s 08:00.0 -v

08:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1) (prog-if 00 [VGA controller])
        Subsystem: Micro-Star International Co., Ltd. [MSI] GP108 [GeForce GT 1030]
        Flags: bus master, fast devsel, latency 0, IRQ 84, IOMMU group 14
        Memory at f6000000 (32-bit, non-prefetchable) [size=16M]    ★BAR0: resource0
        Memory at e0000000 (64-bit, prefetchable) [size=256M]       ★BAR1: resource1(64bitアドレス空間なのでBAR2も使われる)
        Memory at f0000000 (64-bit, prefetchable) [size=32M]        ★BAR3: resource3(64bitアドレス空間なのでBAR4も使われる)
        I/O ports at d000 [size=128]                                ★BAR5: resource5
        Expansion ROM at 000c0000 [virtual] [disabled] [size=128K]
        Capabilities: [60] Power Management version 3
        Capabilities: [68] MSI: Enable- Count=1/1 Maskable- 64bit+
        Capabilities: [78] Express Legacy Endpoint, MSI 00
        Capabilities: [100] Virtual Channel
        Capabilities: [128] Power Budgeting <?>
        Capabilities: [420] Advanced Error Reporting
        Capabilities: [600] Vendor Specific Information: ID=0001 Rev=1 Len=024 <?>
        Capabilities: [900] Secondary PCI Express
        Kernel driver in use: nvidia
        Kernel modules: nvidia

デバイスの位置(08:00.0)と、マッピングしているアドレスがBAR0, 1, 3, 5の4つ あることがわかります。/sys/bus/pci/devices経由でPCIデバイスのBAR1でマップされているメモリを読み出します。

GeForce GT 1030 BAR1の先頭をダンプ
# cd /sys/bus/pci/devices/0000\:08\:00.0/

# ma -k resource1 dd 0

00000000  ff000020 ff00082c  ff000020 ff00082c
00000010  ff3c0020 ff42082c  ff990020 ffa50027
00000020  ffa10020 ffa10027  ffa50020 ff990027
00000030  ff420020 ff3c0027  ff000020 ff000027
00000040  ff000020 ff000027  ff000020 ff000027
00000050  ff000020 ff000027  ff000020 ff000027
00000060  ff000020 ff000027  ff000020 ff000027
00000070  ff000020 ff000027  ff000020 ff000027
00000080  ff000020 ff000027  ff000020 ff000027
00000090  ff3c0020 ff420027  ffb90020 ffa50027
000000a0  ffa50020 ffb90027  ffa90020 ffa50027
000000b0  ff420020 ff3c0027  ff000020 ff000027
000000c0  ff000020 ff000027  ff000020 ff000027
000000d0  ff000020 ff000027  ff000020 ff000027
000000e0  ff000020 ff000027  ff000020 ff000027
000000f0  ff000020 ff000027  ff000020 ff000027
00000100

読み出しツールは何を使っても構いませんが、サイズがめっちゃでかい(256MB)ので「頭から読む系のツール(hexdumpやテキストエディタ)」は使わないほうが無難です。今回は拙作のmemaccess(GitHubへのリンク)を使いました。

ちなみにresourceNを読みだそうとすると怒られるときは、NVIDIAのドライバをロードしていないかチェックしてみてください。

NVIDIAのグラフィックドライバをロードしているとき、アンロードしたときの挙動
# ma -k resource0 dd 0

mmap: Invalid argument

# cat /proc/iomem

...
e0000000-fec2ffff : PCI Bus 0000:00
  e0000000-f1ffffff : PCI Bus 0000:08
    e0000000-efffffff : 0000:08:00.0
    f0000000-f1ffffff : 0000:08:00.0
  f6000000-f70fffff : PCI Bus 0000:08
    f6000000-f6ffffff : 0000:08:00.0
      f6000000-f6ffffff : nvidia    ★BAR0をnvidiaドライバが使用中なのでmmapできない
    f7080000-f7083fff : 0000:08:00.1
      f7080000-f7083fff : ICH HD audio
...

# rmmod nvidia_uvm nvidia_drm nvidia_modeset nvidia

# cat /proc/iomem

...
e0000000-fec2ffff : PCI Bus 0000:00
  e0000000-f1ffffff : PCI Bus 0000:08
    e0000000-efffffff : 0000:08:00.0
    f0000000-f1ffffff : 0000:08:00.0
  f6000000-f70fffff : PCI Bus 0000:08
    f6000000-f6ffffff : 0000:08:00.0    ★nvidiaドライバがアンロードされた
    f7080000-f7083fff : 0000:08:00.1
      f7080000-f7083fff : ICH HD audio
...

# ma -k resource0 dd 0

00000000  138000a1 00000000  00000000 bad00200
00000010  bad00200 bad00200  bad00200 bad00200
00000020  bad00200 bad00200  bad00200 bad00200
00000030  bad00200 bad00200  bad00200 bad00200
00000040  bad00200 bad00200  bad00200 bad00200
00000050  bad00200 bad00200  bad00200 bad00200
00000060  bad00200 bad00200  bad00200 bad00200
00000070  bad00200 bad00200  bad00200 bad00200
00000080  0000008f bad00200  bad00200 bad00200
00000090  bad00200 bad00200  bad00200 bad00200
000000a0  bad00200 bad00200  bad00200 bad00200
000000b0  bad00200 bad00200  bad00200 bad00200
000000c0  bad00200 bad00200  bad00200 bad00200
000000d0  bad00200 bad00200  bad00200 bad00200
000000e0  bad00200 bad00200  bad00200 bad00200
000000f0  bad00200 bad00200  bad00200 bad00200
00000100

ドライバが管理しているメモリを横から読み書きされたらたまったもんじゃないですから、この挙動は当然ですね。え?読み出しなら良いのでは?と思われるかもしれませんが、読み出しをトリガーに動作したり、読み出すと値が変化するレジスタなどは珍しくないので、勝手に読み出すのも実はダメなのです。

パーミッション面倒くさい

先ほど /sys/bus/pci/devices以下のファイルをls -lなどで見た方はお気づきかと思いますが、rootしか読めないようなパーミッションになっています。

resourceNのパーミッション設定
# cd /sys/bus/pci/devices/0000\:08\:00.0/

# ls -l resource*

-r--r--r-- 1 root root      4096  8月  2 22:08 resource
-rw------- 1 root root  16777216  8月  2 22:10 resource0
-rw------- 1 root root 268435456  8月  2 22:10 resource1
-rw------- 1 root root 268435456  8月  2 22:10 resource1_wc
-rw------- 1 root root  33554432  8月  2 22:10 resource3
-rw------- 1 root root  33554432  8月  2 22:10 resource3_wc
-rw------- 1 root root       128  8月  2 22:10 resource5

誰でもデバイスのメモリにアクセスできるのはマズいですから、セキュリティ上この設定は真っ当です。真っ当ですが、実験に使うときはいちいちsudoするのが面倒なのも事実です。手動でchmodやchownしてパーミッションを変えればsudo不要にできますが、いくつか欠点があります。

  • デバイスID 0000:08:00.0が常に同じとは限らない(カードの増設、撤去で変動する)
  • PCをリブートすると元に戻ってしまう

どこにいるかわからないデバイスを操作したいときは、udevの出番です。

udevでパーミッションを変える

前置きが長くなりましたが、やっと本題です。/etc/udev/rules.dに99-hogehoge.rulesのようなファイルを作ります。全てのPCIデバイスのパーミッションを全開にする必要はないので、ベンダーIDとデバイスIDでGeForce GT 1030だけ該当するようにフィルタリングしましょう。

ベンダーIDとデバイスIDの確認(lspciを使う方法)
# lspci -s 08:00.0

08:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1)

# lspci -s 08:00.0 -n

08:00.0 0300: 10de:1d01 (rev a1)
              |    `--- Device ID
              `--- Vendor ID

他の条件をフィルタリングに使いたいときは、udevadm infoを使うと、vendorやdeviceも含んだ全ての属性を見ることができます。

ベンダーIDとデバイスIDの確認(udevadm infoを使う方法)
# udevadm info -a -p /sys/bus/pci/devices/0000:08:00.0 | less

...
  looking at device '/devices/pci0000:00/0000:00:03.1/0000:08:00.0':
    KERNEL=="0000:08:00.0"
    SUBSYSTEM=="pci"
    DRIVER==""
    ATTR{ari_enabled}=="0"
...
    ATTR{revision}=="0xa1"
    ATTR{subsystem_device}=="0x8c98"
    ATTR{subsystem_vendor}=="0x1462"
    ATTR{vendor}=="0x10de"    ★ベンダーID
...

IDが判明したらルールファイルを書きます。/sys/bus/pci/devices/* のディレクトリと配下のファイル全てにgroupとotherのRead/Writeパーミッションを付加します。%pは設定中のデバイスのパスが入る変数です。

udevのルール設定
SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{device}=="0x1d01", RUN+="/bin/chmod -R go+rw /sys%p"

注意点は「RUNはシェルじゃない」ことです。パスの補完、ワイルドカード、パイプ、リダイレクトなどは使えません。私は最初resource* と書いて「そんなファイルはない」と言われたり、/bin/chmodなのに /usr/bin/chmodと書いていて、何も実行されなくて悩みました(Ubuntuは /usr/bin/chmodだったりするので、さらにややこしいです)。

udevルールの反映
# udevadm trigger

# cd /sys/bus/pci/devices/0000\:08\:00.0/

# ls -l resource*

-r--rw-rw- 1 root root      4096  8月  2 23:10 resource
-rw-rw-rw- 1 root root  16777216  8月  2 23:10 resource0
-rw-rw-rw- 1 root root 268435456  8月  2 23:10 resource1
-rw-rw-rw- 1 root root 268435456  8月  2 23:10 resource1_wc
-rw-rw-rw- 1 root root  33554432  8月  2 23:10 resource3
-rw-rw-rw- 1 root root  33554432  8月  2 23:10 resource3_wc
-rw-rw-rw- 1 root root       128  8月  2 23:10 resource5

ルールファイルを書いたら設定を反映させると、resourceNファイルのパーミッションが変わっているはずです。変わっていない場合はおそらくルールの書き方が間違っているので、修正が必要です。

udevルールのデバッグ

むしろこちらが本題かもしれません。udevのRUNルールは「シェルじゃない」ので、普段シェル上で使っているコマンドをコピペしてもまず動かないです。間違っている場合は主に2パターンで、

  • フィルタリングのルールが間違っている
  • RUNの書き方が間違っている

前者に関してはRUNでログを出すとわかります。例えばRUN+="/usr/bin/logger AAAAAAAA" のようにすると、udevadm triggerを実行した後に /var/log/syslogにメッセージが出ます。

ログが出なければフィルタリングの条件を全部削って試してください。それでもログが出なければおそらくloggerのパスが間違っています。which loggerで出てくるパスとRUNに書いたパスが合っているか確認してください。特に /binと /usr/binは間違えやすいです。

後者に関してはRUNに書いたコマンドが失敗するとsyslogにエラーが記録されるはずですので、間違ってワイルドカードやパイプなどを書いていないかチェックしてみると良いです。

syslogに記録されるエラーの例
Aug  1 22:56:51 blackbird systemd-udevd[2775846]: 0000:08:00.0: Process '/bin/chmod -R go+rw /sys/devices/pci0000:00/0000:00:03.1/0000:08:00.0*' failed with exit code 1.

ログを見てもすぐにわからないような場合は、地道に「コマンド名だけにして実行されるか?」「引数を1つずつ足していって動くか?」を確認するのが一番良いです。

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

コメント一覧

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



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

管理用メニュー

link 記事を新規作成

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

最近のコメント5件

  • link 24年6月17日
    すずきさん (06/23 00:12)
    「ありがとうございます。バルコニーではない...」
  • link 24年6月17日
    hdkさん (06/22 22:08)
    「GPSの最初の同期を取る時は見晴らしのい...」
  • link 24年5月16日
    すずきさん (05/21 11:41)
    「あー、確かにdpkg-reconfigu...」
  • link 24年5月16日
    hdkさん (05/21 08:55)
    「システム全体のlocale設定はDebi...」
  • link 24年5月17日
    すずきさん (05/20 13:16)
    「そうですねえ、普通はStandardなの...」

最近の記事3件

  • link 24年4月12日
    すずき (07/05 10:40)
    「[台湾東部沖地震に寄付] ささやかではありますが台湾東部沖地震に寄付しました。日本の赤十字社→台湾の赤十字(正式名称...」
  • link 24年4月16日
    すずき (07/05 10:40)
    「[Zephyr SDKのhosttoolsは移動してはいけない、その2 - インストール時のバイナリ書き換え] 目次: Zep...」
  • link 24年4月17日
    すずき (07/05 10:39)
    「[VSCodeとMarkdownとPlantUMLのローカルサーバー] 目次: LinuxVSCodeのPlantUML Ex...」
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

最終更新: 07/05 10:40