デバイスドライバ、inb()とoutb()

					2011年02月01日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2010/2011-02-01
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■今日の大事な話

■デバイス・ドライバ

デバイス・ドライバとは、 オペレーティング・システム・カーネル内で動作するモジュールで、ディスク やキーボード等のデバイス(周辺装置)を管理し、入出力を制御する。 「オブジェクト指向」よりも、デバイスドライバの考え方が古い。

◆Unix系OSのデバイス・ドライバの種類

◆メジャー番号とマイナー番号

ブロック型と文字型のデバイス・ドライバは、メジャー番号とマイナー番号の 2つの番号で区別される。利用者プログラムからそれらをアクセスする時には、 /dev 以下のファイルをアクセスする。
$ df / [←]
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/sda2            232431456  13088380 207345736   6% /
$ ls -l /dev/sda2 [←]
brw-r----- 1 root disk 8, 2 Jan 24 12:00 /dev/sda2
$ []
ls -l で見ると、ブロック型は、b、文字型は、c で始まる。メ ジャー番号は、デバイスの種類、マイナー番号は、同じ種類で、細かい違い (上の例では、パーティション)等を意味する。

メジャー番号は、静的に決めうちにすることもあるが、 alloc_chrdev_region() を呼び、動的に割り当てられることもできる。使われ ているメジャー番号は、/proc/devices に現れる。

$ cat /proc/devices  [←]
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
...
226 drm
254 pcmcia

Block devices:
  1 ramdisk
  8 sd
  9 md
 11 sr
 65 sd
...
135 sd
253 device-mapper
254 mdp
$ []
/dev/ の下にあるブロック型と文字型のファイルは、mknod コマンド (make node) で作ることができる。
# mknod b /dev/ファイル名 メジャー番号 マイナー番号 [←]
# mknod c /dev/ファイル名 メジャー番号 マイナー番号 [←]
最近の Linux では、起動時に自動的に mknod が行われるので、手で mknod コマンド を打つ必要性はあまりない。

文字型デバイスの登録

まず、struct file_operations 構造体とその内部の関数群を定義する。その構 造体を、struct cdev に設定する。最後に、struct cdev を、cdev_add() で登 録する。
struct file_operations my_fops = { .... };
struct cdev *my_cdevp = cdev_alloc();
my_cdev->ops = &my_fops; 
my_cdev->owner = &my_fops; 
struct file_operations my_fops = { .... };
struct cdev my_cdev ;
cdev_init(&my_cdev,&my_fops);
cdev_add(&my_cdev,num, count)
register_chrdev() という関数で登録することもできる。以前はこの方法が主。
int register_chrdev(unsigned int major, const char *name,
    struct file_operations *fops);

◆file_operations構造体

file_operations構造体は、文字型でバイスのデバイス・ドライバのインタフェー スを定めている構造体。インタフェースは、関数の集合として定義されている。
include/linux/fs.h

1488:	struct file_operations {
1489:	        struct module *owner;
1490:	        loff_t (*llseek) (struct file *, loff_t, int);
1491:	        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1492:	        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1493:	        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1494:	        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1495:	        int (*readdir) (struct file *, void *, filldir_t);
1496:	        unsigned int (*poll) (struct file *, struct poll_table_struct *);
1497:	        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1498:	        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1499:	        int (*mmap) (struct file *, struct vm_area_struct *);
1500:	        int (*open) (struct inode *, struct file *);
1501:	        int (*flush) (struct file *, fl_owner_t id);
1502:	        int (*release) (struct inode *, struct file *);
1503:	        int (*fsync) (struct file *, int datasync);
1504:	        int (*aio_fsync) (struct kiocb *, int datasync);
1505:	        int (*fasync) (int, struct file *, int);
1506:	        int (*lock) (struct file *, int, struct file_lock *);
1507:	        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1508:	        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1509:	        int (*check_flags)(int);
1510:	        int (*flock) (struct file *, int, struct file_lock *);
1511:	        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1512:	        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1513:	        int (*setlease)(struct file *, long, struct file_lock **);
1514:	};
主な手続きの意味

