7.3 The hardware

8253タイマチップ(タイマカウンタ用LSI)は3つの内部タイマ(チャンネル)をもつ.

チャンネル 役割
0 PICに接続され,システムタイマ割り込みの発生を知らせる.
1 ビデオカード・BIOS・メモリコントローラがDRAMをリフレッシュする.
2 スピーカに接続され,2種類の矩形波を出し音を出力する.

出典:http://softwaretechnique.jp/OS_Development/kernel_development04.html

Block diagram of PC speaker connections.

By LINUX Kernel Internals, pp. 197 Figure 7.1

スピーカを介してアナログ信号を出力するためにパルス長変調が使われる.出力すべき瞬間的なアナログ値に一致する,異なる長さのON/OFF位相間の素早い変化により,スピーカの機械的慣性を利用してアナログ出力を得ることが可能となる.しかし,パルス長変調は非常に敏感である.サンプルがひとつでも見つからない場合でも,スピーカから迷惑なクリック音が出てしまう.

パルス長変調を使う際の主な問題は,必要な時間間隔(timer interval)の測定と実装にあると判明する.第一の可能性はチャンネル2を使わずに,システム制御ラッチのビット1を使って出力全体を制御することである.時間間隔は待機ループによって生成できる.この提案は実装が最も単純であるが,2つの決定的な欠点がある.

第二の提案は, retriggerable oneshotとしてチャンネル2をプログラミングすることである.タイマはRestart gateに1を加えることによって開始し,出力に0を生成する.タイマ定数がカウントダウンされると1が出力される.最大のサンプル値に対応する一定の時間の後,新しい定数がチャンネル2に転送され,タイマが再始動する.この一定の時間間隔は,一般にdivider modeで動く遅延ループまたはチャンネル0を使って再生成でき,タイマ定数が0になるたびに0のIRQを生成する.チャンネル0によって生成されるこの周波数は,サンプルが出力されるサンプリングレートでもある.以下では real sampling rateと呼ぶ.この手順は以下の図で表される.

Pulse-length modulation using timers 0 and 2.

By LINUX Kernel Internals, pp. 197 Figure 7.2

タイマチップには4つのI/Oポートがある.

ポート 説明
0x40 チャンネル0
0x41 チャンネル1
0x42 チャンネル2
0x43 モード制御レジスタ

タイマをプログラムするには命令を0x43のモード制御レジスタに書き込む必要があり,タイマは適切なデータポートに定数を設定する必要がある.

ビット 説明
7 プログラムするタイマの番号
6 プログラムするタイマの番号
5 アクセスモードのどれかひとつ
4 アクセスモードのどれかひとつ
3 タイマモード
2 タイマモード
1 タイマモード

アクセスモードの対応は以下のとおり.

ビット5 ビット4 モード 説明
0 0 ラッチ カウンタは内部レジスタに転送され,その後読み出せる.
0 1 LSBのみ カウンタの下位8ビットのみが転送される.
1 0 MSBのみ カウンタの上位8ビットのみが転送される.
1 1 LSB/MSB カウンタの下位8ビットが転送され,その後上位8ビットが転送される.

たとえば,10000 Hzのトーンを生成するには以下のようにする.

/* ANDゲートを開き,活性のためにrestart gateを設定する. */
outb_p (inb_p (0x61) | 3, 0x61);

/* 必要なタイマ定数を計算する. */
tc = 1193180 / 10000;

/* 
 * 命令に対応する: 
 *     timer 2, LSBのあとにMSBを読み/書き, timer 3
 */
outb_p (0xb6, 0x43);

/*
 * チャンネル2にタイマ定数を書き込む.
 * これ以降は内部スピーカがトーンを発する.
 */
 outb_p (tc & 0xff, 0x42);
 outb ((tc >> 8) & 0xff, 0x42);

スピーカの音を消すには

outb(inb_p(0x61) & 0xfc, 0x61);

この処理はスピーカをオフにし,タイマを停止させる.

