2020年 9月 1日

link permalink

link 編集する

Zephyr OS で遊ぼう その 11 - Zephyr 2.3.0 に対応する

目次: Zephyr を調べる - まとめリンク

Zephyr 2.3.0 にバージョンアップしたところ、また Hoge ボードのビルドが通らなくなりました。

一点目は UART ドライバの CMakeLists です。zephyr_library_sources_if_kconfig() がなくなったため、書き方が変わりました。

CMakeLists の書き方が変わった Zephyr 本家のコミット

commit 244f826e3c7333bb92fb53a65c50ee5cbd8a2ea0
Author: Carles Cufi <carles.cufi@nordicsemi.no>
Date:   Fri Jul 31 13:52:40 2020 +0200

    cmake: remove _if_kconfig() functions

    This set of functions seem to be there just because of historical
    reasons, stemming from Kbuild. They are non-obvious and prone to errors,
    so remove them in favor of the `_ifdef()` ones with an explicit
    `CONFIG_` condition.

    Script used:

    git grep -l _if_kconfig | xargs sed -E -i
    "s/_if_kconfig\(\s*(\w*)/_ifdef(CONFIG_\U\1\E \1/g"

    Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
CMakeLists の書き方を変更

# drivers/serial/CMakeLists.txt

zephyr_library_sources_if_kconfig(uart_spike.c)

下記に変更

zephyr_library_sources_ifdef(CONFIG_UART_SPIKE uart_spike.c)

二点目は整数型です。Zephyr は u8_t, u16_t, u32_t のような独自の整数型を持っていましたが、C99 の型に置き換えられました。drivers/serial/uart_spike.c の実装を書き換える必要があります。

C99 の型に置き換えた Zephyr 本家のコミット
commit a1b77fd589dbe7284c17b029f251426a724abd47
Author: Kumar Gala <kumar.gala@linaro.org>
Date:   Wed May 27 11:26:57 2020 -0500

    zephyr: replace zephyr integer types with C99 types

            git grep -l 'u\(8\|16\|32\|64\)_t' | \
                    xargs sed -i "s/u\(8\|16\|32\|64\)_t/uint\1_t/g"
            git grep -l 's\(8\|16\|32\|64\)_t' | \
                    xargs sed -i "s/s\(8\|16\|32\|64\)_t/int\1_t/g"

    Signed-off-by: Kumar Gala <kumar.gala@linaro.org>

三点目は DT_INST_0_SPIKE_UART_SPIKE_LABEL マクロです。こいつは元々、訳のわからない名前で直しようがないので、SiFive のシリアルドライバの履歴を参考に直します。履歴を見ると 2回ほど変わっています。

マクロ名のルールは DT_INST_<INSTANCE>_<COMPAT>_<PROP> だったみたいです。今初めて知りました。やっぱりこの書き方は意味不明と思ったのか、DT_INST_PROP(0, label) という形式になりました。さらに今は DT_INST_LABEL(0) という形式に落ち着いています。

マクロ名の変更を行った Zephyr 本家のコミット
★★DT_INST_PROP(0, label) になったコミット

commit 8f84520130a346957ac2e2bdff1d6a51bca13af0
Author: Kumar Gala <kumar.gala@linaro.org>
Date:   Tue Mar 10 17:24:43 2020 -0500

    drivers: serial: uart_sifive: convert to new DT API

    Use the new devicetree.h API instead of the legacy macros.

    Signed-off-by: Kumar Gala <kumar.gala@linaro.org>


★★DT_INST_LABEL(0) になったコミット

commit 74d459fb66b10a5a0614a582fb0375d8b4a78c9e
Author: Kumar Gala <kumar.gala@linaro.org>
Date:   Thu Apr 2 13:13:47 2020 -0500

    drivers: serial: sifive: use DT_INST_LABEL macro

    Replace a few cases that should have been DT_INST_LABEL instead.

    Signed-off-by: Kumar Gala <kumar.gala@linaro.org>

このマクロの罠はそれだけに留まらず、ソースコードの先頭に下記マクロを定義する必要があります。依然として訳がわかりません。Zephyr は DviceTree 周りの仕様が不安定です。

ソースコードの先頭に必要なマクロ定義

// zephyr/drivers/serial/uart_spike.c

#define DT_DRV_COMPAT spike_uart_spike

最後はリンカーです。これは元々のコードのコンフィグが間違っていたことに起因します。ROM 領域がないのに CONFIG_XIP が有効になっていました。

リンクエラー
$ ninja

...

x-tools/riscv64-zephyr-elf/lib/gcc/riscv64-zephyr-elf/8.3.0/../../../../riscv64-zephyr-elf/bin/ld: invalid origin for memory region ROM
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.

エラーメッセージからは何が原因か読み取れないですね。こういうときはビルドディレクトリのリンカースクリプト(zephyr/linker.cmd)をうまく行く場合と、うまく行かない場合で見比べます。

リンクエラーが起きないとき(qemu_riscv32)のリンカースクリプト

/* zephyr/build/zephyr/linker.cmd */

 OUTPUT_ARCH("riscv")
 OUTPUT_FORMAT("elf32-littleriscv")
MEMORY
{
    ROM (rx) : ORIGIN = 541065216, LENGTH = 12582912
    RAM (rwx) : ORIGIN = 0x80000000, LENGTH = ((16) << 10)
    IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K
}
リンクエラーが起きるとき(Hoge ボード)のリンカースクリプト

/* zephyr/build/zephyr/linker.cmd */

 OUTPUT_ARCH("riscv")
 OUTPUT_FORMAT("elf32-littleriscv")
MEMORY
{
    ROM (rx) : ORIGIN = ROM_BASE, LENGTH = ROM_SIZE    /* ★★ここがおかしい★★ */
    RAM (rwx) : ORIGIN = 0x80000000, LENGTH = ((32) << 10)
    IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K
}

このスクリプトは下記のファイルから生成されているようです。Hoge ボードは ROM 領域を使う前提ではないので、領域そのものが要りません。ROM 領域を葬るには CONFIG_XIP を n にすれば良さそうです。ファイルは boards/riscv/hoge/hoge_defconfig です。

RISC-V のリンカースクリプト

// include/arch/riscv/common/linker.ld

MEMORY
{
#ifdef CONFIG_XIP
#if DT_NODE_HAS_COMPAT_STATUS(DT_CHOSEN(zephyr_flash), soc_nv_flash, okay)
#define ROM_BASE DT_REG_ADDR(DT_CHOSEN(zephyr_flash))
#define ROM_SIZE DT_REG_SIZE(DT_CHOSEN(zephyr_flash))
#elif DT_NODE_HAS_COMPAT_STATUS(DT_CHOSEN(zephyr_flash), jedec_spi_nor, okay)
/* For jedec,spi-nor we expect the spi controller to memory map the flash
 * and for that mapping to be the second register property of the spi
 * controller.
 */
#define SPI_CTRL DT_PARENT(DT_CHOSEN(zephyr_flash))
#define ROM_BASE DT_REG_ADDR_BY_IDX(SPI_CTRL, 1)
#define ROM_SIZE DT_REG_SIZE_BY_IDX(SPI_CTRL, 1)
#endif
    ROM (rx)  : ORIGIN = ROM_BASE, LENGTH = ROM_SIZE    /* ★★ CONFIG_XIP が無効ならこの行ごと消える★★ */
#endif
    RAM (rwx) : ORIGIN = CONFIG_SRAM_BASE_ADDRESS, LENGTH = KB(CONFIG_SRAM_SIZE)
    /* Used by and documented in include/linker/intlist.ld */
    IDT_LIST  (wx)      : ORIGIN = 0xFFFFF7FF, LENGTH = 2K
}

以上の修正を入れて動かします。

