LoginSignup
3
5

More than 5 years have passed since last update.

QEMUに仮想PCIデバイスを追加する(5) : I/Oポートの使用

Last updated at Posted at 2015-01-04

I/O ポートを利用して通常のファイルの様なread/write/lseekのアクセスを試しました。

なお、I/Oポートは 1 byte/ 1 port となっています。
そのため、例えば 4byte 毎アクセスする場合はポート番号も4つずつ増えていきます。

QEMU仮想デバイス

I/O ポートのread/writeに対応するMemoryRegionOpsの関数を用意すれば良いです。
ここではI/O ポートのオフセットの 0 〜 TEST_PIO_DATASIZE を通常の
ファイルの用に(1 バイトずつ)使用する定義にしました。

また、メモリとして使用する領域はオブジェクトの初期化関数で初期化しておきます。

static uint64_t
test_pci_pio_read(void *opaque, hwaddr addr, unsigned size)
{
    TestPCIState *s = opaque;
    tprintf("addr %ld, size %d\n", addr, size);

    if(addr >= TEST_PIO && addr < TEST_PIO_DATASIZE) {
        return s->piodata[addr];
    } else {
         /* .... */
    }
}

static void
test_pci_pio_write(void *opaque, hwaddr addr, uint64_t val,
                       unsigned size)
{
    TestPCIState *s = opaque;
    PCIDevice *pdev = PCI_DEVICE(s);

    tprintf("addr %ld, size %d\n", addr, size);

    if(addr >= TEST_PIO && addr < TEST_PIO_DATASIZE) {
        s->piodata[addr] = (char)val;
    } else {
        /* ... */
    }
}

// test_pci_init 中
    // init local parameter
    for(i=0; i<TEST_PIO_DATASIZE; i++){
        s->piodata[i] = i;
    }

関数の第一引数 opaque はこのデバイスオブジェクトへのポインタとなっているので、
TestPCIState へキャストします。
また、addr は確保されたI/Oポート番号からのオフセットとなります。
なお、I/O ポートのその他の領域は後述のioctlで使用します。

デバイスドライバ

file_operations 構造体で各種関数を指定します。

ssize_t test_pci_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
    int retval;
    char val;
    unsigned int write_num = 0;

    tprintk("count %d pos %lld\n", count, *f_ops);

    while(write_num < count) {
        tprintk("port io number:%lx\n", dev_data->pio_base+*f_ops);

        retval = get_user(val, &buf[write_num++]);
        if(retval) break; // 0 on success

        outb(val, dev_data->pio_base+*f_ops);
        (*f_ops)++;
    }

    return write_num;
}

// read from port io per 1 byte
ssize_t test_pci_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
    int retval;
    char val = 0;
    unsigned int read_num = 0;

    tprintk("count %d pos %lld\n", count, *f_ops);

    // read from ioport
    while (read_num < count) {
        tprintk("port io number:%lx\n", dev_data->pio_base+*f_ops);

        val = inb(dev_data->pio_base + *f_ops); // read 1 byte
        // printk("val %d\n", val);
        retval = put_user(val, &buf[read_num++]);
        if(retval) break; // 0 on success

        (*f_ops)++;
    }

    return read_num;
}

loff_t test_pci_llseek(struct file *filp, loff_t off, int whence)
{
    loff_t newpos =-1;

    tprintk("lseek whence:%d\n", whence);
    switch(whence) {
    case SEEK_SET:
      newpos = off;
      break;

        case SEEK_CUR:
      newpos = filp->f_pos + off;
      break;

        case SEEK_END:
      newpos = dev_data->pio_memsize + off;
      break;

    default: /* can't happen */
            return -EINVAL;
    }

    if (newpos < 0) return -EINVAL;

  filp->f_pos = newpos;
  return newpos;
}

I/Oポートへのアクセスには inb/inw/inl 及びoutb/outw/outl 関数を用います
(それぞれ1,2,4バイト)。なお64ビット環境であっても1度に8バイトアクセスすること
は出来ません。これらの関数は読み込んだ/書き込んだバイト数を返り値とします。

ユーザー空間とのデータのやり取りにはget_user/put_user マクロを使用しています。

