BEEP

Table of Contents

beep

前回は、ioctlを調べた方がいいよって結論だった。ならば、ガチャして、も とえ、次のようにして、炙り出 せばよい。

sakae@fb:~ $ find /usr/src/usr.bin -name '*.c' | xargs grep ioctl -l
/usr/src/usr.bin/backlight/backlight.c
/usr/src/usr.bin/write/write.c
/usr/src/usr.bin/morse/morse.c
/usr/src/usr.bin/beep/beep.c
 :

色々出てきた中からbeepを選んでみた。だって、単純そうだから。morse.sなんてのも魅力的だけどね。 手元によりどりみどりのサンプルが有るって、贅沢このうえないぞ。

sakae@slfb:~ $ beep -h
Usage: beep [parameters]
        -F <frequency in HZ, default 440 Hz>
        -D <duration in ms, from 50 ms to 2000 ms, default 150 ms>
        -r <sample rate in HZ, from 8000 Hz to 48000 Hz, default 48000 Hz>
        -d <OSS device (default /dev/dsp)>
        -g <gain from 0 to 100, default 75>
        -B Run in background
        -h Show usage

beep音を発生させるだけのアプリ。高級にサンプリング音源を使うっぽい。簡単に使い方を確認。

sakae@slfb:~ $ beep -D 500 -g 85

440Hzの音を500ms発生しなさい。ボリューム(音量)は85/100の位置でお願いします。 このゲインだけど、ウェーバー・ヘヒナの人体反応を取り込んでいないな。 ウェーバー・ヘヒナってのは、刺激は対数になるって奴だ。

sound dev

FreeBSDで音はどうやって設定するの? そんなのハンドブックを参照するのさ。 第7章 マルチメディア

sakae@slfb:~ $ dmesg | grep pcm
pcm0: <Realtek ALC269 (Analog 2.0+HP/2.0)> at nid 20,21 and 18 on hdaa0
pcm1: <Realtek ALC269 (Rear Analog Mic)> at nid 24 on hdaa0
pcm2: <Intel Cantiga (HDMI 8ch)> at nid 3 on hdaa1

どんな音装置を認識してるかは、上記で調査できる。

sakae@slfb:~ $ cat /dev/sndstat
Installed devices:
pcm0: <Realtek ALC269 (Analog 2.0+HP/2.0)> (play/rec) default
pcm1: <Realtek ALC269 (Rear Analog Mic)> (rec)
pcm2: <Intel Cantiga (HDMI 8ch)> (play)
No devices installed from userspace.
sakae@slfb:~ $ ls -l /dev/dsp*
crw-rw-rw-  1 root wheel 0x51 Sep 13 06:01 /dev/dsp0
crw-rw-rw-  1 root wheel 0x53 Sep 13 06:01 /dev/dsp1
crw-rw-rw-  1 root wheel 0x55 Sep 13 06:01 /dev/dsp2

このうちの何れが採用されてるかは、sndstatしてデフォになってるのがそれ だ。デフォは、dspってデバイス名になるんだな。 動作チェックは下記の様にして簡単にできます。

cat file > /dev/dsp

fileは何でもいいのですが、rubyのtar玉の中になるpi生成スクリプトで発生 したデータをfileにした奴がお勧めです。

ob$ ruby pi.rb
31415926535897932384626433832795028841971693993751058209 ....

こやつはバッハのフーガとか北島のサブちゃんの祭とか、世界のあらゆる楽 曲が含まれた偉大なアーカイブですからね。

尚、上記は、10年前にWindows7が付属してた、ThinPad SL510 にFreeBSDを入 れたリアルマシンです。

同じマシンでも、OpenBSDだと、

azalia0 at pci0 dev 27 function 0 "Intel 82801I HD Audio" rev 0x03: msi
azalia0: codecs: Realtek ALC269, Intel/0x2802, using Realtek ALC269
audio0 at azalia0

そして、デバイスの扱かいも独自だ。 OpenBSD FAQ - Multimedia

on qemu

qemuでは、どうする? まとめは、こちら。 QEMU audio

これって、外部から輸入してくるんだな。

