これは何か
PCI ボードをデバイスドライバに頼らず python を使って操作してみます。
環境、必要なもの
- Linux (root 権限が必要)
- python
- PCI ボードの I/O 関連資料
背景知識
- PCI : 周辺機器への通信規格
- wikipedia : Peripheral Component Interconnect
- PCI 設定ヘッダ (PCI configuration header)
- 機器の設定情報 (通信方式, 割り当てアドレス, ベンダ ID, 装置 ID, など)
- PCI 設定空間を通してアクセス
- lspci で参照できる
- 参考資料
- The Linux Kernel : 7. PCI
- wikipedia : PCI configuration space
- OS Dev.org : PCI
- OS project : Device/PCI
- PCI 設定空間 (PCI configuration space)
- PCI 設定ヘッダへのアクセスを提供
- PCI I/O 空間
- 機器へ直接通信する I/O のアドレス空間。低速。
- sys/io.h 内に定義された関数 (inb, outb など; 実態はインラインアセンブリ) で CPU に直接指示してアクセス
- python からは io.h の関数をラッパーした portio モジュール を使用可能
- PCI メモリ空間
- 機器内のレジスタと対応づけられたメモリ空間
- /dev/mem を open することでファイルとしてアクセス可能
- 仮想アドレス空間を実メモリと対応づけるために、mmap 関数 を使用する
- python からは、mmap 標準モジュール を使用可能
- 参考になった情報
手順
1. 機器の情報を収集する
lspci を使って、設定ヘッダの情報を確認
$ lspci -v -nn
...
...
05:0c.0 Unassigned class [ff00]: Interface Corp Device [1147:0c5d] (rev 03)
Subsystem: Interface Corp Device [1147:0001]
Flags: slow devsel, IRQ 19
I/O ports at 2000 [size=32]
I/O ports at 2048 [size=4]
05:0d.0 Unassigned class [ff00]: Interface Corp Device [1147:0c8e] (rev 01)
Subsystem: Interface Corp Device [1147:2090]
Flags: slow devsel, IRQ 16
Memory at 90502000 (32-bit, non-prefetchable) [size=64]
Memory at 90501000 (32-bit, non-prefetchable) [size=64]
Memory at 90503000 (32-bit, non-prefetchable) [size=32]
...
...
-
オプション
- -v : 通信方式、アドレス、を含む詳細を表示
- -nn : ベンダ ID、装置 ID と共に、名称も表示
-
見方
- 05:0c.0 に接続された Interface Corp (ID 0x1147) の装置 ID 0x0c5d です
- 2 つの PCI I/O port を持っています
- アドレス 0x2000 から 32 byte
- アドレス 0x2048 から 4 byte
- 2 つの PCI I/O port を持っています
- 05:0d.0 に接続された Interface Corp (ID 0x1147) の装置 ID 0x0c8e です
- 3 つの PCI Memory port を持っています
- アドレス 0x90502000 から 64 byte
- アドレス 0x90501000 から 64 byte
- アドレス 0x90503000 から 32 byte
- 3 つの PCI Memory port を持っています
- 05:0c.0 に接続された Interface Corp (ID 0x1147) の装置 ID 0x0c5d です
2. PCI I/O port へ接続する
$ sudo ipython
PCI I/O port へのアクセスのために、ルート権限が必要です。
>>> import portio
# level を 3 に設定し、I/O ポートへのアクセスを許可します
>>> portio.iopl(3)
# 0x2000 から 1 byte を読み出します
>>> portio.inb(0x2000)
254
- 注意点:
- inb や、outb に渡す port を間違えると、誤った機器に命令を送りつけてしまい、危険です。
3. PCI メモリ port へ接続する
$ sudo ipython
/dev/mem へのアクセスのために、ルート権限が必要です。
>>> import os
>>> import mmap
# メモリを読込専用で開きます
>>> f = os.open('/dev/mem', os.O_RDONLY)
# mmap でマッピングします
>>> m = mmap.mmap(f, 32, prot=mmap.PROT_READ, offset=0x90503000)
# 7 byte 目を読んでみます
>>> m.seek(0x07)
>>> m.read(1)
b'\x00'
- 注意点:
- mmap の offset に渡すアドレス値を間違えると、他のプロセスなどが使用中のメモリ領域へアクセスしてしまい、危険です。
pypci モジュール
- 上記の手順だと、煩雑だし、アドレスを間違える危険性があるので、そこらへんをうまいことしてくれるモジュールを作りました。
- PCI I/O port, PCI メモリ port の違いによらず、統一した API で操作できます。
-
pypci モジュール
- インストール :
pip install pypci
- インストール :
使い方
>>> import pypci
# PCI 設定情報を収集します。
# ベンダー ID、装置 ID を使って絞り込みます。
>>> board = pypci.lspci(vendor=0x1147, device=3214)
# port に関する情報は、Base Address Register に書き込まれています。
>>> board[0].bar
[BaseAddressRegister(type='mem', addr=2421170176, size=64),
BaseAddressRegister(type='mem', addr=2421166080, size=64),
BaseAddressRegister(type='mem', addr=2421174272, size=32)]
# BaseAddressRegister オブジェクトを使って通信します
>>> bar2 = board[0].bar[2]
# 読み込みの例
>>> pypci.read(bar2, 0x0c, 4)
b'\x00\x00\x00\x0c'
# アドレス範囲を超えると、エラーです
>>> pypci.read(bar2, 0x1d, 4)
BadAccessError: PCI addr space is 0x90503000-0x9050301f while tried to access 0x9050301d-0x90503020.
# 書き込みの例
>>> pypci.write(bar[2], 0x04, b'\x01')
>>> data = struct.pack('<I', 1234567)
>>> pypci.write(bar[2], 0x00, data)