LoginSignup
3
5

More than 5 years have passed since last update.

QEMUに仮想PCIデバイスを追加する(6) : メモリマップドI/O / ioctl の使用

Last updated at Posted at 2015-01-04

メモリマップドI/Oの利用 を ioctl システムコールを用いてテストしました。

QEMU仮想デバイス

I/Oポートの場合と同様に、
メモリマップドI/O のread/writeに対応するMemoryRegionOpsの関数を書けば良いです。
なお、ここではアクセスは4byteずつとしているため、addrの値を2bit右シフトして
配列を参照しています。

I/O ポートの場合と同じく使用するメモリ領域は予め初期化しておきます。

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

    if(addr >= TEST_MEM && addr < TEST_MEM + TEST_MMIO_DATASIZE){
        return s->mmiodata[addr>>2]; // 4byte
    }

    return 0xaabbccdd; // not used
}

static void
test_pci_mmio_write(void *opaque, hwaddr addr, uint64_t val,
                       unsigned size)
{
    TestPCIState *s = opaque;

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

    if(addr >= TEST_MEM && addr < TEST_MEM + TEST_MMIO_DATASIZE){
        s->mmiodata[addr>>2] = val; // 4byte
    }
}

// test_pci_init 中
    for(i=0; i<TEST_MMIO_DATASIZE; i++){
        s->mmiodata[i] = i;
    }

デバイスドライバ

ioctl を利用してメモリマップドI/Oをテストしています。
なお、PCIコンフィギュレーション空間から読み取ったメモリマップドI/Oの
開始アドレスは物理アドレスであるため、
アクセスする際には仮想アドレスへの変換が必要です。
これには ioremap 関数を用います。(終了時にiounmapを実行)。
この変換は初期化の probe 関数中で行っています。

// test_pci_probe 関数中
    dev_data->mmio_addr = ioremap(dev_data->mmio_base, TEST_PCI_MEMSIZE);

// test_pci_remove 関数中
    iounmap(dev_data->mmio_addr);


long test_pci_uioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int data[TEST_MMIO_DATANUM];
    test_ioctl_data *d = arg;
    int n;

    tprintk("_cmd:%d\n", cmd);
    switch (cmd) {
        case TEST_CMD_MEMREAD :
            memcpy_fromio(data, dev_data->mmio_addr, TEST_MMIO_DATASIZE);
            copy_to_user(d->mmiodata, data, TEST_MMIO_DATASIZE);
            break;

        case TEST_CMD_MEMWRITE :
            copy_from_user(data, d->mmiodata, TEST_MMIO_DATASIZE);
            memcpy_toio(dev_data->mmio_addr, data, TEST_MMIO_DATASIZE);
            break;

            /* .... */

        default:
            return -EINVAL; // invalid argument(cmd)
    }

    return 0;
}

メモリマップドI/O を使用する場合、in/out 命令と同じように
read/write 命令を用いて数バイト単位でのアクセスが行えます
(read8/read16/read32, write8/write16/write32)。
これに加えて、memcpy_fromio/memcpy_toio 関数を用いることで
一定サイズのデータのやりとりを簡単に行うことが出来、今回はそちらの方法を採用しています。
また、ユーザー空間とのデータのやり取りに
copy_to_user/copy_from_user 関数を使用しています。

ioctl はコマンド(第二引数 unsigned int cmd) と
データ(第三引数 unsined long arg, 適当にキャストする)
を受け取り、コマンドに応じて switch 文を書くことでデバイス固有の動作を
実現します。 read/write システムコールにはI/Oポートを使用したので、
メモリマップドI/Oのテストにはioctlを利用しています。

なお、ioctl用のコマンドは間違いを防ぐために単純な数字ではなく
以下のようにマクロを利用して作成すると良いようです。

// test_pci.h 中
    #define TEST_PCI_MAGIC 123  // 8bit

    /* command for ioctl */
    // for mmio
    #define TEST_CMD_MEMREAD  _IOR(TEST_PCI_MAGIC, 0, test_ioctl_data)
    #define TEST_CMD_MEMWRITE _IOW(TEST_PCI_MAGIC, 1, test_ioctl_data)

リードの時は _IOR, ライトの時は _IOW, 両方なら _IOWRマクロを使います。
第一引数は8bit のマジックナンバーで、第二引数はコマンドの
連番となります。第三引数はやり取りするデータ型です。
これは以下の様にしました。

// test_pci.h
// use for ioctl
typedef struct hello_ioctl_data{
    int mmiodata[TEST_MMIO_DATANUM];

    int cdmabuf[TEST_CDMA_BUFFER_NUM];
    int sdmabuf[TEST_SDMA_BUFFER_NUM];
} test_ioctl_data;

ユーザプログラム

ioctlシステムコールを使用します。
I/Oポートのテストと同じくある領域を
read/その値を+10してwrite/再びread を行っています。

void mmio_test(int fd)
{
    int i;

    test_ioctl_data *d;
    d = malloc(sizeof(test_ioctl_data));
    if(!d) exit(1);

    printf("\n---- start mmio test ----\n");

    ioctl(fd, TEST_CMD_MEMREAD, d);
    for(i = 0; i < TEST_MMIO_DATANUM; i++) {
        printf("%2d ", d->mmiodata[i]);
        d->mmiodata[i] += 10;
    }
    printf("\n");

    ioctl(fd, TEST_CMD_MEMWRITE, d);
    for(i = 0; i < TEST_MMIO_DATANUM; i++) {
        d->mmiodata[i] = 0;
    }

    ioctl(fd, TEST_CMD_MEMREAD, d);
    for(i = 0; i < TEST_MMIO_DATANUM; i++) {
        printf("%2d ", d->mmiodata[i]);
    }
    printf("\n");
    printf("\n---- end mmio test ----\n");

    free(d);
}