sakae@fb:~ $ qemu-system-i386 -audiodev help
Available audio drivers:
none
dbus
oss
sdl
wav
qemu-system-i386 -m 256 -nographic -no-fd-bootchk -s \
  -net nic -net user,hostfwd=tcp::2022-:22  \
  -audiodev oss,id=snd0   -device AC97,audiodev=snd0 \
  -hda disk.img

ハンドブックにossなんて語句があったので、それを輸入指示。そしてそれを qemu内でどう扱うか指示するんだな。 Audio Codec 97 とか、その後継である、 High Definition Audio それから、おまけで、 I/O コントローラー・ハブ (ICHn) とか。Intel入っている風だ。

root@qemu:~ # dmesg | grep pcm
pcm0: <Intel ICH (82801AA)> port 0xc000-0xc3ff,0xc400-0xc4ff irq 11 at device 4.0 on pci0
pcm0: <SigmaTel STAC9700/83/84 AC97 Codec>
pcm0: measured ac97 link rate at 2134 Hz
root@qemu:~ # cat /dev/sndstat
Installed devices:
pcm0: <Intel ICH (82801AA)> (play/rec) default
No devices installed from userspace.

OpenBSDだと、qemuのパラメータを次のように変更。

-audiodev sndio,id=snd0   -device AC97,audiodev=snd0 \

こんな風に、認識してくれた。

auich0 at pci0 dev 4 function 0 "Intel 82801AA AC97" rev 0x01: apic 0 int 11, H
ac97: codec id 0x83847600 (SigmaTel STAC9700)
audio0 at auich0

gdb on qemu

ゲストOS側でも、ひょっとしてgdbが必要になるかと思って入れてみた。色々 な付属品がやってくるんでウザイ。

FreeBSD repository update completed. 33086 packages processed.
All repositories are up to date.
The following 17 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        boost-libs: 1.84.0
        expat: 2.6.2
        gdb: 14.1_2
        gettext-runtime: 0.22.5
        gmp: 6.3.0
        icu: 74.2_1,1
        indexinfo: 0.3.1
        libffi: 3.4.6
        libiconv: 1.17_1
        liblz4: 1.9.4_1,1
        mpdecimal: 4.0.0
        mpfr: 4.2.1,1
        python311: 3.11.9
        readline: 8.2.10
        source-highlight: 3.1.9_9
        xxhash: 0.8.2_1
        zstd: 1.5.6

入れる前後で、容量チェック。固定資産であるpkgのDBはどれぐらいなんだろ う。7Mのtar玉をダウンロードしてsqlしてたけど。。

sakae@fb14:~ $ df
Filesystem   1K-blocks    Used   Avail Capacity  Mounted on
/dev/ada0s1a   3842364 1429996 2104980    40%    /
devfs                1       0       1     0%    /dev
sakae@fb14:~ $ df
Filesystem   1K-blocks    Used   Avail Capacity  Mounted on
/dev/ada0s1a   3842364 2082472 1452504    59%    /
devfs                1       0       1     0%    /dev

652Mも喰うんかい。そのうちの92Mは固定資産ってか、pkgの管理に使用されて いた。

which check

ここからが本題。ioctlが発生した時の挙動をチェックしたい。 何処を見ればいいの? まずはアプリ側。

userland side

beep.cの主要部分。