◆inode 構造体

Linux カーネル内で、1つのファイルを表現する構造体。

デバイス・ファイルでは、open() 等で自分のメジャー番号とマイナー番号を取 り出すために使われることがある。

◆file構造体

開いたファイルを表現するために使われる。
    int fd1 = open("file1");
    int fd2 = open("file1");
ファイル名 "file1" で表現されるファイルの inode 構造体は、1 個でも、 file 構造体は、2 個割り当てられる。

◆ioctl()システム・コール

デバイスには、それぞれ特殊な操作方法がある。read(), write() には合わな いものがある。

このような特殊な操作は、システム・コールを増やすのではなく、全部 ioctl() システム・コールという特殊なシステム・コールを用いる。
int ioctl(int d, int request, ...);
コマンドは、デバイス・ドライバごとにことなる。第3引数は、コマンドにより 使われないことともある。

■x86 CMOS Real-Time Clock

CMOS RTC (Real-Time Clock) とは、バッテリ・バックアップされた時計。 CMOS や Real Time という名前が紛らわしいので、固有名詞と思うと良い。 次の2つの機能がある。
TOD (time of day) clock
時刻を、year/month/day hour:minute:second という形式で持つ。 秒以下は読めない。 PCの主電源を切っていても、この時計はマザーボード上のバッテリで進み続け る。 設定された日時に割り込みを起こす機能がある(alarm)。
定期的な割込み用
2Hz から 8192Hz の範囲で、2 の冪乗の周期で定期的な割込み(periodic interrupt)を起こせる。
Linux は、起動時に TOD clock を読んで、の時計を初期化する。 以後、普段はこのハードウェアを読むことはしない。 時計は、ソフトウェア的に変更できる(date コマンド、 settimeofday() システム・コール)。 Linux では、1秒間に1回、内部的な時刻をこのハードウェアに保存している。

ハードウェアの内容は、hwclock コマンドで参照できる。
$ date [←]
Fri Jan 28 17:33:21 JST 2011
$ hwclock --show [←]
Fri Jan 28 17:33:22 2011  -0.000369 seconds
$ []

◆rtc-read-time.c

/dev/rtc は、CMOS RTC に対応した文字型デバイスのファイルである。これを 開いて ioctl() でアクセスすると、CMOS RTC の内容が読める。
   1:	
   2:	/*
   3:	        rtc-read-time.c -- Read CMOS Realtime Clock in Linux
   4:	        Created on: 2011/01/28 17:12:36
   5:	*/
   6:	
   7:	
   8:	#include <sys/types.h>  /* open() */
   9:	#include <sys/stat.h>   /* open() */
  10:	#include <fcntl.h>      /* open() */
  11:	#include <sys/ioctl.h>  /* ioctl() */
  12:	#include <unistd.h>     /* close() */
  13:	#include <stdio.h>      /* printf() */
  14:	#include <stdlib.h>     /* exit() */
  15:	#include <linux/rtc.h>  /*  RTC_RD_TIME */
  16:	
  17:	#define RTC_DEVICE_FILE "/dev/rtc"
  18:	
  19:	main()
  20:	{
  21:	    int fd;
  22:	    struct rtc_time t1 ;
  23:	        if( (fd = open( RTC_DEVICE_FILE, O_RDONLY ))< 0 )
  24:	        {
  25:	            perror("open");
  26:	            exit( 1 );
  27:	        }
  28:	        if( ioctl( fd, RTC_RD_TIME, &t1 ) < 0 )
  29:	        {
  30:	            perror("ioctl(RTC_RD_TIME)");
  31:	            exit( 2 );
  32:	        }
  33:	        printf("%04d-%02d-%02d %02d:%02d:%02d\n",
  34:	                t1.tm_year+1900, t1.tm_mon+1, t1.tm_mday,
  35:	                t1.tm_hour, t1.tm_min, t1.tm_sec );
  36:	        close( fd );
  37:	}