実行結果

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

---- start mmio test ----
** (151) test_pci_uioctl     : _cmd:-2147190016
## (105) test_pci_mmio_read  : addr 0, size 4
## (105) test_pci_mmio_read  : addr 4, size 4
## (105) test_pci_mmio_read  : addr 8, size 4
## (105) test_pci_mmio_read  : addr 12, size 4
## (105) test_pci_mmio_read  : addr 16, size 4
## (105) test_pci_mmio_read  : addr 20, size 4
## (105) test_pci_mmio_read  : addr 24, size 4
## (105) test_pci_mmio_read  : addr 28, size 4
## (105) test_pci_mmio_read  : addr 32, size 4
## (105) test_pci_mmio_read  : addr 36, size 4
## (105) test_pci_mmio_read  : addr 40, size 4
## (105) test_pci_mmio_read  : addr 44, size 4
## (105) test_pci_mmio_read  : addr 48, size 4
## (105) test_pci_mmio_read  : addr 52, size 4
## (105) test_pci_mmio_read  : addr 56, size 4
## (105) test_pci_mmio_read  : addr 60, size 4
## (105) test_pci_mmio_read  : addr 64, size 4
## (105) test_pci_mmio_read  : addr 68, size 4
## (105) test_pci_mmio_read  : addr 72, size 4
## (105) test_pci_mmio_read  : addr 76, size 4
## (105) test_pci_mmio_read  : addr 80, size 4
## (105) test_pci_mmio_read  : addr 84, size 4
## (105) test_pci_mmio_read  : addr 88, size 4
## (105) test_pci_mmio_read  : addr 92, size 4
## (105) test_pci_mmio_read  : addr 96, size 4
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
** (151) test_pci_uioctl     : _cmd:1074035457
## (139) test_pci_mmio_write : addr 0, size 4
## (139) test_pci_mmio_write : addr 4, size 4
## (139) test_pci_mmio_write : addr 8, size 4
## (139) test_pci_mmio_write : addr 12, size 4
## (139) test_pci_mmio_write : addr 16, size 4
## (139) test_pci_mmio_write : addr 20, size 4
## (139) test_pci_mmio_write : addr 24, size 4
## (139) test_pci_mmio_write : addr 28, size 4
## (139) test_pci_mmio_write : addr 32, size 4
## (139) test_pci_mmio_write : addr 36, size 4
## (139) test_pci_mmio_write : addr 40, size 4
## (139) test_pci_mmio_write : addr 44, size 4
## (139) test_pci_mmio_write : addr 48, size 4
## (139) test_pci_mmio_write : addr 52, size 4
## (139) test_pci_mmio_write : addr 56, size 4
## (139) test_pci_mmio_write : addr 60, size 4
## (139) test_pci_mmio_write : addr 64, size 4
## (139) test_pci_mmio_write : addr 68, size 4
## (139) test_pci_mmio_write : addr 72, size 4
## (139) test_pci_mmio_write : addr 76, size 4
## (139) test_pci_mmio_write : addr 80, size 4
## (139) test_pci_mmio_write : addr 84, size 4
## (139) test_pci_mmio_write : addr 88, size 4
## (139) test_pci_mmio_write : addr 92, size 4
## (139) test_pci_mmio_write : addr 96, size 4
** (151) test_pci_uioctl     : _cmd:-2147190016
## (105) test_pci_mmio_read  : addr 0, size 4
## (105) test_pci_mmio_read  : addr 4, size 4
## (105) test_pci_mmio_read  : addr 8, size 4
## (105) test_pci_mmio_read  : addr 12, size 4
## (105) test_pci_mmio_read  : addr 16, size 4
## (105) test_pci_mmio_read  : addr 20, size 4
## (105) test_pci_mmio_read  : addr 24, size 4
## (105) test_pci_mmio_read  : addr 28, size 4
## (105) test_pci_mmio_read  : addr 32, size 4
## (105) test_pci_mmio_read  : addr 36, size 4
## (105) test_pci_mmio_read  : addr 40, size 4
## (105) test_pci_mmio_read  : addr 44, size 4
## (105) test_pci_mmio_read  : addr 48, size 4
## (105) test_pci_mmio_read  : addr 52, size 4
## (105) test_pci_mmio_read  : addr 56, size 4
## (105) test_pci_mmio_read  : addr 60, size 4
## (105) test_pci_mmio_read  : addr 64, size 4
## (105) test_pci_mmio_read  : addr 68, size 4
## (105) test_pci_mmio_read  : addr 72, size 4
## (105) test_pci_mmio_read  : addr 76, size 4
## (105) test_pci_mmio_read  : addr 80, size 4
## (105) test_pci_mmio_read  : addr 84, size 4
## (105) test_pci_mmio_read  : addr 88, size 4
## (105) test_pci_mmio_read  : addr 92, size 4
## (105) test_pci_mmio_read  : addr 96, size 4
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

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

前: (5) I/Oポートの使用
次: (7) 割り込みの設定

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