ob$ grep ioctl beep.c
        if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0)
                errx(1, "ioctl SOUND_PCM_WRITE_CHANNELS(1) failed");
        if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0)
                errx(1, "ioctl SNDCTL_DSP_SETFMT(AFMT_S32_NE) failed");
        if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0)
                errx(1, "ioctl SNDCTL_DSP_SPEED(%d) failed", sample_rate);
        if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c))
                errx(1, "ioctl SNDCTL_DSP_SETFRAGMENT(0x%x) failed", c);
        if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0)
                errx(1, "ioctl SNDCTL_DSP_GETODELAY failed");
        while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) {

この他で大事なのは、

f = open(oss_dev, O_WRONLY);

oss_dev は、/dev/dsp の事だ。デバイスが使用する音データは、グレイコー ドとやらを計算して用意してる。グレイコードと実数

sakae@fb:/tmp $ cc -g -O0 beep.c -lm
sakae@fb:/tmp $ gdb -q a.out
Reading symbols from a.out...
(gdb) b beep.c:208
Breakpoint 1 at 0x401f82: file beep.c, line 208.
(gdb) r
Starting program: /tmp/a.out

Breakpoint 1, main (argc=1, argv=0xffbfebb8) at beep.c:208
208             if (ioctl(f, SOUND_PCM_WRITE_CHANNELS, &c) != 0)
(gdb) p c
$1 = 1
(gdb) p f
$2 = 3

FreeBSDではdebug用のバイナリーが用意されてるけど、オプチマイズされてて、 変数の細部を確認できない事がある。しょうがないので、自前でコンパイルし たよ。この例だと、モノラル動作を要求してるのだな。

212             if (ioctl(f, SNDCTL_DSP_SETFMT, &c) != 0)
(gdb) p c
$3 = 4096
(gdb) n
215             if (ioctl(f, SNDCTL_DSP_SPEED, &sample_rate) != 0)
(gdb) p sample_rate
$4 = 48000
221             if (ioctl(f, SNDCTL_DSP_SETFRAGMENT, &c))
(gdb) p c
$5 = 131084
224             if (ioctl(f, SNDCTL_DSP_GETODELAY, &c) != 0)
(gdb) p c
$6 = 131084

次々と設定を施していく。

Breakpoint 2, main (argc=1, argv=0xffbfebb8) at beep.c:257
257             if (write(f, buffer, size * sizeof(buffer[0])) !=
(gdb) p buffer
$10 = (int32_t *) 0x20654380
(gdb) p size
$11 = 7200
264             while (ioctl(f, SNDCTL_DSP_GETODELAY, &c) == 0) {
(gdb) p c
$12 = 0

そして、データを計算して、そいつをdspに転送。これで演奏?が始まる。後 は演奏の進行に合わせてデータを供給。全てが終了したら、少し余韻を取って から(usleep(50000);)閉幕。

kernel side

次はカーネル側。デバドラなんで、だまっていてもチェックすべき場所は限定 される。カーネルのソースと言っても、全体の9割は、devの中にあるからね。

sakae@fb:/sys/dev $ fgrep 'Intel ICH (82801AA)' -r .
./sound/pci/ich.c:              "Intel ICH (82801AA)" },

dmesgが報告してくるメッセージを手がかりにガサ入れ。

対応リストを見ると、一番初期のICHだね。最近だと、ICH7までサポートされ てる。

飛んで来そうな所は、ここかなあ?

static __inline void
ich_wr(struct sc_info *sc, int regno, uint32_t data, int size)
{
        switch (size) {
        case 1:
                bus_space_write_1(sc->nabmbart, sc->nabmbarh, regno, data);
                break;
        case 2:
                bus_space_write_2(sc->nabmbart, sc->nabmbarh, regno, data);
                break;
        case 4:
                bus_space_write_4(sc->nabmbart, sc->nabmbarh, regno, data);
                break;
        }
}

watch kernel

どうも、dspと言う奴は、ロジック部とアナログ部(コーディックと称するよう だ)に分かれるようだ。上で捉えたのは、そのうちのロジック部。まずは、こ ちらを追跡。

logic

初回はopenに費される。

(gdb) bt
#0  ich_wr (sc=0xb84e480, size=1, regno=<optimized out>, data=<optimized out>)
    at /usr/src/sys/dev/sound/pci/ich.c:235
#1  ichchan_setblocksize (obj=<optimized out>, data=0xb84e4e4,
    blocksize=<optimized out>) at /usr/src/sys/dev/sound/pci/ich.c:504
#2  0x00dcda99 in CHANNEL_SETBLOCKSIZE (obj=0xb99e490, data=0xb84e4e4,
    blocksize=1024) at ./channel_if.h:117
#3  chn_resizebuf (c=<optimized out>, c@entry=0x3cf8400,
    latency=<optimized out>, blkcnt=<optimized out>, blksz=<optimized out>)
    at /usr/src/sys/dev/sound/pcm/channel.c:1887
#4  0x00dce483 in chn_setlatency (c=0x3cf8400, latency=193585664)
    at /usr/src/sys/dev/sound/pcm/channel.c:1979
