毎年 2 回くらいバージョンアップして API をごっそり変えてくれると、ドラ イバをそれ向けに対応するだけで飯を食ってゆけるのではないか、そう考えて いた時期がわたしにもあり (ry
という人を対象としています。まぁ要するに世界中のほとんどの人ですね。
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 章を読んでくれ。ちと 情報古いけど。あと仮想メモリと仮想関数の「仮想」が意味するものの間には、 お互い何の関係もない。まぎらわしい。
ずーっと昔に「__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/ とか) を覗いてみたら、そいつらも同じよーなことをやっ てたので、たぶん正しい。さすがおれ。
ここ以降は全部、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)。