目次: Linux
PCIデバイスを使った実験をする際にコンフィグ空間やメモリ空間を読み書きしたいときがあります。メモリ空間の読み書き程度ならば、わざわざPCIデバイスドライバを書かなくても /sys/bus/pci/devices/*/ にあるresourceNファイルにアクセスすれば良いです。
PCIデバイスはコンフィグ空間にBAR (Base Address Register) という32bitレジスタが6個あり、メモリ空間をPC側のアドレスのどこにマッピングするかを指定します。BARがそのままresourceNに対応しています。
最近は64bitアドレスでマッピングするデバイスも多いですが、その際は連続するBARを2つ使ってマッピング先のアドレスを指定します。実際に見たほうが早いと思うので、私の使っているマシンのグラフィックカードNVIDIA GeForce GT 1030を例にしましょう。
# lspci | grep VGA 08:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1) # lspci -s 08:00.0 -v 08:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1) (prog-if 00 [VGA controller]) Subsystem: Micro-Star International Co., Ltd. [MSI] GP108 [GeForce GT 1030] Flags: bus master, fast devsel, latency 0, IRQ 84, IOMMU group 14 Memory at f6000000 (32-bit, non-prefetchable) [size=16M] ★BAR0: resource0 Memory at e0000000 (64-bit, prefetchable) [size=256M] ★BAR1: resource1(64bitアドレス空間なのでBAR2も使われる) Memory at f0000000 (64-bit, prefetchable) [size=32M] ★BAR3: resource3(64bitアドレス空間なのでBAR4も使われる) I/O ports at d000 [size=128] ★BAR5: resource5 Expansion ROM at 000c0000 [virtual] [disabled] [size=128K] Capabilities: [60] Power Management version 3 Capabilities: [68] MSI: Enable- Count=1/1 Maskable- 64bit+ Capabilities: [78] Express Legacy Endpoint, MSI 00 Capabilities: [100] Virtual Channel Capabilities: [128] Power Budgeting <?> Capabilities: [420] Advanced Error Reporting Capabilities: [600] Vendor Specific Information: ID=0001 Rev=1 Len=024 <?> Capabilities: [900] Secondary PCI Express Kernel driver in use: nvidia Kernel modules: nvidia
デバイスの位置(08:00.0)と、マッピングしているアドレスがBAR0, 1, 3, 5の4つ あることがわかります。/sys/bus/pci/devices経由でPCIデバイスのBAR1でマップされているメモリを読み出します。
# cd /sys/bus/pci/devices/0000\:08\:00.0/ # ma -k resource1 dd 0 00000000 ff000020 ff00082c ff000020 ff00082c 00000010 ff3c0020 ff42082c ff990020 ffa50027 00000020 ffa10020 ffa10027 ffa50020 ff990027 00000030 ff420020 ff3c0027 ff000020 ff000027 00000040 ff000020 ff000027 ff000020 ff000027 00000050 ff000020 ff000027 ff000020 ff000027 00000060 ff000020 ff000027 ff000020 ff000027 00000070 ff000020 ff000027 ff000020 ff000027 00000080 ff000020 ff000027 ff000020 ff000027 00000090 ff3c0020 ff420027 ffb90020 ffa50027 000000a0 ffa50020 ffb90027 ffa90020 ffa50027 000000b0 ff420020 ff3c0027 ff000020 ff000027 000000c0 ff000020 ff000027 ff000020 ff000027 000000d0 ff000020 ff000027 ff000020 ff000027 000000e0 ff000020 ff000027 ff000020 ff000027 000000f0 ff000020 ff000027 ff000020 ff000027 00000100
読み出しツールは何を使っても構いませんが、サイズがめっちゃでかい(256MB)ので「頭から読む系のツール(hexdumpやテキストエディタ)」は使わないほうが無難です。今回は拙作のmemaccess(GitHubへのリンク)を使いました。
ちなみにresourceNを読みだそうとすると怒られるときは、NVIDIAのドライバをロードしていないかチェックしてみてください。
# ma -k resource0 dd 0 mmap: Invalid argument # cat /proc/iomem ... e0000000-fec2ffff : PCI Bus 0000:00 e0000000-f1ffffff : PCI Bus 0000:08 e0000000-efffffff : 0000:08:00.0 f0000000-f1ffffff : 0000:08:00.0 f6000000-f70fffff : PCI Bus 0000:08 f6000000-f6ffffff : 0000:08:00.0 f6000000-f6ffffff : nvidia ★BAR0をnvidiaドライバが使用中なのでmmapできない f7080000-f7083fff : 0000:08:00.1 f7080000-f7083fff : ICH HD audio ... # rmmod nvidia_uvm nvidia_drm nvidia_modeset nvidia # cat /proc/iomem ... e0000000-fec2ffff : PCI Bus 0000:00 e0000000-f1ffffff : PCI Bus 0000:08 e0000000-efffffff : 0000:08:00.0 f0000000-f1ffffff : 0000:08:00.0 f6000000-f70fffff : PCI Bus 0000:08 f6000000-f6ffffff : 0000:08:00.0 ★nvidiaドライバがアンロードされた f7080000-f7083fff : 0000:08:00.1 f7080000-f7083fff : ICH HD audio ... # ma -k resource0 dd 0 00000000 138000a1 00000000 00000000 bad00200 00000010 bad00200 bad00200 bad00200 bad00200 00000020 bad00200 bad00200 bad00200 bad00200 00000030 bad00200 bad00200 bad00200 bad00200 00000040 bad00200 bad00200 bad00200 bad00200 00000050 bad00200 bad00200 bad00200 bad00200 00000060 bad00200 bad00200 bad00200 bad00200 00000070 bad00200 bad00200 bad00200 bad00200 00000080 0000008f bad00200 bad00200 bad00200 00000090 bad00200 bad00200 bad00200 bad00200 000000a0 bad00200 bad00200 bad00200 bad00200 000000b0 bad00200 bad00200 bad00200 bad00200 000000c0 bad00200 bad00200 bad00200 bad00200 000000d0 bad00200 bad00200 bad00200 bad00200 000000e0 bad00200 bad00200 bad00200 bad00200 000000f0 bad00200 bad00200 bad00200 bad00200 00000100
ドライバが管理しているメモリを横から読み書きされたらたまったもんじゃないですから、この挙動は当然ですね。え?読み出しなら良いのでは?と思われるかもしれませんが、読み出しをトリガーに動作したり、読み出すと値が変化するレジスタなどは珍しくないので、勝手に読み出すのも実はダメなのです。
先ほど /sys/bus/pci/devices以下のファイルをls -lなどで見た方はお気づきかと思いますが、rootしか読めないようなパーミッションになっています。
# cd /sys/bus/pci/devices/0000\:08\:00.0/ # ls -l resource* -r--r--r-- 1 root root 4096 8月 2 22:08 resource -rw------- 1 root root 16777216 8月 2 22:10 resource0 -rw------- 1 root root 268435456 8月 2 22:10 resource1 -rw------- 1 root root 268435456 8月 2 22:10 resource1_wc -rw------- 1 root root 33554432 8月 2 22:10 resource3 -rw------- 1 root root 33554432 8月 2 22:10 resource3_wc -rw------- 1 root root 128 8月 2 22:10 resource5
誰でもデバイスのメモリにアクセスできるのはマズいですから、セキュリティ上この設定は真っ当です。真っ当ですが、実験に使うときはいちいちsudoするのが面倒なのも事実です。手動でchmodやchownしてパーミッションを変えればsudo不要にできますが、いくつか欠点があります。
どこにいるかわからないデバイスを操作したいときは、udevの出番です。
前置きが長くなりましたが、やっと本題です。/etc/udev/rules.dに99-hogehoge.rulesのようなファイルを作ります。全てのPCIデバイスのパーミッションを全開にする必要はないので、ベンダーIDとデバイスIDでGeForce GT 1030だけ該当するようにフィルタリングしましょう。
# lspci -s 08:00.0 08:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1) # lspci -s 08:00.0 -n 08:00.0 0300: 10de:1d01 (rev a1) | `--- Device ID `--- Vendor ID
他の条件をフィルタリングに使いたいときは、udevadm infoを使うと、vendorやdeviceも含んだ全ての属性を見ることができます。
# udevadm info -a -p /sys/bus/pci/devices/0000:08:00.0 | less ... looking at device '/devices/pci0000:00/0000:00:03.1/0000:08:00.0': KERNEL=="0000:08:00.0" SUBSYSTEM=="pci" DRIVER=="" ATTR{ari_enabled}=="0" ... ATTR{revision}=="0xa1" ATTR{subsystem_device}=="0x8c98" ATTR{subsystem_vendor}=="0x1462" ATTR{vendor}=="0x10de" ★ベンダーID ...
IDが判明したらルールファイルを書きます。/sys/bus/pci/devices/* のディレクトリと配下のファイル全てにgroupとotherのRead/Writeパーミッションを付加します。%pは設定中のデバイスのパスが入る変数です。
SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{device}=="0x1d01", RUN+="/bin/chmod -R go+rw /sys%p"
注意点は「RUNはシェルじゃない」ことです。パスの補完、ワイルドカード、パイプ、リダイレクトなどは使えません。私は最初resource* と書いて「そんなファイルはない」と言われたり、/bin/chmodなのに /usr/bin/chmodと書いていて、何も実行されなくて悩みました(Ubuntuは /usr/bin/chmodだったりするので、さらにややこしいです)。
# udevadm trigger # cd /sys/bus/pci/devices/0000\:08\:00.0/ # ls -l resource* -r--rw-rw- 1 root root 4096 8月 2 23:10 resource -rw-rw-rw- 1 root root 16777216 8月 2 23:10 resource0 -rw-rw-rw- 1 root root 268435456 8月 2 23:10 resource1 -rw-rw-rw- 1 root root 268435456 8月 2 23:10 resource1_wc -rw-rw-rw- 1 root root 33554432 8月 2 23:10 resource3 -rw-rw-rw- 1 root root 33554432 8月 2 23:10 resource3_wc -rw-rw-rw- 1 root root 128 8月 2 23:10 resource5
ルールファイルを書いたら設定を反映させると、resourceNファイルのパーミッションが変わっているはずです。変わっていない場合はおそらくルールの書き方が間違っているので、修正が必要です。
むしろこちらが本題かもしれません。udevのRUNルールは「シェルじゃない」ので、普段シェル上で使っているコマンドをコピペしてもまず動かないです。間違っている場合は主に2パターンで、
前者に関してはRUNでログを出すとわかります。例えばRUN+="/usr/bin/logger AAAAAAAA" のようにすると、udevadm triggerを実行した後に /var/log/syslogにメッセージが出ます。
ログが出なければフィルタリングの条件を全部削って試してください。それでもログが出なければおそらくloggerのパスが間違っています。which loggerで出てくるパスとRUNに書いたパスが合っているか確認してください。特に /binと /usr/binは間違えやすいです。
後者に関してはRUNに書いたコマンドが失敗するとsyslogにエラーが記録されるはずですので、間違ってワイルドカードやパイプなどを書いていないかチェックしてみると良いです。
Aug 1 22:56:51 blackbird systemd-udevd[2775846]: 0000:08:00.0: Process '/bin/chmod -R go+rw /sys/devices/pci0000:00/0000:00:03.1/0000:08:00.0*' failed with exit code 1.
ログを見てもすぐにわからないような場合は、地道に「コマンド名だけにして実行されるか?」「引数を1つずつ足していって動くか?」を確認するのが一番良いです。
コードのキーワードハイライトに使っているhighlight.js(公式サイトへのリンク)を11.2.0にしました。導入したのは7年も前(2014年9月11日の日記参照)ですが、バージョンアップはスムーズにできました。
APIに変更があってinitHighlightingOnLoad() がdeprecatedになりました。代わりにhighlightAll() を呼べばOKです。ファイル名も微妙に変わっていました。スクリプトはhighlight.pack.jsからhighlight.min.jsに、CSSはvs.cssからvs.min.cssになっていました(Visual StudioスタイルのCSSを使う場合の設定、この他にもスタイルはたくさんあります)。
< | 2021 | > | ||||
<< | < | 08 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
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 | - | - | - | - |
合計:
本日: