LoginSignup
3
4

More than 5 years have passed since last update.

QEMUに仮想PCIデバイスを追加する(7) : 割り込みの設定

Last updated at Posted at 2015-01-04

仮想デバイスから割り込みを発生させ、
ドライバが割り込みハンドラで対応処理を行うようにしました。

QEMU仮想デバイス

オブジェクトの初期化中(test_pci_init)にirq が必要となる設定をします。

// test_pci_init 中
    pci_conf = pdev->config;
    pci_conf[PCI_INTERRUPT_PIN] = 1; /* if 0 no interrupt pin */

割り込みを使用しない場合はpci_conf[PCI_INTERRUPT_PIN]に0を代入します。
なお、PCIデバイスの割り込み線は4本(A-D, 1-4に対応)から選択できますが、
このあたりはPCIブリッジが適当に処理してくれるので考える必要はありません。

割り込み線の使用には
include/hw/pci/pci.h を見ると pci_irq_assert や、pci_irq_deassert と
いった関数があるのでこれを利用し、必要な処理が終わったら
irq を上げて、ドライバから解除の信号が来たら下ろすといった処理を書きました。

異なる種類の割り込みを1つのirq で行うことを想定して、割り込みをマスクを
用意しています(TestPCIState のinitmask 変数)。
これにビットを立てることでどの割り込み処理が必要かをドライバに判断させます。

    void test_do_something(TestPCIState *s) 
    {
        PCIDevice *pdev = PCI_DEVICE(s);

        tprintf("called\n");

        s->intmask |= INT_DO;
        // raise irq line
        pci_irq_assert(pdev);
    }

    void test_down_irq(TestPCIState *s) 
    {
        PCIDevice *pdev = PCI_DEVICE(s);

        // down irq line
        pci_irq_deassert(pdev);
    }

// test_pci_pio_writeの一部
    case TEST_DO:
        test_do_something(s);
        break;

    case TEST_DOWN_IRQ:
        test_down_irq(s);
        break;

デバイスハンドラ

probe 関数中でrequest_irq 関数により割り込みハンドラを登録しています。

// test_pci_probe 中
    err = request_irq(irq, test_pci_handler, 0, DRIVER_TEST_NAME, pdev);

// test_pci_remobe 中
    free_irq(dev_data->pdev->irq, dev_data->pdev);

割り込み処理はスリープができず、また他からの割り込みを受けることもないため
関数が終わるまで他のプロセスに切り替わりません。そのため、処理は
できるだけ短くあるべきです。よって、一般に割り込みハンドラ自体では
割り込みの種類の検知など基本的な処理を行い、その他の処理は
タスクレットかワークキューに任せます。

タスクレット中ではスリープが出来ませんが、他からの割り込みは可能です。
一方ワークキューではスリープが使用可能で、セマフォなどを使用できます。
ここでは、タスクレットを使用しています。

// interrupt handler
void test_do_tasklet(unsigned long unused_data)
{
    tprintk("tasklet called\n");
}

DECLARE_TASKLET(test_tasklet, test_do_tasklet, 0); // no data

irqreturn_t test_pci_handler(int irq, void *dev_id)
{
    struct pci_dev *pdev = dev_id;
    char intmask;

    tprintk("irq handler called\n");

    intmask = inb(dev_data->pio_base + TEST_GET_INTMASK);
    if(intmask & INT_DO) {
        // register tasklet
        tasklet_schedule(&test_tasklet);
    }

    outb(0, dev_data->pio_base + TEST_SET_INTMASK);

    // down irq line
    outl(0, dev_data->pio_base + TEST_DOWN_IRQ);

    return IRQ_HANDLED; 
}

// ioctlの一部
    case TEST_CMD_DOSOMETHING :
                outl(0, dev_data->pio_base + TEST_DO);
                break;

DECLARE_TASKLET によりタスクレットを宣言し、
割り込みハンドラ中でtasklet_scheduleを呼び出すことでそれを使用します。

割り込みハンドラはまず割り込みマスクの情報をデバイスから受け取り、
対応する割り込み処理があればそれを実行します。
割り込みハンドラを終了する前に割り込みマスクを更新し、
IRQ線を下げる様にデバイスへ指示します。

なお、IRQ線は複数のデバイスで共有することも可能であり
(request_irq 関数の第三引数はフラグであり、そこに
IRQ_SHARED を指定する)、
割り込みマスクを使用して処理を実行するか判断することはその場合にも使えます。

割り込みを処理した場合は IRQ_HANDLED を戻り値とします。
IRQ線を共有している場合、割り込みが自身のものではなく
処理を行わなかった時は IRQ_NONE を返します。

ユーザプログラム

ioctlシステムコールを使用します。

void interrupt_test(int fd)
{
    test_ioctl_data *d;
    d = malloc(sizeof(test_ioctl_data));

    if(!d) exit(1);
    printf("\n---- start interrupt test ----\n");

    ioctl(fd, TEST_CMD_DOSOMETHING, d);

    printf("\n---- end interrupt test ----\n");

    free(d);
}

実行結果

interrupt_test の実行結果は以下のようになります。

---- start interrupt test ----
** (151) test_pci_uioctl     : _cmd:1074035458
## (155) test_pci_pio_write  : addr 100, size 4
## ( 45) test_do_something   : called
** (269) test_pci_handler    : irq handler called
## (118) test_pci_pio_read   : addr 21, size 1
## (155) test_pci_pio_write  : addr 20, size 1
## (155) test_pci_pio_write  : addr 104, size 4
** (259) test_do_tasklet     : tasklet called

---- end interrupt test ----

はじめ: (1) 作ったもの

前: (6) メモリマップドI/O / ioctl の使用
次: (8) DMAの設定

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4