学校では教えてくれないこと

デバイスにアクセスするには

2009.4

やあ、みんな。ビリーだよ。久しぶりだね。

みんなの周りにも、新入社員や部署の異動であたらしい顔ぶれが入ってきたんじゃないかな。はじめて「先輩」という立場に立つ人もいるかもしれないね。このページを教えてあげるもよし、自分だけのものにして新人に偉そうに説明するもよし。

ビリーとしてはなるべく多くの人に読んでほしいけど、人に説明するのも勉強だからね。読んだり聞いたりしてわかったふりになっても、説明できなくては本当に分かったことにはならないからね。今回は、デバイスドライバを開発するときの注意点を説明していくよ。

デバイスドライバの開発

組込みの大きな仕事に、ハードウェア(デバイス)を直接制御するプログラム(デバイスドライバ)の開発があるんだ。アプリケーションの開発よりも、経験がモノをいう分野で、ハードウェアの知識が欠かせないことからも、ある意味では組込みの神髄といえるかもしれない。

「デバイス」とひとくちにいっても、最近はCPUの周辺にたくさんのデバイスがあるよね。ちょっと思いつくだけでも、各種ディスクドライブ、グラフィック、ネットワーク(Ethernet)ポート、USBポート、モデムポート、シリアル(RS-232C)ポート、IEEE1394ポート、PCカードスロット、無線LANカードなどなど。実に様々なデバイスドライバを開発しなくてはならないということだ。

また、同じ機能を提供するデバイスでも、違うメーカのものだと、仕様(構成・動作など)が全く違うのが普通なんだ。例えば Windows PCで無線 LAN カードを買ってきてカードスロットに挿入するような場合、A社のカードとB社のカードでは、それぞれ専用のドライバをインストールする必要があるよね。このデバイス仕様の詳細が記述されているのが「データシート」と呼ばれるドキュメントなんだ。

データシートは、それぞれのデバイスのメーカから提供される。メーカのホームページからダウンロードできるようになっているものも増えてきているんだけど、メーカからの開示を受けるためにNDA(Non Disclosure Agreement=秘密保持契約)を 結ばなくてはならない場合もあるんだ。

それから、デバイスドライバからデバイスを制御するためには、対象デバイスが「どのようにCPUに接続されているのか」も知っておく必要がある。そのデバイスはCPUに内蔵されているのか、外付けされているのか、途中に別のデバイスが介在しているのか、といったことが重要になってくるんだ。

これを知るためには、対象となるデバイスの仕様だけではなく、必要に応じて、デバイスが実装されたボードの設計書、関連する他のデバイスの仕様なども調べなければいけないんだよ。ボードのハードウェア回路図を読み解いていくこともよくあるんだ。

このように、デバイスドライバの開発は、とにかく各種データシートを読み込むことから始める。対象デバイスについてのハードウェア情報のすべてを正確に把握しておかないと、それをちゃんと動かすプログラムは書けないよね。ちなみに、データシートは、ほとんどの場合、英語で書かれていると思っておいたほうがいいよ。

日本製のデバイスでも、海外に向けても販売しているので、ドキュメントは英語である場合が多いんだ。ある程度の英文読解力は必要だけど、どこになにが書いてあるかがなんとなくわかってくるし、きまった表現が多い。きっと、慣れてくると楽になってくると思うよ。

それに、データシートには、タイミングや電気特性などのハードウェア設計者向けの情報と、レジスタ仕様や動作シーケンスなどソフトウェア設計者向けの情報の両方が記載されている。だから、デバイスドライバを作る場合は、基本的にソフトウェア設計者向けの項目を重点的に見ていけば、後の部分は必要に応じて参照すれば事足りるんだ。もし、500ページのデータシートを目の前にしても呆然とすることはないよ。

デバイスとのインターフェース

デバイスは、CPUから見たときの基本的なインターフェースとして「レジスタ」とよばれるものを用意しているんだ。このレジスタを操作することで、設定を変更したり、状態を確認したりすることができる。こうやってデバイスを制御するのがデバイスドライバというわけだ。

