目次: 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 | - | - | - | - | 
 wiki
 wiki Linux JM
 Linux JM Java API
 Java API 2002年
 2002年 2003年
 2003年 2004年
 2004年 2005年
 2005年 2006年
 2006年 2007年
 2007年 2008年
 2008年 2009年
 2009年 2010年
 2010年 2011年
 2011年 2012年
 2012年 2013年
 2013年 2014年
 2014年 2015年
 2015年 2016年
 2016年 2017年
 2017年 2018年
 2018年 2019年
 2019年 2020年
 2020年 2021年
 2021年 2022年
 2022年 2023年
 2023年 2024年
 2024年 2025年
 2025年 過去日記について
 過去日記について アクセス統計
 アクセス統計 サーバ一覧
 サーバ一覧 サイトの情報
 サイトの情報合計: 
本日: