NetBSDでspeech-to-textをしてみる

この記事は、NetBSD Advent Calendar 2024の15日目の記事です。

speech-to-textエンジンを選ぶ

音声からテキストに変換してくれるのが、speech-to-textエンジンです。 OpenAIのWhisper v3というのが、13日目に触れたllama.cppと同じ作者による whisper.cppで利用できるようです。 pkgsrc/wip/whisper.cppからインストールできるようにしたので、それを使ってみることにします。

whisper.cppのインストール

pkgsrc/wip/whisper.cppとして利用できるので、The pkgsrc-wip projectのページを参考に/usr/pkgsrc/wipに用意しておきます。 用意ができたら、以下のようにインストールすれば良いでしょう。

# cd /usr/pkgsrc/wip/whispwer.cpp
# make install

併わせて、ggml形式のWhisper large V3モデルファイルも入手しておきます。 modelsディレクトリーを作って、ggml-large-v3.binというファイル名で置いておくことにします。

whisper.cppへの入力を用意する

LibriVoxというパブリックドメインなテキストを読んたパブリックドメインな音声のMP3ファイルを公開しているプロジェクトがあります。 そこに、新美南吉の「手袋を買いに」をテキスト化してもらうようにします。 ちなみに、「手袋を買いに」は小学校の教科書には既に採用されていないようです。

pkgsrc/wip/whisper.cppは、16ビットのWAVファイルが入力ファイルになるようです。ffmpegでの変換例が提供されているので、以下のようにしてみます。

# cd /usr/pkgsrc/multimedia/ffmpeg7
# make install
$ ftp https://www.archive.org/download/multilingual_shorts_006_1303/msw006_03_tebukurowokaini_niimi_um.mp3
$ ffmpeg7 -y -i msw006_03_tebukurowokaini_niimi_um.mp3 -ar 16000 -ac 1 -c:a pcm_s16le msw006_03_tebukurowokaini_niimi_um.wav

これで、msw006_03_tebukurowokaini_niimi_um.wavという入力ファイルが準備できました。

speech-to-textを実行してみる

llama.cppのように多くのパラメーターはなさそうです。日本語を設定しないと奇妙なテキストが生成されたり、英訳されたテキストが生成されたりするので、-l jaと指定して、日本語を設定するようにします。 結果も含めて、以下のようです。

$ whisper-cli -l ja -m ./models/ggml-large-v3.bin msw006_03_tebukurowokaini_niimi_um.wav
size=   21295KiB time=00:11:21.42 bitrate= 256.0kbits/s speed=2.53e+03x
whisper_init_from_file_with_params_no_state: loading model from './models/ggml-large-v3.bin'
whisper_init_with_params_no_state: use gpu    = 1
whisper_init_with_params_no_state: flash attn = 0
whisper_init_with_params_no_state: gpu_device = 0
whisper_init_with_params_no_state: dtw        = 0
whisper_init_with_params_no_state: devices    = 2
whisper_init_with_params_no_state: backends   = 2
whisper_model_load: loading model
whisper_model_load: n_vocab       = 51866
whisper_model_load: n_audio_ctx   = 1500
whisper_model_load: n_audio_state = 1280
whisper_model_load: n_audio_head  = 20
whisper_model_load: n_audio_layer = 32
whisper_model_load: n_text_ctx    = 448
whisper_model_load: n_text_state  = 1280
whisper_model_load: n_text_head   = 20
whisper_model_load: n_text_layer  = 32
whisper_model_load: n_mels        = 128
whisper_model_load: ftype         = 1
whisper_model_load: qntvr         = 0
whisper_model_load: type          = 5 (large v3)
whisper_model_load: adding 1609 extra tokens
whisper_model_load: n_langs       = 100
whisper_model_load:      CPU total size =  3094.36 MB
whisper_model_load: model size    = 3094.36 MB
whisper_backend_init: using BLAS backend
whisper_init_state: kv self size  =   83.89 MB
whisper_init_state: kv cross size =  251.66 MB
whisper_init_state: kv pad  size  =    7.86 MB
whisper_init_state: compute buffer (conv)   =   36.13 MB
whisper_init_state: compute buffer (encode) =  212.29 MB
whisper_init_state: compute buffer (cross)  =    9.25 MB
whisper_init_state: compute buffer (decode) =   99.10 MB

system_info: n_threads = 4 / 16 | AVX = 1 | AVX2 = 1 | AVX512 = 1 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 | COREML = 0 | OPENVINO = 0 |

main: processing 'msw006_03_tebukurowokaini_niimi_um.mp3.wav' (10902837 samples, 681.4 sec), 4 threads, 1 processors, 5 beams + best of 5, lang = ja, task = transcribe, timestamps = 1 ...