実行例
$ make rtc-read-time [←]
cc     rtc-read-time.c   -o rtc-read-time
$ ls -l /dev/rtc  [←]
crw-r--r-- 1 root root 10, 135 Jan 24 20:59 /dev/rtc
$ ./rtc-read-time  [←]
2011-01-31 16:01:40
$ date [←]
Mon Jan 31 16:01:44 JST 2011
$ ./rtc-read-time ; date [←]
2011-01-31 16:01:54
Mon Jan 31 16:01:54 JST 2011
$ []
RTC_RD_TIME を含めて、/dev/rtc に対する ioctl() では、次のようなコマン ドが使える。詳しくは、man rtc を参照。
/dev/rtc に対する ioctl() のコマンド
コマンド 説明
RTC_RD_TIME RTCのTODを読む(read)
RTC_SET_TIME RTCのTODに値をセットする
RTC_ALM_READ,RTC_ALM_SET RTCのalarmを読む/セットする
RTC_IRQP_READ alarmによる定期的な割り込みの(periodic interrupt)の周波数を読む/セットする
RTC_AIE_ON, RTC_AIE_OFF alarmの割り込みを許可する/禁止する
RTC_UIE_ON, RTC_UIE_OFF clockの更新後との割り込みを許可する/禁止する
RTC_PIE_ON, RTC_PIE_OFF 定期的な割り込みを許可する/禁止する
RTC_EPOCH_READ, RTC_EPOCH_SET RTCのepoch (起点となる年月日) を読む/書く

◆drivers/char/rtc.c

drivers/char/rtc.c

 899:	static const struct file_operations rtc_fops = {
 900:	        .owner          = THIS_MODULE,
 901:	        .llseek         = no_llseek,
 902:	        .read           = rtc_read,
 903:	#ifdef RTC_IRQ
 904:	        .poll           = rtc_poll,
 905:	#endif
 906:	        .unlocked_ioctl = rtc_ioctl,
 907:	        .open           = rtc_open,
 908:	        .release        = rtc_release,
 909:	        .fasync         = rtc_fasync,
 910:	};
 911:	
 912:	static struct miscdevice rtc_dev = {
 913:	        .minor          = RTC_MINOR,
 914:	        .name           = "rtc",
 915:	        .fops           = &rtc_fops,
 916:	};
...
 953:	static int __init rtc_init(void)
 954:	{
1058:	        if (misc_register(&rtc_dev)) {
...
1064:	                rtc_release_region();
1065:	                return -ENODEV;
1066:	        }
...
1132:	        (void) init_sysctl();
1133:	
1134:	        printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n");
1135:	
1136:	        return 0;
1137:	}

include/linux/miscdevice.h
  23:	#define RTC_MINOR               135
dmesg コマンドを使うと、printk() の結果を画面に表示してくれる。
$ dmesg | grep Real [←]
Real Time Clock Driver v1.12ac
$ []
dmesg コマンドの古いものは、(syslogd の働きで) /var/log/messages* 等の ファイルに保存される。
# grep Real /var/log/messages.2  [←]
...
Jan 24 12:00:40 windell50 kernel: Real Time Clock Driver v1.12ac
# []

◆rtc_open()

drivers/char/rtc.c

 181:	#define RTC_IS_OPEN             0x01    /* means /dev/rtc is in use     */
...
 191:	static unsigned long rtc_status;        /* bitmapped status byte.       */
...
 730:	static int rtc_open(struct inode *inode, struct file *file)
 731:	{
 732:	        spin_lock_irq(&rtc_lock);
 733:	
 734:	        if (rtc_status & RTC_IS_OPEN)
 735:	                goto out_busy;
 736:	
 737:	        rtc_status |= RTC_IS_OPEN;
...
 740:	        spin_unlock_irq(&rtc_lock);
 741:	        return 0;
 742:	
 743:	out_busy:
 744:	        spin_unlock_irq(&rtc_lock);
 745:	        return -EBUSY;
 746:	}