#5  chn_notify (c=0x3cf8400, flags=8)
    at /usr/src/sys/dev/sound/pcm/channel.c:2353
#6  0x00dcd6e1 in chn_resizebuf (c=<optimized out>, latency=<optimized out>,
    latency@entry=-2, blkcnt=<optimized out>, blksz=<optimized out>)
    at /usr/src/sys/dev/sound/pcm/channel.c:1842
#7  0x00dcbfd9 in chn_setparam (c=0xb9b8800, format=1048584, speed=8000)
    at /usr/src/sys/dev/sound/pcm/channel.c:2026
#8  0x00dcbd01 in chn_reset (c=0xb9b8800, fmt=1048584, spd=8000)
    at /usr/src/sys/dev/sound/pcm/channel.c:1145
#9  0x00dcff86 in dsp_open (i_dev=0xbb95400, flags=<optimized out>,
    mode=<optimized out>, td=0xcd00780) at /usr/src/sys/dev/sound/pcm/dsp.c:610
#10 0x00ed994e in devfs_open (ap=0xcb849e4)
    at /usr/src/sys/fs/devfs/devfs_vnops.c:1288
#11 0x0144efe8 in VOP_OPEN_APV (vop=0x1aa5fc4 <devfs_specops>, a=0xcb849e4)
    at vnode_if.c:434
#12 0x01108336 in VOP_OPEN (vp=0x11b89a8c, mode=<optimized out>,
    cred=<optimized out>, td=<optimized out>, fp=0xccf6e4c) at ./vnode_if.h:218
#13 vn_open_vnode (vp=0x11b89a8c, fmode=2, cred=0xcc6d000, td=0xcd00780,
    fp=0xccf6e4c) at /usr/src/sys/kern/vfs_vnops.c:444
#14 0x01107f6e in vn_open_cred (ndp=0xcb84b3c, flagp=0xcb84bec, cmode=0,
    vn_open_flags=16, cred=0xcc6d000, fp=0xccf6e4c)
    at /usr/src/sys/kern/vfs_vnops.c:331
#15 0x010fe52e in openatfp (td=td@entry=0xcd00780, dirfd=<optimized out>,
    path=0x4007cb "Z\377s*\276\235", <incomplete sequence \350\034>,
    pathseg=<optimized out>, flags=2, mode=0, fpp=0x0)
    at /usr/src/sys/kern/vfs_syscalls.c:1164
#16 0x010fe232 in kern_openat (td=0xcd00780, dirfd=193585664,
    pathseg=UIO_USERSPACE, mode=2, path=<optimized out>, flags=<optimized out>)
    at /usr/src/sys/kern/vfs_syscalls.c:1269
#17 sys_openat (td=0xcd00780, uap=0xcd00a18)
    at /usr/src/sys/kern/vfs_syscalls.c:1102
#18 0x0143deff in syscallenter (td=<optimized out>)
    at /usr/src/sys/i386/i386/../../kern/subr_syscall.c:188
#19 syscall (frame=0xcb84ce8) at /usr/src/sys/i386/i386/trap.c:1161
#20 0xffc03479 in ?? ()
 :

数度continueすると、ioctlが顔を出してくる。

(gdb) bt 10
#0  ich_wr (sc=0xb84e480, size=1, regno=<optimized out>, data=<optimized out>)
    at /usr/src/sys/dev/sound/pci/ich.c:235
#1  ichchan_setblocksize (obj=<optimized out>, data=0xb84e4e4,
    blocksize=<optimized out>) at /usr/src/sys/dev/sound/pci/ich.c:504
#2  0x00dcda99 in CHANNEL_SETBLOCKSIZE (obj=0xb99e490, data=0xb84e4e4,
    blocksize=1024) at ./channel_if.h:117
#3  chn_resizebuf (c=<optimized out>, c@entry=0x3cf8400,
    latency=<optimized out>, blkcnt=<optimized out>, blksz=<optimized out>)
    at /usr/src/sys/dev/sound/pcm/channel.c:1887
#4  0x00dce483 in chn_setlatency (c=0x3cf8400, latency=193585664)
    at /usr/src/sys/dev/sound/pcm/channel.c:1979
#5  chn_notify (c=0x3cf8400, flags=8)
    at /usr/src/sys/dev/sound/pcm/channel.c:2353