[00:00:00.480 --> 00:00:03.720]  リブリボックス ドットオグのために録音されました
[00:00:03.720 --> 00:00:07.420]  手袋を買いに 新見南吉
[00:00:07.420 --> 00:00:13.460]  寒い冬が北方から 狐の親子の住んでいる森へもやってきました
[00:00:13.460 --> 00:00:18.600]  ある朝 ほら穴から子供の狐が出ようとしましたが
[00:00:18.600 --> 00:00:24.340]  アッと叫んで 目を押さえながら 母さん狐のところ へ転げてきました
[00:00:25.440 --> 00:00:31.040]  母ちゃん 目に何か刺さった 抜いてちょうだい 早く早くと言いました
[00:00:31.040 --> 00:00:36.200]  母さん狐がびっくりして 慌てふためきながら
[00:00:36.200 --> 00:00:39.980]  目を押さえている子供の手を おそるおそる取り除けてみましたが
[00:00:39.980 --> 00:00:42.540]  何も刺さってはいませんでした
[00:00:42.540 --> 00:00:48.960]  母さん狐は ほら穴の入口から外へ出て 初めて脇が わかりました
[00:00:48.960 --> 00:00:53.640]  昨夜のうちに 真っ白な雪がどっさり降ったのです
[00:00:54.720 --> 00:00:59.280]  その雪の上から お日様がキラキラと照らしていたので
[00:00:59.280 --> 00:01:02.960]  雪はまぶしいほど 反射していたのです
[00:01:02.960 --> 00:01:08.120]  雪を知らなかった子供の狐は あまり強い反射を受けたので
[00:01:08.120 --> 00:01:12.460]  目に何か刺さったと思ったのでした
[00:01:12.460 --> 00:01:15.660]  子供の狐は遊びに行きました
[00:01:15.660 --> 00:01:19.140]  真綿のように 柔らかい雪の上を駆け回ると
[00:01:19.140 --> 00:01:24.000]  雪の粉がしぶきのように飛び散って 小さい虹がすっと映るのでした
[00:01:24.000 --> 00:01:31.960]  すると突然 後ろでドタドタザーッと ものすごい音 がして
[00:01:31.960 --> 00:01:38.040]  パン粉のような粉雪が ふわっと子狐におっかぶさってきました
[00:01:38.040 --> 00:01:42.180]  子狐はびっくりして雪の中に転がるようにして
[00:01:42.180 --> 00:01:45.480]  10メートルも向こうへ逃げました
[00:01:45.480 --> 00:01:51.060]  何だろうと思って振り返ってみましたが 何もいませんでした
[00:01:51.060 --> 00:01:53.280]  それは モミの枝から雪がなだれ出てきたので
[00:01:53.280 --> 00:02:01.240]  まだ枝と枝の間から白い絹糸のように 雪がこぼれていました
[00:02:01.240 --> 00:02:05.520]  まもなくほら穴へ帰ってきた小狐は
[00:02:05.520 --> 00:02:11.160]  「お母ちゃん おててが冷たい おててがチンチンす る」と言って
[00:02:11.160 --> 00:02:16.160]  濡れてボタン色になった両手を 母さん狐の前に差し出しました
[00:02:16.160 --> 00:02:21.360]  母さん狐はその手に ハーッと息をふっかけて
[00:02:21.360 --> 00:02:23.080]  ぬくとい母さんの声を聞きました
[00:02:23.080 --> 00:02:23.100]  母さんの声を聞きました
[00:02:23.100 --> 00:02:23.200]  母さんの声を聞きました
[00:02:23.200 --> 00:02:25.400]  母さんの手でやんわり包んでやりながら
[00:02:25.400 --> 00:02:32.860]  「もうすぐ暖かくなるよ 雪をさわると すぐ暖かく なるもんだよ」と言いましたが
[00:02:32.860 --> 00:02:37.180]  かわいい坊やの手にひもやきができては かわいそうだから
[00:02:37.180 --> 00:02:45.020]  夜になったら町まで行って坊やのおててに合うよう な 毛糸の手袋を買ってやろうと思いました
[00:02:45.020 --> 00:02:52.820]  暗い暗い夜が風呂敷のような影を広げて 野原や森を包みにやってきましたが
[00:02:52.820 --> 00:02:53.020]  母さんの手でやんわり包んでやりながら もうすぐ暖かくなるよ 雪をさわるとすぐ暖かくなるもんだよ と言いましたが
[00:02:53.020 --> 00:02:59.300]  雪はあまり白いので包んでも包んでも 白く浮かび上がっていました
[00:02:59.300 --> 00:03:03.200]  親子の銀狐はほら穴から出ました
[00:03:03.200 --> 00:03:11.960]  子供の方はお母さんのお腹の下へ入り込んで そこからまんまるな目をパチパチさせながら
[00:03:11.960 --> 00:03:14.540]  あっちやこっちを見ながら歩いていきました
[00:03:14.540 --> 00:03:19.400]  やがて行く手にぽっつり明かりが一つ見え始めまし た
[00:03:19.400 --> 00:03:22.300]  それを子供の狐が見つけて
[00:03:22.300 --> 00:03:22.640]  子供の狐が見つけて
[00:03:22.640 --> 00:03:28.160]  母ちゃん お星さまはあんな低いところにも落ちてるのね と聞きました
[00:03:28.160 --> 00:03:32.060]  あれはお星さまじゃないのよ と言って
[00:03:32.060 --> 00:03:35.620]  その時 母さん狐の足はすくんでしまいました
[00:03:35.620 --> 00:03:38.700]  あれは町の火なんだよ
[00:03:38.700 --> 00:03:42.400]  その町の火を見た時 母さん狐は
[00:03:42.400 --> 00:03:47.620]  ある時町へお友達と出かけて行って 飛んだ目にあったことを思い出しました
[00:03:47.620 --> 00:03:50.580]  およしなさいって言うのも聞かないで
[00:03:50.580 --> 00:03:52.260]  お友達の狐が
[00:03:52.260 --> 00:03:54.860]  ある家のアヒルを盗もうとしたので
[00:03:54.860 --> 00:03:56.740]  お百姓に見つかって
[00:03:56.740 --> 00:03:58.340]  さんざ追いまくられて
[00:03:58.340 --> 00:04:00.700]  命からがら逃げたことでした
[00:04:00.700 --> 00:04:03.260]  母ちゃん 何してんの
[00:04:03.260 --> 00:04:04.540]  早く行こうよ
[00:04:04.540 --> 00:04:05.980]  と子供の狐が
[00:04:05.980 --> 00:04:08.320]  お腹の下から言うのでしたが
[00:04:08.320 --> 00:04:11.980]  母さん狐はどうしても足がすまないのでした
[00:04:11.980 --> 00:04:14.580]  そこで仕方がないので
[00:04:14.580 --> 00:04:17.760]  坊やだけを一人で町まで行かせることになりました
[00:04:17.760 --> 00:04:21.880]  坊やお手手を片方を出しと
[00:04:21.880 --> 00:04:23.380]  お母さん狐が言いました
[00:04:23.380 --> 00:04:26.240]  その手を母さん狐は
[00:04:26.240 --> 00:04:27.940]  しばらく握っている間に
[00:04:27.940 --> 00:04:31.000]  かわいい人間の子供の手にしてしまいました
[00:04:31.000 --> 00:04:33.140]  坊やの狐は
[00:04:33.140 --> 00:04:35.340]  その手を広げたり握ったり
[00:04:35.340 --> 00:04:36.440]  つねってみたり
[00:04:36.440 --> 00:04:37.440]  かいでみたりしました
[00:04:37.440 --> 00:04:40.160]  なんだか変だなあ母ちゃん
[00:04:40.160 --> 00:04:41.060]  これなあに
[00:04:41.060 --> 00:04:42.080]  と言って
[00:04:42.080 --> 00:04:43.260]  雪あかりに
[00:04:43.260 --> 00:04:46.220]  またその人間の手にかえられてしまった
[00:04:46.220 --> 00:04:48.860]  自分の手をしげしげと見つめました
[00:04:48.860 --> 00:04:51.800]  それは人間の手よ
[00:04:51.800 --> 00:04:53.840]  いいかい坊や
[00:04:53.840 --> 00:04:55.120]  町へ行ったらね
[00:04:55.120 --> 00:04:57.500]  たくさん人間の家があるからね
[00:04:57.500 --> 00:05:02.820]  まず表に丸いシャッポの看板のかかっている家を探 すんだよ
[00:05:02.820 --> 00:05:04.780]  それが見つかったらね
[00:05:04.780 --> 00:05:06.980]  トントンと戸をたたいて
[00:05:06.980 --> 00:05:09.360]  こんばんはって言うんだよ
[00:05:09.360 --> 00:05:10.460]  そうするとね
[00:05:10.460 --> 00:05:14.080]  中から人間が少し戸を開けるからね
[00:05:14.080 --> 00:05:16.800]  その戸の隙間からこっちの手
[00:05:16.800 --> 00:05:19.580]  ほらこの人間の手を差し入れてね
[00:05:19.580 --> 00:05:21.720]  この手にちょうどいい手袋をちょうだいと
[00:05:21.720 --> 00:05:23.400]  言うんだよ
[00:05:23.400 --> 00:05:24.680]  分かったね
[00:05:24.680 --> 00:05:27.640]  決してこっちのお手手を出しちゃだめよ
[00:05:27.640 --> 00:05:30.480]  と母さん狐は言い聞かせました
[00:05:30.480 --> 00:05:31.440]  どうして
[00:05:31.440 --> 00:05:34.420]  と坊やの狐は聞き返しました
[00:05:34.420 --> 00:05:35.640]  人間はね
[00:05:35.640 --> 00:05:37.540]  相手が狐だと分かると
[00:05:37.540 --> 00:05:39.520]  手袋を売ってくれないんだよ
[00:05:39.520 --> 00:05:40.600]  それどころか
[00:05:40.600 --> 00:05:43.580]  捕まえて俺の中へ入れちゃうんだよ
[00:05:43.580 --> 00:05:47.900]  人間って本当に怖いものなんだよ
[00:05:47.900 --> 00:05:50.440]  決してこっちの手を出しちゃいけないよ
[00:05:50.440 --> 00:05:51.640]  こっちの方
[00:05:51.640 --> 00:05:54.260]  ほら人間の手の方を差し出すんだよ
[00:05:54.260 --> 00:05:55.260]  と言って
[00:05:55.260 --> 00:05:58.740]  母さんの狐は持ってきた二つの白銅貨を
[00:05:58.740 --> 00:06:02.520]  人間の手の方へ握らせてやりました
[00:06:02.520 --> 00:06:05.180]  子供の狐は町の火を目当てに
[00:06:05.180 --> 00:06:09.060]  雪明かりの野原をよちよちやっていきました
[00:06:09.060 --> 00:06:11.600]  初めのうちは一つきりだった火が
[00:06:11.600 --> 00:06:13.720]  二つになり三つになり
[00:06:13.720 --> 00:06:16.440]  果ては十にも増えました
[00:06:16.440 --> 00:06:18.360]  狐の子供はそれを見て
[00:06:18.360 --> 00:06:20.300]  日には星と同じように
[00:06:20.300 --> 00:06:21.260]  赤いのや
[00:06:21.260 --> 00:06:25.120]  黄のや青いのがあるんだなと思いました
[00:06:25.120 --> 00:06:27.160]  やがて町に入りましたが
[00:06:27.160 --> 00:06:30.780]  通りの家々はもうみんな扉を閉めてしまって
[00:06:30.780 --> 00:06:33.620]  高い窓から暖かそうな光が
[00:06:33.620 --> 00:06:37.360]  道の雪の上に落ちているばかりでした
[00:06:37.360 --> 00:06:39.740]  けれど表の看板の上には
[00:06:39.740 --> 00:06:42.940]  たいてい小さな電灯が灯っていましたので
[00:06:42.940 --> 00:06:47.480]  狐の子はそれを見ながら帽子屋を探していきました
[00:06:47.480 --> 00:06:48.180]  自転車の看板の上には
[00:06:48.180 --> 00:06:50.140]  メガネの看板や
[00:06:50.140 --> 00:06:54.140]  その他いろんな看板があるものは
[00:06:54.140 --> 00:06:55.940]  新しいペンキで描かれ
[00:06:55.940 --> 00:06:59.380]  あるものは古い壁のように生えていましたが
[00:06:59.380 --> 00:07:02.100]  町に初めて出てきた小狐には
[00:07:02.100 --> 00:07:05.940]  それらのものが一体何であるかわからないのでした
[00:07:05.940 --> 00:07:09.000]  とうとう帽子屋が見つかりました
[00:07:09.000 --> 00:07:12.680]  お母さんがみちみちよく教えてくれた
[00:07:12.680 --> 00:07:15.980]  黒い大きなシルクハットの帽子の看板が
[00:07:15.980 --> 00:07:18.000]  青い電灯に照らされて
[00:07:18.000 --> 00:07:20.260]  かかっていました
[00:07:20.260 --> 00:07:22.780]  小狐は教えられた通り
[00:07:22.780 --> 00:07:25.740]  トントンと戸を叩きました
[00:07:25.740 --> 00:07:27.200]  こんばんは
[00:07:27.200 --> 00:07:31.120]  すると中では何かコトコト音がしていましたが
[00:07:31.120 --> 00:07:34.680]  やがて戸が一寸ほどごろりと開いて
[00:07:34.680 --> 00:07:39.240]  光の帯が道の白い雪の上に長く伸びました
[00:07:39.240 --> 00:07:41.880]  小狐はその光がまばゆかったので
[00:07:41.880 --> 00:07:44.900]  面くらって間違った方の手を
[00:07:44.900 --> 00:07:47.820]  お母様が出しちゃいけないと言ってよく聞かせたの ですが
[00:07:47.820 --> 00:07:51.840]  隙間から差し込んでしまいました
[00:07:51.840 --> 00:07:55.400]  このお手手にちょうどいい手袋ください
[00:07:55.400 --> 00:07:58.700]  すると帽子屋さんはおやおやと思いました
[00:07:58.700 --> 00:08:00.340]  狐の手です
[00:08:00.340 --> 00:08:03.580]  狐の手が手袋をくれと言うのです
[00:08:03.580 --> 00:08:07.240]  これはきっと木の葉で買いに来たんだなと思いまし た
[00:08:07.240 --> 00:08:08.540]  そこで
[00:08:08.540 --> 00:08:11.660]  先にお金をくださいと言いました
[00:08:11.660 --> 00:08:15.160]  小狐は素直に握ってきた白銅貨を二つ
[00:08:15.160 --> 00:08:17.640]  帽子屋さんに渡しました
[00:08:17.640 --> 00:08:20.740]  帽子屋さんはそれを人差し指の先に乗っけて
[00:08:20.740 --> 00:08:22.300]  勝ち合わせてみると
[00:08:22.300 --> 00:08:25.000]  チンチンとよい音がしましたので
[00:08:25.000 --> 00:08:26.920]  これは木の葉じゃない
[00:08:26.920 --> 00:08:29.420]  本当のお金だと思いましたので
[00:08:29.420 --> 00:08:33.900]  棚から子供用の毛糸の手袋を取り出してきて
[00:08:33.900 --> 00:08:35.760]  小狐の手に持たせてやりました
[00:08:35.760 --> 00:08:38.740]  小狐はお礼を言ってまた
[00:08:38.740 --> 00:08:40.920]  元来た道を帰り始めました
[00:08:40.920 --> 00:08:46.260]  お母さんは人間は恐ろしいものだっておっしゃった が
[00:08:46.260 --> 00:08:47.460]  ちっとも恐ろしくなってしまいました
[00:08:47.460 --> 00:08:52.940]  だって僕の手を見てもどうもしなかったものと思い ました
[00:08:52.940 --> 00:08:58.620]  けれど小狐は一体人間なんてどんなものか見たいと 思いました
[00:08:58.620 --> 00:09:03.580]  ある窓の下を通りかかると人間の声がしていました
[00:09:03.580 --> 00:09:07.220]  なんという優しいなんという美しい
[00:09:07.220 --> 00:09:09.780]  なんというほっとりした声なんでしょう
[00:09:09.780 --> 00:09:13.760]  眠れ眠れ母の胸に
[00:09:13.760 --> 00:09:16.840]  眠れ眠れ母の手に
[00:09:17.280 --> 00:09:22.920]  小狐はその歌声はきっと人間のお母さんの声に違い ないと思いました
[00:09:22.920 --> 00:09:26.620]  だって小狐が眠るときにも
[00:09:26.620 --> 00:09:31.300]  やっぱり母さん狐はあんな優しい声でゆすぶってく れるからです
[00:09:31.300 --> 00:09:34.400]  すると今度は子供の声がしました
[00:09:34.400 --> 00:09:38.380]  母ちゃんこんな寒い夜は
[00:09:38.380 --> 00:09:41.520]  森の小狐は寒い寒いって泣いてるでしょうね
[00:09:41.520 --> 00:09:44.100]  すると母さんの声が
[00:09:44.100 --> 00:09:47.100]  森の小狐もお母さんの声を聞きました
[00:09:47.100 --> 00:09:48.240]  小狐のお歌を聞いて
[00:09:48.240 --> 00:09:51.300]  ほら穴の中で眠ろうとしているでしょうね
[00:09:51.300 --> 00:09:54.420]  さあ坊やも早くねんねしなさい
[00:09:54.420 --> 00:09:56.620]  森の小狐と坊やと
[00:09:56.620 --> 00:09:58.620]  どっちが早くねんねするか
[00:09:58.620 --> 00:10:01.940]  きっと坊やの方が早くねんねしますよ
[00:10:01.940 --> 00:10:04.380]  それを聞くと小狐は
[00:10:04.380 --> 00:10:06.460]  急にお母さんが恋しくなって
[00:10:06.460 --> 00:10:10.100]  お母さん狐の待っている方へ飛んでいきました
[00:10:10.100 --> 00:10:13.980]  お母さん狐は心配しながら
[00:10:13.980 --> 00:10:15.780]  坊やの狐の帰ってくるのを
[00:10:15.780 --> 00:10:16.920]  今か今かと
[00:10:16.920 --> 00:10:18.820]  震えながら待っていましたので
[00:10:18.820 --> 00:10:20.520]  坊やが来ると
[00:10:20.520 --> 00:10:22.100]  温かい胸に抱きしめて
[00:10:22.100 --> 00:10:24.040]  泣きたいほど喜びました
[00:10:24.040 --> 00:10:28.160]  2匹の狐は森の方へ帰っていきました
[00:10:28.160 --> 00:10:30.040]  月が出たので
[00:10:30.040 --> 00:10:32.760]  狐の毛並みが銀色に光り
[00:10:32.760 --> 00:10:35.580]  その足跡にはコバルトの影がたまりました
[00:10:35.580 --> 00:10:40.280]  母ちゃん人間ってちっとも怖かないや
[00:10:40.280 --> 00:10:41.340]  どうして
[00:10:41.340 --> 00:10:45.100]  坊間違えて本当のお手手出しちゃったの
[00:10:45.100 --> 00:10:46.740]  でも坊やさん
[00:10:46.740 --> 00:10:48.440]  捕まえやしなかったもの
[00:10:48.440 --> 00:10:51.920]  ちゃんとこんないい温かい手袋くれたもの
[00:10:51.920 --> 00:10:53.400]  と言って
[00:10:53.400 --> 00:10:55.840]  手袋のはまった両手を
[00:10:55.840 --> 00:10:57.240]  パンパンやってみせました
[00:10:57.240 --> 00:10:59.280]  お母さん狐は
[00:10:59.280 --> 00:11:01.300]  まあと呆れましたが
[00:11:01.300 --> 00:11:04.160]  本当に人間はいいものかしら
[00:11:04.160 --> 00:11:07.400]  本当に人間はいいものかしら
[00:11:07.400 --> 00:11:08.560]  と呟きました
[00:11:08.560 --> 00:11:11.060]  手袋を買いに
[00:11:11.060 --> 00:11:13.080]  にみなんきち終わり
[00:11:13.080 --> 00:11:16.080]  この録音はパブリックドメインです
[00:11:16.740 --> 00:11:21.400]  ご視聴ありがとうございました


