|
○ はじめに
LECTURE 8 I/O ポートの制御 で、基本的なハードウェアの制御について説明しました。しかし、ハードウェア制御における重要な要素である割り込み処理がまだ残っています。
パラレルポートを例に挙げると、コントロールポートの 4 番ビットを 1 にすることで割り込みが有効になり、ステータスポートの 6 番ビット ( パラレルポートの 10 番ピン )が Low から High になる瞬間に割り込みが発生します。
前回自作した電子回路をそのまま使うと、プッシュボタンを押した状態から離した際にパラレルポートからの割り込みを発生させることができます。ここでは、自作回路を使って割り込み処理の実現方法を説明します。
○ サンプルプログラム
LECTURE 8 I/O ポートの制御 で作成した自作電子回路を使って、割り込みハンドラを実現するためのサンプルプログラム irq_io.h、irq_io.c 、irq_main.c です。はじめに のページから、全てのサンプルプログラムを 固めた tar ボールをダウンロードすることもできます。
gcc -DMODULE -D__KERNEL__ -O -c irq_io.c gcc -DMODULE -D__KERNEL__ -O -c irq_main.c ld -r -o irq.o irq_main.o irq_io.o
としてコンパイル/リンクを行います。
割り込みハンドラ以外は、LECTURE 8 I/O ポートの制御 の時と同じ動きをします。プッシュボタンを押して離した瞬間に割り込みが発生しますが、割り込みハンドラ内で一番下の LED の状態を反転するため、プッシュボタンを押す度に一番下の LED が点灯/消灯を繰り返す点が異なります。
--- 全 LED 発光中 ---
一番下の LED も点灯することで、全部の LED が点灯している状態です。
--- irq_io.h ---
1 #ifndef ___irq_io_h_included_ 2 #define ___irq_io_h_included_ 3 4 5 #define IOPORT_BASE 0x378 6 #define IOPORT_RANGE 3 7 8 #define IOPORT_DATA ( IOPORT_BASE + 0 ) 9 #define IOPORT_STATUS ( IOPORT_BASE + 1 ) 10 #define IOPORT_CONTROL ( IOPORT_BASE + 2 ) 11 12 #define IRQ_NUMBER 7 13 14 #define DATA_LED_1 0 15 #define DATA_LED_2 1 16 #define DATA_LED_3 2 17 #define DATA_PULLUP 3 18 #define STATUS_ACK 6 19 #define IRQ_ENABLE 4 20 21 22 void 23 io_write_or( unsigned short port, unsigned int value ); 24 25 void 26 io_write_xor( unsigned short port, unsigned int value ); 27 28 void 29 io_write_set( unsigned short port, unsigned int value ); 30 31 int 32 io_is_button_pressed(); 33 34 void 35 io_enable_irq(); 36 37 38 #endif /* ___irq_io_h_included_ */ 39 40 /* End of irq_io.h */
--- irq_io.c ---
1 #include <asm/bitops.h> /* test_and_set_bit, clear_bit */ 2 #include <asm/io.h> /* outb(), inb() */ 3 #include <asm/system.h> /* rmb() */ 4 #include "irq_io.h" 5 6 static int lock; 7 8 9 /* 10 * 現在の指定ポートの値と指定値を or した結果を 11 * 指定ポートに書き込む 12 */ 13 void 14 io_write_or( unsigned short port, unsigned int value ) 15 { 16 unsigned int data; 17 18 if ( test_and_set_bit( 0, ( void* )( &lock ) ) != 0 ) 19 return; 20 21 data = inb( port ); 22 data |= value; 23 outb( data, port ); 24 25 clear_bit( 0, ( void* )( &lock ) ); 26 } 27 28 29 /* 30 * 現在の指定ポートの値と指定値を xor した結果を 31 * 指定ポートに書き込む ( 指定ビットの値反転 ) 32 */ 33 void 34 io_write_xor( unsigned short port, unsigned int value ) 35 { 36 unsigned int data; 37 38 if ( test_and_set_bit( 0, ( void* )( &lock ) ) != 0 ) 39 return; 40 41 data = inb( port ); 42 data ^= value; 43 outb( data, port ); 44 45 clear_bit( 0, ( void* )( &lock ) ); 46 } 47 48 49 /* 50 * 指定値を指定ポートに書き込む 51 */ 52 void 53 io_write_set( unsigned short port, unsigned int value ) 54 { 55 if ( test_and_set_bit( 0, ( void* )( &lock ) ) != 0 ) 56 return; 57 58 outb( value, port ); 59 60 clear_bit( 0, ( void* )( &lock ) ); 61 } 62 63 64 /* 65 * ステータスポートの値を読み込み、ACK ( 10 番ピン ) 66 * が LOW かどうか調べる。 67 */ 68 int 69 io_is_button_pressed() 70 { 71 unsigned int data; 72 73 data = inb( IOPORT_STATUS ); 74 rmb(); 75 76 if ( ! ( data & ( 1 << STATUS_ACK ) ) ) 77 return 1; 78 79 return 0; 80 } 81 82 83 /* 84 * 割り込みを有効にする。 85 * 割り込み制御用ピン以外のピンの状態は変えない。 86 */ 87 void 88 io_enable_irq() 89 { 90 unsigned int data; 91 92 if ( test_and_set_bit( 0, ( void* )( &lock ) ) != 0 ) 93 return; 94 95 data = inb( IOPORT_CONTROL ); 96 data |= ( 1 << IRQ_ENABLE ); 97 outb( data, IOPORT_CONTROL ); 98 99 clear_bit( 0, ( void* )( &lock ) ); 100 } 101 102 103 /* End of irq_io.c */
--- irq_main.c ---
1 #include <asm/io.h> /* outb(), inb() */ 2 #include <asm/uaccess.h> /* copy_from_user, copy_to_user */ 3 #include <linux/fs.h> /* inode, file, file_operations, ... */ 4 #include <linux/ioport.h> /* check_region(), request_region(), release_region() */ 5 #include <linux/kernel.h> /* printk() */ 6 #include <linux/module.h> 7 #include <linux/sched.h> /* task_struct */ 8 #include <linux/timer.h> 9 10 #include "irq_io.h" 11 12 13 MODULE_LICENSE( "GPL" ); 14 15 16 static int devmajor = 77; 17 static char* devname = "chardev"; 18 static char* msg = "module [irq.o]"; 19 20 21 /* 100 tick = 1 sec. */ 22 static int timeout = 100; 23 MODULE_PARM( timeout, "i" ); 24 25 static struct timer_list chardev_timer; 26 static int timerstop = 0; 27 28 static unsigned int initial_data; 29 static unsigned int initial_control; 30 31 static void chardev_timer_handler( unsigned long data ); 32 static void chardev_del_timer(); 33 34 35 /* 36 * ログ出力フォーマット用関数 37 */ 38 static void 39 log_format( char* body ) 40 { 41 if ( current != NULL ) { 42 printk( KERN_INFO "%s : PID = %-5i cmdline = \"%-10s\" : %s\n", 43 msg, current->pid, current->comm, body ); 44 } 45 else { 46 printk( KERN_INFO "%s : %s\n", msg, body ); 47 } 48 } 49 50 /* 51 * 割り込みハンドラ。本当はここでステータスポートを読まないと 52 * 以降の割り込みが保留される模様。本サンプルでは cat している間中 chardev_read() 53 * でステータスポートを読むため大丈夫。本文の説明参照。 54 */ 55 static void 56 chardev_interrupt( int irq, void* dev_id, struct pt_regs* regs ) 57 { 58 io_write_xor( IOPORT_DATA, ( 1 << DATA_LED_3 ) ); 59 } 60 61 /* 62 * タイマー登録処理 63 */ 64 static void 65 chardev_timer_register( void ) 66 { 67 init_timer( &chardev_timer ); 68 69 chardev_timer.expires = jiffies + timeout; 70 chardev_timer.data = ( unsigned long )jiffies; 71 chardev_timer.function = chardev_timer_handler; 72 73 add_timer( &chardev_timer ); 74 } 75 76 77 /* 78 * タイマー削除関数 79 * タイマ停止フラグを設定し、del_timer_sync で既存のタイマー関数を 80 * 削除する(実行中の場合は終了するまで待つ)。 81 */ 82 static void 83 chardev_del_timer() 84 { 85 timerstop = 1; 86 del_timer_sync( &chardev_timer ); 87 } 88 89 90 /* 91 * タイムアウト処理 92 */ 93 static void 94 chardev_timer_handler( unsigned long data ) 95 { 96 io_write_xor( IOPORT_DATA, ( 1 << DATA_LED_2 ) ); 97 98 if ( timerstop ) { 99 printk( KERN_INFO "%s : Timer is not to be restarted.\n", msg ); 100 return; 101 } 102 103 chardev_timer_register(); 104 } 105 106 107 /* 108 * open() 109 */ 110 static int 111 chardev_open( struct inode* inode, struct file* filp ) 112 { 113 log_format( "open() called" ); 114 return 0; 115 } 116 117 118 /* 119 * release() 120 */ 121 static int 122 chardev_release( struct inode* inode, struct file* filp ) 123 { 124 log_format( "close() called" ); 125 return 0; 126 } 127 128 129 /* 130 * read() 131 */ 132 static ssize_t 133 chardev_read( struct file* filp, char* buf, size_t count, loff_t* pos ) 134 { 135 char status[32]; 136 int copy_len = 0; 137 int len = 0; 138 139 log_format( "read() called" ); 140 141 if ( *pos == 0 ) { 142 if ( io_is_button_pressed() ) 143 len = sprintf( status, "[Switch] pressed.\n" ); 144 else 145 len = sprintf( status, "[Switch] NOT pressed.\n" ); 146 } 147 148 copy_len = ( len > count ) ? count : len; 149 150 if ( copy_to_user( buf, status, copy_len ) ) { 151 log_format( "copy_to_user failed\n" ); 152 return -EFAULT; 153 } 154 155 *pos += copy_len; 156 157 return copy_len; 158 } 159 160 161 /* 162 * write() 163 */ 164 static ssize_t 165 chardev_write( struct file* filp, const char* buf, size_t count, loff_t* pos ) 166 { 167 log_format( "write() called" ); 168 io_write_xor( IOPORT_DATA, ( 1 << DATA_LED_1 ) ); 169 return count; 170 } 171 172 173 static struct file_operations chardev_fops = 174 { 175 owner : THIS_MODULE, 176 read : chardev_read, 177 write : chardev_write, 178 open : chardev_open, 179 release : chardev_release, 180 }; 181 182 183 /* 184 * モジュールの初期処理 185 * insmod 時に呼ばれる 186 */ 187 int 188 init_module( void ) 189 { 190 EXPORT_NO_SYMBOLS; 191 192 if ( register_chrdev( devmajor, devname, &chardev_fops ) ) { 193 log_format( "register_chrdev failed" ); 194 return -EBUSY; 195 } 196 197 198 if ( request_region( IOPORT_BASE, IOPORT_RANGE, devname ) == NULL ) { 199 log_format( "request_region returned NULL" ); 200 unregister_chrdev( devmajor, devname ); 201 return -EBUSY; 202 } 203 204 205 if ( request_irq( IRQ_NUMBER, chardev_interrupt, 0, devname, NULL ) ) { 206 log_format( "request_irq failed" ); 207 release_region( IOPORT_BASE, IOPORT_RANGE ); 208 unregister_chrdev( devmajor, devname ); 209 return -EBUSY; 210 } 211 212 initial_data = inb( IOPORT_DATA ); 213 initial_control = inb( IOPORT_CONTROL ); 214 215 io_write_or( IOPORT_DATA, ( 1 << DATA_PULLUP ) ); 216 io_enable_irq(); 217 218 chardev_timer_register(); 219 220 log_format( "loaded into kernel" ); 221 return 0; 222 } 223 224 225 /* 226 * モジュールの解放処理 227 * モジュールの参照数が 0 であれば、rmmod 時に呼ばれる 228 */ 229 void 230 cleanup_module( void ) 231 { 232 chardev_del_timer(); 233 free_irq( IRQ_NUMBER, NULL ); 234 235 io_write_set( IOPORT_CONTROL, initial_control ); 236 io_write_set( IOPORT_DATA, initial_data ); 237 238 release_region( IOPORT_BASE, IOPORT_RANGE ); 239 240 if ( unregister_chrdev( devmajor, devname ) ) { 241 log_format( "unregister_chrdev failed" ); 242 /* 対策無し ... */ 243 } 244 245 log_format( "removed from kernel" ); 246 } 247 248 249 /* End of irq_main.c */
○ サンプルプログラムの説明
では、サンプルプログラムについて説明していきます。 LECTURE 8 I/O ポートの制御 で紹介した hw_io.h、 hw_io.c、 hw_main.c をベースに必要な箇所を 追加して hw_ を irq_ に変えただけなので、追加箇所について説明します。
--- irq_io.h ---
- 12 行目
パラレルポートからの割り込み通知に使用される割り込み番号をマクロ定義しています。 ベースアドレスが異なる場合は、割り込み番号も異なるので注意してください。
- 19 行目
パラレルポートは初期状態では割り込みが無効になっているため、プログラム内 で有効にする必要があります。コントロールポートの 4 番ビット ( 対応するピンはありません ) を 1 にすることで割り込みが有効になります。ここでは 4 番ビットを表すマクロを 定義しています。
- 34 〜 35 行目
割り込みを有効にする io_enable_irq のプロトタイプを追加しています。
--- irq_io.c ---
- 83 〜 100 行目 ( io_enable_irq )
パラレルポートからの割り込みを有効にする関数です。 コントロールポートの 4 番ビットを 1 にし、それ以外のビットの状態を 変えない様にしています。
--- irq_main.c ---
- 29 行目
irq_main.c ではコントロールポートへの書き込みも行う為、 コントロールポートの初期状態保存用変数を追加しています。
- 50 〜 59 行目 ( chardev_interrupt )
割り込み発生時に実行される割り込みハンドラです。登録方法は後述しますが、 ここでは割り込みハンドラの形式を覚えておいてください。
割り込みハンドラは、以下のプロトタイプに従って実装します。
void interrupt_func( int irq, void* dev_id, struct pt_regs* regs );
irq は割り込み番号です。dev_id には登録時に設定したポインタがそのまま渡されます。 また、regs は直前の CPU コンテキストが格納されていますが、ここでは触れません。
chardev_interrupt では、io_write_xor によって一番下の LED の状態を反転しています。これにより、割り込みが発生する度 ( プッシュボタンスイッチを押して離す度 ) に一番下の LED が点灯/消灯を繰り返します。
2008/08/11 追記。パラレルポートの割り込みを使う場合、ステータスポートのビット 6 -ACK が Low から High に変わる際に割り込みが発生しますが、この時にステータスポートのビット 2 -IRQ Status が 0 になる様です。ここを 1 にセットしないと、以降の割り込みが保留になってしまいます。1 にセットするためには、ステータスポートの読み込みが必要です。本サンプルでは、デバイスファイルを cat することで chardev_read() にてステータスポートの読み込みを行うため、その度に -IRQ Status が 1 になるので割り込みが継続して発生します。しかし、この様な副作用ではなくきちんと割り込みを扱うためには、割り込みハンドラにてステータスポートの読み出しを行う必要があります。木原さん、ありがとうございました。
割り込みハンドラ実装時に考慮すべき点として、まずあまり長時間に渡る処理 を行わない様にしてください。割り込みハンドラ実行中は、同割り込み番号の割り込み が無効になっているため、複数回の割り込み発生を正常に扱えなくなってしまいます。 割り込みを契機とした長時間の処理が必要な場合は、割り込みハンドラで タスクレットと呼ばれる機能を起動し、タスクレットにて長時間かかる処理を行います。 そして割り込みハンドラ自体は迅速に終了する様にします。タスクレットについては、 次回に説明します。
また、プロセスコンテキスト ( read や write 等のプロセスから実行される関数の実行状態 ) と割り込みハンドラとの間で排他処理を行う場合は、注意する必要があります。
まず、セマフォはプロセスと関連付けられるため使用することができません。 また、複数プロセス間の排他処理用に通常のスピンロックを行うと、read 等の関数において スピンロックを取得した後に割り込みハンドラが起動し、割り込みハンドラ内で スピンロック取得待ちになることでデッドロックを起こしてしまいます。
この様な場合は、ロック取得中のローカル CPU の割り込みを無効にする spin_lock_irqsave / spin_unlock_irqrestore を使用します。
#include <linux/spinlock.h> spin_lock_irqsave( spinlock_t* lock, unsigned long flags ); spin_unlock_irqrestore( spinlock_t* lock, unsigned long flags );
spin_lock_irqsave の flags は、割り込みを無効にする直前の割り込み状態を 格納する領域です。ポインタではない点に注意してください。ここで退避した割り込み状態を spin_unlock_irqrestore に指定することで、割り込み状態を元に戻すことができます。
read 等のプロセスコンテキストにおいて spin_lock_irqsave / spin_unlock_irqrestore を使うことで、プロセスコンテキストでのロック取得中は割り込みハンドラが起動せず、 競合を避けることができます。同じ割り込み番号に対する割り込みが二重に処理されることは ない ( 確か。。。 ) ため、割り込みハンドラが一つであればその中では spin_lock / spin_unlock で良いかもしれません。しかし、複数の割り込み番号に対する 割り込みハンドラ間で排他を行う場合は、割り込みハンドラ内でも spin_lock_irqsave / spin_unlock_irqrestore を使って割り込みを禁止するべきかもしれません。 この辺り筆者はあまり追求していないので、必要な場合は各自で調査してみてください。
なお、irq_main.c では、 タイムアウトハンドラとの排他処理もあるため irq_io.c 内での test_and_set_bit / clear_bit によって処理しています。
- 183 〜 222 行目 ( init_module )
205 〜 210 行目で、request_irq によって割り込みハンドラを登録しています。 登録と同時に割り込みハンドラが有効になりますが、デバイスが open されていなければ 割り込みハンドラが必要ない様な場合は、init_module で実行する必要はないです。 irq_main.c では、ユーザプロセスとは無関係に 制御を行う必要があるため、init_module で request_irq を実行しています。
#include <linux/sched.h> int request_irq( unsigned int irq, void (*handler)( int, void*, struct pt_regs* ), unsigned long flags, const char* dev_name, void* dev_id );
irq は割り込みハンドラを登録する割り込み番号で、handler は割り込みハンドラ のポインタを指定します。flags は、0 あるいは linux/sched.h で定義されている 以下の定数を設定したビットマスクを指定します。
定数名 説明 SA_INTERRUPT ローカル CPU における割り込み検出を無効にして、割り込みハンドラを起動する SA_SHIRQ 割り込み番号が複数デバイス間で共有可能である。これを指定する場合、 dev_id に NULL を指定してはならず、かつ各 dev_id はユニークでなければならない ( 割り込みハンドラを識別する必要があるため )。また、同じ割り込み番号で 複数の割り込みハンドラが起動されるため、ハンドラ内で割り込み状態を操作しては いけない SA_SAMPLE_RANDOM 生成された割り込みが、システムのエントロピー生成に使われる。エントロピーは乱数の生成源となり、暗号処理等で真の乱数を必要とするソフトウェアで必要となる。割り込みのタイミングが予測可能な場合や、外部から操作可能である場合は、エントロピーの複雑さを下げてしまうため、このフラグを指定してはいけない dev_name は /proc/interrupts において割り込み番号の所有者として使用される文字列を指定します。 また、dev_id は割り込み共有時に割り込みハンドラを識別するためのユニークな識別子となる データへのポインタを指定します。割り込み共有時には、dev_id を NULL にしてはいけません。 共有しない場合は NULL でも構いません。ここで指定したポインタは、割り込みハンドラ起動時に 引数として渡されます。
212 〜 213 行目では、最後に初期状態に戻す為にデータポートおよび コントロールポートの状態を保存しています。
216 行目で io_enable_irq によって、パラレルポートからの割り込みを有効にしています。 これ以降、プッシュボタンを押した状態から離した際に、割り込み番号 7 番 ( お使いの環境によって異なります ) の割り込みが発生します。
- 225 〜 246 行目 ( cleanup_module )
233 行目で、free_irq によって割り込みハンドラ登録を削除しています。
#include <linux/sched.h> void free_irq( unsigned int irq, void* dev_id );
割り込み番号の使用が終わったら、free_irq を使って必ず解放してください。
また、235 〜 256 行目でデータポートおよびコントロールポートを 初期状態に戻しています。
以上で irq_io.h、irq_io.c、irq_main.c の解説は終わりです。