#6  0x00dcd6e1 in chn_resizebuf (c=<optimized out>, latency=<optimized out>,
    latency@entry=-2, blkcnt=<optimized out>, blksz=<optimized out>)
    at /usr/src/sys/dev/sound/pcm/channel.c:1842
#7  0x00dcbfd9 in chn_setparam (c=0xb9b8800, format=1048584, speed=8000)
    at /usr/src/sys/dev/sound/pcm/channel.c:2026
#8  0x00dcc01d in chn_setformat (c=0xb9b8800, format=1048584)
    at /usr/src/sys/dev/sound/pcm/channel.c:2076
#9  0x00dd30f1 in dsp_ioctl (i_dev=0xbb95400, cmd=3221508102,
    arg=0xcb84b68 "\001", mode=<optimized out>, td=0xcd00780)
    at /usr/src/sys/dev/sound/pcm/dsp.c:1529
(More stack frames follow...)

更に進めると、終了待ちのループでグルグルするのだな。

(gdb) bt
#0  ich_wr (size=<optimized out>, sc=<optimized out>, regno=<optimized out>,
    data=<optimized out>) at /usr/src/sys/dev/sound/pci/ich.c:235
#1  ich_intr (p=0xb84e480) at /usr/src/sys/dev/sound/pci/ich.c:651
#2  0x00fecd7a in intr_event_execute_handlers (ie=0x3c4c300, p=<optimized out>)
    at /usr/src/sys/kern/kern_intr.c:1167
#3  ithread_execute_handlers (ie=0x3c4c300, p=<optimized out>)
    at /usr/src/sys/kern/kern_intr.c:1180
#4  ithread_loop (arg=0xb99e500) at /usr/src/sys/kern/kern_intr.c:1268
#5  0x00fea0e5 in fork_exit (callout=0xfecb90 <ithread_loop>, arg=0xb99e500,
    frame=0x3abfce8) at /usr/src/sys/kern/kern_fork.c:1151
#6  0xffc0348e in ?? ()

codec

A/D とか D/A いわゆる、録音と再生をつかさどるアナログ部ね。どこで担当 してるかメーカー名で検索。

sakae@fb:/sys/dev/sound $ fgrep 'SigmaTel' -l -r .
./pcm/ac97.c

ここに登録されてる関数にBPを置いてみたけど、どれもヒットせず。ミキサー とかを利用しないと、呼び出される事は、無いのかな。それとも、インテルの 石の中に、すっぽり吸収されちゃって、出る幕が無いのかな?

STAC9700 こんな石のスペックシートでも眺めながら、お茶を濁しましょうか ね。

sys_ioctl

今度は、システムコールを追求

Breakpoint 1, kern_ioctl (td=0xccfdb40, fd=0, com=2150396949,
    data=0xcb61b68 "\002k") at /usr/src/sys/kern/sys_generic.c:727
727             AUDIT_ARG_FD(fd);
(gdb) bt
#0  kern_ioctl (td=0xccfdb40, fd=0, com=2150396949, data=0xcb61b68 "\002k")
    at /usr/src/sys/kern/sys_generic.c:727
#1  0x0108ef19 in sys_ioctl (td=0xccfdb40, uap=0xccfddd8)
    at /usr/src/sys/kern/sys_generic.c:709
#2  0x0143deff in syscallenter (td=<optimized out>)
    at /usr/src/sys/i386/i386/../../kern/subr_syscall.c:188
#3  syscall (frame=0xcb61ce8) at /usr/src/sys/i386/i386/trap.c:1161
#4  0xffc03479 in ?? ()
:

継続すると、

Breakpoint 1, kern_ioctl (td=0xccfe3c0, fd=10, com=2147775606, data=0xcb6bb68 "\017\003") at /usr/src/sys/kern/sys_generic.c:\
727
727             AUDIT_ARG_FD(fd);
(gdb) c
Continuing.

Breakpoint 1, kern_ioctl (td=0xccfe3c0, fd=3, com=3221508102, data=0xcb6bb68 "\001") at /usr/src/sys/kern/sys_generic.c:727
727             AUDIT_ARG_FD(fd);
(gdb) c
Continuing.