不幸にもチャンネル0だけが標準的なPCで割り込みを生成できる.つまり,上記で説明した2つ目の可能性は,Linuxで非常に重要なタイマ割り込みIRQ 0が変更されるため,危険はない.新しい割り込みルーチンは,元の手続きが同じ間隔で再び呼び出されるようにする必要がある.加えて,保護モードでの割り込み処理はリアルモードよりもかなり時間がかかるため,引き起こされる割り込みの数が多ければ多いほど計算時間が増えてしまう.

7.3.1 Hardware detection

ハードウェアの検出はI/Oポートを読んでいけば良い.

include/linux/symtab_begin.h

#define X(a) a

kernel/ksyms.c

struct symbol_table symbol_table = {
#include <linux/symtab_begin.h>
...略...
    /* IOポート操作 */
    X(check_region),
    X(request_region),
    X(release_region),

kernel/resource.c

/*
 * ioport領域を登録するためにこれをデバイスドライバから呼び出す.
 */

/*
 * unsigned int from -- ブロックされる最初のI/Oポートの数
 * unsigned int num  -- ブロックされるポートの数
 * const char *name  -- ポートをブロックするドライバの名前
 *                      ドライバの名前はProcファイルシステムでのみ使われ,
 *                      ユーザはどのドライバがどのポートを使っているのか
 *                      調べることができる.
 */
void request_region(unsigned int from, unsigned int num, const char *name)
{
    resource_entry_t *p;
    int i;

    for (i = 0; i < IOTABLE_SIZE; i++)
        if (iotable[i].num == 0)
            break;
    if (i == IOTABLE_SIZE)
        printk("warning: ioport table is full\n");
    else {
        p = find_gap(&iolist, from, num);
        if (p == NULL)
            return;
        iotable[i].name = name;
        iotable[i].from = from;
        iotable[i].num = num;
        iotable[i].next = p->next;
        p->next = &iotable[i];
        return;
    }
}
...略...
/* 
 * デバイスドライバが開放されたときにこれを呼び出せ
 */
void release_region(unsigned int from, unsigned int num)
{
    resource_entry_t *p, *q;

    for (p = &iolist; ; p = q) {
        q = p->next;
        if (q == NULL)
            break;
        if ((q->from == from) && (q->num == num)) {
            q->num = 0;
            p->next = q->next;
            return;
        }
    }
}
...略...
/*
 * 調査する前にこれを呼んでioport領域を確認せよ
 */
int check_region(unsigned int from, unsigned int num)
{
    /*
     * もし0でない値を返したなら,この領域の少なくとも1つのポートは
     * アクセス用に閉じられており,テストは省略すべきだ.
     */
    return (find_gap(&iolist, from, num) == NULL) ? -EBUSY : 0;
}

デバイスドライバがI/Oポートをテストしている例

drivers/net/skeleton.c

int
netcard_probe(struct device *dev)
{
    int i;
    int base_addr = dev ? dev->base_addr : 0;

    if (base_addr > 0x1ff)        /* ひとつの特定の場所を確認する. */
        return netcard_probe1(dev, base_addr);
    else if (base_addr != 0)    /* すべて調べない */
        return ENXIO;

    for (i = 0; netcard_portlist[i]; i++) {
        int ioaddr = netcard_portlist[i];
        if (check_region(ioaddr, NETCARD_IO_EXTENT))
            continue;
        if (netcard_probe1(dev, ioaddr) == 0)
            return 0;
    }

    return ENODEV;
}

Automatic interrupt detection

ハードウェアのIRQの決定について.どのIRQを使うかを探す関数がLinuxにはある.

plip -- パラレルラインIP

drivers/net/plip.c

int
plip_init(struct device *dev)
{
    ...略...
        int irq = 0;
        unsigned int irqs = probe_irq_on();

        outb(0x00, PAR_CONTROL(dev));
        udelay(1000);
        outb(PAR_INTR_OFF, PAR_CONTROL(dev));
        outb(PAR_INTR_ON, PAR_CONTROL(dev));
        outb(PAR_INTR_OFF, PAR_CONTROL(dev));
        udelay(1000);
        irq = probe_irq_off(irqs);

        if (irq > 0) {
            dev->irq = irq;
            printk("using probed IRQ %d.\n", dev->irq);
        } else
            printk("failed to detect IRQ(%d) --"
                   " Please set IRQ by ifconfig.\n", irq);
    }
    ...略...
}