whisper_print_timings:     load time =  1491.99 ms
whisper_print_timings:     fallbacks =   1 p /   8 h
whisper_print_timings:      mel time =   550.17 ms
whisper_print_timings:   sample time = 38510.57 ms / 18110 runs (    2.13 ms per run)
whisper_print_timings:   encode time = 322445.44 ms /    24 runs (13435.23 ms per run)
whisper_print_timings:   decode time =  4208.81 ms /    46 runs (   91.50 ms per run)
whisper_print_timings:   batchd time = 702576.00 ms / 17948 runs (   39.15 ms per run)
whisper_print_timings:   prompt time = 74664.91 ms /  5137 runs (   14.53 ms per run)
whisper_print_timings:    total time = 1145307.75 ms

私のHP Envy 14では、実時間の2倍程度でspeech-to-textすることができました。 実時間は681秒程度で、speech-to-textにかかった時間は1145秒程度でした。 テキストもおおむね正しそうです。

HP Envy 14 fa0000のタッチパッドを使えるようにしてみる2

この記事は、NetBSD Advent Calendar 2024の17日目の記事です。

AMD GPIOに対応させてみる

前回はQualcom製SOC用のGPIOドライバーを一読して、AMD GPIO用に改造するベース にできそうな感触を持ちました。 今回は、AMD GPIO用に変更してみます。用意するのはamdgpioreg.hとamdgpio.cです。

まず最初にソースコードを掲載しておきます。

$ cat /usr/src/sys/dev/acpi/amdgpioreg.h
#ifndef _AMDGPIOREG_H
#define _AMDGPIOREG_H

#define AMDGPIO_NPINS                   184
#define AMDGPIO_PIN_REG(pin)            ((pin) * 4)

#define AMDGPIO_CONF_LEVEL              0x00000100
#define AMDGPIO_CONF_ACTLO              0x00000200
#define AMDGPIO_CONF_ACTBOTH            0x00000400
#define AMDGPIO_CONF_MASK               0x00000600
#define AMDGPIO_CONF_INTR_EN            0x00000800
#define AMDGPIO_CONF_INTR_MASK_EN       0x00001000
#define AMDGPIO_CONF_GPIORXSTATE        0x00010000
#define AMDGPIO_CONF_GPIOTXSTATE        0x00400000
#define AMDGPIO_CONF_GPIOTXSTATE_EN     0x00800000
#define AMDGPIO_CONF_INTR_STATUS        0x10000000

/* n should be 0 or 1. */
#define AMDGPIO_INTR_STATUS(n)          (0x2f8 + (n) * 4)

#define AMDGPIO_INTR_MASTER             0xfc
#define AMDGPIO_INTR_MASTER_EIO         0x20000000
#define AMDGPIO_INTR_STATUS_NBITS       46
#define AMDGPIO_INTR_NPINS              4

#endif /* _AMDGPIOREG_H */
$ cat /usr/src/sys/dev/acpi/amdgpio.c
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/gpio.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/queue.h>

#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpi_event.h>
#include <dev/acpi/acpi_gpio.h>
#include <dev/acpi/acpi_intr.h>
#include <dev/acpi/amdgpioreg.h>

#include <dev/gpio/gpiovar.h>

struct amdgpio_config {
	u_int	num_pins;
	int	(*translate)(ACPI_RESOURCE_GPIO *);
};

struct amdgpio_intr_handler {
	int	(*ih_func)(void *);
	void	*ih_arg;
	int	ih_pin;
	LIST_ENTRY(amdgpio_intr_handler) ih_list;
};

struct amdgpio_softc {
	device_t			sc_dev;
	device_t			sc_gpiodev;
	bus_space_handle_t		sc_bsh;
	bus_space_tag_t			sc_bst;
	const struct amdgpio_config	*sc_config;
	struct gpio_chipset_tag		sc_gc;
	gpio_pin_t			*sc_pins;
	LIST_HEAD(, amdgpio_intr_handler) sc_intrs;
	kmutex_t			sc_lock;
};

#define RD4(sc, reg)		\
	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
#define WR4(sc, reg, val)	\
	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))

static int	amdgpio_match(device_t, cfdata_t, void *);
static void	amdgpio_attach(device_t, device_t, void *);

static int	amdgpio_pin_read(void *, int);
static void	amdgpio_pin_write(void *, int, int);
static void	amdgpio_pin_ctl(void *, int, int);
static void *	amdgpio_intr_establish(void *, int, int, int,
					int (*)(void *), void *);
static void	amdgpio_intr_disestablish(void *, void *);
static bool	amdgpio_intr_str(void *, int, int, char *, size_t);
static void	amdgpio_intr_mask(void *, void *);
static void	amdgpio_intr_unmask(void *, void *);

static int	amdgpio_acpi_translate(void *, ACPI_RESOURCE_GPIO *, void **);
static void	amdgpio_register_event(void *, struct acpi_event *,
					ACPI_RESOURCE_GPIO *);
static int	amdgpio_intr(void *);

CFATTACH_DECL_NEW(amdgpio, sizeof(struct amdgpio_softc),
    amdgpio_match, amdgpio_attach, NULL, NULL);

#define AMDGPIO_NUM_PINS	184

static int
amdgpio_translate(ACPI_RESOURCE_GPIO *gpio)
{
	const ACPI_INTEGER pin = gpio->PinTable[0];

	if (pin < AMDGPIO_NUM_PINS) {
		return gpio->PinTable[0];
	}

	switch (pin) {
	case 0x0:
	case 0x8: /* TPDD */
	case 0x28: /* TPNL */
	case 0x3a:
	case 0x3b:
	case 0x3d:
	case 0x3e:
		return pin;
	default:
		return -1;
	}
}

static struct amdgpio_config amdgpio_config = {
	.num_pins = AMDGPIO_NUM_PINS,
	.translate = amdgpio_translate, /* TODO: REMOVE */
};

static const struct device_compatible_entry compat_data[] = {
	{ .compat = "AMDI0030",	.data = &amdgpio_config },
	DEVICE_COMPAT_EOL
};

static int
amdgpio_match(device_t parent, cfdata_t cf, void *aux)
{
	struct acpi_attach_args *aa = aux;

	return acpi_compatible_match(aa, compat_data);
}

static void
amdgpio_attach(device_t parent, device_t self, void *aux)
{
	struct amdgpio_softc * const sc = device_private(self);
	struct acpi_attach_args *aa = aux;
	struct gpiobus_attach_args gba;
	ACPI_HANDLE hdl = aa->aa_node->ad_handle;
	struct acpi_resources res;
	struct acpi_mem *mem;
	struct acpi_irq *irq;
	ACPI_STATUS rv;
	int error, pin;
	void *ih;

	sc->sc_dev = self;
	sc->sc_config = acpi_compatible_lookup(aa, compat_data)->data;
	sc->sc_bst = aa->aa_memt;
	KASSERT(sc->sc_config != NULL);
	LIST_INIT(&sc->sc_intrs);
	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM);

	rv = acpi_resource_parse(sc->sc_dev, hdl, "_CRS",
	    &res, &acpi_resource_parse_ops_default);
	if (ACPI_FAILURE(rv)) {
		return;
	}

	mem = acpi_res_mem(&res, 0);
	if (mem == NULL) {
		aprint_error_dev(self, "couldn't find mem resource\n");
		goto done;
	}

	irq = acpi_res_irq(&res, 0);
	if (irq == NULL) {
		aprint_error_dev(self, "couldn't find irq resource\n");
		goto done;
	}

	error = bus_space_map(sc->sc_bst, mem->ar_base, mem->ar_length, 0,
	    &sc->sc_bsh);
	if (error) {
		aprint_error_dev(self, "couldn't map registers\n");
		goto done;
	}

	sc->sc_pins = kmem_zalloc(sizeof(*sc->sc_pins) *
	    sc->sc_config->num_pins, KM_SLEEP);
	for (pin = 0; pin < sc->sc_config->num_pins; pin++) {
		sc->sc_pins[pin].pin_num = pin;
		sc->sc_pins[pin].pin_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT;
		sc->sc_pins[pin].pin_intrcaps =
		    GPIO_INTR_POS_EDGE | GPIO_INTR_NEG_EDGE |
		    GPIO_INTR_DOUBLE_EDGE | GPIO_INTR_HIGH_LEVEL |
		    GPIO_INTR_LOW_LEVEL | GPIO_INTR_MPSAFE;
		/* It's not safe to read all pins, so leave pin state unknown */
		sc->sc_pins[pin].pin_state = 0;
	}

	sc->sc_gc.gp_cookie = sc;
	sc->sc_gc.gp_pin_read = amdgpio_pin_read;
	sc->sc_gc.gp_pin_write = amdgpio_pin_write;
	sc->sc_gc.gp_pin_ctl = amdgpio_pin_ctl;
	sc->sc_gc.gp_intr_establish = amdgpio_intr_establish;
	sc->sc_gc.gp_intr_disestablish = amdgpio_intr_disestablish;
	sc->sc_gc.gp_intr_str = amdgpio_intr_str;
	sc->sc_gc.gp_intr_mask = amdgpio_intr_mask;
	sc->sc_gc.gp_intr_unmask = amdgpio_intr_unmask;

	rv = acpi_event_create_gpio(self, hdl, amdgpio_register_event, sc);
	if (ACPI_FAILURE(rv)) {
		if (rv != AE_NOT_FOUND) {
			aprint_error_dev(self, "failed to create events: %s\n",
			    AcpiFormatException(rv));
		}
		goto done;
	}

	ih = acpi_intr_establish(self, (uint64_t)(uintptr_t)hdl,
	    IPL_VM, false, amdgpio_intr, sc, device_xname(self));
	if (ih == NULL) {
		aprint_error_dev(self, "couldn't establish interrupt\n");
		goto done;
	}

	memset(&gba, 0, sizeof(gba));
	gba.gba_gc = &sc->sc_gc;
	gba.gba_pins = sc->sc_pins;
	gba.gba_npins = sc->sc_config->num_pins;
	sc->sc_gpiodev = config_found(self, &gba, gpiobus_print,
	    CFARGS(.iattr = "gpiobus"));
	if (sc->sc_gpiodev != NULL) {
		acpi_gpio_register(aa->aa_node, self,
		    amdgpio_acpi_translate, sc); 
	}