rtc_status の RTC_IS_OPEN ビットは、close() システム・コールで呼ばれる rtc_release() ( struct file_operations rtc_fops の .release) で、落とさ れる。

◆rtc_ioctl()とrtc_do_ioctl()

 718:	static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 719:	{
 720:	        long ret;
 721:	        ret = rtc_do_ioctl(cmd, arg, 0);
 722:	        return ret;
 723:	}

 398:	static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
 399:	{
 400:	        struct rtc_time wtime;
...
 418:	        switch (cmd) {
...
 480:	        case RTC_ALM_READ:      /* Read the present alarm time */
 481:	        {
...
 487:	                memset(&wtime, 0, sizeof(struct rtc_time));
 488:	                get_rtc_alm_time(&wtime);
 489:	                break;
 490:	        }
 491:	        case RTC_ALM_SET:       /* Store a time into the alarm */
...
 540:	        case RTC_RD_TIME:       /* Read the time/date from RTC  */
 541:	        {
 542:	                memset(&wtime, 0, sizeof(struct rtc_time));
 543:	                rtc_get_rtc_time(&wtime);
 544:	                break;
 545:	        }
 546:	        case RTC_SET_TIME:      /* Set the RTC */
...
 711:	        default:
 712:	                return -ENOTTY;
 713:	        }
 714:	        return copy_to_user((void __user *)arg,
 715:	                            &wtime, sizeof wtime) ? -EFAULT : 0;
 716:	}

◆copy_from_user()とcopy_to_user()

デバイス・ドライバが動作するカーネル空間とユーザ空間では、「基本的に」 異なるアドレス空間を持っている。たとえば、同じ 1000 番地でも、別の内容 が含まれていることがある。

カーネル空間とユーザ空間でデータをコピーする時には、次のような特殊な関 数を使う必要がある。

unsigned long
copy_to_user(void __user *to, const void *from, unsigned long n)
unsigned long
copy_from_user(void *to, const void __user *from, unsigned long n)

これらの関数は、コピーの途中でページフォールトが発生した時にもうまくコ ピーできる(ページインの処理でプロセスがスリープすることがある)。また、 引数の番地が有効かどうかをチェックする。

Linux x86 アーキテクチャでは、カーネル空間とユーザ空間が一部重なってい ることがある。この場合、カーネルでmemcpy() を使ったり、直接ポインタを操 作してもユーザ空間がアクセスできてしまうが、それは誤りである。

◆rtc_get_rtc_time()

1297:	static void rtc_get_rtc_time(struct rtc_time *rtc_tm)
1298:	{
1299:	        unsigned long uip_watchdog = jiffies, flags;
1300:	        unsigned char ctrl;
...
1325:	        spin_lock_irqsave(&rtc_lock, flags);
1326:	        rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS);
1327:	        rtc_tm->tm_min = CMOS_READ(RTC_MINUTES);
1328:	        rtc_tm->tm_hour = CMOS_READ(RTC_HOURS);
1329:	        rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH);
1330:	        rtc_tm->tm_mon = CMOS_READ(RTC_MONTH);
1331:	        rtc_tm->tm_year = CMOS_READ(RTC_YEAR);
...
1338:	        ctrl = CMOS_READ(RTC_CONTROL);
1339:	        spin_unlock_irqrestore(&rtc_lock, flags);
1340:	
1341:	        if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
1342:	                rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec);
1343:	                rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min);
1344:	                rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour);
1345:	                rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday);
1346:	                rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon);
1347:	                rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);
...
1349:	        }
...
1359:	        rtc_tm->tm_year += epoch - 1900;
1360:	        if (rtc_tm->tm_year <= 69)
1361:	                rtc_tm->tm_year += 100;
1363:	        rtc_tm->tm_mon--;
1364:	}