レジスタとは、ディップスイッチのようなものといえるかもしれない。ビットの集まりで構成されていて、それぞれのビットごとに意味(機能)を持っているんだ。ビット数はデバイスの仕様で決まっていて、8/16/32bitが一般的で、これを「レジスタの幅」と呼ぶ。「このデバイスは32bit幅でレジスタにアクセスできる」といった言い方をするよね。

レジスタの幅は一律ではなくて、同じデバイスの中に、異なる幅のレジスタが混在することもあるんだ。また、数個しかレジスタがないようなデバイスから、100個を超えるレジスタをもつものまでいろいろなデバイスがある。こんなことがデータシートにかかれているわけだね。

では、レジスタへのアクセス方法や注意点について具体的に見ていこう。「レジスタアクセスには幅がある」ってことを言いたいんだけど、何のことかわかるかな?順を追って説明していくよ。

レジスタはどこに見えるのか

CPUからはレジスタはどんなふうに見えているだろうか。大きく分けて2種類あるんだ。

(1)メモリマップドI/O

これは、CPUメモリ空間の一部に見え、通常のメモリと同じようにアクセスできるタイプだ。CPUに内蔵されているデバイスであれば、ボードが違っても同じアドレスに見えるはずだよね。でも、デバイスを外付けした場合は、ボードの設計によるんだ。この場合は、ボードの仕様書にメモリマップが書かれているはずだよ。

(2)I/OマップドI/O

これは、メモリ空間とは別のI/O空間に割り付けられたポートに対して、CPUのI/O命令を使ってアクセスするタイプのことだ。昔のVGAグラフィックコントローラなんかはこのタイプだったんだ。最近は、これらのほかに、CPUに接続されたシリアルバス上のデバイスとしてレジスタがI2C,SPI等を介してアクセスできるというものも増えてきているんだけど、主流はもっぱら(1)メモリマップドI/Oなんだよ。ここでも、メモリマップドI/Oの場合についてもう少し詳しく書くことにしよう。

レジスタへのアクセス方法

メモリマップドI/Oデバイスのレジスタが、あるアドレスに見えているとしよう。C言語プログラム(デバイスドライバ)からは、どうやってアクセスするのだろうか。これは、0xB0001000 に見える16bitレジスタREG16に読み書きする例だよ。

例

レジスタアドレスに対するポインタを使ってアクセスするんだね。ポイントとなる部分をまとめてみたよ。

(a) volatile修飾がついている。

レジスタのあるアドレスの値は、実際にはデバイスの動作に応じて変化していくんだけど、ソースコードをみただけではそれは読みとれないよね。だから、コンパイラの最適化によって、レジスタアクセスのコードが削除されてしまうことがあるんだ。

これを避けるために、volatile修飾をつけて最適化を抑制するんだ。ビリーの講義を毎回読んでいる人は覚えてくれているかな?九の巻:コードが消える?~最適化の罠~でやったよね。読んでない人はこっちも読んでみてね。

(b) unsigned shortというデータ型を使っている。

これが「レジスタの幅」を意識した部分なんだ。アクセスするレジスタの幅に応じて、使うデータ型を変える必要があるんだね。

レジスタ

たいていのレジスタは、この方法でアクセスできるはずなんだけど、それでもうまく動かないことがある。そんなときにデータシートをよく読むと、こんな注意書きがあったりするんだ。

REGレジスタは、アドレス 0xB0001000 から 16bit幅で見えるようにしてあるのですが、
アクセス幅は32bitで下位16bitを有効データとして読み書きしてください。

この場合は、(volatile unsigned long *) 0xB0001000 としてアクセスしなくちゃいけないということだね。

データシートとにらめっこして、ポイントを見逃さないように正確なコーディングを心がけることがとっても大事なんだ。慣れればポイントのつかみ方もわかってくるから、データシートをたくさん読んで、どんどんデバイスドライバを書いてみてね。

デバイスドライバそのものの書き方については、OSによって違うし、ここで詳しく紹介するスペースは残念ながらないので、それぞれのOSのデバイスドライバ手法については、書籍などをあたってほしい。

注意点も、もちろんこれだけではないんだけど、周辺デバイスのレジスタアクセスには幅がある、ということ、その幅の取り方がデバイスによって違っているということは、常に気にしておくようにしてね。

ページのトップへ