done:
	acpi_resource_cleanup(&res);
}

static int
amdgpio_acpi_translate(void *priv, ACPI_RESOURCE_GPIO *gpio, void **gpiop)
{
	struct amdgpio_softc * const sc = priv;
	const ACPI_INTEGER pin = gpio->PinTable[0];
	int xpin;

	xpin = sc->sc_config->translate(gpio);

	aprint_debug_dev(sc->sc_dev, "translate %#lx -> %u\n", pin, xpin);

	if (gpiop != NULL) {
		if (sc->sc_gpiodev != NULL) {
			*gpiop = device_private(sc->sc_gpiodev);
		} else {
			device_printf(sc->sc_dev,
			    "no gpiodev for pin %#lx -> %u\n", pin, xpin);
			xpin = -1;
		}
	}

	return xpin;
}

static int
amdgpio_acpi_event(void *priv)
{
	struct acpi_event * const ev = priv;

	acpi_event_notify(ev);

	return 1;
}

static void
amdgpio_register_event(void *priv, struct acpi_event *ev,
    ACPI_RESOURCE_GPIO *gpio)
{
	struct amdgpio_softc * const sc = priv;
	int irqmode;
	void *ih;

	const int pin = amdgpio_acpi_translate(sc, gpio, NULL);

	if (pin < 0 || pin == 0x8) {
		aprint_error_dev(sc->sc_dev,
		    "ignoring event for pin %#x (out of range)\n",
		    gpio->PinTable[0]);
		return;
	}

	if (gpio->Triggering == ACPI_LEVEL_SENSITIVE) {
		irqmode = gpio->Polarity == ACPI_ACTIVE_HIGH ?
		    GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
	} else {
		KASSERT(gpio->Triggering == ACPI_EDGE_SENSITIVE);
		if (gpio->Polarity == ACPI_ACTIVE_LOW) {
			irqmode = GPIO_INTR_NEG_EDGE;
		} else if (gpio->Polarity == ACPI_ACTIVE_HIGH) {
			irqmode = GPIO_INTR_POS_EDGE;
		} else {
			KASSERT(gpio->Polarity == ACPI_ACTIVE_BOTH);
			irqmode = GPIO_INTR_DOUBLE_EDGE;
		}
	}

	ih = amdgpio_intr_establish(sc, pin, IPL_VM, irqmode,
	    amdgpio_acpi_event, ev);
	if (ih == NULL) {
		aprint_error_dev(sc->sc_dev,
		    "couldn't register event for pin %#x\n",
		    gpio->PinTable[0]);
		return;
	}
	if (gpio->Triggering == ACPI_LEVEL_SENSITIVE) {
		acpi_event_set_intrcookie(ev, ih);
	}
}

static int
amdgpio_pin_read(void *priv, int pin)
{
	struct amdgpio_softc * const sc = priv;
	uint32_t val;

	if (pin < 0 || pin >= sc->sc_config->num_pins) {
		return 0;
	}
	if ((sc->sc_pins[pin].pin_caps & GPIO_PIN_INPUT) == 0) {
		return 0;
	}

	val = RD4(sc, AMDGPIO_PIN_REG(pin));
	return (val & AMDGPIO_CONF_GPIORXSTATE) ? 1 : 0;
}

static void
amdgpio_pin_write(void *priv, int pin, int pinval)
{
	struct amdgpio_softc * const sc = priv;
	uint32_t val;

	if (pin < 0 || pin >= sc->sc_config->num_pins) {
		return;
	}
	if ((sc->sc_pins[pin].pin_caps & GPIO_PIN_OUTPUT) == 0) {
		return;
	}

	val = RD4(sc, AMDGPIO_PIN_REG(pin));
	if (pinval) {
		val |= AMDGPIO_CONF_GPIOTXSTATE;
	} else {
		val &= ~AMDGPIO_CONF_GPIOTXSTATE;
	}
	WR4(sc, AMDGPIO_PIN_REG(pin), val);
}

static void
amdgpio_pin_ctl(void *priv, int pin, int flags)
{
	/* Nothing to do here, as firmware has already configured pins. */
}

static void *
amdgpio_intr_establish(void *priv, int pin, int ipl, int irqmode,
			int (*func)(void *), void *arg)
{
	struct amdgpio_softc * const sc = priv;
	struct amdgpio_intr_handler *aih, *aihp;
	uint32_t dect;
	uint32_t val;

	if (pin < 0 || pin >= sc->sc_config->num_pins) {
		return NULL;
	}
	if (ipl != IPL_VM) {
		device_printf(sc->sc_dev, "%s: only IPL_VM supported\n",
		    __func__);
		return NULL;
	}

	aih = kmem_alloc(sizeof(*aih), KM_SLEEP);
	aih->ih_func = func;
	aih->ih_arg = arg;
	aih->ih_pin = pin;

	mutex_enter(&sc->sc_lock);

	LIST_FOREACH(aihp, &sc->sc_intrs, ih_list) {
		if (aihp->ih_pin == aih->ih_pin) {
			mutex_exit(&sc->sc_lock);
			kmem_free(aih, sizeof(*aih));
			device_printf(sc->sc_dev,
			    "%s: pin %d already establish\n", __func__, pin);
			return NULL;
		}
	}

	LIST_INSERT_HEAD(&sc->sc_intrs, aih, ih_list);

	if ((irqmode & GPIO_INTR_LEVEL_MASK) != 0) {
		dect = AMDGPIO_CONF_LEVEL;
	} else {
		KASSERT((irqmode & GPIO_INTR_EDGE_MASK) != 0);
		if ((irqmode & GPIO_INTR_NEG_EDGE) != 0) {
			dect = AMDGPIO_CONF_ACTLO;
#if 0
/* XXX */
		} else if ((irqmode & GPIO_INTR_POS_EDGE) != 0) {
			dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_POS;
#endif
		} else {
			KASSERT((irqmode & GPIO_INTR_DOUBLE_EDGE) != 0);
			dect = AMDGPIO_CONF_ACTBOTH;
		}
	}

	val = RD4(sc, AMDGPIO_PIN_REG(pin));
	val |= dect;
	val |= AMDGPIO_CONF_INTR_MASK_EN | AMDGPIO_CONF_INTR_EN;
	WR4(sc, AMDGPIO_PIN_REG(pin), val);

	mutex_exit(&sc->sc_lock);

	return aih;
}

static void
amdgpio_intr_disestablish(void *priv, void *ih)
{
	struct amdgpio_softc * const sc = priv;
	struct amdgpio_intr_handler *aih = ih;
	uint32_t val;

	mutex_enter(&sc->sc_lock);

	LIST_REMOVE(aih, ih_list);

	val = RD4(sc, AMDGPIO_PIN_REG(aih->ih_pin));
	val &= ~(AMDGPIO_CONF_INTR_EN | AMDGPIO_CONF_INTR_MASK_EN);
	WR4(sc, AMDGPIO_PIN_REG(aih->ih_pin), val);

	mutex_exit(&sc->sc_lock);

	kmem_free(aih, sizeof(*aih));
}

static bool
amdgpio_intr_str(void *priv, int pin, int irqmode, char *buf, size_t buflen)
{
	struct amdgpio_softc * const sc = priv;
	int rv;

	rv = snprintf(buf, buflen, "%s pin %d", device_xname(sc->sc_dev), pin);

	return rv < buflen;
}

static void
amdgpio_intr_mask(void *priv, void *ih)
{
	struct amdgpio_softc * const sc = priv;
	struct amdgpio_intr_handler *aih = ih;
	uint32_t val;

	val = RD4(sc, AMDGPIO_PIN_REG(aih->ih_pin));
	val &= ~AMDGPIO_CONF_INTR_MASK_EN;
	WR4(sc, AMDGPIO_PIN_REG(aih->ih_pin), val);
}

static void
amdgpio_intr_unmask(void *priv, void *ih)
{
	struct amdgpio_softc * const sc = priv;
	struct amdgpio_intr_handler *aih = ih;
	uint32_t val;

	val = RD4(sc, AMDGPIO_PIN_REG(aih->ih_pin));
	val |= AMDGPIO_CONF_INTR_MASK_EN;
	WR4(sc, AMDGPIO_PIN_REG(aih->ih_pin), val);
}

static int
amdgpio_intr(void *priv)
{
	struct amdgpio_softc * const sc = priv;
	struct amdgpio_intr_handler *aih;
	int rv = 0;
	uint64_t status;
	uint32_t val;

	mutex_enter(&sc->sc_lock);

	status = RD4(sc, AMDGPIO_INTR_STATUS(1));
	status <<= 32;
	status |= RD4(sc, AMDGPIO_INTR_STATUS(0));
	status &= __BITS(0, AMDGPIO_INTR_STATUS_NBITS - 1);

	if (status == 0) {
		rv = 1;
		goto out;
	}

	LIST_FOREACH(aih, &sc->sc_intrs, ih_list) {
		const int pin = aih->ih_pin;

		if ((status & __BIT(pin / 4)) == 0) {
			continue;
		}

		val = RD4(sc, AMDGPIO_PIN_REG(pin));
		if ((val & AMDGPIO_CONF_INTR_STATUS) != 0) {
			rv |= aih->ih_func(aih->ih_arg);

			val &= ~(AMDGPIO_CONF_INTR_MASK_EN | AMDGPIO_CONF_INTR_EN);
			WR4(sc, AMDGPIO_PIN_REG(pin), val);
		}
	}

	/* Signal end of interrupt */
	val = RD4(sc, AMDGPIO_INTR_MASTER);
	val |= AMDGPIO_INTR_MASTER_EIO;
	WR4(sc, AMDGPIO_INTR_MASTER, val);

out:
	mutex_exit(&sc->sc_lock);

	return rv;
}

実際に動かすには、以下のパッチも必要です。

$ cvs diff -u sys/dev/acpi/files.acpi
Index: sys/dev/acpi/files.acpi
===================================================================
RCS file: /cvsroot/src/sys/dev/acpi/files.acpi,v
retrieving revision 1.135
diff -u -r1.135 files.acpi
--- sys/dev/acpi/files.acpi     13 Dec 2024 13:30:10 -0000      1.135
+++ sys/dev/acpi/files.acpi     28 Dec 2024 01:01:01 -0000
@@ -347,4 +347,9 @@
 attach qcomiic at acpinodebus
 file   dev/acpi/qcomiic.c              qcomiic

+# AMD GPIO
+device amdgpio: gpiobus
+attach amdgpio at acpinodebus
+file   dev/acpi/amdgpio.c              amdgpio
+
 include        "dev/acpi/wmi/files.wmi"

qcomgpio(4)との違いは、単純にレジスターを読み取る部分がメインです。amdgpioreg.hにあるAMDGPIO_PIN_REGのマクロにあるように 配置されているのに気を付けるくらいかなかと思います。 また、前回も書きましたが、割り込みで処理を完了したらアクノリッジが必要です。

man pageを書いたら、commitできればと思っています。

HP Envy 14 fa0000のタッチパッドを使えるようにしてみる1

この記事は、NetBSD Advent Calendar 2024の12日目の記事です。

書いてみると長くなってしまったので、何回かに分割したいと思います。

I2CとGPIOの組み合わせをACPIで使う

000

どうやら最近のトラックパッドは、I2Cで接続されており、その割り込みはGPIOでトリガーされるようになっているようです。 NetBSDのACPIの仕組みは、先日までこのようなデバイスをサポートしていませんでした。 ですが、QualcommのSnapdragonを搭載したLenovo ThinkPad T14s Gen 6のサポートがjmcneillが追加する際に、 そのような構成のサポートが追加されした。 https://mail-index.netbsd.org/source-changes/2024/12/08/msg154763.html の辺りのコミットを見てもらえればと思います。 ちなみに、dmesgもあります。

そこで、私の使っているHP Envy 14 fa0001AUでも、その仕組みを使ってトラックパッドを利用するようにしてみました。 この機種には、AMD製のCPUが搭載されています。 実を言うと、これまで何度かAMDのGPIO用のデバイスドライバーを書いてみていたのですが、ACPIとの接続方法が想像できずトラックパッドを動かせるようにするのには役立てられていませんでした。

qcomgpio(4)のソースコードを読む

どうやらLenovo ThinkPad T14s Gen 6に搭載されているGPIOはピン番号の変換が必要なハードウェアであるようです。 ですが、AMDのGPIOはそんな変換は不要な単純なハードウェアです。 ただ、割り込みで実行した処理を終わらせる場合には、アクノリッジする必要があります。 OpenBSDのamdgpio(4)を参考に、qcomgpio(4)を改造してamdgpio(4)を実装してみました。

qcomgpio.cは以下のようです。

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: qcomgpio.c,v 1.8 2024/12/17 22:05:21 riastradh Exp $");

#include <sys/param.h>
#include <sys/types.h>

#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/evcnt.h>
#include <sys/gpio.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/queue.h>

#include <dev/acpi/acpi_event.h>
#include <dev/acpi/acpi_gpio.h>
#include <dev/acpi/acpi_intr.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/qcomgpioreg.h>

#include <dev/gpio/gpiovar.h>

typedef enum {
	QCOMGPIO_X1E,
} qcomgpio_type;

struct qcomgpio_reserved {
	int	start;
	int	count;
};

struct qcomgpio_config {
	struct qcomgpio_reserved *reserved;
	u_int	num_reserved;
	u_int	*pdc_filter;
	u_int	num_pdc_filter;
};

struct qcomgpio_intr_handler {
	int	(*ih_func)(void *);
	void	*ih_arg;
	int	ih_pin;
	int	ih_type;
	struct evcnt ih_evcnt;
	char	ih_name[16];
	LIST_ENTRY(qcomgpio_intr_handler) ih_list;
};

struct qcomgpio_pdcmap {
	int	pm_pin;
	u_int	pm_irq;
};

struct qcomgpio_softc {
	device_t			sc_dev;
	device_t			sc_gpiodev;
	bus_space_handle_t		sc_bsh;
	bus_space_tag_t			sc_bst;
	const struct qcomgpio_config	*sc_config;
	struct gpio_chipset_tag		sc_gc;
	gpio_pin_t			*sc_pins;
	u_int				sc_npins;
	LIST_HEAD(, qcomgpio_intr_handler) sc_intrs;
	kmutex_t			sc_lock;

	struct qcomgpio_pdcmap		*sc_pdcmap;
	u_int				sc_npdcmap;
};

ここまででsoftcが定義されています。 基本的には、qcomgpio_softcとqcomgpio_config、qcomgpio_intr_handlerがあれば良さそうです。 qcomgpio_configは、上述したピン番号の変換に使うようですが、amdgpio(4)では使うピンだけマスクするのに利用してみることにします。 それ以外は、そのまま流用できそうです。

#define RD4(sc, reg)		\
	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
#define WR4(sc, reg, val)	\
	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))

この32ビットデータの読み書き用のマクロは、そのまま利用することができそうです。

static int	qcomgpio_match(device_t, cfdata_t, void *);
static void	qcomgpio_attach(device_t, device_t, void *);

static bool	qcomgpio_pin_reserved(struct qcomgpio_softc *, int);
static int	qcomgpio_pin_read(void *, int);
static void	qcomgpio_pin_write(void *, int, int);
static void	qcomgpio_pin_ctl(void *, int, int);
static void *	qcomgpio_intr_establish(void *, int, int, int,
		    int (*)(void *), void *);
static void	qcomgpio_intr_disestablish(void *, void *);
static bool	qcomgpio_intr_str(void *, int, int, char *, size_t);
static void	qcomgpio_intr_mask(void *, void *);
static void	qcomgpio_intr_unmask(void *, void *);

static u_int	qcomgpio_acpi_num_pins(device_t, ACPI_HANDLE);
static void	qcomgpio_acpi_fill_pdcmap(struct qcomgpio_softc *,
		    ACPI_HANDLE);
static int	qcomgpio_acpi_translate(void *, ACPI_RESOURCE_GPIO *, void **);
static void	qcomgpio_register_event(void *, struct acpi_event *,
		    ACPI_RESOURCE_GPIO *);
static int	qcomgpio_intr(void *);

CFATTACH_DECL_NEW(qcomgpio, sizeof(struct qcomgpio_softc),
    qcomgpio_match, qcomgpio_attach, NULL, NULL);

qcomgpio_acpi_num_pins()とqcomgpio_acpi_fill_pdcmap()は必要なさそうです。 他はそのまま流用できそうです。

static UINT8 qcomgpio_gpio_dsm_uuid[ACPI_UUID_LENGTH] = {
	0xa4, 0xb2, 0xb9, 0x98, 0x63, 0x16, 0x5f, 0x4a,
	0x82, 0xf2, 0xc6, 0xc9, 0x9a, 0x39, 0x47, 0x26
};
#define QCOMGPIO_GPIO_DSM_REV		0
#define QCOMGPIO_GPIO_DSM_FUNC_NUM_PINS	2

static UINT8 qcomgpio_pdc_dsm_uuid[ACPI_UUID_LENGTH] = {
	0xd4, 0x0f, 0x1b, 0x92, 0x7c, 0x56, 0xa0, 0x43,
	0xbb, 0x14, 0x26, 0x48, 0xf7, 0xb2, 0xa1, 0x8c
};
#define QCOMGPIO_PDC_DSM_REV		0
#define QCOMGPIO_PDC_DSM_FUNC_CIPR	2

static struct qcomgpio_reserved qcomgpio_x1e_reserved[] = {
	{ .start = 34, .count = 2 },
	{ .start = 44, .count = 4 },
	{ .start = 72, .count = 2 },
	{ .start = 238, .count = 1 },
};

static int qcomgpio_x1e_pdc_filter[] = {
	0x140,	/* Interrupt storm due to missing SMI support. */
};

static struct qcomgpio_config qcomgpio_x1e_config = {
	.reserved = qcomgpio_x1e_reserved,
	.num_reserved = __arraycount(qcomgpio_x1e_reserved),
	.pdc_filter = qcomgpio_x1e_pdc_filter,
	.num_pdc_filter = __arraycount(qcomgpio_x1e_pdc_filter),
};

この部分は必要なさそうです。AMDのGPIOでは、ピンの読み替えやフィルタリングは必要ありません。

static const struct device_compatible_entry compat_data[] = {
	{ .compat = "QCOM0C0C",	.data = &qcomgpio_x1e_config },
	DEVICE_COMPAT_EOL
};

HP Envy 14ではAMDI0030がGPIOなのですが、この部分は流用できそうです。

static int
qcomgpio_match(device_t parent, cfdata_t cf, void *aux)
{
	struct acpi_attach_args *aa = aux;

	return acpi_compatible_match(aa, compat_data);
}

この部分はそのまま利用できそうです。