◆入出力の方法

x86 CMOS RTC は、前者。

◆outb()とinb()

Linux における入出力命令を使った入出力は、inb() と outb() で行われる。
outb(unsigned char value, int port)
  ポート番号 port に 1 バイトの value を出力する

unsigned char inb(int port)
  ポート番号 port から 1 バイトの value を入力してその値を返す
1 バイト 8 ビットではなくて 2 バイト 16 ビット 単位のもの (inw(), outw()) や4 バイト 32 ビット単位のもの( inl(), outl() ) もある。

◆CMOS RTCの入出力ポート

x86 CMOS RTC は、ポート番号 0x70 と 0x71 の入出力ポートを使う。
0x70
インデックス用ポート。out のみ
0x71
データ用ポート。in と out
データを読む方法
  1. インデックス用ポートに読みたい日付やデータのインデックス (RTC_SECONDS, RTC_MINUTES, RTC_HOURS, RTC_DAY_OF_MONTH, RTC_MONTH, RTC_YEAR)を out する。
  2. 直後にデータ用ポートらか in する
データを書く方法
  1. インデックス用ポートに読みたい日付やデータのインデックス を out する。
  2. 直後にデータ用ポートにデータを out する

◆/proc/ioports

/proc/ioports を見ると、使われている入出力ポートがわかる。
$ cat /proc/ioports [←]
0000-001f : dma1
0020-0021 : pic1
0040-0043 : timer0
0050-0053 : timer1
0060-0060 : keyboard
0064-0064 : keyboard
0070-0077 : rtc
0080-008f : dma page reg
...
ff80-ff9f : 0000:00:1d.0
  ff80-ff9f : uhci_hcd
$ []

◆rtc_cmos_read()

arch/x86/include/asm/mc146818rtc.h
  94:	#define CMOS_READ(addr) rtc_cmos_read(addr)
  95:	#define CMOS_WRITE(val, addr) rtc_cmos_write(val, addr)

arch/x86/kernel/rtc.c
 146:	/* Routines for accessing the CMOS RAM/RTC. */
 147:	unsigned char rtc_cmos_read(unsigned char addr)
 148:	{
 149:	        unsigned char val;
 150:	
 151:	        lock_cmos_prefix(addr);
 152:	        outb(addr, RTC_PORT(0));
 153:	        val = inb(RTC_PORT(1));
 154:	        lock_cmos_suffix(addr);
 155:	
 156:	        return val;
 157:	}

include/linux/mc146818rtc.h
  43:	#define RTC_SECONDS             0
  44:	#define RTC_SECONDS_ALARM       1
  45:	#define RTC_MINUTES             2
  46:	#define RTC_MINUTES_ALARM       3
  47:	#define RTC_HOURS               4
  48:	#define RTC_HOURS_ALARM         5
...
  52:	#define RTC_DAY_OF_WEEK         6
  53:	#define RTC_DAY_OF_MONTH        7
  54:	#define RTC_MONTH               8
  55:	#define RTC_YEAR                9
...
  86:	#define RTC_CONTROL     RTC_REG_B
...
  92:	# define RTC_DM_BINARY 0x04     /* all time/date values are BCD if clear */

arch/x86/include/asm/mc146818rtc.h
  13:	#define RTC_PORT(x)     (0x70 + (x))
  14:	#define RTC_ALWAYS_BCD  1       /* RTC operates in binary mode */

◆asm()文

C言語のプログラムの中にアセンブリ言語のプログラムを混ぜて書く方法。
asm ( アセンブラの命令列
 : 出力オペランド(省略可)
 : 入力オペランド(省略可)
 : 破壊するレジスタ(省略可) )

volatile をつけると、コンパイラによる最適化(命令の順番の入れ替え等)を 抑止できる。

◆outb()とinb()

