体験!マイコンボードで組込みLinux

第13回 組込みLinux上での割り込み

この記事を読むのに必要な時間:およそ 6 分

一般的なデバイスドライバ

デバイスドライバについて

今回はSH7706LSRのLinux上での割り込みを扱います。割り込みはハードウェア資源なので,ユーザプログラムでは扱うことはできず,デバイスドライバでの扱いが必須となります。まずは,SH7706LSR上にある汎用スイッチと汎用LEDの入出力を行う一般的なデバイスについてプログラミングをしてみます。

デバイスドライバもユーザプログラムと同様のソフトウェアですが,デバイスドライバはLinuxカーネルの一部分として動作しますので,Linuxカーネル内部実装に従ったプログラミング作法が要求されます。ユーザプログラムはLinuxカーネルと切り離されているので,Linuxカーネルのバージョンが新しくなっても基本的にはユーザプログラムの変更が必要になることはないですが,デバイスドライバではLinuxカーネル内部実装の進化にあわせてデバイスドライバも変更していかなければなりません。

Linuxカーネルのバージョンアップによって,突然,デバイスドライバが不正な動作をしたり,コンパイルが通らないことが珍しくありません。Linuxカーネル内部に関する情報は乏しいので,この点がデバイスドライバのハードルを高くしている原因かもしれません。

Linuxデバイスドライバにはいくつもの種類があり,それらのなかでキャラクタ型デバイスは比較的にハードルも低く,Linuxカーネル内部の実装における変化の影響を受けにくい傾向にあるので,今回は,このキャラクタ型デバイスでデバイスドライバのプログラミングを行います。

対象ハードウェアについて

デバイスドライバの対象とするハードウェアは,SH7706LSR上にある汎用スイッチと汎用LEDです。汎用スイッチはSH7706プロセッサのPTG4(ポートG 4ビット目)の端子とGND端子の間に配線されています。ボード初期化時にコントロールレジスタによる端子設定済みで,入力プルアップの設定になっています。

スイッチはプルアップになっているため,スイッチを押していない(すなわちOFF)時はプルアップにより信号レベルは「1」となっており,スイッチを押す(すなわちON)とGND端子と短絡するために信号レベルは「0」になっています。スイッチ状態と信号レベルが逆になっているので,汎用スイッチは負倫理となります。

汎用LEDはSH7706プロセッサのSCP4(ポートSC 4ビット目)の端子とGND端子の間に配線されています。ボード初期化時にコントロールレジスタによる端子設定済みで,出力設定になっています。LED点灯状態と信号レベルが同じなので,汎用LEDスイッチは正倫理となります。

ハードウェアアクセスについて

デバイスドライバはLinuxカーネルの一部分なのでI/Oポートを直接にアクセスできますが,PC以外の組込みプロセッサの場合は少々注意が必要となります。

Linuxはもともとインテルi80386プロセッサのプロテクトモードを活用するためのソフトウェアが出発点となっているので,現在でもLinuxはIBM PCアーキテクチャのみを対象としたシステムであり,PC以外の組込みプロセッサではIBM-PCをエミュレーション部分を追加して,擬似的なIBM-PCとして動作しています。

そのため,I/Oポートをアクセスするための outx_p関数,inx_p関数はPC-Linuxでは正常に動作をしますが,PC以外の組込みプロセッサでは正常に動作をしません。SHプロセッサでのLinuxでは,ctrl_outx関数,ctrl_inx関数でSHプロセッサのハードウェアにアクセスを行います。

また,LinuxカーネルではSH7706内蔵レジスタが定義されているケースもありますが,ほとんどの内蔵レジスタの定義がされていないので,個別のデバイスドライバでSH7706内蔵レジスタの定義を行う必要があります。SH7706の汎用入出力ポートに関する定義はLinuxでされていないので,汎用スイッチと汎用LEDを使う場合は以下のように個別に定義をします。

#define PORT_PGDR	0xa400012cUL
#define PORT_SCDR	0xa4000136UL

汎用スイッチと汎用LEDのデバイスドライバ

デバイスドライバの入力仕様は,汎用スイッチがOFFの場合は「0」という文字が入力され,汎用スイッチがONの場合は「1」という文字が入力されるようにします。デバイスドライバの出力仕様は,⁠0」という文字が出力された場合はLEDが消灯し,⁠1」という文字が出力された場合はLEDが点灯し,それ以外の文字の場合は何もしないようにします。標準的なデバイスドライバの場合はデバイスドライバのメジャー番号が定義されていますが,そうでない場合はメジャー番号が定義されていないので,今回はLinuxカーネルに動的にメジャー番号の割り当てを任せるようにします。

汎用スイッチと汎用LEDのデバイスドライバのソースコードはリスト1となります。

リスト1