Breakpoint 1, kern_ioctl (td=0xccfe3c0, fd=3, com=3221508101, data=0xcb6bb68 "") at /usr/src/sys/kern/sys_generic.c:727
727             AUDIT_ARG_FD(fd);
:

fdに色々出てくるなあ。これって雑音? ならば

truss

sakae@fb:/tmp $ truss -o LOG ./a.out
sakae@fb:/tmp $ grep ioctl LOG
ioctl(3,SOUND_PCM_WRITE_CHANNELS,0xffbfeb64)     = 0 (0x0)
ioctl(3,SNDCTL_DSP_SETFMT,0xffbfeb64)            = 0 (0x0)
ioctl(3,SNDCTL_DSP_SPEED,0x404934)               = 0 (0x0)
ioctl(3,SNDCTL_DSP_SETFRAGMENT,0xffbfeb64)       = 0 (0x0)
ioctl(3,SNDCTL_DSP_GETODELAY,0xffbfeb64)         = 0 (0x0)
ioctl(3,SNDCTL_DSP_GETODELAY,0xffbfeb64)         = 0 (0x0)
ioctl(3,SNDCTL_DSP_GETODELAY,0xffbfeb64)         = 0 (0x0)
ioctl(3,SNDCTL_DSP_GETODELAY,0xffbfeb64)         = 0 (0x0)
ioctl(3,SNDCTL_DSP_GETODELAY,0xffbfeb64)         = 0 (0x0)

北里柴三郎先生みたいに、純粋培養成功。

この場合は、ktrace/kdumpを使うより楽です。

rain

USBHDDのFreeBSDを利用してきたけど、ノーパソのコンソールは利用してない。 スケリーン・セーバーを動かして画面の焼け付け(LCDでそんな事が起きるかは、 不問)防止したい。OpenBSDならOFFに出来たんだけど。

手軽に使えるものないか? たとえばdialogとかでだ。が、どう探しても、そ んな機能は無い(当然か)。ならば、cursesだな。

cursesでメダカっぽい動きを表現する こんなのが有った。けど、余計なnumpy とかを利用してる。もっとシンプルなのでいいのにな。Demoに、rain.py なんてのがあった。これ、ピッタリですよ。

ただ、このまま使うと、線条降水帯の豪雨のごとく、すさまじい速さで雨が落 ちてくる。心臓に良くないぞ。もっと慈雨がいいんだけど。

ch = stdscr.getch()
if ch == ord('q') or ch == ord('Q'):
    return
elif ch == ord('s'):
    stdscr.nodelay(0)
elif ch == ord(' '):
    stdscr.nodelay(1)

curses.napms(50)

こんな風になってた。多分50ms間隔で雨が降る設定だろう。200に変更したら、 穏やかな雨になった。これにて一件落着。でもいいんだけど、どんな意味が有 るか確認。

Python 3.11.9 (main, Jul  9 2024, 01:16:22) [Clang 17.0.6 (https://github.com/llvm/llvm-project.git llvmorg-17.0.6-0-g600970 on freebsd13
Type "help", "copyright", "credits" or "license" for more information.
>>> import curses
>>> help(curses)

pythonに聞けがいいんだろうけど、nodelayは掲載されていなかった。中途半 端です。こういう時は、manだな。

sakae@fb:~ $ man nodelay
curs_inopts(3X)                                                curs_inopts(3X)

NAME
       cbreak, nocbreak, echo, noecho, halfdelay, intrflush, keypad, meta,
       nodelay, notimeout, raw, noraw, noqiflush, qiflush, timeout, wtimeout,
       typeahead - curses input options

   nodelay
       The nodelay option causes getch to be a non-blocking call.  If no input
       is ready, getch returns ERR.  If disabled (bf is FALSE), getch waits
       until a key is pressed.

1/0のように一見して時間の指定かと思ったら、True/Falseのエイリアスとし て利用してんだな。デモなんだから、紛らわしい記述はヤメてくれ。

なお、こやつはpython3系で姿を消している。 python2 には存在してた。3系は、もはやプラットフォームだから、何か他の ものと混ぜて使ってくださいって方針になっちまったのか。

ついでに見つけた、これ、格好いいな。 matrix-rain

etc