目次: Linux
以前、OpenSBIがRISC-V CPUの拡張機能をどのように認識し有効にするか調べました。今回はどのようにCPUの数を認識するか調べます。OpenSBIのプラットフォームは今まで同様にgenericを使います。
実はそんなに難しくなく、デバイスツリーの/cpuノードを読みに行くだけのようです。/cpuノードの例としてQEMU virtマシン、4CPUで起動したときにQEMUが生成するデバイスツリーを下記に示します。長いのでCPU_2, 3は省略しています。名前やラベル、regが2や3になるだけです。
/ { cpus { #address-cells = <1>; #size-cells = <0>; timebase-frequency = <10000000>; cpu0: cpu@0 { compatible = "riscv"; reg = <0>; device_type = "cpu"; riscv,cbop-block-size = <64>; riscv,cboz-block-size = <64>; riscv,cbom-block-size = <64>; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "h", "zic64b", "zicbom", "zicbop", "zicboz", "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "za64rs", "zawrs", "zfa", "zca", "zcd", "zba", "zbb", "zbc", "zbs", "ssccptr", "sscounterenw", "sstc", "sstvala", "sstvecd", "svadu"; riscv,isa-base = "rv64i"; riscv,isa = "rv64imafdch_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ziccrse_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_za64rs_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_ssccptr_sscounterenw_sstc_sstvala_sstvecd_svadu"; mmu-type = "riscv,sv57"; status = "okay"; cpu0_intc: interrupt-controller { compatible = "riscv,cpu-intc"; interrupt-controller; #interrupt-cells = <1>; }; }; cpu1: cpu@1 { compatible = "riscv"; reg = <1>; device_type = "cpu"; riscv,cbop-block-size = <64>; riscv,cboz-block-size = <64>; riscv,cbom-block-size = <64>; riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "h", "zic64b", "zicbom", "zicbop", "zicboz", "ziccamoa", "ziccif", "zicclsm", "ziccrse", "zicntr", "zicsr", "zifencei", "zihintntl", "zihintpause", "zihpm", "za64rs", "zawrs", "zfa", "zca", "zcd", "zba", "zbb", "zbc", "zbs", "ssccptr", "sscounterenw", "sstc", "sstvala", "sstvecd", "svadu"; riscv,isa-base = "rv64i"; riscv,isa = "rv64imafdch_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ziccrse_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_za64rs_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_ssccptr_sscounterenw_sstc_sstvala_sstvecd_svadu"; mmu-type = "riscv,sv57"; status = "okay"; cpu1_intc: interrupt-controller { compatible = "riscv,cpu-intc"; interrupt-controller; #interrupt-cells = <1>; }; }; cpu2: cpu@2 { /* 省略 */ }; cpu3: cpu@3 { /* 省略 */ };
OpenSBIがCPU数を確認するコードは下記のようになっています。
// opensbi/platform/generic/platform.c
unsigned long fw_platform_init(unsigned long arg0, unsigned long arg1,
unsigned long arg2, unsigned long arg3,
unsigned long arg4)
{
const char *model;
void *fdt = (void *)arg1; //★★デバイスツリーのアドレス、レジスタa1を使ってOpenSBIに渡す★★
u32 hartid, hart_count = 0;
int rc, root_offset, cpus_offset, cpu_offset, len;
//...
cpus_offset = fdt_path_offset(fdt, "/cpus");
if (cpus_offset < 0)
goto fail;
fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
//★★device_typeがcpu、regプロパティに値が入っていれば、CPUのノードとみなす★★
rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
if (rc)
continue;
if (SBI_HARTMASK_MAX_BITS <= hartid)
continue;
//★★statusプロパティがokかokayなら有効なCPUとみなす★★
if (!fdt_node_is_enabled(fdt, cpu_offset))
continue;
//★★ハート数を1つ増やす★★
//★★hartidは連番とは限らないため、hartindexとhartidの対応表を作る★★
generic_hart_index2id[hart_count++] = hartid;
}
//★★platform領域に記録する★★
platform.hart_count = hart_count;
platform.heap_size = fw_platform_calculate_heap_size(hart_count);
platform_has_mlevel_imsic = fdt_check_imsic_mlevel(fdt);
デバイスツリー(正確にはFDT: flattened device tree)の/cpuノードを見て、子ノードがCPUの定義であり、有効ならばhart_countを+1するシンプルなコードです。hartidは連番とは限らないため、hartindex(0からCPU数 - 1までの連番)とhartidの対応表generic_hart_index2id[]も同時に作ります。
無効なCPUの扱いも見ておきます。先程のコードからわかる通りstatus = "disabled"つまり無効なCPUの場合は、hartindexとhartidの対応表(platform.hart_index2id[])にhartidが載りません。例としてQEMUにてCPU 4つで起動し、デバイスツリーでCPU 2だけstatus = "disabled"にしたときのhartindexとhartidの対応表をダンプします。
(gdb) p platform $11 = {opensbi_version = 65541, platform_version = 1, name = "riscv-virtio,qemo", '\000' <repeats 46 times>, features = 2, hart_count = 3, hart_stack_size = 8192, heap_size = 39936, reserved = 0, platform_ops_addr = 2148010200, firmware_context = 0, hart_index2id = 0x80083700 <generic_hart_index2id>} (gdb) p generic_hart_index2id $15 = {0, 1, 3, 0 <repeats 125 times>}
全CPUが有効ならば0, 1, 2, 3となりますが、CPU 2が無効なので0, 1, 3となっていることがわかります。
次にメインCPUがサブCPUを起こしに行くコードを見ます。アセンブラなので若干分かりづらいですが、hartindexとhartidの対応表に自CPUのhartidが登録されていればCPUを起動し、登録されていない場合はCPUを起動しません。
// opensbi/firmware/fw_base.S
_fdt_reloc_done:
//★★メインCPUはこちら★★
//★★初期化処理が終わるとここに到達する★★
//★★_boot_statusにBOOT_STATUS_BOOT_HART_DONEを書き込むとサブCPU側がループを抜ける★★
/* mark boot hart done */
li t0, BOOT_STATUS_BOOT_HART_DONE
lla t1, _boot_status
fence rw, rw
REG_S t0, 0(t1)
j _start_warm
//★★サブCPUはこちら★★
//★★_boot_statusを読みながらループで待っている★★
/* waiting for boot hart to be done (_boot_status == 2) */
_wait_for_boot_hart:
li t0, BOOT_STATUS_BOOT_HART_DONE
lla t1, _boot_status
REG_L t1, 0(t1)
/* Reduce the bus traffic so that boot hart may proceed faster */
div t2, t2, zero
div t2, t2, zero
div t2, t2, zero
bne t0, t1, _wait_for_boot_hart
_start_warm:
/* Reset all registers except ra, a0, a1, a2, a3 and a4 for non-boot HART */
li ra, 0
call _reset_regs
/* Disable all interrupts */
csrw CSR_MIE, zero
//★★platform.hart_index2idを見て、hartidと一致する要素があるか確かめる★★
/* Find HART count and HART stack size */
lla a4, platform
#if __riscv_xlen > 32
lwu s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4)
lwu s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4)
#else
lw s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4)
lw s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4)
#endif
//★★s9はplatform.hart_index2id[0]のアドレス★★
REG_L s9, SBI_PLATFORM_HART_INDEX2ID_OFFSET(a4)
/* Find HART id */
csrr s6, CSR_MHARTID
//★★platform.hart_index2idがNULLだったら処理を止める★★
/* Find HART index */
beqz s9, 3f
li a4, 0
//★★a4 = hartindex
//★★a5 = platform.hart_index2id[hartindex]の値(hartindexに対応するhartid)
//★★s6 = CPUのhartid
//★★s7 = hart数
//★★s9 = platform.hart_index2id[hartindex]のアドレス
1:
#if __riscv_xlen > 32
lwu a5, (s9)
#else
lw a5, (s9)
#endif
//★★hartidとplatform.hart_index2id[n]が一致していたら、ループを抜ける★★
beq a5, s6, 2f
//★★s9を進めて、platform.hart_index2id[0], [1], [2], ...を順に調べる★★
add s9, s9, 4
add a4, a4, 1
blt a4, s7, 1b
//★★s6 = hartindex
2: add s6, a4, zero
//★★hartindexがhart数を超えているか?
//★★ 超える : CPUが無効である、サブCPUを起動しない -> _start_hangへ
//★★ 超えない: CPUが有効である、サブCPUを起動する -> ブランチ命令の先へ
3: bge s6, s7, _start_hang
//...
OpenSBIのロゴが出たあたりでブレークして各スレッドの実行している関数名を見ると、下記のようになります。
(gdb) info thr Id Target Id Frame 1 Thread 1.1 (CPU#0 [halted ]) sbi_hsm_hart_wait (scratch=0x8008c000, hartid=0) at opensbi/lib/sbi/sbi_hsm.c:177 2 Thread 1.2 (CPU#1 [running]) 0x0000000080020ffe in fdt32_to_cpu (x=50331648) at opensbi/lib/utils/libfdt/libfdt_env.h:57 * 3 Thread 1.3 (CPU#2 [halted ]) _start_hang () at opensbi/firmware/fw_base.S:409 4 Thread 1.4 (CPU#3 [halted ]) sbi_hsm_hart_wait (scratch=0x80088000, hartid=3) at opensbi/lib/sbi/sbi_hsm.c:177
Thread Id 3つまりCPU 2(※)だけ_start_hang()に居ることがわかりますね。
(※)GDBのThread Idは1スタートなのでThread Id 1, 2, 3, 4がCPU 0, 1, 2, 3に相当します。
クラッキングでぶっ壊されてしまったニコニコ動画が復活し、シン・ニコニコ動画になって帰ってきました。いちユーザーとしては嬉しい気分でいっぱいです。
しかし前と比べて困ったことが起きていて、BizHawkで出力した動画をシン・ニコニコ動画にアップロードしようとすると「この動画の映像形式には対応していません」とエラーが出てしまって投稿できません。試行錯誤の末、解決方法を見つけたのでメモしておきます。
端的に言えば原因はピクセルフォーマットでした。BizHawkは特に何も言わないとYUV444で出力しますが、ニコニコ動画はYUV420でないと受け付けてくれないようです。どこにもそんな制約は書いていないため、仕様か?バグか?どちらともわかりません。
YUV420にする方法ですけども、BizHawkの出力設定を[Custom]にして、下記のようにピクセルフォーマットを明示的に指定すればYUV420で出力してくれました。
-c:a aac -c:v libx264 -preset ultrafast -pix_fmt yuv420p -f mp4
もしすでにYUV444で出力済みの動画ならば、ffmpegを使って再エンコードすれば良いです。
$ ffmpeg -i input.mp4 \ -vcodec libx264 -vb 2048000 -r 60 -s 1280x720 -pix_fmt yuv420p \ -acodec aac -ar 44100 -ab 192000 output.mp4
動画ビットレート(-vb)、フレームレート(-r)、動画サイズ(-s)は元の動画に応じて調整してください。
目次: Linux
WindowsからUbuntu 22.04 LTSのxrdpに接続すると真っ黒な画面しか出ず、すぐに接続が切断されてしまう問題に遭遇しました。どこでクラッシュしているか調べるため、xrdpがGNOMEを起動するところまでを追いかけると下記のようになっていました。
/etc/xrdp/startwm.sh exec /bin/sh /etc/X11/Xsession /etc/X11/Xsession.d/99x11-common_start /usr/bin/ssh-agent /usr/bin/im-launch x-session-manager /usr/bin/x-session-manager /usr/libexec/gnome-session-binary
スクリプトx-session-managerを書き換えてgnome-session-binaryの引数に--debugを足すと、GNOMEはより詳細なメッセージを出力します。メッセージはホームディレクトリの.xsession-errorsに記録されます。GNOMEのエラーメッセージは下記です。
** (gnome-session-check-accelerated-gles-helper:3301100): WARNING **: 13:50:04.884: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[3300977]: DEBUG(+): hardware acceleration check failed: Child process exited with code 1 gnome-session-binary[3300977]: GLib-DEBUG(+): setenv()/putenv() are not thread-safe and should not be used after threads are created gnome-session-binary[3300977]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-is-accelerated: No hardware 3D support. gnome-session-check-accelerated: GL Helper exited with code 256 ★★ここでエラーが発生している amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) ** (gnome-session-check-accelerated-gles-helper:3301174): WARNING **: 13:50:09.927: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[3300977]: WARNING: software acceleration check failed: Child process exited with code 1 gnome-session-binary[3300977]: CRITICAL: We failed, but the fail whale is dead. Sorry....
エラーメッセージ曰くamdgpu_get_auth()が失敗しています。関数名から推測するにAMDのGPUが装着されているマシンでしか発生しないエラーなのかもしれません。この関数は何者なのかDRMのソースコード(Mesa/libdrmのソースコードリポジトリへのリンク)を見ると、
// drm/amdgpu/amdgpu_device.c
/**
* Get the authenticated form fd,
*
* \param fd - \c [in] File descriptor for AMD GPU device
* \param auth - \c [out] Pointer to output the fd is authenticated or not
* A render node fd, output auth = 0
* A legacy fd, get the authenticated for compatibility root
*
* \return 0 on success\n
* >0 - AMD specific error code\n
* <0 - Negative POSIX Error code
*/
static int amdgpu_get_auth(int fd, int *auth)
{
int r = 0;
drm_client_t client = {};
if (drmGetNodeTypeFromFd(fd) == DRM_NODE_RENDER)
*auth = 0;
else {
client.idx = 0;
r = drmIoctl(fd, DRM_IOCTL_GET_CLIENT, &client); //★★エラーを返すのはこの関数だけ★★
if (!r)
*auth = client.auth;
}
return r;
}
// drm/xf86drm.c
/**
* Call ioctl, restarting if it is interrupted
*/
drm_public int
drmIoctl(int fd, unsigned long request, void *arg)
{
int ret;
do {
ret = ioctl(fd, request, arg); //★★drmIoctl()はioctl()が返すエラーをそのまま返す★★
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
return ret;
}
ソースコードから推測するにグラフィクス系のデバイスファイルにioctl()しようとして失敗しているものと思われます。
XのグラフィックスはDRI(Direct Rendering Infrastructure)と呼ばれるインタフェースを経由してGPUを使用します。Xとカーネルの間にはlibDRMがおり、libDRMはLinuxカーネルのDRM(Direct Rendering Manager)を使用してGPUにアクセスします。DRMのデバイスファイルは/dev/driディレクトリの下にありますが、名前がDRIだったりDRMだったりして謎ですね。グラフィクス周りはあまり詳しくなくて理由はわかりません……。
# ls /dev/dri/* -la crw-rw-rw-+ 1 root video 226, 0 4月 30 15:00 /dev/dri/card0 crw-rw-rw-+ 1 root render 226, 128 4月 30 15:00 /dev/dri/renderD128
上記のようにキャラクタデバイス/dev/dri/card0と/dev/dri/renderD128のパーミッションを0666にしたところエラーが出なくなりました。今回はこの2つのデバイスファイルで解決しましたが、他のデバイスファイルにもアクセスしていた場合はさらに調査が必要かもしれません。グラフィクス系のエラーは簡単に調べる方法がないのが辛いですね。
ちなみにこの症状はQEMUだと再現しません。card0のパーミッションを0000にするとlibEGLがエラーになるのですが、なぜかGNOMEはエラーを無視して起動してしまいます。AMDのときだけなんで止まるんでしょうか。いまいち釈然としません……。
目次: Linux
昨日(2024年10月1日の日記参照)のUbuntu 22とxrdpの組み合わせで起きるエラーですが、VirtualBox 7.0.18で再現できるかやってみました。グラフィクスはVMSVGAを選択しています。VMSVGAは/dev/driの下にcard0とrenderD128が生成されるようです。ちなみにgdm3は終了させてxrdpのみ起動しています。
ちなみに結論から言っておくと再現しません。以下が通常時のログです。正常に画面が表示されます。
gnome-session-binary[2774]: DEBUG(+): Enabling debugging gnome-session-binary[2774]: GLib-GIO-DEBUG(+): _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for ‘gsettings-backend’
ログにはデバッグ有効であることと、libEGLが何かしら見つけて反応しています。それ以外は特に何も出ません。
次にcard0とrenderD128をリネームしてアクセスできないようにして試しました。しかし正常に画面が表示されます。えぇー?ログは下記のようになりました。
gnome-session-binary[4319]: DEBUG(+): Enabling debugging gnome-session-check-accelerated: GL Helper exited with code 512 libEGL warning: DRI2: failed to authenticate gnome-session-check-accelerated: GLES Helper exited with code 512 gnome-session-binary[4319]: GLib-GIO-DEBUG(+): _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for ‘gsettings-backend’
AMDのGPUでも見かけたfailed to authenticateが出ています。が、エラーは無視されるんでしょうか?謎の動きですね。
目次: Linux
先日(2024年10月1日の日記参照)のUbuntu 22とxrdpの組み合わせで起きるエラーですが、Ubuntu 20で再現できるかやってみました。グラフィクスは種類が違いますがAMDのGPU(Radeon RX 6900 XT)です。条件は同様でgdm3は終了させてxrdpのみ起動しています。
結論から言うと再現しました。以下が通常時のログです。正常に画面が表示されます。
gnome-session-binary[2712263]: DEBUG(+): Enabling debugging gnome-session-binary[2712263]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-binary[2712263]: GLib-GIO-DEBUG(+): _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for ‘gsettings-backend’ gnome-session-binary[2712263]: WARNING: Error creating FIFO: File exists
ログにはデバッグ有効であること以外は特に何も出ません。FIFOが作れない警告が出ます、これは何だろう?まあいいか。
次にcard0とrenderD128をリネームしてアクセスできないようにして試しました。画面は真っ黒になってすぐに切断されます。Ubuntu 20.04 LTSでも再現するんですね。ログは下記のようになりました。
gnome-session-binary[2712010]: DEBUG(+): Enabling debugging gnome-session-binary[2712010]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-is-accelerated: No hardware 3D support. gnome-session-check-accelerated: GL Helper exited with code 256 amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) ** (gnome-session-check-accelerated-gles-helper:2712146): WARNING **: 14:44:50.573: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[2712010]: DEBUG(+): hardware acceleration check failed: Child process exited with code 1 gnome-session-binary[2712010]: GLib-DEBUG(+): setenv()/putenv() are not thread-safe and should not be used after threads are created gnome-session-binary[2712010]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-is-accelerated: No hardware 3D support. gnome-session-check-accelerated: GL Helper exited with code 256 amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) ** (gnome-session-check-accelerated-gles-helper:2712203): WARNING **: 14:44:55.610: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[2712010]: WARNING: software acceleration check failed: Child process exited with code 1 gnome-session-binary[2712010]: CRITICAL: We failed, but the fail whale is dead. Sorry....
前回も見かけたamdgpu_get_auth()のエラーが出て止まりました。AMDのGPUの場合、エラーが起きると止まってしまう疑いが強まりました。本当はエラーが起きたらソフトウェアレンダリングにフォールバックしてほしいんですけど、そうならないようです……。
デバイスファイルが/dev/driに全く存在しなくてもamdgpu_get_auth()が怒ってくるところを見ると、X.orgはPCのグラフィクスの種類(VirtualBoxなのかAMDのGPUなのか)を最初から決め打ちにしているんですね。
< | 2024 | > | ||||
<< | < | 09 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
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 | - | - | - | - | - |
合計:
本日: