How to Catch up with Kernel 2.6.26 API Changes

毎年 2 回くらいバージョンアップして API をごっそり変えてくれると、ドラ イバをそれ向けに対応するだけで飯を食ってゆけるのではないか、そう考えて いた時期がわたしにもあり (ry

対象とする読者

という人を対象としています。まぁ要するに世界中のほとんどの人ですね。

nopage が無くなったのはどうすりゃいいのさアハハン

vm_operations_struct のメンバから nopage が消えて、代わりに fault が付 け加わった。offset の計算を勝手にやってくれるようになったのと、求めた page を返り値として返さずに struct vm_fault の page というメンバに入れ て返すようになったのが主な変更点。

従来の nopage method と新しい fault method の例:

static Page
xxx_vma_nopage(struct vm_area_struct *vp, unsigned long address, int *type)
{
    int minor = MINOR(vp->vm_file->f_dentry->d_inode->i_rdev);
    Page pageptr;
    unsigned long offset = address - vp->vm_start + VMA_OFFSET(vp);

    pageptr = virt_to_page((unsigned long)dmabuf[minor] + offset);
    get_page(pageptr);

    return pageptr;
}

こう書いてたのを

static int
xxx_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
    int minor = MINOR(vma->vm_file->f_dentry->d_inode->i_rdev);
    Page pageptr;
    unsigned long offset = vmf->pgoff << PAGE_SHIFT;

    pageptr = virt_to_page((unsigned long)dmabuf[minor] + offset);
    vmf->page = pageptr;
    get_page(pageptr);

    return 0;
}

こう書くようになった。dmabuf ってのはわしが適当にとったバッファ。詳し い説明は面倒だからパス。

なんだかよく分からないひと向けの説明: nopage とか fault は仮想メモリに page fault が起きると呼ばれる関数へのポインタを入れとく変数だ。かっこ よく言うと vm_operations_struct クラスの仮想関数だ。どうだかっこいいだ ろう。特定の仮想メモリとそいつが使う vm_operations_struct のインスタン スとの関連づけは mmap とかの中であらかじめやっておく。詳しくはこれの 15 章を読んでくれ。ちと 情報古いけど。あと仮想メモリと仮想関数の「仮想」が意味するものの間には、 お互い何の関係もない。まぎらわしい。

set_page_count() とか get_page() でコケるんですけど

ずーっと昔に「__get_free_pages() とかpci_alloc_consistent() とかを使っ て連続した複数ページを確保すると、最初のページしか reference count が セットされない。だから get_page() (古くは set_page_count()) で reference count を 1 にセットしてから使いやがれ」とゆう 説明 をした覚えがある気がしなくもない。が、2.6.26 では get_page() の仕 様が変わって、reference count が 0 なページに対してこいつを呼ぶと、エ ラー終了するようになってた。最初の 1 回は get_page() の代わりに init_page_count() を呼べ、ってことになったぽい。勘だけど (カーネルソー スをみて勝手に推測した)。あと reference count を 0 にするには put_page_testzero() を使うぽい。

そいから、以前 reference count を無理矢理 1 にする実装をした当時には、 それが正しい方法なのかどうか確証を持てなかったが、今回他のドライバ (drivers/gpu/drm/ とか) を覗いてみたら、そいつらも同じよーなことをやっ てたので、たぶん正しい。さすがおれ。

PAT サイデリア大好きな街だから

ここ以降は全部、x86 特有のへんな制約を気にしなくて良くなって良かった、 とゆう話なので、それ以外のアーキテクチャのひとは関係ないですさようなら。

I/O bus 空間 (ていうか PCI の BAR 空間とか) への write を combine して 欲しい場合には、これまでは MTRR とゆうレジスタで指定しなきゃならなかっ たんだ。でも MTRR を使った設定方法は、ヘボめの mother board にメモリを いっぱい積んで bus 空間が 4GB より上位のアドレスにマップされちゃうとう まくゆかないとか、そもそも MTRR てゆうレジスタは 8 個しかないとか、い ろいろ制限があって面倒だったんだ。

それがですよ奥さま、なんと 2.6.26 では、page attribute table (PAT) と ゆうテーブルに属性を設定することによって、ページ単位で write combine 属性を設定できるようになったんだ。もう設定しまくりのやり放題ハァハァ。

とゆう夢見がちな少年たちの希望をうち砕くがごとく、なんかまだあんまりちゃ んと正式にはサポートされていないぽい。当然ドキュメントも皆無。しかたが ないのでカーネルソースをみて勝手な推測でなんとかしたらなんとかなった。

/*
 PTE encoding (see arch/x86/pat.c for detail).
   PAT,PCD,PWT
     0   0   0    write back
     0   0   1    write combine
     0   1   0    uncached-
     0   1   1    uncached
*/
vp->vm_page_prot =  __pgprot(pgprot_val(vp->vm_page_prot) & ~_PAGE_PAT);
vp->vm_page_prot =  __pgprot(pgprot_val(vp->vm_page_prot) & ~_PAGE_PCD);
vp->vm_page_prot =  __pgprot(pgprot_val(vp->vm_page_prot) |  _PAGE_PWT);
ret = remap_pfn_range(vp, virtual, physical >> PAGE_SHIFT, vsize, vp->vm_page_prot);

I/O bus 空間を user 空間に map するときに、page protection 関連のフラ グをみっつ設定するとできるぽい。でもたぶん近い将来にもっと上位のマクロ が用意されて、運が良ければドキュメントも用意されるかも知れない。そうなっ たらそっちを使うのが良い子だと思う。

vp->vm_page_prot = pgprot_writecombine(vp->vm_page_prot); // ???
ret = remap_pfn_range(vp, virtual, physical >> PAGE_SHIFT, vsize, vp->vm_page_prot);

たぶんこんな感じになるんじゃねーの。勘だけど。

あと user 空間じゃなくて kernel 空間から write combine を設定するのは もうちょっと簡単で、単に ioremap() を使ってたとこで代わりに ioremap_wc() を使えば良い。このマクロは実在するし、超てきとーながら、 ドキュメントもある (kernel-2.6.26/Documentation/x86/pat.txt)。



How to Top
おれんち
ためしにこんなんつけてみた。どうか? プライバシー侵害? うへへへへ