動作確認

$ qemu-system-riscv32 -nographic -machine spike -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/zephyr.elf -bios none

*** Booting Zephyr OS build zephyr-v2.3.0-2349-g0769bb760b2a  ***
Hello World! hoge

やっと動きました。良かった良かった。

[編集者: すずき]
[更新: 2020年 9月 2日 19:05]

コメント一覧

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



2020年 9月 2日

link permalink

link 編集する

Zephyr の 16550 シリアルドライバ その 2

目次: Zephyr を調べる - まとめリンク

以前、RISC-V QEMU の -machine virt で、シリアルドライバ 16550 を使う設定を作りましたが、Zephyr 2.3.0 で動かなくなってしまいました。悲しい。

変更点はレジスタアドレスシフト量の設定方法です。DT_NS16550_REG_SHIFT で設定する方式でしたが、デバイスツリーから設定するように変更されました。

NS16550 のコードを変えている Zephyr 本家のコミット
commit 70a0063b69b06812c5726077646cffae3b8e199c
Author: Kumar Gala <kumar.gala@linaro.org>
Date:   Fri Mar 27 06:03:59 2020 -0500

    drivers: serial: uart_ns16550: Convert to new DT_INST macros

    Convert older DT_INST_ macro use the new include/devicetree.h
    DT_INST macro APIs.

    Signed-off-by: Kumar Gala <kumar.gala@linaro.org>
NS16550 のコードの変更内容

// zephyr/drivers/serial/uart_ns16550.c

-#ifdef DT_INST_0_NS16550_REG_SHIFT
-#define UART_REG_ADDR_INTERVAL (1<<DT_INST_0_NS16550_REG_SHIFT)
+#if DT_INST_NODE_HAS_PROP(0, reg_shift)
+#define UART_REG_ADDR_INTERVAL (1<<DT_INST_PROP(0, reg_shift))
 #endif

デバイスツリーに何を書けば良いのかは、デバイスツリーのドキュメント(ns16550.yaml)を見ましょう。プロパティ名と説明が書いてあります。

デバイスツリーの変更内容

// zephyr/dts/bindings/serial/ns16550.yaml

properties:
    reg:
      required: true

    reg-shift:
      type: int
      required: false
      description: quantity to shift the register offsets by


// zephyr/dts/riscv/riscv32-virt.dtsi

...

		uart0: serial@10000000 {
			compatible = "ns16550";
			reg = <0x10000000 0x100>;
			clock-frequency = <3686400>;
			label = "uart_0";
			current-speed = <115200>;
			reg-shift = <0>;    /* ★これを追加★ */
		};


// zephyr/soc/riscv/riscv-privilege/rv32-virt/soc.h

...

/* ★★下記定義は全て不要★★ */

#define DT_UART_NS16550_PORT_0_BASE_ADDR    DT_INST_0_NS16550_BASE_ADDRESS
#define DT_UART_NS16550_PORT_0_BAUD_RATE    DT_INST_0_NS16550_CURRENT_SPEED
#define DT_UART_NS16550_PORT_0_CLK_FREQ     DT_INST_0_NS16550_CLOCK_FREQUENCY
#define DT_UART_NS16550_PORT_0_NAME         DT_INST_0_NS16550_LABEL

#define DT_NS16550_REG_SHIFT                0

以上の修正を入れて動かします。

動作確認

$ qemu-system-riscv32 -nographic -machine virt -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/zephyr.elf -bios none

*** Booting Zephyr OS build zephyr-v2.3.0-2350-g2f294fcc2da8  ***               
Hello World! QEMU RV32 virt board

動きました。良かった良かった。

[編集者: すずき]
[更新: 2020年 9月 2日 19:55]

コメント一覧

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



2020年 9月 3日

link permalink

link 編集する

FreeRTOS で遊ぼう その 1 - FreeRTOS 入門

目次: FreeRTOS を調べる - まとめリンク

以前 RTOS 界の新星 Zephyr を調べて、新たな RISC-V ボードの定義を作りました。今回は RTOS の老舗 FreeRTOS を調べます。FreeRTOS は GPLv2 で開発されていましたが、Amazon が買収した後は MIT ライセンスになっています。IoT 分野での企業ユーザー(大抵コード公開を嫌がる)を重視したんでしょう。

FreeRTOS のコード取得
$ git clone https://github.com/FreeRTOS/FreeRTOS freertos

Cloning into 'freertos'...
remote: Enumerating objects: 149823, done.
Receiving objects:   0% (1/149823)
remote: Total 149823 (delta 0), reused 0 (delta 0), pack-reused 149823
Receiving objects: 100% (149823/149823), 115.38 MiB | 8.29 MiB/s, done.
Resolving deltas: 100% (107018/107018), done.
Updating files: 100% (12962/12962), done.

$ git submodule update --init --recursive

FreeRTOS のカーネルは FreeRTOS/Source に配置されており、リポジトリは https://github.com/FreeRTOS/FreeRTOS-Kernel です。

FreeRTOS 上で動く何かを作成する場合は freertos/FreeRTOS/Demo の下に作るルールになっているようです。たくさんのアーキテクチャ、ボード向けのコードが格納されています。統一感がなくて、どれを見たら良いのか良くわからないのが難点です。

ツールチェーンの準備

RISC-V 32ビット用でしたら、以前 Zephyr 用に作成したツールチェーン2020年 1月 31日の日記参照)が流用できます。ARM やそれ以外の環境でも Crosstool-NG を使えばたいてい作成できるはずです。

既存のデモを作り変える

Demo ディレクトリの下には RISC-V QEMU 向けのプロジェクト(正確には SiFive HiFive1 エミュレーション環境向け)が既に 1つあります。FreeRTOS/Demo/RISC-V-Qemu-sifive_e-Eclipse-GCC です。このデモは Eclipse 向けになっているので、Makefile 向けに作り直します。Eclipse 関連のファイルを削除して Makefile を作成するだけです。

Makefile

CROSS=riscv64-unknown-elf-
CC=$(CROSS)gcc
OBJCOPY=$(CROSS)objcopy
ARCH=$(CROSS)ar

RTOS_SOURCE_DIR=../../Source
DEMO_SOURCE_DIR=../Common/Minimal
LIBWRAP_SOURCE_DIR=./freedom-e-sdk/libwrap

CPPFLAGS = -g -O2 -Wall -march=rv32ima -mabi=ilp32 -mcmodel=medlow \
	-fmessage-length=0 \
	-ffunction-sections \
	-fdata-sections \
	-fno-builtin-printf \
	-DportasmHANDLE_INTERRUPT=handle_trap \
	-I . -I ../Common/include \
	-I $(RTOS_SOURCE_DIR)/include \
	-I $(RTOS_SOURCE_DIR)/portable/GCC/RISC-V \
	-I $(RTOS_SOURCE_DIR)/portable/GCC/RISC-V/chip_specific_extensions/RV32I_CLINT_no_extensions \
	\
	-I freedom-e-sdk/include \
	-I freedom-e-sdk/env \
	-I freedom-e-sdk/env/freedom-e300-hifive1

CFLAGS =
ASFLAGS =
LDFLAGS = \
	-march=rv32ima -mabi=ilp32 -mcmodel=medlow \
	-Tfreedom-e-sdk/env/freedom-e300-hifive1/flash.lds \
	-Xlinker --gc-sections \
	-Xlinker --defsym=__stack_size=300

