SystemTapのprobeにおける"!"とは何か?

SystemTapのtapsetのソースコードを読んでいると、以下のような記述が出てくる。

# systemtap/tapset/linux/x86_64/sysc_mmap.stp 
probe syscall.mmap = dw_syscall.mmap !, nd_syscall.mmap ? {}

このびっくりマークというかexclamation markは、"probe pointが見つかったらそこで評価しろ"の意味。 IBMのRedbook(http://www.redbooks.ibm.com/redpapers/pdfs/redp4469.pdf)にわかりやすい例が載っていたので、引用しておく。

# SystemTap:  Instrumenting the Linux Kernel for Analyzing Performance and Functional Problems
# P16より
kernel.function(“this_might_exist”) !,
kernel.function(“if_not_then_this_should”) !,
kernel.function(“if_all_else_fails”) { ... }

この例では、kernel.function(“this_might_exits”)があれば、そこで{…}を評価する。 なければ、kernel.function(“if_not_then_this_should”)を探し、存在すれば{…}を評価する。 どちらもなければ、kernel.function(“if_all_else_fails”)を探す。

この例、SystemTap開発元のLanguage Referenceに載っていないのでつらい。

Systemtapを使ってKernelのWarningメッセージを止めた(かったがダメだった)

サマリ

  • オンボードUSB3.0コントローラとUSB ICカードリーダの相性が悪いのか、使っているとWarningが出続けてつらい
  • Systemtapを使って抑止しようとしたが、一部止めきれず。何が悪いのかは不明。
  • 試したスクリプトは以下。

gist.github.com

はじめに

うちのICカードリーダ*1ルネサス製と思しきUSB3.0コントローラ *2 の相性が悪く、使っていると以下のようなメッセージが2秒おきに出続ける。

xhci_hcd 0000:03:00.0: WARN Successful completion on short TX: needs XHCI_TRUST_TX_LENGTH quirk?
xhci_hcd 0000:03:00.0: WARN Event TRB for slot 1 ep 8 with no TDs queued?  

前者は、ソースコードをみた感じだと、前者はデータ転送に成功しているけど、 期待したよりも転送長が長くないときに出るもののようだ。

// drivers/usb/host/xhci-ring.c
2300 static int handle_tx_event(struct xhci_hcd *xhci,
2301         struct xhci_transfer_event *event)
<snipped...>
2373     switch (trb_comp_code) {
2374     /* Skip codes that require special handling depending on
2375      * transfer type
2376      */
2377     case COMP_SUCCESS:
2378         if (EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) == 0)
2379             break;
2380         if (xhci->quirks & XHCI_TRUST_TX_LENGTH)
2381             trb_comp_code = COMP_SHORT_TX;
2382         else
2383             xhci_warn_ratelimited(xhci,
2384                     "WARN Successful completion on short TX: needs XHCI_TRUST_TX_LENGTH quirk?\n"); ★これ

後者はというと、2483行目と2484行目にひっかかっているらしい。 リストが空っぽなのにSTOPじゃないのがダメとか、そういう感じなんだろうか。

// drivers/usb/host/xhci-ring.c
2477         if (list_empty(&ep_ring->td_list)) {
2478             /*
2479              * A stopped endpoint may generate an extra completion
2480              * event if the device was suspended.  Don't print
2481              * warnings.
2482              */
2483             if (!(trb_comp_code == COMP_STOP ||
2484                         trb_comp_code == COMP_STOP_INVAL)) {
2485                 xhci_warn(xhci, "WARN Event TRB for slot %d ep %d with no TDs queued?\n",
2486                         TRB_TO_SLOT_ID(le32_to_cpu(event->flags)),
2487                         ep_index);
2488                 xhci_dbg(xhci, "Event TRB with TRB type ID %u\n",
2489                         (le32_to_cpu(event->flags) &
2490                          TRB_TYPE_BITMASK)>>10);
2491                 xhci_print_trb_offsets(xhci, (union xhci_trb *) event);
2492             }

出続けたから何だというわけでもないが、dmesgが埋め尽くされるのはなんとなく嫌なので*3、 これをKernelを改変せずに抑止したい。どうすればよいか?今回は、Systemtapを使ってみる。

実行環境

MOCHI /home/bisco% uname -r
4.4.0-78-generic

MOCHI /home/bisco% cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"

Systemtapとは何か

具体的にはここ。 ざっくり言うと、動作中のKernelやユーザプロセスに対し、動的に情報を取ったりメモリを書き換えたりできるツール*4

Systemtapのインストー

今回はKernelをいじるので、Systemtapバイナリに加えてKernelのデバッグ情報をインストールする。 全部まとめてここに書いてあることに従えばよい。

ソースコードの取得

Systemtapで変数を書き換える行を特定するためにソースコードを取得する必要がある。 ソースコードもaptで取得できる。

$ sudo vim /etc/apt/sources.list # deb-srcから始まる行のコメントを外す
$ sudo apt update
$ sudo apt source linux-image-$(uname -r) # カレントディレクトリにソースコードをダウンロードする

書き換え箇所の特定

needs XHCI_TRUST_TX_LENGTH quirk?に関しては、以下の2ステップ。

  1. 2380行目でxhci->quirksXHCI_TRUST_TX_LENGTH bitを立てる
  2. 2477行目でtrb_comp_codeをCOMP_SUCCESSに戻す

Systemtapは、tapした行が実行される前に効果を発揮するようなので、 2381行目で変数を書き換えても期待した結果が得られない。

// drivers/usb/host/xhci-ring.c(再掲)
2300 static int handle_tx_event(struct xhci_hcd *xhci,
2301         struct xhci_transfer_event *event)
<snipped...>
2373     switch (trb_comp_code) {
2374     /* Skip codes that require special handling depending on
2375      * transfer type
2376      */
2377     case COMP_SUCCESS:
2378         if (EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)) == 0)
2379             break;
2380         if (xhci->quirks & XHCI_TRUST_TX_LENGTH) ★ここ
2381             trb_comp_code = COMP_SHORT_TX;
2382         else
2383             xhci_warn_ratelimited(xhci,
2384                     "WARN Successful completion on short TX: needs XHCI_TRUST_TX_LENGTH quirk?\n"); 
<snipped...>
2463     default:
2464         if (xhci_is_vendor_info_code(xhci, trb_comp_code)) {
2465             status = 0;
2466             break;
2467         }
2468         xhci_warn(xhci, "ERROR Unknown event condition %u, HC probably busted\n",
2469               trb_comp_code);
2470         goto cleanup;
2471     }
2472
2473     do {
2474         /* This TRB should be in the TD at the head of this ring's
2475          * TD list.
2476          */
2477         if (list_empty(&ep_ring->td_list)) { ★ここ

with no TDs queued?に関しては、以下の2ステップ。

  1. 2477行目の条件がTRUEなら、trb_comp_codeをCOMP_STOPに書き換える
  2. 2493行目でtrb_comp_codeを戻す

ここでは2483行目でtrb_comp_codeを書き換えたいんだけど、 2483行目からはtrb_comp_codeにアクセスできないので、しょうがなくこうする。

2473     do {
2474         /* This TRB should be in the TD at the head of this ring's
2475          * TD list.
2476          */
2477         if (list_empty(&ep_ring->td_list)) {
2478             /*
2479              * A stopped endpoint may generate an extra completion
2480              * event if the device was suspended.  Don't print
2481              * warnings.
2482              */
2483             if (!(trb_comp_code == COMP_STOP ||
2484                         trb_comp_code == COMP_STOP_INVAL)) {
2485                 xhci_warn(xhci, "WARN Event TRB for slot %d ep %d with no TDs queued?\n",
2486                         TRB_TO_SLOT_ID(le32_to_cpu(event->flags)),
2487                         ep_index);
2488                 xhci_dbg(xhci, "Event TRB with TRB type ID %u\n",
2489                         (le32_to_cpu(event->flags) &
2490                          TRB_TYPE_BITMASK)>>10);
2491                 xhci_print_trb_offsets(xhci, (union xhci_trb *) event);
2492             }
2493             if (ep->skip) {
2494                 ep->skip = false;
2495                 xhci_dbg(xhci, "td_list is empty while skip "
2496                         "flag set. Clear skip flag.\n");
2497             }

ちなみに、アクセスできる/できない変数は、-Lオプションで調べることができる。 アクセスできる変数は$xxxxと表示されるんだけど、2483行目にはtrb_comp_codeがない。

$ stap -L 'kernel.statement("handle_tx_event@drivers/usb/host/xhci-ring.c:*")' | grep 248
kernel.statement("handle_tx_event@/build/linux-0XAgc4/linux-4.4.0/drivers/usb/host/xhci-ring.c:2483") $xhci:struct xhci_hcd* $event:struct xhci_transfer_event* $xdev:struct xhci_virt_device* $ep_ring:struct xhci_ring* $event_trb:union xhci_trb* $status:int $td_num:int $__func__:char const[] const
kernel.statement("handle_tx_event@/build/linux-0XAgc4/linux-4.4.0/drivers/usb/host/xhci-ring.c:2485") $xhci:struct xhci_hcd* $event:struct xhci_transfer_event* $xdev:struct xhci_virt_device* $ep_ring:struct xhci_ring* $event_trb:union xhci_trb* $status:int $trb_comp_code:u32 $td_num:int $__func__:char const[] const
kernel.statement("handle_tx_event@/build/linux-0XAgc4/linux-4.4.0/drivers/usb/host/xhci-ring.c:2488") $descriptor:struct _ddebug $xhci:struct xhci_hcd* $event:struct xhci_transfer_event* $xdev:struct xhci_virt_device* $ep_ring:struct xhci_ring* $status:int $__func__:char const[] const

Systemtapスクリプト

上記の変更をSystemtapスクリプトにすると、こうなる。

  • kernel.statement(<PATH>) で、ソースコードの位置を指定する。<PATH>のフォーマットは関数名@ファイルへのパス(ビルドしたときのルートディレクトリからの相対パスでよい)
  • $xxxxでKernel内の変数アクセス。構造体メンバへのアクセスは->を使う。
  • グローバル変数globalをつけて宣言すると使える

gist.github.com

このスクリプト-gオプションを付けて実行する。 -gオプションを渡すと、guruモードと呼ばれている変数を書き換えられるモードにしてくれる。

$ sudo stap -gv usb.stp

結果

quirks?のほうは抑止できたけど、with no TDs queued?はなぜか抑止できなかった。うーん、原因がわからん。

その他

Systemtapスクリプトの実行をCtrl-C止めても、quirks?のメッセージが出なくなった。何か残ってるのか・・・?

[2017/05/22 23:11追記]

このコードに対して

2473     do {
2474         /* This TRB should be in the TD at the head of this ring's
2475          * TD list.
2476          */
2477         if (list_empty(&ep_ring->td_list)) {
2478             /*
2479              * A stopped endpoint may generate an extra completion
2480              * event if the device was suspended.  Don't print
2481              * warnings.
2482              */
2483             if (!(trb_comp_code == COMP_STOP ||
2484                         trb_comp_code == COMP_STOP_INVAL)) {
2485                 xhci_warn(xhci, "WARN Event TRB for slot %d ep %d with no TDs queued?\n",
2486                         TRB_TO_SLOT_ID(le32_to_cpu(event->flags)),
2487                         ep_index);
2488                 xhci_dbg(xhci, "Event TRB with TRB type ID %u\n",
2489                         (le32_to_cpu(event->flags) &
2490                          TRB_TYPE_BITMASK)>>10);
2491                 xhci_print_trb_offsets(xhci, (union xhci_trb *) event);
2492             }

このtap

 probe begin {                                                                         
     printf("force stop warning short TX\n")                                           
 }                                                                                     
                                                                                       
 probe end {                                                                           
     printf("stop probe\n")                                                            
 }                                                                                     
                                                                                       
 probe kernel.statement("handle_tx_event@drivers/usb/host/xhci-ring.c:2477") {         
     next_addr = @cast(&$ep_ring->td_list, "list_head", "kernel")->next                
     list_addr = &$ep_ring->td_list                                                    
     if(next_addr == list_addr) {                                                      
         $trb_comp_code = 26                                                           
         printf(">> trb_comp_code = %d\n", $trb_comp_code)                             
     }                                                                                 
 }                                                                                     
                                                                                       
 probe kernel.statement("handle_tx_event@drivers/usb/host/xhci-ring.c:2485") {         
     printf("trb_comp_code = %d\n", $trb_comp_code)                                    
 }                                                                                     

結果がこれ

Pass 1: parsed user script and 110 library script(s) using 109624virt/43860res/6236shr/37692data kb, in 320usr/40sys/359real ms.
Pass 2: analyzed script: 4 probe(s), 4 function(s), 0 embed(s), 0 global(s) using 302812virt/236012res/7292shr/230880data kb, in 3640usr/130sys/3774real ms.
Pass 3: translated to C into "/tmp/stapdRn9iB/stap_072980ba0aa3ed0cb26e4f8ce5aa1922_3963_src.c" using 302812virt/236140res/7420shr/230880data kb, in 10usr/10sys/12real ms.
Pass 4: compiled C into "stap_072980ba0aa3ed0cb26e4f8ce5aa1922_3963.ko" in 3850usr/440sys/4690real ms.
Pass 5: starting run.
force stop warning short TX
>> trb_comp_code = 26
trb_comp_code = 26
>> trb_comp_code = 26
trb_comp_code = 26
>> trb_comp_code = 26
trb_comp_code = 26
>> trb_comp_code = 26
trb_comp_code = 26

COMP_STOP = 26という事実からすると、systemtapがおかしいようにも思うし、 自分の理解が間違っているのではという感もあり、よくわからなくなった。

*1:SCR3310-NTTCom

*2:lspciによるとRenesas Technology Corp. uPD720201 USB 3.0 Host Controller

*3:実害は多分ない

*4:文字通り"tap"である

C用Makefileのテンプレートと覚書

サマリ

たまに使うときに毎回調べなくてもよいようにC用Makefileのテンプレートを作った。

はじめに

Cでプログラムを書くときに、Makefileを書こうとして毎回書き方を探し回っている間抜けの極みなんですが、 だいたい書く内容は変わらないので、テンプレを作って使いまわせるようにしておきたくなった。

このテンプレがカバーしない範囲

  • 複数のMakefileを連動させるケースはカバーしない
  • GNU Make以外はカバーしない

テンプレ

gist.github.com

覚書

GNU Makeにおける関数コール

$(<関数名> <引数>)みたいに書くと関数をコールできる。

使っているGNU Makeの関数

Makeにはいくつかビルトインの便利関数がある。カンマ前後はスペースを入れないように。

  • realpath:指定されたディレクトリの単一絶対パス(canonical absolute path)を取得する。 canonicalというのは、...、symlinkを含まないpathのこと。abspathと違ってsymlinkを解決するという違いがあるけども、 この違いがMakefileに及ぼす影響はよくわからない・・・
  • wildcard:wildcardで指定したパターンに該当するものをスペース区切りで出力してくれる関数。 ファイルパスを渡すと、指定したパターンに該当するファイルを出力してくれる。
  • subst$(subst <from>,<to>,<置き換え対象文字列(空白区切り)>)というように、<置き換え対象文字列>内の全てのへ置き換えてくれるもの。
  • patsubst$(<変数名>:<置き換え前suffix>=<置き換え後suffix>)という形式で使っている。指定したパターンを残して置き換えをしてくれる。
  • filter-out:あるパターンに一致するものを除外する。$(filter-out <除外したいパターン>,<空白区切り文字列>)とすると、 除外パターンに完全一致する文字列を除外して文字列を返してくれる。

Phonyターゲット

make cleanみたいに、何もファイルを作らないがコマンドを実行したいときに指定しておくターゲット。 テンプレでは、defaultconfcleanの3つのPhonyターゲットを指定している。

  • default:Makeを引数無しで実行すると実行されるもの。Makefileの変数を出力して、バイナリを作ってくれるようにしている。
  • confMakefileの変数を出力するターゲット。
  • clean:おなじみのやつ。オブジェクトファイルと実行ファイルを消すターゲット。

パターンマッチルール

Makefileにおける以下の部分のこと。%ワイルドカードで、$(OBJDIR)以下の*.oファイルは、 $(SRCDIR)/*.cから作られますよということをMakeに教えてあげるのがパターンマッチルール。

$(OBJDIR)/%.o : $(SRCDIR)/%.c

変数のメモ

  • SRCDIR:ソースファイルを格納するディレクトリ。テンプレではMakefileと同じディレクトリ。 Makefileの置いてあるディレクトリは毎回変わるけども、realpath関数でカレントディレクトリを取得するようにしている。
  • INCDIR:ヘッダファイルを格納するディレクトリ。テンプレではSRCDIRと同じ。
  • OBJDIR:オブジェクトファイルを格納するディレクトリ。テンプレではOBJDIRと同じ。
  • IGNOREコンパイル時に無視してほしいファイルを指定する。SRCDIRがフルパスなので、IGNOREもテンプレみたいにフルパスで指定する必要がある。
  • SRCSSRCDIR内のCファイル全部。いちいち指定するのが大変なので、wildcard関数を使って、ワイルドカードで指定できるようにしている。
  • OBJS:オブジェクトファイル全部。patsubst関数($(SRCS:.c=.o):のこと)を使って、Cファイルの".c"部分を".o"に変えている。 オブジェクトファイル置き場が変わったときのことを考えて、subst関数によりファイルパスのSRCDIR部分をOBJDIRに置き換えるようにしている。

おわりに

だいたい事足りそうなMakefileが書けたと思う。 これでちょこっとCでプログラムを書くときに、長いオプションをつけてgccコマンドを打たなくてよくなると思うと少し気が楽になる。

参考

fishを使っているときにtmuxのwindow nameをコマンド履歴にしたい

サマリ

fishのfish_preexecイベントをフックして、tmuxのwindow nameをコマンド履歴に変えることができる。

はじめに

tmuxを使っていると、このwindowでは一体何をしていたんだっけという状況に陥ることがままある。 そういうとき、window nameが最後に実行したコマンドだと、何をしていたか思い出すのに役立つ*1

そんなことができるのか

以下の2つの材料から結構簡単にできちゃう。 ちなみに、"フックをかける"というのは、イベント発生時に何かコマンドが実行できるよ、って意味。

  • tmux rename-window <window name>でwindowの名前が変えられる
  • fishには、fish_preexecという、コマンド実行前にフックをかけられるイベントがある

どうするのか

fish_preexecイベントフックは、~/.config/fish/config.fishに書くのが作法らしい*2。 実際にはこんな感じになる。 tmuxを使っているときは、環境変数$TERMがscreenかtmuxになるので、そのときだけwindow nameを変えるコマンドを打つ。 あんまり長いコマンドを表示してもあれなので、16文字くらいで打ち止めするようにしている。

 # rename tmux window
 function window_rename --on-event fish_preexec
     if test -n (echo $TERM | grep -e screen -e tmux)
         tmux rename-window (printf "%.16s" $argv[1])
     end
 end

おわりに

fish_preexecイベントのおかげで、簡単にtmuxのwindow nameを動的に変えることができる。

雑感

  • fishの設定ファイルは人間に読みやすいのがよい。bashとの互換性はないけど、別にどうでもいいやという感。
  • fishは手入れが楽なのでありがたい

*1:気がする。既に慣れてしまってるのでよくわからん・・・

*2:ソースは失念

fishで手間をそこそこに使えるLinuxのシェル環境を作る

サマリ

  • fishは最悪設定ファイル書かなくても、そこそこ使えるので便利

はじめに

新しいことを覚えるのはなかなかに大変で、しかも普段使わないようなものに 労力を払いたくない人はきっと多いと思う。特に文字だけしか表示されない端末。

端末操作って見た目黒魔術っぽくて、何かとっつきにくいように思うかもしれない。 なんとなく覚えている範囲で不足なく使えるからどうでもいいやと思うかもしれない。 でも、どうせなら作業効率を上げて早く帰りたい、もっと楽をしたいと思う人もたくさんいるはずだ。

そんなあなた、特に以下に該当するあなたに、fishを導入することをおすすめしたい。

  • bashでひたすら↑キーを押して履歴を辿るが、なかなか期待の履歴までたどり着けない
  • lsに色がついていなくて見づらいんだけど、色付けするのに何をしていいのかよくわからない
  • .xxshrcが読みにくくてつらい思いをしている

fishについて

fishとは何か

いろいろ便利機能があるシェルで、設定をあまりしなくてもそれなりに整ったものが使えるのが特徴。 いろいろな便利機能とは、例えば以下。きっともっといっぱいあるけど、慣れてからでいい。

  • 何も設定しなくてもそれなりに整った見栄えを提供してくれる
  • lsに勝手に色がついている
  • 履歴を元にコマンドをサジェストしてくれる
  • 履歴を辿りやすい

fishをインストールしたい

UbuntuとかDebianだったら、aptで入る。CentOSのあなたは諦めてソースコードからビルドしろ。

$ sudo apt install fish

fishを使いたい

インストールが終わったら、単にfishと打てばいい。

サジェストされたコマンドを使いたい? コマンドを途中まで打つと履歴等々含めてサジェストしてくれるので、 Ctrl-Eか、→キーを押してくれれば確定する。

履歴を簡単に辿りたい? コマンドを途中まで打って、↑キーを押せば、そのコマンドの履歴だけ辿ってくれる。例えばこんな感じ。

# cdの履歴を辿る例:まずはcdと打つ
$ cd 

# 次に↑キーを押すと、cdの履歴が順繰りに出てくるので、使いたい履歴を引いたらエンターを押す
$ cd ../xxxx/xxxx/

[やる気のあるあなたへ] PATHにデフォルト以外のディレクトリを追加する

以下のコマンドを打つと、fishのほうでPATHに追加してくれる*1。 設定ファイルに書く必要はない(書いちゃダメ)。

set -U fish_user_paths <追加したいディレクトリ> $fish_user_path

[やる気のあるあなたへ] fishの見栄えを少し調整する(その1)

まあデフォルトでそれなりに使えるfishだけど、見栄えには好みがあるので、好みの見栄えになるように調整したい。 調整するには設定ファイルを書く必要があるので、"設定ファイルを書くくらいならそのままでいいや"のあなたは 飛ばしてくれればOK。

プロンプトは、"~/.config/fish/functions/fish_prompt.fish"というファイルで設定する。

僕は

  • ログインしているユーザ名とサーバ名を、色をcyanで常に出したい
  • カレントディレクトリは省略せずに、色を黄色で出したい
  • gitで管理中のディレクトリ内では、ブランチ名を出したい
  • コマンドの開始は、">“にしたい

といったような趣味があるので、設定ファイルとしてはこんな感じになる。

function fish_prompt
    set_color cyan
    printf "%s@%s " (whoami) (hostname)
    set_color yellow
    printf "%s" (pwd)
    set_color normal
    printf "%s " (__fish_git_prompt)
    printf "\n> "
end

この設定ファイルを使うと、こんな感じに表示される。色は正しく表示されていないので、各自で。。。

bisco@bisco-server /home/bisco/.config/fish/functions
> 

[やる気のあるあなたへ] fishの見栄えを少し調整する(その2)

gitのブランチ名だけだとちょっと物足りないので、状態(未コミットのファイルがあるとか、そういうこと)を 表示するようにしたい。

その場合は、"~/.config/fish/config.fish"に以下の設定を追記すればよい。意味に関してはぐぐろう。。

# fish git prompt
set __fish_git_prompt_showdirtystate 'yes'
set __fish_git_prompt_showstashstate 'yes'
set __fish_git_prompt_showupstream 'yes'
set __fish_git_prompt_showuntrackedfiles 'yes'
set __fish_git_prompt_show_informative_status 'yes'
set __fish_git_prompt_showcolorhints 'yes'

この設定を書くと、何か状態が変化するとそれっぽいフォントで表示してくれる。なお、フォントがない場合は?になるので注意。

# 変更がないとき
bisco@bisco-server /home/bisco/dotfiles (master|✔)
# 未トラックのファイルが1つあるとき
bisco@bisco-server /home/bisco/dotfiles (master|…1)
# addしていないファイルが1つ、未トラックのファイルが1つあるとき
bisco@bisco-server /home/bisco/dotfiles (master|✚1…1)
# addしたファイルが1つあるとき
bisco@bisco-server /home/bisco/dotfiles (master|●1)
# リモートブランチよりも1つコミットが進んでいるとき
bisco@bisco-server /home/bisco/dotfiles (master↑1|✔)
# リモートブランチよりも1つコミットが遅れているとき
bisco@bisco-server /home/bisco/dotfiles (master↓1|✔)

まとめ

fishを使うと設定ファイル書かなくてもある程度使えるし、設定ファイルを書くとさらに快適になりますね。

*1:正確にはPATHに追加するんじゃなくて、fish_user_pathsという変数に追加して、PATHに付け足してくれる

Linuxにおけるplatform device APIについて

はじめに

自分でドライバ(というかカーネルモジュール)を作ることなんてほとんどないし、 昨今はそういうローレベルな話は流行らないような気がするけど、 それでもやはりドライバを作りたいときがある。 今回は、Linuxが勝手に情報を見つけてよしなにやってくれないデバイスに関して ドライバを作る際に便利そうなplatform device APIを紹介する。

platform device APIとは、PCIeデバイスやUSBデバイスのようなLinux KernelとかBIOSが うまいこと情報を見つけてきてくれるようなデバイではないものに対して、 IRQ X番で割り込みが上げられるよ、とか、MMIO空間はアドレスxxxxからxxxxまでだよ、などの デバイスが持つ資源の情報を手動でカーネルに登録するためのもの。

そもそもそんなデバイスなんてあるのか

例えばOn Chipのデバイスがそうらしい。情報を出す規格があるわけではないので、MMIO空間は見えても、 何ができるか*1まではスペックシートを 見ないとわからんことが多い(と思う)。

なお、PCI(PCIeも含む)は規格でどういうことができるかを示すレジスタ(Capability Register)を 実装しなきゃいけないので、そういう心配は無用。 Capability Registerに関してはこれがわかりやすい。 BIOSがPCI Expressを初期化する手順が見えてきた: なひたふJTAG日記

USBデバイスもたぶんPCIバイスと同じ。

バイス登録までのおおよその流れ

以下の2ステップで終わる。簡単!

  1. platform deviceの資源情報を持つ構造体を作成する
  2. platform_device_register_simpleでplatform deviceを登録する

platform deviceの資源情報を持つ構造体の作成

これはまあ普通に構造体を作るだけ。新しく例を作るのも何なので、 The platform device API [LWN.net]に乗ってる例(MMIO空間が0x10000000~0x10001000まで、 IRQ 20に繋がれているデバイス)を以下に引用する。

#include <linux/platform_device.h>

static struct resource foomatic_resources[] = {
    {
        .start  = 0x10000000,
        .end    = 0x10001000,
        .flags  = IORESOURCE_MEM,
        .name   = "io-memory"
    },
    {
        .start  = 20,
        .end    = 20,
        .flags  = IORESOURCE_IRQ,
        .name   = "irq",
    }
}

platform_device_register_simpleでplatform deviceを登録する

platform_device_register_simpleのインタフェースは以下のとおり。

  • nameには、登録するデバイスの名前(自分でつけてよい)を
  • idには、(たぶん)連番でデバイスに番号を
  • *resには、先に定義した構造体のポインタを
  • numには、resの要素数

入れればよい。

// linux 4.9 include/linux/platform_device.h
136 static inline struct platform_device *platform_device_register_simple(
137                 const char *name, int id,
138                 const struct resource *res, unsigned int num)
139 {
140         return platform_device_register_resndata(NULL, name, id,
141                         res, num, NULL, 0);
142 }

platform deviceを登録したあとはどうすればいいの

platform deviceのdriverを登録する。 以下のplatform_driverの構造体のうち、 最低限

  • probe
  • remove
  • driver

があればOK。ID tableはなくてもよい。

struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
};

要はこんな感じ(The platform device API [LWN.net]に乗ってる例を引用)。

static struct platform_driver i2c_gpio_driver = {
    .driver     = {
        .name   = "i2c-gpio",
        .owner  = THIS_MODULE,
    },
    .probe      = i2c_gpio_probe,
    .remove     = __devexit_p(i2c_gpio_remove),
};

driverを作るのに役に立つかもしれないAPI

このへんのAPIで登録したデバイス情報を引いてこられるけど、 どういう場面で使うのかいまいちよくわからない。 platform deviceのようなニッチデバイスの情報を登録する人とドライバ作る人が別なのかな。

    struct resource *platform_get_resource(struct platform_device *pdev, 
                       unsigned int type, unsigned int n);
    struct resource *platform_get_resource_byname(struct platform_device *pdev,
                       unsigned int type, const char *name);
    int platform_get_irq(struct platform_device *pdev, unsigned int n);

参考

*1:例えば割り込みがあげられるとか、そういうの