Vengineerの戯言

人生は短いけど、長いです。人生を楽しみましょう!

vfio (Virtual Funtion I/O) と SystemC を繋げる (その2)

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

前回は、QEMU 上で起動した Ubuntu cloud image に SystemC/Verilatorをインストールし、SystemC側のモデルをビルド後、vfio-pciバイスを登録しました。
今回は、SystemC側のモデル、test-pcie-ep-master-vfio.cc の中を見ていきます。

test-pcie-ep-master-vfio.cc の内容

test-pcie-ep-master-vfio.cc のブロック図を下記に示します。

f:id:Vengineer:20210404133913p:plain

図の右側にある vfio_dev が vfio と接続することになります。

vfio_dev

vfio_dev は、ここ で定義されています。

vfio_dev の constructor は、下記のようになっていて、/dev/vfio/vfio および /dev/vfio/3 (I/O MMUのグループ、3)を open して、いろいろとやっています。vfio_pciバイスにして何をしなければいけないのかは、こちら に説明があるので、確認してみてください。

vfio_dev::vfio_dev(const char *devname, int iommu_group)
{
  ..... 途中略

	container = open("/dev/vfio/vfio", O_RDWR);
	if (container < 0) {
		printf("Failed to open /dev/vfio/vfio, %d (%s)\n",
				container, strerror(errno));
		print_vfio_iommu_err();
		goto error;
	}

	snprintf(path, sizeof(path), "/dev/vfio/%d", iommu_group);
	group = open(path, O_RDWR);
	if (group < 0) {
		printf("Failed to open %s, %d (%s)\n",
				path, group, strerror(errno));
		print_vfio_device_id_err();
		print_vfio_iommu_err();
		goto error;
	} 
  .....
};

tlm2vfio_bridge

vfio_dev は、SystemCのモジュールではありません。。vfio_dev は、下記のように tlm2vfio_bridge の constructor にて bindされています。

tlm2vfio_bridge::tlm2vfio_bridge(sc_module_name name,
		int nr_sockets,
		class vfio_dev& dev,
		int region, uint64_t offset,
		bool handle_irq) :
	sc_module(name),
	tgt_socket("tgt-socket", nr_sockets),
	irq("irq"),
	dev(dev),
	offset(offset),
	region(region),
	event("ev"),
	irq_val(false),
	irq_dummy("irq-dummy"),
	handle_irq(handle_irq)

vfio_dev が使われるのは、

  • b_transport
  • get_direct_mem_ptr
  • irq_ack
  • irq_poll

です。b_transport と get_direct_mem_ptr は tlm2vfio が target デバイスとして動作する場合に使われます。また、irq_ackは、b_transport 内で使われています。
一方、irq_poll は、poll_trampoline という static な関数内で使われています。

poll_trampoline 関数は、tlm2vfio_bridge の constractor 内で thread として起動しています。引数は自分自身(this) です。

	if (handle_irq) {
		SC_THREAD(irq_proxy);
		pthread_create(&thread, NULL, poll_trampoline, this);
	}

irq_proxy は、下記のようになっていて、割り込み信号 irq の出力を event によって制御しています。

void tlm2vfio_bridge::irq_proxy(void)
{
	while (true) {
		irq.write(irq_val);
		wait(event);
	}
}

ここまでに見てきたことから、vfio_dev は tlm2vfio_bridge 経由で割り込み付の target device の働きをするようです。

test-pcie-ep-master-vfio

test-pcie-ep-master-vfio コマンドによって、SystemC側のモデルを起動します。この時に、デバイス、I/O MMU のグループ等を引数に渡しています。実行すると以下のようなメッセージが表示されます。
sudo が必要なのは、/dev/vfio/ ファイルを test-pcie-ep-master-vfio の中でアクセスしているためです。sudo コマンドを使わないでもいいようにするには、/dev/vfio/ を chown にて所有者を変えればいいですが、それはちょっとね。

$ sudo ./test-pcie-ep-master-vfio 0000:01:00.0 3 0

        SystemC 2.3.2-Accellera --- Jan  8 2021 14:33:28
        Copyright (c) 1996-2017 by all Contributors,
        ALL RIGHTS RESERVED
Device supports 9 regions, 5 irqs
mapped 0 at 0x7fb2352b3000

Info: (I702) default timescale unit used for tracing: 1 ps (./test-pcie-ep-master-vfio.vcd)
Bridge ID c3a89fe1
Position 0
version=100
type=12 pcie-axi-master
Bridge version 1.0
Bridge data-width 128
Bridge nr-descriptors: 16
--------------------------------------------------------------------------------
[15 us]

Write : 0x0, length = 16384, streaming_width = 16384

data = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0....

上記のログの内容:
Bridge ID から Bridge nr-descriptors: までは、tlm_hw_bridge_base の bridge_probe メソッドにて出力されているようです。tlm2axi_hw_bridge の親クラスが tlm_hw_bridge_base なので、tlm2axi_hw_bridge のインスタンスの tlm_hw_bridge が出力しているっぽいですね。

Write: 0x0, length = ... の部分は、tg-tlm.h の debugWrite メソッドからのようです。

最初のブロック図を再度見てみましょう。左側のTLMTrafficGeneratorが tg-tlm.h の debugWriteメソッドを実行しています。

f:id:Vengineer:20210404133913p:plain

TLMTrafiicGererator に送られてくる traffic は、TLMTrafficGenerator の上の RandomTraffic によって生成されます。下記のようなコードで RandonTraffic のインスタンス rand_xfers を TLMTrafficGeneratorのインスタンス tgに addTransfers で割り当てています。

		rand_xfers = new RandomTraffic(0, ram_size, UINT64_MAX, 1,
						ram_size, ram_size, 40000);

		// Wire up the clock and reset signals.
		tlm_hw_bridge.rst(rst);

		rand_xfers->setInitMemory(true);
		rand_xfers->setMaxStreamingWidthLen(ram_size);

		tg.enableDebug();
		tg.addTransfers(*rand_xfers, 0, DoneCallback);
		tg.setStartDelay(sc_time(15, SC_US));

生成された Trans は、splitter によって、memory あるいは、tlm2axi_hw_bridge に渡されます。tlm2axi_hw_bridge にて適当さサイズの trans に分割され、tlm2vfio_bridge に送られ、vfio_dev に bind された vfio_pci から mmap された PCIe の BAR[x] のメモリ空間にアクセスしています。

おわりに

今回紹介したSystemC側のモデルから vfio (vfio_pci) 経由にて実際のPCIe デバイスにアクセスできます。SystemCから実際のデバイスにアクセスできて何が嬉しいって?

まー、そこは 何かの?目的があったからだからでしょうね。