Linux のソースコードの中に outb() や inb() の定義を探しても見つからない。 それらは、C言語のプリプロセッサで次のように inline 関数として定義される。
arch/x86/include/asm/io.h

 287:	#define BUILDIO(bwl, bw, type)                                          \
 288:	static inline void out##bwl(unsigned type value, int port)              \
 289:	{                                                                       \
 290:	        asm volatile("out" #bwl " %" #bw "0, %w1"                       \
 291:	                     : : "a"(value), "Nd"(port));                       \
 292:	}                                                                       \
 293:	                                                                        \
 294:	static inline unsigned type in##bwl(int port)                           \
 295:	{                                                                       \
 296:	        unsigned type value;                                            \
 297:	        asm volatile("in" #bwl " %w1, %" #bw "0"                        \
 298:	                     : "=a"(value) : "Nd"(port));                       \
 299:	        return value;                                                   \
 300:	}                                                                       \
...
 327:	BUILDIO(b, b, char)
 328:	BUILDIO(w, w, short)
 329:	BUILDIO(l, , int)
Cプリプロセッサを通し、文字列の結合を行うと、次のようになる。
static inline unsigned char inb(int port) {
    unsigned char value;
    asm volatile("inb %w1, %b0" : "=a"(value) : "Nd"(port));
    return value;
}

static inline void outb(unsigned char value, int port) {
    asm volatile("outb %b0, %w1" : : "a"(value), "Nd"(port));
}

◆bcd2bin()

BCD では、10進数1桁を4ビット、10進数n桁をn*4ビットで表現する。たとえば、 12(10進2桁) は、8ビットの数0x12 (バイナリなら 16+2==18)で表現する。
lib/bcd.c
   1:	#include <linux/bcd.h>
   2:	#include <linux/module.h>
   3:	
   4:	unsigned bcd2bin(unsigned char val)
   5:	{
   6:	        return (val & 0x0f) + (val >> 4) * 10;
   7:	}
   8:	EXPORT_SYMBOL(bcd2bin);
   9:	
  10:	unsigned char bin2bcd(unsigned val)
  11:	{
  12:	        return ((val / 10) << 4) + val % 10;
  13:	}
  14:	EXPORT_SYMBOL(bin2bcd);

■クイズ6 デバイスドライバ、inb()とoutb()

★問題(601) rtc-read-time.cのデバイスドライバ

rtc-read-time.cのプログラムでシステム・コー ルが実行されると、CMOS RTC のデバイス・ドライバに含まれる関数が実行され る。次のシステム・コールが実行された時に、どんな関数が実行されるかを答 えなさい。 drivers/char/rtc.c (rtc-read-time.c) に含まれる関数の名前で答えなさい。

★問題(602) copy_to_user()とmemcpy()

関数 rtc_do_ioctl() は、copy_to_user() を呼び出している。これは正しいプログラムであるが、も しも copy_to_user() を memcpy() やポインタを操作して直接ユーザ空間のメ モリにアクセスすると問題がある。714 行目から 715 行目を、memcpy() を使っ て「間違ったプログラム」に書き換えなさい。ただし、copy_to_user() は必ず 成功するものとして、エラー処理を省略しなさい。以下の「/*空欄*/」を埋め なさい。
     memcpy( /*空欄(a)*/,/*空欄(b)*/,/*空欄(c)*/ );
     return 0;
なお、memcpy() のインタフェースは、次のようになっている。
void * memcpy(void *destination, const void *source, size_t len);

★問題(603) x86 CMOS RTCからの時間データの入力

次のプログラムは、x86 CMOS RTC ハードウェアから時間(hours)データを読み 出し変数hh に入れるプログラムの一部である。/*空欄(a)*/と/*空欄(b)*/を埋 めて完成させなさい。
    unsigned char hh;
    outb( /*空欄(a)*/, 0x70 );
    hh = inb( /*空欄(b)*/ );

Last updated: 2011/02/01 14:52:39
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>