static void
qcomgpio_attach(device_t parent, device_t self, void *aux)
{
	struct qcomgpio_softc * const sc = device_private(self);
	struct acpi_attach_args *aa = aux;
	struct gpiobus_attach_args gba;
	ACPI_HANDLE hdl = aa->aa_node->ad_handle;
	struct acpi_resources res;
	struct acpi_mem *mem;
	struct acpi_irq *irq;
	ACPI_STATUS rv;
	int error, pin, n;
	void *ih;

	sc->sc_dev = self;
	sc->sc_config = acpi_compatible_lookup(aa, compat_data)->data;
	sc->sc_bst = aa->aa_memt;
	KASSERT(sc->sc_config != NULL);
	LIST_INIT(&sc->sc_intrs);
	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM);

	rv = acpi_resource_parse(sc->sc_dev, hdl, "_CRS",
	    &res, &acpi_resource_parse_ops_default);
	if (ACPI_FAILURE(rv)) {
		return;
	}

	mem = acpi_res_mem(&res, 0);
	if (mem == NULL) {
		aprint_error_dev(self, "couldn't find mem resource\n");
		goto done;
	}

	irq = acpi_res_irq(&res, 0);
	if (irq == NULL) {
		aprint_error_dev(self, "couldn't find irq resource\n");
		goto done;
	}

	error = bus_space_map(sc->sc_bst, mem->ar_base, mem->ar_length, 0,
	    &sc->sc_bsh);
	if (error) {
		aprint_error_dev(self, "couldn't map registers\n");
		goto done;
	}

	sc->sc_npdcmap = res.ar_nirq;
	sc->sc_pdcmap = kmem_zalloc(sizeof(*sc->sc_pdcmap) * sc->sc_npdcmap,
	    KM_SLEEP);
	for (n = 0; n < sc->sc_npdcmap; n++) {
		sc->sc_pdcmap[n].pm_irq = acpi_res_irq(&res, n)->ar_irq;
		sc->sc_pdcmap[n].pm_pin = -1;
		aprint_debug_dev(self, "IRQ resource %u -> %#x\n",
		    n, sc->sc_pdcmap[n].pm_irq);
	}
	qcomgpio_acpi_fill_pdcmap(sc, hdl);

	sc->sc_npins = qcomgpio_acpi_num_pins(self, hdl);
	if (sc->sc_npins == 0) {
		aprint_error_dev(self, "couldn't determine pin count!\n");
		goto done;
	}
	sc->sc_pins = kmem_zalloc(sizeof(*sc->sc_pins) * sc->sc_npins,
	    KM_SLEEP);
	for (pin = 0; pin < sc->sc_npins; pin++) {
		sc->sc_pins[pin].pin_caps = qcomgpio_pin_reserved(sc, pin) ?
		    0 : (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT);
		sc->sc_pins[pin].pin_num = pin;
		sc->sc_pins[pin].pin_intrcaps =
		    GPIO_INTR_POS_EDGE | GPIO_INTR_NEG_EDGE |
		    GPIO_INTR_DOUBLE_EDGE | GPIO_INTR_HIGH_LEVEL |
		    GPIO_INTR_LOW_LEVEL | GPIO_INTR_MPSAFE;
	}

	sc->sc_gc.gp_cookie = sc;
	sc->sc_gc.gp_pin_read = qcomgpio_pin_read;
	sc->sc_gc.gp_pin_write = qcomgpio_pin_write;
	sc->sc_gc.gp_pin_ctl = qcomgpio_pin_ctl;
	sc->sc_gc.gp_intr_establish = qcomgpio_intr_establish;
	sc->sc_gc.gp_intr_disestablish = qcomgpio_intr_disestablish;
	sc->sc_gc.gp_intr_str = qcomgpio_intr_str;
	sc->sc_gc.gp_intr_mask = qcomgpio_intr_mask;
	sc->sc_gc.gp_intr_unmask = qcomgpio_intr_unmask;

	rv = acpi_event_create_gpio(self, hdl, qcomgpio_register_event, sc);
	if (ACPI_FAILURE(rv)) {
		if (rv != AE_NOT_FOUND) {
			aprint_error_dev(self, "failed to create events: %s\n",
			    AcpiFormatException(rv));
		}
		goto done;
	}

	ih = acpi_intr_establish(self, (uint64_t)(uintptr_t)hdl,
	    IPL_VM, false, qcomgpio_intr, sc, device_xname(self));
	if (ih == NULL) {
		aprint_error_dev(self, "couldn't establish interrupt\n");
		goto done;
	}

	memset(&gba, 0, sizeof(gba));
	gba.gba_gc = &sc->sc_gc;
	gba.gba_pins = sc->sc_pins;
	gba.gba_npins = sc->sc_npins;
	sc->sc_gpiodev = config_found(self, &gba, gpiobus_print,
	    CFARGS(.iattr = "gpiobus"));
	if (sc->sc_gpiodev != NULL) {
		acpi_gpio_register(aa->aa_node, self,
		    qcomgpio_acpi_translate, sc);
	}

done:
	acpi_resource_cleanup(&res);
}

この部分は、基本的にはこの構造を維持することになりそうです。

static u_int
qcomgpio_acpi_num_pins(device_t dev, ACPI_HANDLE hdl)
{
	ACPI_STATUS rv;
	ACPI_INTEGER npins;

	rv = acpi_dsm_integer(hdl, qcomgpio_gpio_dsm_uuid,
	    QCOMGPIO_GPIO_DSM_REV, QCOMGPIO_GPIO_DSM_FUNC_NUM_PINS,
	    NULL, &npins);
	if (ACPI_FAILURE(rv)) {
		aprint_error_dev(dev, "GPIO _DSM failed: %s\n",
		    AcpiFormatException(rv));
		return 0;
	}

	aprint_debug_dev(dev, "GPIO pin count: %u\n", (u_int)npins);

	return (u_int)npins;
}

この部分は、本当はAMD GPIOでも同様にできるかもしれませんが、ピンの数をハードコードするのでも進められそうです。

static void
qcomgpio_acpi_fill_pdcmap(struct qcomgpio_softc *sc,
    ACPI_HANDLE hdl)
{
	ACPI_STATUS rv;
	ACPI_OBJECT *obj;
	u_int n, filt;

	rv = acpi_dsm_typed(hdl, qcomgpio_pdc_dsm_uuid,
	    QCOMGPIO_PDC_DSM_REV, QCOMGPIO_PDC_DSM_FUNC_CIPR,
	    NULL, ACPI_TYPE_PACKAGE, &obj);
	if (ACPI_FAILURE(rv)) {
		aprint_error_dev(sc->sc_dev, "PDC _DSM failed: %s\n",
		    AcpiFormatException(rv));
		return;
	}

	for (n = 0; n < obj->Package.Count; n++) {
		ACPI_OBJECT *map = &obj->Package.Elements[n];
		bool filter = false;
		u_int irq, pdc;
		int pin;

		if (map->Type != ACPI_TYPE_PACKAGE ||
		    map->Package.Count < 3 ||
		    map->Package.Elements[0].Type != ACPI_TYPE_INTEGER ||
		    map->Package.Elements[1].Type != ACPI_TYPE_INTEGER ||
		    map->Package.Elements[2].Type != ACPI_TYPE_INTEGER) {
			continue;
		}

		irq = (u_int)map->Package.Elements[2].Integer.Value;
		pin = (int)map->Package.Elements[1].Integer.Value;
		for (pdc = 0; pdc < sc->sc_npdcmap; pdc++) {
			if (sc->sc_pdcmap[pdc].pm_irq == irq) {
				for (filt = 0;
				     filt < sc->sc_config->num_pdc_filter;
		     		     filt++) {
					if (sc->sc_config->pdc_filter[filt] ==
					    pdc * 64) {
						filter = true;
						break;
					}
				}

				if (!filter) {
					sc->sc_pdcmap[pdc].pm_pin = pin;
				}
				break;
			}
		}

		aprint_debug_dev(sc->sc_dev,
		    "PDC irq %#x -> pin %d%s%s\n", irq, pin,
		    filter ? " (filtered)" : "",
		    pdc == sc->sc_npdcmap ? " (unused)" : "");
	}

	ACPI_FREE(obj);
}

この部分は、本当はAMD GPIOでも同様にできるかもしれませんが、なくても進められそうです。

static int
qcomgpio_acpi_translate(void *priv, ACPI_RESOURCE_GPIO *gpio, void **gpiop)
{
	struct qcomgpio_softc * const sc = priv;
	const ACPI_INTEGER vpin = gpio->PinTable[0];
	int pin = -1;

	if (vpin < sc->sc_npins) {
		/* Virtual pin number is 1:1 mapping with hardware. */
		pin = vpin;
	} else if (vpin / 64 < sc->sc_npdcmap) {
		/* Translate the virtual pin number to a hardware pin. */
		pin = sc->sc_pdcmap[vpin / 64].pm_pin;
	}

	aprint_debug_dev(sc->sc_dev, "translate %#lx -> %u\n", vpin, pin);

	if (gpiop != NULL) {
		if (sc->sc_gpiodev != NULL) {
			*gpiop = device_private(sc->sc_gpiodev);
		} else {
			device_printf(sc->sc_dev,
			    "no gpiodev for pin %#lx -> %u\n", vpin, pin);
			pin = -1;
		}
	}

	return pin;
}

この部分は、ピンの変換なので、AMD GPIOでは採用しません。

static int
qcomgpio_acpi_event(void *priv)
{
	struct acpi_event * const ev = priv;

	acpi_event_notify(ev);

	return 1;
}

この部分は、そのまま利用できそうです。

static void
qcomgpio_register_event(void *priv, struct acpi_event *ev,
    ACPI_RESOURCE_GPIO *gpio)
{
	struct qcomgpio_softc * const sc = priv;
	int irqmode;
	void *ih;

	const int pin = qcomgpio_acpi_translate(sc, gpio, NULL);

	if (pin < 0) {
		aprint_error_dev(sc->sc_dev,
		    "ignoring event for pin %#x (out of range)\n",
		    gpio->PinTable[0]);
		return;
	}

	if (gpio->Triggering == ACPI_LEVEL_SENSITIVE) {
		irqmode = gpio->Polarity == ACPI_ACTIVE_HIGH ?
		    GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL;
	} else {
		KASSERT(gpio->Triggering == ACPI_EDGE_SENSITIVE);
		if (gpio->Polarity == ACPI_ACTIVE_LOW) {
			irqmode = GPIO_INTR_NEG_EDGE;
		} else if (gpio->Polarity == ACPI_ACTIVE_HIGH) {
			irqmode = GPIO_INTR_POS_EDGE;
		} else {
			KASSERT(gpio->Polarity == ACPI_ACTIVE_BOTH);
			irqmode = GPIO_INTR_DOUBLE_EDGE;
		}
	}

	ih = qcomgpio_intr_establish(sc, pin, IPL_VM, irqmode,
	    qcomgpio_acpi_event, ev);
	if (ih == NULL) {
		aprint_error_dev(sc->sc_dev,
		    "couldn't register event for pin %#x\n",
		    gpio->PinTable[0]);
		return;
	}
	if (gpio->Triggering == ACPI_LEVEL_SENSITIVE) {
		acpi_event_set_intrcookie(ev, ih);
	}
}

この部分は、基本的にはそのまま利用できそうです。

static bool
qcomgpio_pin_reserved(struct qcomgpio_softc *sc, int pin)
{
	u_int n;

	for (n = 0; n < sc->sc_config->num_reserved; n++) {
		if (pin >= sc->sc_config->reserved[n].start &&
		    pin < sc->sc_config->reserved[n].start +
			  sc->sc_config->reserved[n].count) {
			return true;
		}
	}

	return false;
}

この部分は、そのまま利用できそうです。

static int
qcomgpio_pin_read(void *priv, int pin)
{
	struct qcomgpio_softc * const sc = priv;
	uint32_t val;

	if (pin < 0 || pin >= sc->sc_npins) {
		return 0;
	}
	if ((sc->sc_pins[pin].pin_caps & GPIO_PIN_INPUT) == 0) {
		return 0;
	}

	val = RD4(sc, TLMM_GPIO_IN_OUT(pin));
	return (val & TLMM_GPIO_IN_OUT_GPIO_IN) != 0;
}

この部分は、AMD GPIOのレジスターの読み方に変更する以外は、そのまま利用できそうです。