01: #include <linux/module.h>
02: #include <linux/init.h>
03: #include <linux/device.h>
04: #include <linux/ctype.h>
05: #include <linux/poll.h>
06: #include <asm/io.h>
07: 
08: #define PORT_PGDR    0xa400012cUL
09: #define PORT_SCDR    0xa4000136UL
10: 
11: static int    leddev_major;
12: 
13: static ssize_t led_read(struct file * file, char __user * buf, size_t count, loff_t * ppos) {
14:     ssize_t    n;
15:     char    c;
16: 
17:     for(n = 0;n < count;n++) {
18:         if(ctrl_inb(PORT_PGDR) & 0x10) {
19:             c = '0';
20:         } else {
21:             c = '1';
22:         }
23:         copy_to_user(buf + n, &c, 1);
24:     }
25:     return count;
26: }
27: 
28: static ssize_t led_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) {
29:     ssize_t    n;
30:     char    c;
31: 
32:     for(n = 0;n < count;n++) {
33:         copy_from_user(&c, buf + n, 1);
34:         if(c == '1') {
35:             ctrl_outb(ctrl_inb(PORT_SCDR) | 0x10, PORT_SCDR);
36:         }
37:         if(c == '0') {
38:             ctrl_outb(ctrl_inb(PORT_SCDR) & ~0x10, PORT_SCDR);
39:         }
40:     }
41:     return count;
42: }
43: 
44: static const struct file_operations led_fops = {
45:     .owner    = THIS_MODULE,
46:     .read    = led_read,
47:     .write    = led_write,
48: };
49: 
50: #define CHRDEV "leddev"
51: static int __init leddev_init (void) {
52:     leddev_major = register_chrdev(0, CHRDEV, &led_fops);
53:     printk(KERN_INFO "LED/SW Device Driver\n");
54:     return 0;
55: }
56: 
57: static void __exit leddev_cleanup (void) {
58:     unregister_chrdev(leddev_major, CHRDEV);
59:     printk(KERN_INFO "LED/SW Device Driver Exit\n");
60: }
61: 
62: module_init(leddev_init);
63: module_exit(leddev_cleanup);
64: 
65: MODULE_LICENSE("GPL");

通常のユーザプログラムとLinuxとの呼び出しインターフェースはmain関数となりますが,キャラクタ型デバイスドライバとLinuxカーネルとのインターフェースは初期化関数と終了関数の2つです。ソースコード内での初期化関数の定義はリスト1の62行目のmodule_initのパラメータで初期化関数を指定します。ソースコード内での終了関数の定義はリスト1の63行目の module_exitのパラメータで終了関数を指定します。

初期化関数と終了関数の名称は任意でかまいません。初期化関数では対象となるハードウェアの初期化やキャラクタ型デバイスドライバの登録を行います。今回はハードウェアの初期化は不要なので,リスト1の52行目のregister_chrdev関数でキャラクタ型デバイスドライバの登録をします。

register_chrdev関数の第1引数はメジャー番号を指定しますが,動的にメジャー番号の割り当てる場合は「0」を指定,関数の戻り値がメジャー番号となり,第2引数はデバイスドライバ名称,第3引数はファイル入出力関数構造体のポインタを指定します。リスト1でのファイル入出力関数構造体は44~48行目となります。

終了関数では対象となるハードウェアの停止処理やキャラクタ型デバイスドライバの登録解除を行い,リスト1の58行目のunregister_chrdev関数でキャラクタ型デバイスドライバの登録解除をします。

汎用スイッチと汎用LEDのデバイスドライバでは入力と出力のみを行うので,ファイル入出力関数構造体では .readメンバでは汎用スイッチを対象とした led_read関数を定義し(リスト1の46行目)⁠.writeメンバでは汎用LEDを対象とした led_write関数を定義します(リスト1の47行目)⁠ファイル入出力関数構造体での.readメンバと.writeメンバの関数仕様の引数で必要となるのが,第2引数のデータ領域のポインタと第3引数のデータサイズとなります。

ここで気をつけなければいけない点は,ユーザプログラムとLinuxカーネルではメモリ空間そのものが異なります。入力関数では,リスト1の23行目のようにcopy_to_user関数でLinuxカーネル空間のメモリをユーザ空間のメモリへコピーしなければなりません。出力関数では,リスト1の33行目のようにcopy_from_user関数でユーザ空間のメモリをLinuxカーネル空間のメモリへコピーしなければなりません。

著者プロフィール

みついわゆきお

1986年日立製作所入所,その3年後に自社ワークステーションでの開発業務をきっかけにBSDを経てLinux利用を始める。

1991年日立を退社し,その後,ボランティアでLinux関連ツールの整備と開発しながらWindows否定運動およびLinux普及運動を開始し,Linuxディストリビューション草創期にはPlamoLinuxのメンテナンスにもかかわる。

2001年ごろより非営利ベースでボードコンピュータの開発を開始し,やがて,無償によりハードとソフトを開発したH8マイコンボードの販売を秋月電子にて開始した。

現在,ボードコンピュータ用基本ソフトMES2.5や,SHプロセッサ向けLinuxパッチおよびTOPPERS/JSPパッチを無償で一般に提供しながら,ティーエーシーやエムイーシステムより原価率100%を目標(ただし,販売店の営業・販売費用や開発・製造の際の差損を除く)としたSuperHボードコンピュータを販売中。

また,現在でも頑固にMS社否定及びWindows撲滅運動に邁進中。

コメント

コメントの記入