SRCS = \
	main.c \
	blinky_demo/main_blinky.c \
	$(DEMO_SOURCE_DIR)/EventGroupsDemo.c \
	$(DEMO_SOURCE_DIR)/TaskNotify.c \
	$(DEMO_SOURCE_DIR)/TimerDemo.c \
	$(DEMO_SOURCE_DIR)/blocktim.c \
	$(DEMO_SOURCE_DIR)/dynamic.c \
	$(DEMO_SOURCE_DIR)/recmutex.c \
	$(RTOS_SOURCE_DIR)/event_groups.c \
	$(RTOS_SOURCE_DIR)/list.c \
	$(RTOS_SOURCE_DIR)/queue.c \
	$(RTOS_SOURCE_DIR)/stream_buffer.c \
	$(RTOS_SOURCE_DIR)/tasks.c \
	$(RTOS_SOURCE_DIR)/timers.c \
	$(RTOS_SOURCE_DIR)/portable/MemMang/heap_4.c \
	$(RTOS_SOURCE_DIR)/portable/GCC/RISC-V/port.c

ASMS = \
	$(RTOS_SOURCE_DIR)/portable/GCC/RISC-V/portASM.S \
	\
	freedom-e-sdk/env/start.S \
	freedom-e-sdk/env/entry.S

OBJS = $(SRCS:.c=.o) $(ASMS:.S=.o)

a.out: $(OBJS) $(CRT0) Makefile
	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -nostartfiles $(CRT0) $(LINKER_FLAGS) -o $@

clean:
	rm -rf $(OBJS)

元のコードでは --defsym=__stack_size=350 なんですが、そのまま使うとなぜか下記のリンクエラーが出るので、少しだけ減らしています。

__stack_size=350 のときのリンクエラー
x-tools/riscv64-unknown-elf/lib/gcc/riscv64-unknown-elf/10.2.0/../../../../riscv64-unknown-elf/bin/ld: section .stack VMA [0000000080003e00,0000000080003fff] overlaps section .bss VMA [0000000080000440,0000000080003ebb]
collect2: error: ld returned 1 exit status
make: *** [Makefile:105: rtosdemo.elf] Error 1

リンカースクリプトを見る限り HiFive1 は RAM が 16KB しかないようで、あまり大きな領域を取ろうとするとすぐに溢れてしまいます。

リンカースクリプト

// freertos/FreeRTOS/Demo/RISC-V-Qemu-virt_GCC/freedom-e-sdk/env/freedom-e300-hifive1/flash.lds

MEMORY
{
  flash (rxai!w) : ORIGIN = 0x20400000, LENGTH = 512M
  ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 16K
}

Makefile を作ったら make し、動作確認します。

動作確認
$ qemu-system-riscv32 -nographic -machine sifive_e -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -bios none -kernel a.out

StartingBlink
Blink
Blink
Blink
Blink
...

動作しました。QEMU を止めるまで Blink という文字が延々と出続けます。最初の Starting に改行が入っていないのは元々です。理由は良くわかりません、作った人がミスっただけかな?

[編集者: すずき]
[更新: 2020年 9月 5日 22:39]

コメント一覧

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



2020年 9月 4日

link permalink

link 編集する

FreeRTOS で遊ぼう その 2 - FreeRTOS を virt に移植(ドライバ準備編)

目次: FreeRTOS を調べる - まとめリンク

前回は既にあるデモアプリのビルドシステムを組み替えて RISC-V QEMU sifive_e マシン(SiFive HiFive1 相当)上で FreeRTOS を動かしました。今回は RISC-V QEMU virt マシン上で FreeRTOS を動かします。

マシンの違いですが、まず UART が違います。HiFive1 は SiFive UART、virt は 16550 です。UART を動かすための簡易的なドライバを書きます。

16550 の出力だけするドライバ

// freertos/FreeRTOS/Demo/RISC-V-Qemu-virt_GCC/ns16550.c

#include <stdint.h>

#include "ns16550.h"

/* register definitions */
#define REG_RBR		0x00 /* Receiver buffer reg. */
#define REG_THR		0x00 /* Transmitter holding reg. */
#define REG_IER		0x01 /* Interrupt enable reg. */
#define REG_IIR		0x02 /* Interrupt ID reg. */
#define REG_FCR		0x02 /* FIFO control reg. */
#define REG_LCR		0x03 /* Line control reg. */
#define REG_MCR		0x04 /* Modem control reg. */
#define REG_LSR		0x05 /* Line status reg. */
#define REG_MSR		0x06 /* Modem status reg. */
#define REG_SCR		0x07 /* Scratch reg. */
#define REG_BRDL	0x00 /* Divisor latch (LSB) */
#define REG_BRDH	0x01 /* Divisor latch (MSB) */

/* Line status */
#define LSR_DR			0x01 /* Data ready */
#define LSR_OE			0x02 /* Overrun error */
#define LSR_PE			0x04 /* Parity error */
#define LSR_FE			0x08 /* Framing error */
#define LSR_BI			0x10 /* Break interrupt */
#define LSR_THRE		0x20 /* Transmitter holding register empty */
#define LSR_TEMT		0x40 /* Transmitter empty */
#define LSR_EIRF		0x80 /* Error in RCVR FIFO */

uint8_t readb( uintptr_t addr )
{
	return *((uint8_t *) addr );
}

void writeb( uint8_t b, uintptr_t addr )
{
	*((uint8_t *) addr ) = b;
}

void ns16550_out( struct device *dev, unsigned char c )
{
	uintptr_t addr = dev->addr;

	while ( (readb( addr + REG_LSR ) & LSR_THRE) == 0 ) {
		/* busy wait */
	}

	writeb( c, addr + REG_THR );
}

このドライバは初期化も設定も何もせず、いきなり出力だけ行う手抜き実装です。QEMU では動きますが、おそらく実機では動かないでしょう。

main を書き換える

元のコードは main.c にSiFive UART 用のシリアルの出力コードが入っているので、これを削ります。また main.c と main_blinky.c, main_full.c に別れていますが、あまり複雑なデモは要りません。main_full.c の方は削って、main.c に統合します。

main.c(一部)

// freertos/FreeRTOS/Demo/RISC-V-Qemu-virt_GCC/main.c

static void prvQueueSendTask( void *pvParameters )
{
TickType_t xNextWakeTime;
const unsigned long ulValueToSend = 100UL;
BaseType_t xReturned;

	/* Remove compiler warning about unused parameter. */
	( void ) pvParameters;

	/* Initialise xNextWakeTime - this only needs to be done once. */
	xNextWakeTime = xTaskGetTickCount();

	for( ;; )
	{
		/* Place this task in the blocked state until it is time to run again. */
		vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS );

		/* Send to the queue - causing the queue receive task to unblock and
		toggle the LED.  0 is used as the block time so the sending operation
		will not block - it shouldn't need to block as the queue should always
		be empty at this point in the code. */
		xReturned = xQueueSend( xQueue, &ulValueToSend, 0U );
		configASSERT( xReturned == pdPASS );
	}
}

/*-----------------------------------------------------------*/

static void prvQueueReceiveTask( void *pvParameters )
{
unsigned long ulReceivedValue;
const unsigned long ulExpectedValue = 100UL;
const char * const pcPassMessage = "Blink\r\n";
const char * const pcFailMessage = "Unexpected value received\r\n";

	/* Remove compiler warning about unused parameter. */
	( void ) pvParameters;

	for( ;; )
	{
		/* Wait until something arrives in the queue - this task will block
		indefinitely provided INCLUDE_vTaskSuspend is set to 1 in
		FreeRTOSConfig.h. */
		xQueueReceive( xQueue, &ulReceivedValue, portMAX_DELAY );

		/*  To get here something must have been received from the queue, but
		is it the expected value?  If it is, toggle the LED. */
		if( ulReceivedValue == ulExpectedValue )
		{
			puts( pcPassMessage );
			ulReceivedValue = 0U;
		}
		else
		{
			puts( pcFailMessage );
		}
	}
}

/*-----------------------------------------------------------*/