arch/i386/kernel/irq.c

unsigned int probe_irq_on (void)
{
    unsigned int i, irqs = 0, irqmask;
    unsigned long delay;

    /* はじめに,割り当てられていないirqをつかみとる */
    for (i = 15; i > 0; i--) {
        /* 
         * SA_PROBEは内部使用のみを目的として渡す.
         * この定数は,この関数によって割り当てられたIRQが
         * 高速割り込みとしても,低速割り込みとしても導入されないことを
         * 保証するものの,BADとマークされる.
         * BAD割り込みの処理ルーチンは,単に割り込みコントローラ内の
         * 割り込みを再びオフにする.
         */
        if (!request_irq(i, no_action, SA_PROBE, "probe")) {
            enable_irq(i);
            irqs |= (1 << i);
        }
    }

    /* にせの割り込みが再び自身を隠すのを待つ. */
    for (delay = jiffies + 2; delay > jiffies; );    /* 最小10msの遅延 */

    /* にせの割り込みを除外する. */
    irqmask = (((unsigned int)cache_A1)<<8) | (unsigned int)cache_21;
    for (i = 15; i > 0; i--) {
        if (irqs & (1 << i) & irqmask) {
            irqs ^= (1 << i);
            free_irq(i);
        }
    }
    ...デバッグ用処理...
    return irqs;
}

/* 
 * IRQの検出が終わったあと呼び出される. 
 * 引数にはprobe_irq_on()で得られた値を使う.
 */
int probe_irq_off (unsigned int irqs)
{
    unsigned int i, irqmask;

    irqmask = (((unsigned int)cache_A1)<<8) | (unsigned int)cache_21;
    for (i = 15; i > 0; i--) {
        if (irqs & (1 << i)) {
            free_irq(i);
        }
    }
    ...デバッグ用処理...
    /*
     * probe_irq_off()は,probe_irq_on()によって割り当てられたIRQが
     * その後再びオフになったかどうかをテストする必要がある.
     * このテストは,引数irqsと現在アクティブなすべてのIRQの
     * ビットマスクを比較することでおこなわれる.
     * 2つのマスクが1ビットだけ異なる場合,1つのBAD割り込みが引き起こされ,
     * その数が容易に決定される.
     */
    irqs &= irqmask;
    if (!irqs)
        return 0;
    i = ffz(~irqs);
    if (irqs != (irqs & (1 << i)))
        i = -i;
    return i;
}

int request_irq(unsigned int irq, void (*handler)(int, struct pt_regs *),
    unsigned long irqflags, const char * devname)
{
    struct irqaction * action;
    unsigned long flags;

    ...エラー処理...
    action = irq + irq_action;
    ...エラー処理...
    save_flags(flags);
    cli();
    action->handler = handler;
    action->flags = irqflags;
    action->mask = 0;
    action->name = devname;
    if (!(action->flags & SA_PROBE)) { /* SA_ONESHOTは調査に使われる */
        if (action->flags & SA_INTERRUPT)
            set_intr_gate(0x20+irq,fast_interrupt[irq]);
        else
            set_intr_gate(0x20+irq,interrupt[irq]);
    }
    if (irq < 8) {
        cache_21 &= ~(1<<irq);
        outb(cache_21,0x21);
    } else {
        cache_21 &= ~(1<<2);
        cache_A1 &= ~(1<<(irq-8));
        outb(cache_21,0x21);
        outb(cache_A1,0xA1);
    }
    restore_flags(flags);
    return 0;
}