目次: ROCK64/ROCKPro64
ROCKPro64でI2S0を無効にすると、なぜか無関係なはずのアナログオーディオ(I2S1)が鳴らなくなる、謎の挙動を示します。原因を調べてみると搭載SoCであるRockchip RK3399の不思議な設計が原因でした。
I2Sは大まかにいうと4種類の信号を使います。
RK3399の仕様をみるとMCLKの出力(RK3399のピン名だとI2S_CLK)をI2S0とI2S1で共用しています。普通、MCLKはI2Sに流す信号によって周波数が変わりますから、共用はしません。できる場合もありますが限定的です。
I2Sのハードとしては性能は等価に見えます。ただしSoCのピン設定の仕様を見る限りでは、I2S0は8ch出力まで可能、I2S1は2ch出力のみ可能です。
I2S0はRaspberryPi互換ピンヘッダに出力されていますが、MCLKは出力されていない不思議な構成です。MCLKがなくても動くDACはあるのでしょうか……?
I2S1はEverest ES8316というDACに接続され、アナログオーディオIn/Outを実現しています。I2S_CLKはI2S1用、つまりES8316のMCLKに接続されています。
ROCKPro64の仕様としては、I2S0は遊ばせていて、I2S1はアナログオーディオ用に接続している、と考えれば、特に違和感はない構成です。
Device Treeを見ると、I2S_CLKはI2S0の有効、無効の設定に連動して、出力ピンが制御されるように実装されています。
しかし先ほども言った通りROCKPro64の場合は、I2S_CLKはI2S1のために使われているので、この設定はボードの配線と合っていません。
直し方としては、I2S_CLKをI2S0に連動させる設定(既に存在する)に加えて、I2S_CLKをI2S1に連動させる設定を加えて、ボード側でピン設定を選ぶようにすると直せそうです。Device Tree内のピン設定がやたら増えるのは難点ですが、RK3399の仕様に由来するので仕方ないですね。
Twitterでこんな問題(リンク)を見かけたので、やってみました。緑色の図形の面積を求めよ、という問題です。
算数で解く=方程式やルートを使わない、という意味だと理解し、図形の合同性だけで解いてみます。
こんな感じで答えは4です。小学生にも解ける問題といえばそうなんでしょうけど、自分が小学生だったころに解けただろうか、と考えるとどうだろうね?
目次: ROCK64/ROCKPro64
ROCK64ブート周りの話のまとめ。
ROCK64オーディオ周りの話のまとめ。
ROCKPro64シリアル文字化けの話のまとめ。
ROCKPro64オーディオの話のまとめ。
ROCKPro64のその他の話のまとめ。
ARM関連の話。
【速報】テスラ「バッテリー・デー」のポイントを解説 - EVsmartブログ を読んで。
約1か月前のニュースですが「電池は自分で作るんで!さよなら!!」と鮮やかにポイ捨てされたパナソニックさん。
一緒に5000億の工場(ギガファクトリー1)を作り始めた(※1)かと思いきや、投資回収どころか、工場完成してないのに縁切り宣言を始める辺り、テスラは気が短すぎます。この決断スピードには、パナソニックはとても付いていけないでしょう。
今だから思いますが、ギガファクトリー1はうまく(?)できていて、セル:パナソニック、アセンブリ:テスラの分担となっていますので、テスラは離脱してもほぼ損害がありません。テスラは最初からバッテリー自社生産を狙っていたのでは?とすら感じます。
いずれにせよ困るのはパナソニックで、テスラに離脱されると、大量の2170セル生産能力が余ります(※2)。18650に転換してもテスラ並みの需要を持つ顧客はいるでしょうか?
(※1)ギガファクトリー1は合弁で建てているので、パナソニックとテスラの負担割合はわかりません。さすがにゼロってことはないでしょう。
(※2)ギガファクトリー1は、テスラ専用の2170(直径21mm x高さ70mm)という微妙にでかいバッテリーセルを作っており、標準的な18650(18mm x 65.0mm)セル使う機器には使いまわし効かないように見えます。
5年位前にギガファクトリー1のニュースを見たときは「テスラと組むなんて、パナソニックも変わったなあ〜」なんて感動しました。パナソニックの社運を賭けた投資、なんてニュースも目にしたものです。
ぼーっとしているとテスラに置いて行かれ、数年後にはギガファクトリー1が、パナソニックの大型失敗案件、砺波CCD(1000億)、尼崎プラズマ(4000億?)、三洋合併(6000億円?)にランクインしてしまいそうです。
完全にテスラに寄りかかって、何も考えてないパナソニックが悪い、ダシにされて当然だろ?っていわれたら、何も言い返せないですが、さすがに合弁作ってハイさようならは、ご無体すぎて可哀想ですね……。
メモ: 技術系の話はFacebookから転記しておくことにした。加筆修正。
目次: Zephyr
前回はリグレッションテストの実行環境を整備しました。今回はリグレッションテストで見つけたバグを修正します。
テストtests/kernel/smp/kernel.multiprocessing.smpが失敗しています。
ASSERTION FAIL [!arch_is_in_isr()] @ ZEPHYR_BASE/kernel/sched.c:1209
テスト対象のarch_is_in_isr() の実装を見ると、シングルコアを前提とした実装になっています。
// zephyr/arch/riscv/include/kernel_arch_func.h
static inline bool arch_is_in_isr(void)
{
return _kernel.cpus[0].nested != 0U; //★シングルコア前提になっている★
}
// (修正後)
static inline bool arch_is_in_isr(void)
{
return arch_curr_cpu()->nested != 0U;
}
直し方はarch_curr_cpu() に置き換えるだけで良さそうです。
他のテストではsched_ipi_has_calledが0のままらしく、怒られています。
Assertion failed at ZEPHYR_BASE/tests/kernel/smp/src/main.c:602: test_smp_ipi: (sched_ipi_has_called != 0 is false)
テスト対象のsched_ipi_has_calledをカウントアップする処理は下記のとおりです。
// zephyr/kernel/sched.c
#ifdef CONFIG_SMP
void z_sched_ipi(void)
{
/* NOTE: When adding code to this, make sure this is called
* at appropriate location when !CONFIG_SCHED_IPI_SUPPORTED.
*/
#ifdef CONFIG_TRACE_SCHED_IPI
z_trace_sched_ipi();
#endif
}
// zephyr/tests/kernel/smp/src/main.c
#ifdef CONFIG_TRACE_SCHED_IPI
/* global variable for testing send IPI */
static volatile int sched_ipi_has_called;
void z_trace_sched_ipi(void)
{
sched_ipi_has_called++;
}
コンフィグCONFIG_TRACE_SCHED_IPIが有効になっているときは、カーネルがz_trace_sched_ipi() を呼び出します。テストではCONFIG_TRACE_SCHED_IPIを有効にするとともに、この関数を定義して、カーネルから正常にコールバックされるかどうかを見ているようです。
以前(2020年10月16日の日記参照)、IPIのハンドラを実装した際にコメントアウトしてくれ、と言っていた部分がありました。あの部分が役に立ちます。
// zephyr/drivers/timer/riscv_machine_timer.c
#ifdef CONFIG_SMP
void z_riscv_sched_ipi(void);
static void soft_isr(const void *arg)
{
volatile uint32_t *r = (uint32_t *)RISCV_MSIP;
ARG_UNUSED(arg);
*r = 0;
z_riscv_sched_ipi(); //★この行を足す★
}
#endif
// zephyr/arch/riscv/core/cpu_smp.c
#ifdef CONFIG_SMP
void z_riscv_sched_ipi(void)
{
z_sched_ipi();
}
#endif
本当は直接z_sched_ipi() を呼べば良いんですが、drivers以下のソースコードからはz_sched_ipi() を呼ばない方が良さそう(関数プロトタイプが見えない)だったので、arch/riscvを経由させる変な実装になっています。どう実装するのが正しいんでしょうねえ?
これでSMP系のテストを通過しました。良かった良かった。
目次: Zephyr
前回はSMPに対応しました。今回はリグレッションテストを行う準備をします。
Zephyrにはsanitycheckというツールが用意されています。テストレポートやテスト用バイナリが生成されるので、Zephyrのトップディレクトリではなく、空ディレクトリを作ってから実行すると良いです。オプション -pでテストしたいプラットフォームを指定します。
$ mkdir __tmp $ cd __tmp $ sanitycheck -p qemu_riscv32 INFO - JOBS: 16 INFO - Building initial testcase list... INFO - 928 test configurations selected, 752 configurations discarded due to filters. INFO - Adding tasks to the queue... ...
いちいちsanitycheckを全部実行するとかなり時間が掛かります。テストにはタグが付いていて、sanitycheckはオプション -tで特定のタグが付いたテストのみを実行できます。便利ですね。
タグはどこから来ているかというとtestsディレクトリの下に存在するtestcase.yamlというファイルに書いてあります。
// zephyr/kernel/smp/testcase.yaml
tests:
kernel.multiprocessing.smp:
tags: smp //★これがタグ★
filter: (CONFIG_MP_NUM_CPUS > 1) //★フィルタ、この条件が真でないとテストがスキップされる★
SMP系のテストにはsmpというタグが付いているので、-t smpと指定します。
$ sanitycheck -p qemu_riscv32 -t smp INFO - JOBS: 16 INFO - Building initial testcase list... INFO - 928 test configurations selected, 925 configurations discarded due to filters. INFO - Adding tasks to the queue... INFO - Total complete: 3/ 3 100% skipped: 3, failed: 0 INFO - 0 of 0 tests passed (0.00%), 0 failed, 928 skipped with 0 warnings in 1.95 seconds INFO - In total 0 test cases were executed on 1 out of total 292 platforms (0.34%) INFO - 0 tests executed on platforms, 0 tests were only built.
残念ながらテストは全てスキップされてしまいます。原因はqemu_riscv32ボードはSMPに対応していない(CONFIG_SMPをselectしない)ため、testcase.yamlに書かれたフィルタに引っかかって除外されるからです。
先日作成したqemu_rv32_virtボードならばCONFIG_SMPが有効なので、テストが実行されるはずです。
$ sanitycheck -p qemu_rv32_virt -t smp INFO - JOBS: 16 INFO - Building initial testcase list... INFO - 0 test configurations selected, 0 configurations discarded due to filters. INFO - Adding tasks to the queue... INFO - 0 of 0 tests passed (0.00%), 0 failed, 0 skipped with 0 warnings in 0.65 seconds INFO - In total 0 test cases were executed on 0 out of total 291 platforms (0.00%) INFO - 0 tests executed on platforms, 0 tests were only built.
ダメですね。こういうときは既存のボードと見比べて差分を見るとわかりやすいです。どうやらboard.cmake, qemu_rv32_virt.yamlを作らないと、ボードが認識されないようです。
# zephyr/boards/riscv/qemu_rv32_virt/board.cmake # SPDX-License-Identifier: Apache-2.0 set(EMU_PLATFORM qemu) set(QEMU_binary_suffix riscv32)ARCH riscv32) ARCH -nographic -machine virt -cpu rv32 -bios none ) board_set_debugger_ifnset(qemu) // zephyr/boards/riscv/qemu_rv32_virt/qemu_rv32_virt.yaml identifier: qemu_rv32_virt name: QEMU RISCV32 virt target type: qemu simulation: qemu arch: riscv32 ram: 256 toolchain: - zephyr - xtools testing: default: true ignore_tags: - net - bluetooth
もう一度実行します。
$ sanitycheck -p qemu_rv32_virt -t smp INFO - JOBS: 16 INFO - Building initial testcase list... INFO - 928 test configurations selected, 925 configurations discarded due to filters. INFO - Adding tasks to the queue... ERROR - qemu_rv32_virt tests/kernel/smp/kernel.multiprocessing.smp FAILED: Timeout ERROR - see: zephyr/__tmp/sanity-out/qemu_rv32_virt/tests/kernel/smp/kernel.multiprocessing.smp/handler.log INFO - Total complete: 1/ 3 33% skipped: 0, failed: 1 ERROR - qemu_rv32_virt tests/kernel/spinlock/kernel.multiprocessing.spinlock FAILED: Failed ERROR - see: zephyr/__tmp/sanity-out/qemu_rv32_virt/tests/kernel/spinlock/kernel.multiprocessing.spinlock/handler.log INFO - Total complete: 3/ 3 100% skipped: 0, failed: 2 INFO - 1 of 3 tests passed (33.33%), 2 failed, 925 skipped with 0 warnings in 72.61 seconds INFO - In total 13 test cases were executed on 1 out of total 292 platforms (0.34%) INFO - 2 tests executed on platforms, 1 tests were only built.
いくつかのテストがFAILEDしている、すなわちデグレードしていることを示していますが、ひとまずテストは実行できました。次回はデグレードした箇所を直します。
目次: Zephyr
前回はマルチコアのブート処理を実装しました。今回はIPI (Inter-Processor Interrupt、プロセッサ間割り込み) を実装します。長きに渡ったSMP対応もようやく終盤です。
IPIとはInter-Processor Interrupt、プロセッサ間割り込みのことで、SMPの核となる機能です。プロセッサ間で何かイベントを伝えたい(今回の場合はスレッドスケジューラを動かしてほしい)ときにIPIを発生させます。
RISC-V Privilegeの場合、IPIを発生させるにはCLINTを使います。CLINTのmsipレジスタの最下位ビットは、それぞれのHARTのmipレジスタのMSIPビットに繋がっています。平たく言えばmsipレジスタに1を書き込むと他のHARTにソフトウェア割り込みが発生する仕組みです。
CLINTはタイマードライバの実装のときに出てきました(2020年10月14日の日記参照)。IPIの実装は、他アーキテクチャだとzephyr/arch/*/coreの下に実装していることが多いですが、RISC-Vの場合はタイマードライバzephyr/drivers/timer/riscv_machine_timer.cに実装すると早いです。このやり方で合っているのかはちょっとわかりません。割り込みコントローラとして新たに実装した方が筋が良さそうではあります。
IPIの実装を発生させる側と受け取る側に分けて説明します。
// zephyr/drivers/timer/riscv_machine_timer.c
#define RISCV_MSIP_OTHER(id) (RISCV_MSIP_BASE + (uintptr_t)(id) * 4)
#define RISCV_MSIP RISCV_MSIP_OTHER(z_riscv_hart_id())
...
#ifdef CONFIG_SMP
void arch_sched_ipi(void)
{
uint32_t id = z_riscv_hart_id();
for (int i = 0; i < CONFIG_MP_NUM_CPUS; i++) {
volatile uint32_t *r = (uint32_t *)RISCV_MSIP_OTHER(i);
if (i == id)
continue; //★自分自身には割り込みを発生させない★
*r = 1;
}
}
...
発生させる側の実装はarch_sched_ipi() 関数を定義して、自分以外のHARTに割り込みを発生させます。シンプルで良いですね。
IPIを発生させる処理も確認します。何箇所かありますが、短めのものを例として挙げます。
// zephyr/kernel/sched.c
static void ready_thread(struct k_thread *thread)
{
if (z_is_thread_ready(thread)) {
sys_trace_thread_ready(thread);
_priq_run_add(&_kernel.ready_q.runq, thread);
z_mark_thread_as_queued(thread);
update_cache(0);
#if defined(CONFIG_SMP) && defined(CONFIG_SCHED_IPI_SUPPORTED)
arch_sched_ipi(); //★ここで呼ばれている★
#endif
}
}
コンフィグCONFIG_SMPは既に有効にしていますが、それ以外にもCONFIG_SCHED_IPI_SUPPORTEDを有効にする必要があるようです。
// zephyr/drivers/timer/Kconfig
config RISCV_MACHINE_TIMER
bool "RISCV Machine Timer"
depends on SOC_FAMILY_RISCV_PRIVILEGE
select TICKLESS_CAPABLE
select SCHED_IPI_SUPPORTED #★この行を足す★
help
This module implements a kernel device driver for the generic RISCV machine
timer driver. It provides the standard "system clock driver" interfaces.
今回IPIの機構を実装したのはタイマードライバですので、タイマーのKconfigに追加しています。
IPIを受け取る側の実装です。マスターコアとスレーブコアで呼ばれる関数が違う点は少しややこしいですが、基本的にやることは一緒です。前回(2020年10月10日の日記参照)、空関数で実装したsmp_timer_init() を真面目に実装するときが来ました。
#ifdef CONFIG_SMP
void z_riscv_sched_ipi(void);
static void soft_isr(const void *arg)
{
volatile uint32_t *r = (uint32_t *)RISCV_MSIP;
ARG_UNUSED(arg);
*r = 0; //★ソフトウェア割り込みをクリア★
z_riscv_sched_ipi(); //★IPIテスト用の関数(後日に説明予定)今はリンクエラーになるはずなので、コメントアウトしてOK★
}
#endif
//★マスターコア用のタイマー初期化関数★
int z_clock_driver_init(const struct device *device)
{
ARG_UNUSED(device);
IRQ_CONNECT(RISCV_MACHINE_TIMER_IRQ, 0, timer_isr, NULL, 0);
last_count = mtime();
set_mtimecmp(last_count + CYC_PER_TICK);
irq_enable(RISCV_MACHINE_TIMER_IRQ);
#ifdef CONFIG_SMP
IRQ_CONNECT(RISCV_MACHINE_SOFT_IRQ, 0, soft_isr, NULL, 0); //★ソフトウェア割り込みの割り込みハンドラを設定する★
irq_enable(RISCV_MACHINE_SOFT_IRQ); //★ソフトウェア割り込み有効★
#endif
return 0;
}
...
//★スレーブコア用のタイマー初期化関数★
//★マスターコアが割り込みハンドラの設定をするので、割り込みを有効にするだけに留める★
void smp_timer_init(void)
{
last_count = mtime();
set_mtimecmp(last_count + CYC_PER_TICK);
irq_enable(RISCV_MACHINE_TIMER_IRQ);
irq_enable(RISCV_MACHINE_SOFT_IRQ);
}
#endif /* CONFIG_SMP */
割り込みを有効にして、ソフトウェア割り込みハンドラでCLINTのmsipレジスタをクリアします。msipのクリアを忘れると割り込みハンドラが終わった直後、またすぐソフトウェア割り込みが入って、ハンドラが呼ばれて、割り込みが入って、ハンドラが呼ばれて、、、を繰り返してしまい処理が先に進まなくなって、ハングします。
前回作成した環境を流用して動作確認します。
$ ninja run [0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: riscv32 *** Booting Zephyr OS build zephyr-v2.4.0-546-g720718653f92 *** 1: thread_a: Hello World from QEMU RV32 virt board! 2: thread_b: Hello World from QEMU RV32 virt board! 0: thread_a: Hello World from QEMU RV32 virt board! 2: thread_b: Hello World from QEMU RV32 virt board! 1: thread_a: Hello World from QEMU RV32 virt board! 3: thread_b: Hello World from QEMU RV32 virt board! 1: thread_a: Hello World from QEMU RV32 virt board! ...
やった!動きました。スレッドがHART 0だけでなく、別のHARTでも実行されている様子がわかります。
リグレッションテストについては、また次回。
< | 2020 | > | ||||
<< | < | 10 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)