int main( void )
{
	puts( "Hello FreeRTOS!" );

	/* Create the queue. */
	xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );

	if( xQueue != NULL )
	{
		/* Start the two tasks as described in the comments at the top of this
		file. */
		xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL,
					mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
		xTaskCreate( prvQueueSendTask, "TX", configMINIMAL_STACK_SIZE * 2U, NULL,
					mainQUEUE_SEND_TASK_PRIORITY, NULL );
	}

	vTaskStartScheduler();

	return 0;
}


// freertos/FreeRTOS/Demo/RISC-V-Qemu-virt_GCC/riscv-virt.c

int puts( const char *s )
{
	struct device dev;
	size_t i;

	dev.addr = NS16550_ADDR;

	for (i = 0; i < strlen(s); i++)
	{
		ns16550_out( &dev, s[i] );
	}
	ns16550_out( &dev, '\n' );

	return 0;
}

別に POSIX 信者というわけでもないんですが、ついでに puts() もどきを実装しておきました。

続きはまた今度。

[編集者: すずき]
[更新: 2020年 9月 5日 22:39]

コメント一覧

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



2020年 9月 5日

link permalink

link 編集する

FreeRTOS で遊ぼう その 3 - FreeRTOS を virt に移植(ブート編)

目次: FreeRTOS を調べる - まとめリンク

前回 UART ドライバと main() 関数を実装しました。今回は main() に至るまでのブート部分を実装します。ざっくり言うとアセンブラで書いたスタートコードと、リンカスクリプトが必要です。

メモリマップ

RISC-V QEMU virt マシンのメモリマップは下記のようになっています。使わないハードウェアは載せていません。

  • 0x02000000: CLINT
  • 0x10000000: 16550 UART
  • 0x80000000: RAM

今回は RAM の一部を ROM の代わりとして使います。0x80000000 は本来 RAM ですが ROM の代わりとして扱います。0x80080000 以降を RAM として扱います。本当は SPI Flash ROM を使ったほうが良いですが、手抜き実装です。リンカスクリプトは下記のようにしました。

リンカスクリプト

OUTPUT_ARCH( "riscv" )
ENTRY( _start )

MEMORY
{
	rom (rxa) : ORIGIN = 0x80000000, LENGTH = 512K
	ram (wxa) : ORIGIN = 0x80080000, LENGTH = 512K
}

SECTIONS
{
	.init :
	{
		_text = .;
		KEEP (*(SORT_NONE(.init)))
	} >rom AT>rom
	
	.text :
	{

		*(.text.unlikely .text.unlikely.*)
		*(.text.startup .text.startup.*)
		*(.text .text.*)
		*(.gnu.linkonce.t.*)
	} >rom AT>rom
	
	.fini :
	{
		KEEP (*(SORT_NONE(.fini)))
		_etext = .;
	} >rom AT>rom

	.rodata.align :
	{
		. = ALIGN(4);
		_rodata = .;
	} >rom AT>rom

	.rodata.start :
	{
		_rodata_lma = LOADADDR(.rodata.start);
	} >rom AT>rom

	.rodata :
	{
		*(.rdata)
		*(.rodata .rodata.*)
		*(.gnu.linkonce.r.*)

		. = ALIGN(4);
		_erodata = .;
	} >rom AT>rom

	.data.align :
	{
		. = ALIGN(4);
		_data = .;
	} >ram AT>rom

	.data.start :
	{
		_data_lma = LOADADDR(.data.start);
	} >ram AT>rom

	.data :
	{
		*(.data .data.*)
		*(.gnu.linkonce.d.*)
		. = ALIGN(8);
		PROVIDE( __global_pointer$ = . + 0x800 );
		*(.sdata .sdata.*)
		*(.sdata2 .sdata2.*)
		*(.gnu.linkonce.s.*)
		. = ALIGN(8);
		*(.srodata.cst16)
		*(.srodata.cst8)
		*(.srodata.cst4)
		*(.srodata.cst2)
		*(.srodata .srodata.*)

		. = ALIGN(4);
		_edata = .;
	} >ram AT>rom

	.bss.align :
	{
		. = ALIGN(4);
		_bss = .;
	} >ram AT>rom

	.bss.start :
	{
		_bss_lma = LOADADDR(.bss.start);
	} >ram AT>rom

	.bss :
	{
		*(.sbss*)
		*(.gnu.linkonce.sb.*)
		*(.bss .bss.*)
		*(.gnu.linkonce.b.*)
		*(COMMON)

		. = ALIGN(4);
		_ebss = .;
	} >ram AT>rom

	. = ALIGN(8);
	_end = .;

	.stack :
	{
		. = ALIGN(16);
		_stack0_bottom = .;
		. += __stack_size;
		_stack0_top = .;
	} >ram AT>ram
}

ビルドしたバイナリを nm や readelf で見るときわかりやすくするために、あえて変なセクション(.*.align, .*.start)をいくつか作っています。このように見えます。

nm や readelf で見たとき
$ riscv64-unknown-elf-nm -n a.out | less

★.bss.align に ALIGN(4) と書いたとき

80002ea8 R __clz_tab
80002fa8 A _data_lma
80002fa8 R _erodata  ★.rodata の終わり(ROM 領域を 0x80000000 としている)
80002fb8 A _bss_lma
80080000 D _data     ★.data の始まり(RAM 領域を 0x80080000 としている)
80080000 D pullNextTime
80080008 D uxTimerIncrementsForOneTick
8008000c D xISRStackTop
80080010 B _bss      ★.bss の始まり    ★4bytes align になっている(.data の終わりと連続している)
80080010 D _edata    ★.data の終わり
80080010 b xQueue

...


★.data.align に ALIGN(2048) と書いたとき

80002ca8 R __clz_tab
80002da8 A _data_lma
80002da8 R _erodata  ★.rodata の終わり(ROM 領域を 0x80000000 としている)
80002db8 A _bss_lma
80080000 D _data     ★.data の始まり(RAM 領域を 0x80080000 としている)
80080000 D pullNextTime
80080008 D uxTimerIncrementsForOneTick
8008000c D xISRStackTop
80080010 D _edata    ★.data の終わり
80080800 D __global_pointer$
80080800 B _bss      ★.bss の始まり    ★2KB align になっている(.data の終わりと連続して「いない」)
80080800 b xQueue

...


$ riscv64-unknown-elf-readelf -a a.out | less

★.bss.align に ALIGN(4) と書いたとき

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .init             PROGBITS        80000000 001000 00006c 00  AX  0   0  1
  [ 2] .text             PROGBITS        80000100 001100 002d2c 00  AX  0   0 256
  [ 3] .rodata.align     PROGBITS        80002e2c 004010 000000 00  WA  0   0  1
  [ 4] .rodata           PROGBITS        80002e2c 003e2c 00017c 00   A  0   0  4
  [ 5] .data.align       PROGBITS        80080000 004010 000000 00  WA  0   0  1
  [ 6] .data             PROGBITS        80080000 004000 000010 00  WA  0   0  4
  [ 7] .bss.align        NOBITS          80080010 000000 000000 00  WA  0   0  1
  [ 8] .bss              NOBITS          80080010 004010 0040c0 00  WA  0   0 16    ★4bytes align になっている
  [ 9] .stack            NOBITS          800840d0 0040d0 00012c 00  WA  0   0  1


★.data.align に ALIGN(2048) と書いたとき