また、*f_posは初期値0で(このように通常のファイルの用にデバイスを操作するならば)
ドライバがオフセットを更新します。
今回はread/write ともに1バイトずつ読み書きを行い、オフセットを更新しています。

ユーザプログラム

通常のファイルと同じようにread/write/lseek関数を呼べます。
一度read/write をするとオフセットがずれるので、lseekではじめからに戻しています。
ここでは、I/O ポートからTEST_PIO_DATASIZE分 read,
それらの値を+10してwrite,再度read といったテストをしています。

void read_device(int fd)
{
    char buf[TEST_PIO_DATASIZE], *p;
    int ret; // ssize_t

    ret = read(fd, &buf, sizeof(buf)); 
    if(ret > 0) {
        p = buf;
        printf("read device : %d bytes read\n", ret);
        while(ret--) printf("%2d ", *p++);
    } else {
        printf("read error");
    }

    printf("\n");
}

void write_device(int fd, void *buf, unsigned int size)
{
    int ret;

    ret = write(fd, buf, size);
    if(ret < 0) {
        printf("write error");
    }
    printf("write device : %d bytes write\n", ret);
}

void portio_test(int fd)
{
    char buf[TEST_PIO_DATASIZE];
    int i;

    printf("\n---- start portio test ----\n");
    read_device(fd);
  lseek(fd, 0, SEEK_SET);

    for (i = 0; i < TEST_PIO_DATASIZE; i++) {
        buf[i] = i+10;
    }
    write_device(fd, buf, TEST_PIO_DATASIZE);
  lseek(fd, 0, SEEK_SET);

    read_device(fd);

    printf("\n---- end portio test ----\n");
}

実行結果

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

---- start portio test ----
** (100) test_pci_read       : count 6 pos 0
** (104) test_pci_read       : port io number:c000
## (118) test_pci_pio_read   : addr 0, size 1
** (104) test_pci_read       : port io number:c001
## (118) test_pci_pio_read   : addr 1, size 1
** (104) test_pci_read       : port io number:c002
## (118) test_pci_pio_read   : addr 2, size 1
** (104) test_pci_read       : port io number:c003
## (118) test_pci_pio_read   : addr 3, size 1
** (104) test_pci_read       : port io number:c004
## (118) test_pci_pio_read   : addr 4, size 1
** (104) test_pci_read       : port io number:c005
## (118) test_pci_pio_read   : addr 5, size 1
read device : 6 bytes read
0 1 2 3 4 5
** (121) test_pci_llseek     : lseek whence:0
** ( 78) test_pci_write      : count 6 pos 0
** ( 81) test_pci_write      : port io number:c000
## (155) test_pci_pio_write  : addr 0, size 1
** ( 81) test_pci_write      : port io number:c001
## (155) test_pci_pio_write  : addr 1, size 1
** ( 81) test_pci_write      : port io number:c002
## (155) test_pci_pio_write  : addr 2, size 1
** ( 81) test_pci_write      : port io number:c003
## (155) test_pci_pio_write  : addr 3, size 1
** ( 81) test_pci_write      : port io number:c004
## (155) test_pci_pio_write  : addr 4, size 1
** ( 81) test_pci_write      : port io number:c005
## (155) test_pci_pio_write  : addr 5, size 1
write device : 6 bytes write
** (121) test_pci_llseek     : lseek whence:0
** (100) test_pci_read       : count 6 pos 0
** (104) test_pci_read       : port io number:c000
## (118) test_pci_pio_read   : addr 0, size 1
** (104) test_pci_read       : port io number:c001
## (118) test_pci_pio_read   : addr 1, size 1
** (104) test_pci_read       : port io number:c002
## (118) test_pci_pio_read   : addr 2, size 1
** (104) test_pci_read       : port io number:c003
## (118) test_pci_pio_read   : addr 3, size 1
** (104) test_pci_read       : port io number:c004
## (118) test_pci_pio_read   : addr 4, size 1
** (104) test_pci_read       : port io number:c005
## (118) test_pci_pio_read   : addr 5, size 1
read device : 6 bytes read
10 11 12 13 14 15

---- end portio test ----

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

前: (4) 仮想デバイス/ドライバの登録
次: (6) メモリマップドI/O / ioctl の使用

3
5
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
5