static void
qcomgpio_pin_write(void *priv, int pin, int pinval)
{
	struct qcomgpio_softc * const sc = priv;
	uint32_t val;

	if (pin < 0 || pin >= sc->sc_npins) {
		return;
	}
	if ((sc->sc_pins[pin].pin_caps & GPIO_PIN_OUTPUT) == 0) {
		return;
	}

	val = RD4(sc, TLMM_GPIO_IN_OUT(pin));
	if (pinval) {
		val |= TLMM_GPIO_IN_OUT_GPIO_OUT;
	} else {
		val &= ~TLMM_GPIO_IN_OUT_GPIO_OUT;
	}
	WR4(sc, TLMM_GPIO_IN_OUT(pin), val);
}

この部分は、AMD GPIOのレジスターの書き込み方に変更する以外は、そのまま利用できそうです。

static void
qcomgpio_pin_ctl(void *priv, int pin, int flags)
{
	/* Nothing to do here, as firmware has already configured pins. */
}

static void *
qcomgpio_intr_establish(void *priv, int pin, int ipl, int irqmode,
    int (*func)(void *), void *arg)
{
	struct qcomgpio_softc * const sc = priv;
	struct qcomgpio_intr_handler *qih, *qihp;
	uint32_t dect, pol;
	uint32_t val;

	if (pin < 0 || pin >= sc->sc_npins) {
		return NULL;
	}
	if (ipl != IPL_VM) {
		device_printf(sc->sc_dev, "%s: only IPL_VM supported\n",
		    __func__);
		return NULL;
	}

	qih = kmem_alloc(sizeof(*qih), KM_SLEEP);
	qih->ih_func = func;
	qih->ih_arg = arg;
	qih->ih_pin = pin;
	qih->ih_type = (irqmode & GPIO_INTR_LEVEL_MASK) != 0 ?
	    IST_LEVEL : IST_EDGE;
	snprintf(qih->ih_name, sizeof(qih->ih_name), "pin %d", pin);

	mutex_enter(&sc->sc_lock);

	LIST_FOREACH(qihp, &sc->sc_intrs, ih_list) {
		if (qihp->ih_pin == qih->ih_pin) {
			mutex_exit(&sc->sc_lock);
			kmem_free(qih, sizeof(*qih));
			device_printf(sc->sc_dev,
			    "%s: pin %d already establish\n", __func__, pin);
			return NULL;
		}
	}

	LIST_INSERT_HEAD(&sc->sc_intrs, qih, ih_list);

	if ((irqmode & GPIO_INTR_LEVEL_MASK) != 0) {
		dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_LEVEL;
		pol = (irqmode & GPIO_INTR_HIGH_LEVEL) != 0 ?
		    TLMM_GPIO_INTR_CFG_INTR_POL_CTL : 0;
	} else {
		KASSERT((irqmode & GPIO_INTR_EDGE_MASK) != 0);
		if ((irqmode & GPIO_INTR_NEG_EDGE) != 0) {
			dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_NEG;
			pol = TLMM_GPIO_INTR_CFG_INTR_POL_CTL;
		} else if ((irqmode & GPIO_INTR_POS_EDGE) != 0) {
			dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_POS;
			pol = TLMM_GPIO_INTR_CFG_INTR_POL_CTL;
		} else {
			KASSERT((irqmode & GPIO_INTR_DOUBLE_EDGE) != 0);
			dect = TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_EDGE_BOTH;
			pol = 0;
		}
	}

	val = RD4(sc, TLMM_GPIO_INTR_CFG(pin));
	val &= ~TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_MASK;
	val |= __SHIFTIN(dect, TLMM_GPIO_INTR_CFG_INTR_DECT_CTL_MASK);
	val &= ~TLMM_GPIO_INTR_CFG_INTR_POL_CTL;
	val |= pol;
	val &= ~TLMM_GPIO_INTR_CFG_TARGET_PROC_MASK;
	val |= __SHIFTIN(TLMM_GPIO_INTR_CFG_TARGET_PROC_RPM,
	    TLMM_GPIO_INTR_CFG_TARGET_PROC_MASK);
	val |= TLMM_GPIO_INTR_CFG_INTR_RAW_STATUS_EN;
	val |= TLMM_GPIO_INTR_CFG_INTR_ENABLE;
	WR4(sc, TLMM_GPIO_INTR_CFG(pin), val);

	mutex_exit(&sc->sc_lock);

	evcnt_attach_dynamic(&qih->ih_evcnt, EVCNT_TYPE_INTR,
	    NULL, device_xname(sc->sc_dev), qih->ih_name);

	return qih;
}

この部分は、AMD GPIOのレジスターのアクセス方法に変更する以外は、そのまま利用できそうです。

static void
qcomgpio_intr_disestablish(void *priv, void *ih)
{
	struct qcomgpio_softc * const sc = priv;
	struct qcomgpio_intr_handler *qih = ih;
	uint32_t val;

	evcnt_detach(&qih->ih_evcnt);

	mutex_enter(&sc->sc_lock);

	LIST_REMOVE(qih, ih_list);

	val = RD4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin));
	val &= ~TLMM_GPIO_INTR_CFG_INTR_ENABLE;
	WR4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin), val);

	mutex_exit(&sc->sc_lock);

	kmem_free(qih, sizeof(*qih));
}

この部分は、AMD GPIOのレジスターの扱い方に変更する以外は、そのまま利用できそうです。

static bool
qcomgpio_intr_str(void *priv, int pin, int irqmode, char *buf, size_t buflen)
{
	struct qcomgpio_softc * const sc = priv;
	int rv;

	rv = snprintf(buf, buflen, "%s pin %d", device_xname(sc->sc_dev), pin);

	return rv < buflen;
}

この部分は、そのまま利用できそうです。

static void
qcomgpio_intr_mask(void *priv, void *ih)
{
	struct qcomgpio_softc * const sc = priv;
	struct qcomgpio_intr_handler *qih = ih;
	uint32_t val;

	val = RD4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin));
	if (qih->ih_type == IST_LEVEL) {
		val &= ~TLMM_GPIO_INTR_CFG_INTR_RAW_STATUS_EN;
	}
	val &= ~TLMM_GPIO_INTR_CFG_INTR_ENABLE;
	WR4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin), val);
}

この部分は、AMD GPIOのレジスターの扱い方に変更する以外は、そのまま利用できそうです。

static void
qcomgpio_intr_unmask(void *priv, void *ih)
{
	struct qcomgpio_softc * const sc = priv;
	struct qcomgpio_intr_handler *qih = ih;
	uint32_t val;

	val = RD4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin));
	if (qih->ih_type == IST_LEVEL) {
		val |= TLMM_GPIO_INTR_CFG_INTR_RAW_STATUS_EN;
	}
	val |= TLMM_GPIO_INTR_CFG_INTR_ENABLE;
	WR4(sc, TLMM_GPIO_INTR_CFG(qih->ih_pin), val);
}

この部分は、AMD GPIOのレジスターの扱い方に変更する以外は、そのまま利用できそうです。

static int
qcomgpio_intr(void *priv)
{
	struct qcomgpio_softc * const sc = priv;
	struct qcomgpio_intr_handler *qih;
	int rv = 0;

	mutex_enter(&sc->sc_lock);

	LIST_FOREACH(qih, &sc->sc_intrs, ih_list) {
		const int pin = qih->ih_pin;
		uint32_t val;

		val = RD4(sc, TLMM_GPIO_INTR_STATUS(pin));
		if ((val & TLMM_GPIO_INTR_STATUS_INTR_STATUS) != 0) {
			qih->ih_evcnt.ev_count++;

			rv |= qih->ih_func(qih->ih_arg);

			val &= ~TLMM_GPIO_INTR_STATUS_INTR_STATUS;
			WR4(sc, TLMM_GPIO_INTR_STATUS(pin), val);
		}
	}

	mutex_exit(&sc->sc_lock);

	return rv;
}

この部分は、AMD GPIOのレジスターの読み取り方法とアクノリッジの処理を追加に変更する以外は、そのまま利用できそうです。

一読してみると、qcomgpio.cは、基本的な構造はそのままAMD GPIOに利用できるそうなことが分かりました。 長くなってしまったので、次回はAMD GPIOのためにamdgpio.cとamdgpioreg.hを用意して、 実際にトラックパッドを動かしてみたいと思います。

plgarc/wip/llama.cppでpkgsrcのBLASサポートを探る

この記事は、NetBSD Advent Calendar 2024の13日目の記事です。

llama.cppを使ってみる

以前に、NetBSD/amd64でllama.cppを使ってみるという記事でllama.cppを使ってみていました。 あれから、llama.cppはバージョンアップを繰り返し、様々なLLMも公開されました。 それほど頻繁ではないものの、私もllama.cppを使ってみています。 今回はb4333にアップデートするに当たって気付いたpkgsrcでのBLASサポートについて書きたいと思います。

シングルスレッドでしか動いていない

簡単ではない、例えば数百字以上の出力を求めるプロンプトを与えると、何も返事か返って来ない事象が発生していました。 また、以前も書いたようにプロンプトのテンプレートの問題なのかと思っていたのですが、とても長く待つと、ちゃんと返事が返って来ました。 そこで、処理中にtop(1)コマンドで状況を確認すると、1CPUしか使っていませんでした。 実行しているラップトップには8コア16スレッドを内蔵したCPUが搭載されています。 ログを見てみると、8スレッド以上は使ってくれそうです。 そこで、どのようになっているのか調べてみることにしました。

BLASバックエンドのソースコードを確認する

BLASサポートは、b4333だと、ggml/src/ggml-blasというディレクトリー内に格納されているようです。 このディレクトリー内のファイルを読んでも、マルチコア/マルチスレッドサポートの記述はなさそうです。 となると、BLASライブラリーがマルチコア/マルチスレッドサポートをしていると考えるのが適切そうです。

pkgsrcでのBLASサポート

pkgsrcには、いくつもBLASに相当するライブラリーのパッケージがあります。以下に挙げてみます。

  • math/blas
  • math/blas64
  • math/openblas
  • math/openblas64
  • math/openblas_openmp
  • math/openblas64_openmp
  • math/openblas_pthread
  • math/openblas64_pthread

これらは直接利用するのではなく、mk/blas.buildlink3.mkを経由して使うことで、 切り替えてビルド時に利用することができます。 mk/blas.buildlink3.mkで設定できるのは、まずは3つのBLAS_ACCEPTEDです。

  • netlib
  • openblas
  • openblas_pthread
  • openblas_openmp
  • accelerate.framework

ここで、accelerate.frameworkはNetBSDでは利用できないmacOSの機能ですので、除外して考えると、さきほどのパッケージとの関係は以下のようです。

  • netlib → math/blas、math/blas64
  • openblas → math/openblas、math/openblas64
  • openblas_pthread → math/openblas_pthread、math/openblas64_pthread
  • openblas_openmp → math/openblas_openmp、math/openblas64_openmp

でもこれだけでは、例えばmath/blasとmath/blas64を区別できません。これを区別するのがBLAS_INDEX64です。 これをyesに設定することで64付きのパッケージを選択できます。

pkgsrc/wip/llama.cppをマルチスレッド対応にする