ection Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .init             PROGBITS        80000000 001000 000068 00  AX  0   0  1
  [ 2] .text             PROGBITS        80000100 001100 002b2c 00  AX  0   0 256
  [ 3] .rodata.align     PROGBITS        80002c2c 004010 000000 00  WA  0   0  1
  [ 4] .rodata           PROGBITS        80002c2c 003c2c 00017c 00   A  0   0  4
  [ 5] .data.align       PROGBITS        80080000 004010 000000 00  WA  0   0  1
  [ 6] .data             PROGBITS        80080000 004000 000010 00  WA  0   0  4
  [ 7] .bss.align        NOBITS          80080010 004010 0007f0 00  WA  0   0  1
  [ 8] .bss              NOBITS          80080800 004800 0040c0 00  WA  0   0 16    ★2KB align になっている
  [ 9] .stack            NOBITS          800848c0 0048c0 00012c 00  WA  0   0  1

ALIGN(4) を ALIGN(2048) など、大きめの値に変えたときの様子を載せました。変なセクション .*.align がアドレスのアラインメントをしている様子が nm でも readelf でもわかりやすいですよね?だめ?上記は .bss の例ですが、他のセクションでも同様です。

ブートコード

ブートコードはアセンブラで書く必要があります。

ブートコード

// freertos/FreeRTOS/Demo/RISC-V-Qemu-virt_GCC/start.S

#include "riscv-reg.h"

#if __riscv_xlen == 32
#define REGSIZE		4
#define LOAD		lw
#define STOR		sw
#elif __riscv_xlen == 64
#define REGSIZE		8
#define LOAD		ld
#define STOR		sd
#endif /* __riscv_xlen */

	.section .init
	.globl _start
	.type _start,@function
_start:
	.cfi_startproc
	.cfi_undefined ra
.option push
.option norelax
	la gp, __global_pointer$
.option pop

	la sp, _stack0_top

	# Load data section
	la a0, _data_lma
	la a1, _data
	la a2, _edata
	bgeu a1, a2, 2f
1:
	LOAD t0, (a0)
	STOR t0, (a1)
	addi a0, a0, REGSIZE
	addi a1, a1, REGSIZE
	bltu a1, a2, 1b
2:

	# Clear bss section
	la a0, _bss
	la a1, _ebss
	bgeu a0, a1, 2f
1:
	STOR zero, (a0)
	addi a0, a0, REGSIZE
	bltu a0, a1, 1b
2:

	/* argc, argv, envp is 0 */
	li a0, 0
	li a1, 0
	li a2, 0
	j main
	.cfi_endproc

実装は非常に単純です。.data を ROM 領域から RAM 領域にコピーし、.bss を 0 クリアして main() に飛ぶだけです。

以上の実装でビルドして(make するだけ)、動かします。

動作確認
$ qemu-system-riscv32 -nographic -machine virt -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -bios none -kernel a.out

Hello FreeRTOS!
Blink

Blink

Blink

...

動きましたね。良かった良かった。 改行が余計に入っちゃってるのが気になる場合は main.c の "Blink\r\n" を "Blink" にすると治ります。

[編集者: すずき]
[更新: 2020年 9月 5日 22:39]

コメント一覧

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



2020年 9月 6日

link permalink

link 編集する

FreeRTOS を調べる - まとめリンク

日記が増えすぎて、一覧が欲しくなってきたので作りました。

今後、日記が増えたら追加します。

[編集者: すずき]
[更新: 2020年 11月 18日 22:45]

コメント一覧

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



2020年 9月 10日

link permalink

link 編集する

MATLAB のインストール

自動車業界(だけじゃないですが)で重宝されている MATLAB を家で使えることになったので、インストールしてみました。

ダウンロードは Mathworks のサイトからできますが、たぶん会社や学校からもらえるアカウント、アクティベーション番号が必要です。MATLAB は個人でも買えますがメチャクチャ値段が高く、私は買う気は起きません……。

MATLAB は Debian 9, Debian 10 には対応しています(System Requirements for MATLAB R2020a - MATLAB & Simulink)が、Debian Testing には対応していません。インストーラを起動した瞬間にクラッシュします。

Debian Testing: 通常版インストーラはクラッシュ
$ cd matlab_archive
$ unzip matlab_R2020a_glnxa64.zip

$ ./install

terminate called after throwing an instance of 'std::runtime_error'
  what():  Unable to launch the MATLABWindow application
Aborted

ありがたいことに、Linux 向けの MATLAB インストーラは通常版と legacy 版が同梱されています。

Debian Testing: legacy 版のインストーラは動く
$ cd matlab_archive
$ unzip matlab_R2020a_glnxa64.zip

$ bin/glnxa64/install_unix_legacy

推奨された使い方ではないと思いますが legacy 版ならばインストーラが動きます。質問にはハイハイ答えておけば、そんなに問題ないはずです。

起動するとき

MATLAB を起動するときに変なエラーが出ます。

g_ptr_array_copy が見つからない
$ matlab

MATLAB is selecting SOFTWARE OPENGL rendering.
matlab_install_dir/bin/glnxa64/jcef_helper: symbol lookup error: /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0: undefined symbol: g_ptr_array_copy

このエラーは MATLAB の動的ライブラリの構成がおかしいことが原因です。libglib-2.0.so を MATLAB 内部に抱えているのですが、libpango-1.0.so はシステム側を使うため、バージョンの非互換が発生します。libpango-1.0.so も内部に抱えれば良いのに??何だか中途半端な作りですね。

システム側の libglib-2.0.so を使う
$ ldd bin/glnxa64/jcef_helper | grep glib

libglib-2.0.so.0 => matlab_install_dir/bin/glnxa64/../../cefclient/sys/os/glnxa64/libglib-2.0.so.0 (0x00007f43ac828000)

★MATLAB 内部に抱えているライブラリをダイナミックリンクする


$ cd matlab_install_dir/cefclient/sys/os/glnxa64
$ mv libglib-2.0.so _libglib-2.0.so
$ mv libglib-2.0.so.0 _libglib-2.0.so.0
$ mv libglib-2.0.so.0.5600.1 _libglib-2.0.so.0.5600.1

$ ldd bin/glnxa64/jcef_helper | grep glib

libglib-2.0.so.0 => /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007f10b2e6d000)

★システム側のライブラリをダイナミックリンクする

MATLAB の内部で抱えている libglib-2.0.so を無視して、システム側の libglib-2.0.so をダイナミックリンクすればエラーは出ません。これも推奨された使い方ではないと思いますが、とりあえず動いたのでめでたしめでたし。

[編集者: すずき]
[更新: 2020年 9月 11日 07:54]

コメント一覧

  • すずき 
    追加情報。最新の Debian Testing では動かなくなってしまいました。
    おとなしく Ubuntu か Debian Stable にインストールした方が、余計な苦労をしなくて良いと思います。 
    (2020年10月07日 16:48:21)
open/close この記事にコメントする



2020年 9月 13日

link permalink

link 編集する

FreeRTOS で遊ぼう その 4 - FreeRTOS のパッチ投稿

目次: FreeRTOS を調べる - まとめリンク

FreeRTOS に RISC-V QEMU virtpc のパッチをぶん投げてみました。やり方は GitHub でプルリクエストを送れば良いみたいです(説明へのリンク(FreeRTOS/CONTRIBUTING.md))。

FreeRTOS には既に QMEU のプロジェクトはある(SiFive HiFive1 エミュレーション環境向けの Eclipse プロジェクトがある)から要らないよ!?と言われることが目に浮かびますが、それならそれで良し。今後なにかの役に立つでしょう。

FreeRTOS はインデントやコード記法が非常に特徴的で、とても書きにくかったです。

  • 関数名、変数名はハンガリアン記法
  • 変数宣言だけなぜか字下げなし
  • キャメルケース
  • Tab を使う(タブ幅は 4)
  • カッコ前後にはスペースを入れる
  • 中カッコは次の行
FreeRTOS の特徴的なインデントルールの例

void vFunc( int aaa )
{
int xBbb;

    if( aaa )
    {
        /* do something */
        xBbb = 0;
    }
}

クセが強すぎる。何でこんな記法にしたのやら……??

メモ: 技術系?の話は Facebook から転記しておくことにした。色々と加筆。

[編集者: すずき]
[更新: 2020年 9月 14日 10:20]

コメント一覧

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



2020年 9月 14日

link permalink

link 編集する

Arm 買収

NVIDIA が Arm 全株式買収、ソフトバンク G から最大 400億ドルで - ケータイ Watch を読んで。

NVIDIA が Arm 買収です。買収額の 2/3 は株式交換で、ソフトバンクは NVIDIA の大株主になります。買収が成立すれば、ソフトバンクは 1兆円超の利益を得ます。

Arm が NVIDIA 配下となれば、NVIDIA と思い切り食い合う、Ethos(エッジ AI プロセッサ)と Mali(モバイル向け GPU)はゴミ箱行きですかね?ディスコンにしなくても、まともに開発しなくなりそうです。

さらに NVIDIA は NVIDIA GPU をライセンスするメリットがない(自分で作れる)ので、組み込み業界は Imagination PowerVR と Arm Mali という 2大モバイル GPU IP を揃って失ってしまう悲しい未来になりそうです。

他陣営(RISC-V とか)はまともな GPU IP がありませんし、Arm がどんな地獄になろうとも、グラフィック必須のスマホ系 SoC ベンダーに逃げ場はなく、Arm で作り続けるしかありません。

Arm の築いたエコシステムが Arm ごとひっくり返されるとは思いませんでしたね。耐震構造のビルを建てていたら、いきなり一面の海になるような……バグったゲームみたいですが、現実なんですよね。

中国は Imagination ごと PowerVR を買ってるので、Arm もしくは RISC-V 用に PowerVR を売り出す可能性はあります(もう売ってる?)けど、PowerVR って性能的に今の時代の GPU に追いついているんでしょうか??

メモ: 技術系?の話は Facebook から転記しておくことにした。

[編集者: すずき]
[更新: 2020年 9月 23日 02:47]

コメント一覧

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



2020年 9月 15日

link permalink

link 編集する

Windows 10 謎の高負荷

今に始まったことではないのですが、Windows 10 が突然高負荷に陥ってしまうことがあります。大抵 Windows Update が裏で動いているだけのことが多いですが、今日は様子が違ったのでメモしておきます。


System プロセスが高負荷

いつもと違う点、その 1 は、System プロセスが高負荷に陥っていることです。Windows Update が裏で実行されている場合、マウスを動かしたりキーボードに触ったりすると負荷が下がります。今回はマウスを触っても負荷が下がりません。


1つのスレッドが高負荷

Process Explorer で見ると PsReturnProcessNonPagedPoolQuota() という関数がめちゃくちゃ頑張っていました。なんでしょうね?これ?

[編集者: すずき]
[更新: 2020年 9月 30日 17:58]

コメント一覧

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



2020年 9月 17日

link permalink

link 編集する

Zephyr SMP 対応

Zephyr RISC-V SMP 対応にチャレンジしてます。

Zephyr は既に他のアーキテクチャで SMP 対応されていますから、真似するだけの何がチャレンジなの?簡単でしょ?と思われるかもしれません。その考え方は間違ってないんですが、少なくとも私にとっては「余裕」と言えるほど簡単ではなさそうです。

RISC-V 向けの実装は AArch32 をパクっているようで「お前、シングルコアだろ?な?」的な実装がそこら中にあり、SMP を有効にした途端、猛烈にコンパイルエラーが出ます。

一個一個見てますが、先ほど、コンテキストスイッチから直さないとダメなことに気づきました。こりゃ先は長そうだ〜……。

[編集者: すずき]
[更新: 2020年 9月 23日 01:26]

コメント一覧

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



2020年 9月 18日

link permalink

link 編集する

読みが違う

Google 翻訳で「October」を翻訳すると、正しい訳の「10月」が出てくるのですが、


October の英→日訳

残念ながら読みが間違っていて「じゅうがつ」ではなく「じゅうつき」になっています。惜しい!

「コミュニティ確認済み」のバッジが付いていますが、読みはチェック漏れ?ですかねえ?日本語は同じ文字でも読みが違って難しいですね。

[編集者: すずき]
[更新: 2020年 9月 30日 17:46]

コメント一覧

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



2020年 9月 19日

link permalink

link 編集する

SHARP のマスク

今となっては、高級マスクになってしまった SHARP MA-1050 が届きました。マスクはその辺で買えるので、もはや SHARP から購入する必要はないんですけど、まあ、一種の記念です。


SHARP MA-1050

参考までに、先日マツキヨで買った中国製のマスクは 30枚入り 1,000円程度で、1枚 33円です。MA-1050 は 50枚入り 2,980円(送料別)で、1枚 60円ですから、かなり高級品の部類です。送料も含めると 1枚 80円近いです。

包装が綺麗すぎる

届いた箱を見たときに「異様に綺麗な段ボールだな……」って思いました。宛名シールも真っ直ぐはみ出ず貼ってあります。近所(千葉県)から送られていることも要因ですが、それにしても綺麗です。


段ボールがすごい綺麗

開けてさらにびっくりしたんですけど、段ボールとマスクの箱が全く同じ大きさでした。包装材は省けますけど、入れにくそうですね。


段ボールとマスクの箱の大きさがぴったり

DigiKey くらいのマトモな海外セラーから買っても、段ボールなんて大抵ベコベコで汚ないし、宛名やインボイスも斜めに貼られてます。でも、中身が無事で、宛先に正しく届けば良いんですよ。段ボールは包装材なんですから。それ以上こだわる意味がありません。

日本人はどうでも良いところにこだわりすぎで、コスト高になってるんじゃないの……??なんてことを段ボールを片付けながら思いました。

[編集者: すずき]
[更新: 2020年 9月 23日 04:48]

コメント一覧

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



2020年 9月 20日

link permalink

link 編集する

ヘッドフォンが壊れた

以前(2012年 11月 8日の日記参照)買ったヘッドフォン(audio-technica ATH-TAD500)が壊れました。

イヤーパッドは無事ですが、ヘッドバンドの合皮部分が破れて、内部のプラスチックの芯が見えています。まさかイヤーパッドより先にヘッドバンドが壊れるとは思わなんだ。

8年もの長きにわたって頑張ってくれて本当にありがたかったです。とても良い製品でした。

ヘッドフォン探し

家や会社でいくつかヘッドフォンを使ってきてわかりましたが、長時間の使用において楽なのは圧倒的に「イヤーパッドが布地」のヘッドフォンです。合皮的な材質は汗で肌にくっついて嫌なのと、傷んでくるとバリバリ剥がれてきて不快です。

今使っているヘッドフォンの後継者としては audio-technica ATH-AD500X ですかね。上位グレードの ATH-AD700X, ATH-AD900X も布地のイヤーパッドで良さそうです。

しかし形が今使っているヘッドフォンとかなり似ているため、おそらく耳が痛くなる欠点も共通だろうと思われます。耳が痛いのは何とかしたいため、他社でも探すことにしました。

オーディオの世界は上を見るとキリがないですけど、10万とかするヘッドフォンは検討しません。違いが全くわからん。

新しいヘッドフォン

SENNHEISER HD 599 を買いました。Amazon で 22,000円くらいでした。以前購入(2020年 8月 1日の日記参照)した SONY MDR-HW700DS もそうでしたけど、購入に勇気が要るお値段です。

付け心地はとても軽く、耳も痛くなりません。耳が痛いのは辛かったので、非常にありがたいです。あと、色が良いですね。ヘッドフォンはたいてい真っ黒けですけど、HD 599 は白と茶という面白い色です。