と言うことで、llama.cppで使えるCPUで演算するBLASライブラリーを調べてみると、BLAS_ACCEPTEDで言うと、netlibとopenblas64ということがわかりました。 netlibはこれまで使って来て、マルチコアを活用できないものでした。活用できそうなmath/openblas64_pthread、math/openblas64_openmpを使えるようにしてみます。 pkgsrc/wip/llama.cppl/Makefileに以下のように追記してみます

BLAS_INDEX64=           yes
BLAS_ACCEPTED=          openblas_pthread openblas_openmp

ビルドして動作確認をしてみる

結果から言うと、openblas64_openmpは私の環境では正常にllama.cppを動かしてはくれませんでした。 全てのコアを使い切ってはくれますが、openblas_pthreadのように速やかに演算をしてはくれませんでした。 openblas_pthread、openblas_openmpそれぞれを使ったllama.cppをビルドするには、以下のようにします。

# make PKGSRC_BLAS_TYPES=openblas_openmp install
または
# make PKGSRC_BLAS_TYPES=openblas_pthread install

動作確認には、以前はllama-cliを使っていましたが、llama-serverとウェブブラウザーを使うのが良さそうです。 ChatGPT的なウェブページが提供されます。 IPv6で自ホスト以外からもアクセスできるようにする例です。 http://localhost:8080/をwww/firefox等で開くと利用できます。

$ llama-server --host :: --port 8080 -m ./models/gemma-2-2b-jpn-it-Q4_K_M.gguf

Firefoxで、espeak-ngによるtext-to-speechをする

この記事は、NetBSD Advent Calendar 2024の6日目の記事です。

Firefox for NetBSDでのtext-to-speech

昨年の今ごろに、festivalによるtext-to-speechをFirefox for NetBSDで試していました。 試してみると、pkgsrc/audio/festivalではなく、pkgsrc/audio/espeak-ngでも発声させることができました。

Firefoxをインストールする

pkgsrc/www/firefoxは、speechdオプションを有効にしてビルドしインストールする必要があります。 既定値ではspeechdオプションは有効ではないので、以下のように実行すれば良いでしょう。

# cd /usr/pkgsrc/www/firefox
# make PKG_OPTIONS.firefox=speechd install

必要なパッケージをインストールする

必要なのは、pkgsrc/audio/espeak-ngだけであるようです。 以下のように実行してインストールします。

# cd /usr/pkgsrc/audio/espeak-ng
# make install

speech-dispatcherを設定する

festivalの場合に設定した箇所に、espeak-ngを設定して行きます。 以下のパッチのように/usr/pkg/etc/speech-dispatcher/speechd.confを修正します。

--- /usr/pkg/etc/speech-dispatcher/speechd.conf.orig    2024-12-10 11:52:48.513672931 +0000
+++ /usr/pkg/etc/speech-dispatcher/speechd.conf
@@ -114,6 +114,7 @@ DefaultVolume 100
 # configuration.

 # DefaultLanguage "en-US"
+DefaultLanguage "en-US"


 # ----- MESSAGE DISPATCHING CONTROL -----
@@ -256,7 +257,7 @@ SymbolsPreprocFile "orca-chars.dic"
 #    or ~/.config/speech-dispatcher) or absolute

 #AddModule "espeak"                   "sd_espeak"    "espeak.conf"
-#AddModule "espeak-ng"                "sd_espeak-ng" "espeak-ng.conf"
+AddModule "espeak-ng"                "sd_espeak-ng" "espeak-ng.conf"
 #AddModule "festival"                 "sd_festival"  "festival.conf"
 #AddModule "flite"                    "sd_flite"     "flite.conf"
 #AddModule "ivona"                    "sd_ivona"     "ivona.conf"
@@ -290,11 +291,13 @@ SymbolsPreprocFile "orca-chars.dic"
 # must use one of the names of the modules loaded with AddModule.

 # DefaultModule espeak-ng
+DefaultModule espeak-ng

 # The LanguageDefaultModule selects which output modules are preferred
 # for specified languages.

 #LanguageDefaultModule "en"  "espeak"
+LanguageDefaultModule "en"  "espeak-ng"
 #LanguageDefaultModule "cs"  "festival"
 #LanguageDefaultModule "es"  "festival"
 
 

発声させてみる

Firefoxを一度終わらせ、再度起動します。 その上で、適当な英語で書かれたウェブページを開き、reader modeにします。 すると、ヘッドホンマークのtext-to-speechメニューが表示されます。 ここで、音声を選んで、▶アイコンで発声させられます。

ただ、festivalに増してあまり音質は良くないような気がします。 別途サーバーを起動しておかなくて良いのは利点かもしれません。

cdce(4)なUSB-Ethernetアダプターを使っている時に、ブリッジネットワークが使えなかった話

この記事は、NetBSD Advent Calendar 2024の3日目の記事です。

qemuで使うブリッジネットワークを用意したい

qemuで仮想マシンを使ってみるときには、qemuのユーザーネットワークが手軽ですが、ホスト側からアクセスしようとすると、自由度が低く面倒です。 そうなると、tap(4)とbridge(4)を使って、ホストと同じネットワークに接続したくなります。 私が通常使っているラップトップPCには、内蔵のEthernetデバイスはありません。 そして、過去にcdce(4)でしか認識されず、ure(4)としては認識されないUSB-Ethernetアダプターを使っていたのもあって、 以下のように/boot.cfgに設定しています。 これにより、滑てのure(4)なデバイスがcdce(4)なデバイスとして認識されます。

# cat /boot.cfg
(snip)
userconf=disable ure*
(snip)

ところが、cdce(4)として認識されているUSB-Ethernetデバイスをブリッジネットワークに参加させようとすると、 どうやっても通信ができないようでした。 もしかしたら、私のラップトップPCのxhciのせいかもしれませんが…。

ブリッジネットワークを設定する

The NetBSD guideのConfiguring bridged networking on a NetBSD host に記載のように、以下のようにすれば、ブリッジネットワークを設定できるはずです。 私は、以下のようにcdce(4)で設定してみました。

# ifconfig tap0 create
# ifconfig tap0 descr "NetBSD VM" up
# ifconfig bridge0 create
# ifconfig bridge0 descr "LAN VM bridge" up
# brconfig bridge0 add tap0 add cdce0

これで、qemuの仮想マシンを起動させても、そこで動かしているNetBSDから外のホストと同じネットワークにはアクセスできませんでした。 その時、以下のようにカーネルをエラーを出していました。

cdce0: watchdog timeout
cdce0: usb error on tx: IOERROR
cdce0: watchdog timeout
cdce0: usb error on tx: IOERROR
cdce0: watchdog timeout
cdce0: usb error on tx: IOERROR

とりあえずure(4)として使えばブリッジネットワークは構築できますが、同じ状態の人がいたら、参考になればと思います。

NetBSDからCanon LBP3100に印刷する

この記事は、NetBSD Advent Calendar 2024の2日目の記事です。

Canon LBP3100というプリンターについて

Canon LBP3100というプリンターは。Canonの安価なwinprinterです。 私は2015年3月に5,580円で購入しました。 トナーカートリッジはしばらく前になくなってしまいましたが、2,280円でサードパーティーのリサイクルトナーカートリッジを買ったので、まだしばらく使えればと思っています。

LBP3100はwinprinterだと書きましたが、実際にはCAPT (Canon Advanced Printing Technology)というページ記述言語を受け付けてくれます。 ただ、単純にCAPTフォーマットのデータをLBP3100に送ったら印刷されるという訳ではないようです。

CUPSで印刷する準備をする

CUPSは、今はPrinter ApplicationというIPSを受け付けて独自のプリンターへ出力する仕組みを推進しているようですが、今回はこれまで通りのフィルターコマンドを 使ってみます。 今回は、CAPTをリバースエンジニアリングして作られたcaptdriver 0.1.4.2-GxBというのを 使ってみることにします。

captdriverをビルドする

まず以下のようにして、https://github.com/mounaiban/captdriver/tree/0.1.4.2-GxB からソースコードをcloneします。

$ git clone git@github.com:mounaiban/captdriver.git
$ cd captdriver
$ git checkout 0.1.4.2-GxB

その上で、以下のように実行すれば、ビルドできます。

$ autoreconf -fi
$ ./configure
$ make

こうすると、src/rastertocaptというバイナリーファイルができます。

フィルタープログラムをインストールする

CUPSで印刷するには、フィルタープログラムとppdファイルが必要です。以下のようにインストールすれば良いでしょう。

# cp src/rastertocapt /usr/pkg/libexec/cups/filter
# mkdir /usr/pkg/share/cups/model

ppdファイルは0.4.2-GxBブランチには存在しないので、以下のようにブランチを切り替えてppdファイルを入手します。

$ git checkout master
# cp ppd/CanonLBP-2900-3000.ppd ppd/CanonLBP-3010-3018-3050.ppd /usr/pkg/share/cups/model

LBP3100で使うのはCanonLBP-3010-3018-3050.ppdの方です。

印刷する準備をする

次に、必要なパッケージをインストールしておきます。 以下のようにすれば良いでしょう。

# cd /usr/pkgsrc/print/cups-filters
# make install

cups-filtersは必須です。これがないと、captdriverはエラーになり利用することができません。

他にもCUPSを利用して印刷するプログラムが必要です。今回はpkgsrc/www/firefox-133.0を使うことにします。 (firefox-133.0はまだpkgsrc treeにcommitされていません。)

CUPS daemonを起動させ、プリンターを追加する

LBP3100はUSB接続しかないプリンターです。 Canon LBP3100をつなぐと、ulpt(4)として認識されます。 ですが、ugen(4)として認識されないとうまくプリンターとして認識されないようです。 /boot.cfgに以下のように追記して、再起動後にultp(4)を無効化するように設定します。

$ cat /boot.cfg
userconf=disable ulpt*

続いて、cupsdをNetBSD起動時に起動させるようにします。

$ cat /etc/rc.conf
(snip)
cupsd=YES
(snip>

ここまで設定できたら、NetBSDを再起動させます。

プリンターを追加する

ウェブブラウザーでhttp://localhost:631/を開きます。Administrationを選択します。ログインが求められたらNetBSDへのログイン情報を入力します。

LBP3100とNetBSDマシンをUSB A to Bケーブルで接続し、LBP3100の電源を入れた上で、 Add Printerボタンを押します。

Local PrinterのCanon LBP3100/LBP3108/LBP3150 (Canon LBP3100/LBP3108/LBP3150)を選択します。

私はプリンター名は単に「LBP3100」と変更しました。

インストールしてあるPPDファイルを選択するため、MakeではCanon Incを選択します。

PPDは「Canon Inc LBP3010/LBP3018/LBP3050 r2c, 0.1.4 (en, en)」を選択します。

無事、LBP3100をプリンターとして追加することができました。

印刷してみる

例えばFirefoxだと、下図のように印刷することができます。画質も悪くなさそうです。

NetBSDでspeech-to-textをしてみる

この記事は、 NetBSD Advent Calendar 2024 の15日目の記事です。 speech-to-textエンジンを選ぶ 音声からテキストに変換してくれるのが、speech-to-textエンジンです。 OpenAIのWhisper v3とい...