SENNHEISER HD 599

難点は長時間使っていると頭のてっぺんが痛くなることです。ヘッドバンドは柔らかいクッションが入っていますが、カーブが自分の頭の形と合わないようです。

耳の問題が解決したと思ったら、今度は頭が痛くなるとは……想定外でした。うーん。

音質

正直、音質は良いね!ってくらいしかわからんですが、ドスドスした低音や、キンキンした高音は目立たず、フラットな印象です。

キンキンした音は断然 Superlux HD681B(2019年 8月 25日の日記参照)の方が強いですし、低音は audio-technica ATH-TAD500 の方がやや強いです。

鳴らないわけじゃなくて、目立たないバランスになっているだけなので、気に入らないならイコライザでいじれば好きなテイストにできます。能力は高いからカスタマイズ自由って感じですね。

[編集者: すずき]
[更新: 2020年 9月 23日 03:06]

コメント一覧

  • hdk 
    うちのATH-AD300もやはり頭にプラスチックパーツが直に当たるようになりましたが、小一時間なら我慢できるのでそのまま使っています(^^; 布地のイヤーパッドは長持ちしていいですよね。 
    (2020年09月23日 12:26:45)
  • すずき 
    ありゃー、同じ壊れ方ですね。
    新たなヘッドフォンは買わないんですか? 
    (2020年09月24日 00:23:32)
  • hdk 
    最近は音楽聞く時やビデオ視聴時はミニコンポを使っているので、ヘッドフォンは仕事の電話会議にしか使っていないんですよね... 
    (2020年09月24日 21:43:16)
open/close この記事にコメントする



2020年 9月 22日

link permalink

link 編集する

3DMark

Steam のセールで 3DMark が意味不明なほど安くなっていたので、買いました。Steam は突然 90% OFF とか平然とやってくるので、定価とは……??という気分になります。


3DMark セール中@Steam

新しいマシンを買う(もしくはパーツを買う)時に役に立ちそうです。

[編集者: すずき]
[更新: 2020年 9月 23日 02:55]

コメント一覧

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



2020年 9月 28日

link permalink

link 編集する

Zephyr OS で遊ぼう その 12 - SMP 対応の準備、コンテキストスイッチのコンフィグ

目次: Zephyr を調べる - まとめリンク

以前(2020年 2月 21日の日記参照)ご紹介したとおり、Zephyr の RISC-V 向け実装は SMP に対応していません。Zephyr のマルチコア対応を有効にするコンフィグは CONFIG_SMP ですが、有効にすると大量のエラーが出て、何から直したら良いのかわからなくなります。

コンフィグや実装を見た限りでは、下記の 4つのステップで対応していくと良さそうです。

  • (今ここ)SMP の前提条件、新しいコンテキストスイッチ方式に対応する(CONFIG_USE_SWITCH, CONFIG_USE_SWITCH_SUPPORTED)
  • SMP に対応する(CONFIG_SMP)、ただし CPU コア数は 1
  • 先頭ではないコア(mhartid != 0)で動作させる、ただし CPU コア数は 1
  • CPU コア数を 1以上にする(CONFIG_SMP)

このコンフィグを有効にすその前に対応すべきコンフィグがあります。CONFIG_USE_SWITCH と CONFIG_USE_SWITCH_SUPPORTED です。

CONFIG_USE_SWITCH

// zephyr/kernel/Kconfig

config USE_SWITCH
	bool "Use new-style _arch_switch instead of arch_swap"
	depends on USE_SWITCH_SUPPORTED

...

config SMP
	bool "Enable symmetric multithreading support"
	depends on USE_SWITCH

既存の各アーキテクチャの対応状況を見ると、

CONFIG_USE_SWITCH 対応状況(一例)

// ARM
// Aarch32 は USE_SWITCH は未サポート
// AArch64 は Cortex-A のみ USE_SWITCH をサポート

// zephyr/arch/arm/core/aarch64/Kconfig

config CPU_CORTEX_A
	bool
	select CPU_CORTEX
	select HAS_FLASH_LOAD_OFFSET
	select USE_SWITCH
	select USE_SWITCH_SUPPORTED


// ARC
// ARCV2 は USE_SWITCH をサポート

// zephyr/arch/arc/Kconfig

config CPU_ARCV2
	bool
	select ARCH_HAS_STACK_PROTECTION if ARC_HAS_STACK_CHECKING || ARC_MPU
	select ARCH_HAS_USERSPACE if ARC_MPU
	select USE_SWITCH
	select USE_SWITCH_SUPPORTED
	default y

他は載せませんが AArch64, ARC, x86_64, xtensa が対応しているようです。

Zephyr のコンテキストスイッチ

Zephyr にはコンテキストスイッチが 2種類存在しています。違いは下記の通りです。

  • arch_swap: シングルコア専用のコンテキストスイッチ、RISC-V は対応済み
  • arch_switch: SMP に対応したコンテキストスイッチ、シングルコアだと若干効率が悪い、RISC-V は未対応

CONFIG_USE_SWITCH_SUPPORTED を有効にするには arch_switch を実装する必要がありますが、RISC-V ではシングルコアもマルチコアもあり得ますから、CONFIG_USE_SWITCH を無条件に有効にするのは得策ではないと考えられます。

SoC 側の Kconfig で有効にしても良いですし、ユーザーに menuconfig から選んでもらう手もあります。あまり悩んでも仕方ないので、ユーザーが選べるようにして先に進めます。RISC-V の Kconfig に SMP 対応かどうか?を選べるコンフィグを一つ追加します。

CONFIG_USE_SWITCH を有効にする RISC-V 向けコンフィグを追加

// zephyr/arch/riscv/Kconfig

config RISCV_SMP
	bool "Does SOC has SMP"
	select USE_SWITCH
	select USE_SWITCH_SUPPORTED

有効にするとビルドエラーだらけになります。続きはまた今度。

[編集者: すずき]
[更新: 2020年 10月 14日 20:02]

コメント一覧

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



2020年 9月 29日

link permalink

link 編集する

Zephyr OS で遊ぼう その 13 - SMP 対応の準備、コンテキストスイッチの実装、前編、ラッパー関数まで実装

目次: Zephyr を調べる - まとめリンク

CONFIG_USE_SWITCH を有効にするとビルドエラーが発生しますので、都度対処します。

arch_thread_return_value_set()

最初は arch_thread_return_value_set() が二重定義だと怒られます。

arch_thread_return_value_set() が二重定義
../kernel/include/kernel_internal.h: At top level:
../kernel/include/kernel_internal.h:84:1: error: redefinition of 'arch_thread_return_value_set'
 arch_thread_return_value_set(struct k_thread *thread, unsigned int value)
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

USE_SWITCH を有効にした場合、arch_thread_return_value_set() はカーネル側で定義されるので、アーキテクチャ側では実装不要です。

arch_thread_return_value_set() カーネル側の定義

// zephyr/arch/riscv/include/kernel_arch_func.h

static ALWAYS_INLINE void
arch_thread_return_value_set(struct k_thread *thread, unsigned int value)
{
	thread->arch.swap_return_value = value;
}


// zephyr/include/arch/riscv/thread.h

struct _thread_arch {
	uint32_t swap_return_value; /* Return value of z_swap() */
};


// zephyr/arch/riscv/core/offsets/offsets.c

GEN_OFFSET_SYM(_thread_arch_t, swap_return_value);

この辺りは要らないので #ifndef CONFIG_USE_SWITCH で囲って無効化します。

arch_switch

次は arch_switch() が未定義だと言われます。

arch_switch() が未定義
../kernel/include/kernel_arch_interface.h:126:20: warning: 'arch_switch' declared 'static' but never defined [-Wunused-function]
 static inline void arch_switch(void *switch_to, void **switched_from);
                    ^~~~~~~~~~~

この関数が USE_SWITCH の本体です。他のアーキテクチャを見ると、arch_switch() はラッパー関数で、本体はアセンブラで書かれていることが多いようです。他の流儀に習っておきます。

arch_switch() ラッパー

// arch/riscv/include/kernel_arch_func.h

#ifdef CONFIG_USE_SWITCH
extern void z_riscv_switch(void *switch_to, void **switched_from);

static inline void arch_switch(void *switch_to, void **switched_from)
{
	z_riscv_switch(switch_to, switched_from);
}
#else

...

肝心の中身はまた今度実装します。

[編集者: すずき]
[更新: 2020年 10月 9日 00:29]

コメント一覧

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



2020年 9月 30日

link permalink

link 編集する

Zephyr OS で遊ぼう その 14 - SMP 対応の準備、コンテキストスイッチの実装、中編 1、既存実装調査

目次: Zephyr を調べる - まとめリンク

前回は、新しい形式のコンテキストスイッチ関数 arch_switch() のラッパー関数まで実装しました。RISC-V 向け実装をする前に、既に新しいコンテキストスイッチ関数に対応している AArch64 の実装を調べます。

共通部分からアーキ依存部分

コンテキストスイッチ処理の共通部分(do_swap())から、アーキテクチャ依存部分(AArch64 の arch_switch())に至るまでを見ます。

Zephyr の新しい形式のコンテキストスイッチ関数(共通部分)

// zephyr/kernel/include/kswap.h

/* New style context switching.  arch_switch() is a lower level
 * primitive that doesn't know about the scheduler or return value.
 * Needed for SMP, where the scheduler requires spinlocking that we
 * don't want to have to do in per-architecture assembly.
 *
 * Note that is_spinlock is a compile-time construct which will be
 * optimized out when this function is expanded.
 */
static ALWAYS_INLINE unsigned int do_swap(unsigned int key,
					  struct k_spinlock *lock,
					  int is_spinlock)
{

...

	if (new_thread != old_thread) {
#ifdef CONFIG_TIMESLICING
		z_reset_time_slice();
#endif

		old_thread->swap_retval = -EAGAIN;

#ifdef CONFIG_SMP
		_current_cpu->swap_ok = 0;

		new_thread->base.cpu = arch_curr_cpu()->id;

		if (!is_spinlock) {
			z_smp_release_global_lock(new_thread);
		}
#endif
		sys_trace_thread_switched_out();
		_current_cpu->current = new_thread;
		wait_for_switch(new_thread);
		arch_switch(new_thread->switch_handle,
			     &old_thread->switch_handle);    //★コンテキストスイッチ★

	}

...

前々回に紹介した新しいコンテキストスイッチの方式(arch_switch())を使っていることがわかります。

AArch64 の新しい形式のコンテキストスイッチ arch_switch() 実装、入り口

// zephyr/arch/arm/include/aarch64/arch_func.h

static inline void arch_switch(void *switch_to, void **switched_from)
{
	z_arm64_call_svc(switch_to, switched_from);    //★アセンブラ実装へ★

	return;
}


// zephyr/arch/arm/core/aarch64/switch.S

GTEXT(z_arm64_call_svc)
SECTION_FUNC(TEXT, z_arm64_call_svc)
	svc	#_SVC_CALL_CONTEXT_SWITCH    //★スーパーバイザーコール命令★
	ret

アセンブラ側の実装では、いきなりスーパーバイザーコール命令(svc 命令)をぶっ放して、スーパーバイザーコール例外を起こすだけになっています。AArch64 は例外ハンドラ内でコンテキストスイッチを行う仕組みのようです。

arch_switch() の引数

関数 arch_switch() はスレッドを 2つ受け取ります。old_thread は切り替え元のスレッド、new_thread は切り替え先のスレッドです。old_thread -> new_thread にコンテキストスイッチするわけです。ただし直接スレッド構造体を受け取るわけではなく、若干クセのある渡し方をします。

第 1引数 new_thread->switch_handle(void * 型)は切り替え先のスレッド構造体へのポインタ(struct k_thread * 型) new_thread が入っています。このポインタをキャストすると new_thread が求められます。

実はここには罠があり、何も実装せずにいると new_thread->switch_handle には NULL が入ります。すると、あとで CONFIG_SMP を有効にした段階で wait_for_switch() 関数にてハングアップします。こんなのパッと見ではわかりません……。

switch_handle の初期化

// zephyr/kernel/include/kswap.h

/* New style context switching.  arch_switch() is a lower level
 * primitive that doesn't know about the scheduler or return value.
 * Needed for SMP, where the scheduler requires spinlocking that we
 * don't want to have to do in per-architecture assembly.
 *
 * Note that is_spinlock is a compile-time construct which will be
 * optimized out when this function is expanded.
 */
static ALWAYS_INLINE unsigned int do_swap(unsigned int key,
					  struct k_spinlock *lock,
					  int is_spinlock)
{

...

	if (new_thread != old_thread) {

...

		sys_trace_thread_switched_out();
		_current_cpu->current = new_thread;
		wait_for_switch(new_thread);    //★これ★
		arch_switch(new_thread->switch_handle,
			     &old_thread->switch_handle);

	}

	if (is_spinlock) {
		arch_irq_unlock(key);
	} else {
		irq_unlock(key);
	}

	return _current->swap_retval;
}


/* There is an unavoidable SMP race when threads swap -- their thread
 * record is in the queue (and visible to other CPUs) before
 * arch_switch() finishes saving state.  We must spin for the switch
 * handle before entering a new thread.  See docs on arch_switch().
 *
 * Note: future SMP architectures may need a fence/barrier or cache
 * invalidation here.  Current ones don't, and sadly Zephyr doesn't
 * have a framework for that yet.
 */
static inline void wait_for_switch(struct k_thread *thread)
{
#ifdef CONFIG_SMP
	volatile void **shp = (void *)&thread->switch_handle;

	while (*shp == NULL) {    //★CONFIG_SMP 有効で new_thread->switch_handle が NULL だとこのループでハング★
		k_busy_wait(1);
	}
#endif
}

AArch64 向けのスレッド作成する関数を良く見ると、しれっと switch_handle を初期化しています。この処理は RISC-V 向けには存在しないため、追加する必要がありそうです。

switch_handle の初期化

// zephyr/arch/arm/core/aarch64/thread.c

/*
 * An initial context, to be "restored" by z_arm64_context_switch(), is put at
 * the other end of the stack, and thus reusable by the stack when not needed
 * anymore.
 */
void arch_new_thread(struct k_thread *thread, k_thread_stack_t *stack,
		     char *stack_ptr, k_thread_entry_t entry,
		     void *p1, void *p2, void *p3)
{

...

	/*
	 * We are saving:
	 *
	 * - SP: to pop out entry and parameters when going through
	 *   z_thread_entry_wrapper().
	 * - x30: to be used by ret in z_arm64_context_switch() when the new
	 *   task is first scheduled.
	 */

	thread->callee_saved.sp = (uint64_t)pInitCtx;
	thread->callee_saved.x30 = (uint64_t)z_thread_entry_wrapper;

	thread->switch_handle = thread;    //★ここで初期化している★
}

関数 arch_switch() の第 2引数 &old_thread->switch_handle(void ** 型)は切り替え元のスレッド構造体の switch_handle のポインタです。switch_handle の値(たいてい NULL)自体には意味がなくて、このポインタから old_thread が計算できることが大事です。

長くなってきたので続きは次回。

[編集者: すずき]
[更新: 2020年 10月 9日 00:29]

コメント一覧

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



こんてんつ

open/close wiki
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 過去日記について

その他の情報

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