File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / virtio_base.cpp
Revision 1.1.1.7 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//

//
// VirtIO
//

#include "virtio_base.h"
#include "goldfish_pic.h"
#include "virtio_def.h"
#include "mainbus.h"
#include "mainram.h"
#include "scheduler.h"

//
// VirtIO 基本クラス
//

// コンストラクタ
VirtIODevice::VirtIODevice(uint objid_, uint slot_)
	: inherited(objid_)
{
	slot = slot_;
}

// デストラクタ
VirtIODevice::~VirtIODevice()
{
}

// 動的なコンストラクション
bool
VirtIODevice::Create()
{
	try {
		backend.reset(new VirtIOThread(this));
	} catch (...) { }
	if ((bool)backend == false) {
		warnx("Failed to initialize VirtIOThread at %s", __method__);
		return false;
	}

	return true;
}

// ログレベルを設定。
void
VirtIODevice::SetLogLevel(int loglevel_)
{
	inherited::SetLogLevel(loglevel_);

	if ((bool)backend) {
		backend->SetLogLevel(loglevel_);
	}
}

// 初期化
bool
VirtIODevice::Init()
{
	mainbus = GetMainbusDevice();
	mainram = GetMainRAMDevice();

	// GFPIC に割り込みを登録する。
	// 継承側のコンストラクタで intrname の設定をしておくこと。
	assert(intrname[0]);
	uint pic = (slot / 32) + 2;
	uint irq = (slot % 32) + 1;
	auto gfpic = GetGFPICDevice(pic);
	gfpic->RegistIRQ(irq, intrname, this);

	interrupt = gfpic;

	// DEVICE_FEATURES (共通部) を用意。
	SetDeviceFeatures(VIRTIO_F_RING_INDIRECT_DESC);
	SetDeviceFeatures(VIRTIO_F_VERSION_1);

	// 裏スレッドからの DONE コールバックを登録。
	assert(msgid != 0);
	scheduler->ConnectMessage(msgid, this,
		ToMessageCallback(&VirtIODevice::DoneMessage));

	return true;
}

// リセット
void
VirtIODevice::ResetHard(bool poweron)
{
	Reset();
}

// BusIO のオフセットはこのポート幅で先頭から 0個目、1個目と数えるが、
// VirtIO の仕様でレジスタは $00、$04 のようにアドレスで数えるので
// オフセットで渡されたものをもう一回アドレスに戻す…。

// レジスタ部 (<0x100) はゲストエンディアン (BE)
// コンフィグ部 (>=0x100) はリトルエンディアン。

busdata
VirtIODevice::ReadPort(uint32 offset)
{
	busdata data;

	data = PeekPort(offset);

	if (__predict_false(loglevel >= 2)) {
		uint32 addr = offset << 2;
		if (addr < 0x100) {
			switch (addr) {
			 case VirtIO::MAGIC_VALUE:
				putlogn("MAGIC_VALUE -> $%08x", data.Data());
				break;
			 case VirtIO::VERSION:
				putlogn("VERSION -> $%08x", data.Data());
				break;
			 case VirtIO::DEVICE_ID:
				putlogn("DEVICE_ID -> $%08x", data.Data());
				break;
			 case VirtIO::VENDOR_ID:
				putlogn("VENDOR_ID -> $%08x", data.Data());
				break;
			 case VirtIO::DEVICE_FEATURES:
				putlogn("DEVICE_FEATURES[%x] -> $%08x",
					device_sel, data.Data());
				break;
			 case VirtIO::STATUS:
				putlogn("STATUS -> $%08x%s", data.Data(),
					VirtIO::StatusBits(data).c_str());
				break;
			 case VirtIO::QUEUE_NUM_MAX:
				putlogn("QUEUE_NUM_MAX(%u) -> $%08x", queue_sel, data.Data());
				break;
			 case VirtIO::INTERRUPT_STATUS:
				putlogn("INTERRUPT_STATUS -> $%08x", data.Data());
				break;
			 default:
				putlogn("REG%03x -> $%08x", addr, data.Data());
				break;
			}
		} else {
			putlogn("CONFIG:%03x -> $%08x", addr, data.Data());
		}
	}

	data |= BusData::Size4;
	return data;
}

busdata
VirtIODevice::WritePort(uint32 offset, uint32 data)
{
	busdata r = BusData::Size4;

	uint32 addr = offset << 2;
	if (addr >= 0x100) {
		putlog(1, "Write $%03x (Not writeable)", addr);
		return r;
	}

	switch (addr) {
	 case VirtIO::STATUS:
		// 0 を書き込むとデバイスリセット。
		// 0 以外はステータスの書き込み。
		device_status = data;
		putlog(2, "STATUS <- $%08x%s", device_status,
			VirtIO::StatusBits(device_status).c_str());
		if (device_status == 0) {
			Reset();
		}
		break;

	 case VirtIO::DEVICE_FEATURES_SEL:
		device_sel = data;
		if (device_sel >= MAX_FEATURES_SEL) {
			putlog(1, "DEVICE_FEATURES_SEL <- $%08x (out of range!)",
				device_sel);
		} else {
			putlog(2, "DEVICE_FEATURES_SEL <- $%08x", device_sel);
		}
		break;

	 case VirtIO::DRIVER_FEATURES:
		if (driver_sel >= MAX_FEATURES_SEL) {
			putlog(1, "DRIVER_FEATURES[%x] <- $%08x (!)", driver_sel, data);
		} else {
			driver_features[driver_sel] = data;
			putlog(2, "DRIVER_FEATURES[%x] <- $%08x", driver_sel, data);
		}
		break;

	 case VirtIO::DRIVER_FEATURES_SEL:
		driver_sel = data;
		if (driver_sel >= MAX_FEATURES_SEL) {
			putlog(1, "DRIVER_FEATURES_SEL <- $%08x (out of range!)",
				driver_sel);
		} else {
			putlog(2, "DRIVER_FEATURES_SEL <- $%08x", driver_sel);
		}
		break;

	 case VirtIO::QUEUE_SEL:
		queue_sel = data;
		if (vqueues.empty() && queue_sel == 0) {
			vq = NULL;
			putlog(3, "QUEUE_SEL <- $%08x", queue_sel);
		} else if (queue_sel < vqueues.size()) {
			vq = &vqueues[queue_sel];
			putlog(3, "QUEUE_SEL <- $%08x", queue_sel);
		} else {
			putlog(1, "QUEUE_SEL <- $%08x (out of range!)", queue_sel);
		}
		break;

	 case VirtIO::QUEUE_NUM:
		putlog(2, "QUEUE_NUM(%u) <- $%08x", queue_sel, data);
		if (vq) {
			if (data > vq->num_max) {
				data = vq->num_max;
				putlog(1, "QUEUE_NUM(%u) =  $%08x", queue_sel, data);
			}
			vq->num = data;
		}
		break;

	 case VirtIO::QUEUE_READY:
		putlog(2, "QUEUE_READY(%u) <- $%08x", queue_sel, data);
		if (vq) {
			vq->SetReady(data);
		}
		break;

	 case VirtIO::QUEUE_NOTIFY:
		putlog(2, "QUEUE_NOTIFY(%u) <- $%08x", queue_sel, data);
		if (vq) {
			backend->RequestQueueNotify(vq->idx);
		}
		break;

	 case VirtIO::INTERRUPT_ACK:
		intr_status &= ~data;
		putlog(2, "INTERRUPT_ACK <- $%08x (STATUS = $%08x)", data, intr_status);
		ChangeInterrupt();
		break;

	 case VirtIO::QUEUE_DESC_LOW:
		putlog(2, "QUEUE_DESC_LOW(%u) <- $%08x", queue_sel, data);
		if (vq) {
			vq->desc = data;
		}
		break;
	 case VirtIO::QUEUE_DESC_HIGH:
		putlog((vq == NULL || data != 0) ? 1 : 3,
			"QUEUE_DESC_HIGH(%u) <- $%08x", queue_sel, data);
		break;

	 case VirtIO::QUEUE_DRIVER_LOW:
		putlog(2, "QUEUE_DRIVER_LOW(%u) <- $%08x", queue_sel, data);
		if (vq) {
			vq->driver = data;
		}
		break;
	 case VirtIO::QUEUE_DRIVER_HIGH:
		putlog((vq == NULL || data != 0) ? 1 : 3,
			"QUEUE_DRIVER_HIGH(%u) <- $%08x", queue_sel, data);
		break;

	 case VirtIO::QUEUE_DEVICE_LOW:
		putlog(2, "QUEUE_DEVICE_LOW(%u) <- $%08x", queue_sel, data);
		if (vq) {
			vq->device = data;
		}
		break;
	 case VirtIO::QUEUE_DEVICE_HIGH:
		putlog((vq == NULL || data != 0) ? 1 : 3,
			"QUEUE_DEVICE_HIGH(%u) <- $%08x", queue_sel, data);
		break;

	 default:
		putlog(0, "Write $%03x <- $%08x (NOT IMPLEMENTED)", addr, data);
		break;
	}

	return r;
}

busdata
VirtIODevice::PeekPort(uint32 offset)
{
	uint32 data;

	uint32 addr = offset << 2;

	if (addr < 0x100) {
		data = GetReg(addr);
	} else {
		// コンフィグ部はバイトイメージなのでそのまま。
		addr -= 0x100;
		data = (device_config[addr + 0] << 24)
		     | (device_config[addr + 1] << 16)
		     | (device_config[addr + 2] << 8)
		     | (device_config[addr + 3]);
	}

	return busdata(data);
}

// 下請け、デバイス情報部分。
int
VirtIODevice::MonitorScreenDev(TextScreen& screen, int y) const
{
// 0         1         2         3         4         5         6         7
// 012345678901234567890123456789012345678901234567890123456789012345678901234
// DeviceType: 2(Block Device)                DeviceAddr: $00000000 (Slot $00)

	screen.Print(0, y, "DeviceType: %u(%s)",
		device_id, VirtIO::DeviceIDStr(device_id).c_str());
	screen.Print(43, y, "DeviceAddr: $%08x (Slot $%02x)",
		baseaddr + slot * 0x200, slot);
	y++;

	// Features
	// XXX まだ折り返しは考慮してない。
	uint64 features = ((uint64)device_features[1] << 32) | device_features[0];
	screen.Puts(0, y, "Features:");
	int x = 12;
	for (int f = 63; f >= 0; f--) {
		if ((features & (1ULL << f)) == 0)
			continue;
		bool enabled = GetDriverFeatures(f);
		const char *name = GetFeatureName(f);
		if (__predict_true(name)) {
			screen.Puts(x, y, TA::OnOff(enabled), name);
			x += strlen(name) + 1;
		} else {
			std::string buf = string_format("(%u)", f);
			screen.Puts(x, y, TA::OnOff(enabled), buf.c_str());
			x += buf.size() + 1;
		}
	}
	y++;

	return y;
}

// 下請け、VirtQueue 1本の概要。
int
VirtIODevice::MonitorScreenVirtQueue(TextScreen& screen, int y,
	const VirtQueue& q) const
{
// 0         1         2         3         4         5         6         7
// 012345678901234567890123456789012345678901234567890123456789012345678901234
// VirtQueue0 "TransmitQ1" (NUM=4/128)
//  TableAddr: Desc=$00000000 Avail=$00000000(NoIntr) Used=$00000000(NoNotify)
//  Ring:      LastAvail=$0000  Avail=$0000 (Head=$0000)  Used=$0000

	screen.Print(0, y++, "VirtQueue%u: \"%s\" (NUM=%u/%u)",
		q.idx, q.name.c_str(), q.num, q.num_max);

	screen.Puts(1, y, "TableAddr:");
	if (__predict_true(q.desc != 0xcccccccc)) {
		screen.Print(12, y, "Desc=$%08x", q.desc);
	}
	if (__predict_true(q.driver != 0xcccccccc)) {
		uint16 flag = PeekLE16(q.driver + 0);
		screen.Print(27, y, "Avail=$%08x(", q.driver);
		screen.Puts(43, y, TA::OnOff(flag & VIRTQ_AVAIL_F_NO_INTERRUPT),
			"NoIntr");
		screen.Putc(49, y, ')');
	}
	if (__predict_true(q.device != 0xcccccccc)) {
		uint16 flag = PeekLE16(q.device + 0);
		screen.Print(51, y, "Used=$%08x(", q.device);
		screen.Puts(66, y, TA::OnOff(flag & VIRTQ_USED_F_NO_NOTIFY),
			"NoNotify");
		screen.Putc(74, y, ')');
	}
	y++;

	screen.Puts(1, y, "Ring:");
	if (__predict_true(q.GetReady())) {
		uint16 aidx = PeekLE16(q.driver + 2);
		uint16 head = PeekLE16(q.driver + 4 + q.Mod(aidx) * 2);
		uint16 uidx = PeekLE16(q.device + 2);

		screen.Print(12, y, "LastAvail=$%04x", q.last_avail_idx);
		screen.Print(29, y, "Avail=$%04x (Head=$%04x)", aidx, head);
		screen.Print(55, y, "Used=$%04x", uidx);
	}
	y++;

	return y;
}

// 下請け、VirtQDesc テーブル。(デバッグ用)
int
VirtIODevice::MonitorScreenVirtQDesc(TextScreen& screen, int y,
	const VirtQueue& q) const
{
	int x0;
	int x1;
	int x2;

	// 0         1         2         3         4         5         6         7
	// 01234567890123456789012345678901234567890123456789012345678901234567890
	//  Descriptor                            AvailRing   UsedRing
	//  No Address   Length   Flags     Next     ID       ID       Len
	//  00 $00000000 00000000 0001(IRN) 0000  G> 0000 <H  00000000 00112233 <H
	//  01 $00000000 00000000 0001(IRN) 0000     0000     00000000 00000000
	//  02 $00000000 00000000 0001(IRN) 0000     0000     00000000 00000000
	//  03 $00000000 00000000 0001(IRN) 0000     0000     00000000 00000000

	x0 = 1;
	x1 = 42;
	x2 = 51;

	screen.Puts(x0, y, "Descriptor");
	screen.Puts(x1 - 3, y, "AvailRing");
	screen.Puts(x2, y, "UsedRing");
	y++;
	screen.Puts(x0, y, "No Address   Length   Flags     Next");
	screen.Puts(x1, y, "ID");
	screen.Puts(x2, y, "ID       Length");
	y++;

	int ytop = y;
	int i;
	for (i = 0; i < q.num; i++) {
		// Desc Table
		uint32 desc = q.desc + i * 16;
		uint32 addr = PeekLE32(desc + 0x00);
		uint32 len  = PeekLE32(desc + 0x08);
		uint32 flag = PeekLE16(desc + 0x0c);
		uint32 next = PeekLE16(desc + 0x0e);
		screen.Print(x0, y, "%02x $%08x %08x %04x(%c%c%c) %04x",
			i, addr, len, flag,
			((flag & VIRTQ_DESC_F_INDIRECT) ? 'I' : '-'),
			((flag & VIRTQ_DESC_F_WRITE) ? 'W' : 'R'),
			((flag & VIRTQ_DESC_F_NEXT)  ? 'N' : '-'),
			next);

		// Avail Ring
		uint16 a_ring = PeekLE16(q.driver + 4 + i * 2);
		screen.Print(x1, y, "%04x", a_ring);

		// Used Ring
		uint32 u_id  = PeekLE32(q.device + 4 + i * 8 + 0);
		uint32 u_len = PeekLE32(q.device + 4 + i * 8 + 4);
		screen.Print(x2, y, "%08x %08x", u_id, u_len);

		y++;
	}
	for (; i < q.num_max; i++) {
		screen.Print(x0, y++, TA::Disable, "%02x", i);
	}

	if (__predict_true(q.GetReady())) {
		uint avail_idx = PeekLE16(q.driver + 2);
		uint used_idx  = PeekLE16(q.device + 2);
		screen.Puts(x1 -  3, ytop + q.Mod(avail_idx), "G>");
		screen.Puts(x1 +  5, ytop + q.Mod(q.last_avail_idx), "<H");
		screen.Puts(x2 + 18, ytop + q.Mod(used_idx), "<H");
	}

	return y;
}

// デバイスをリセット。
void
VirtIODevice::Reset()
{
	device_status = VirtIO::STATUS_RESET;
	device_sel = 0;
	driver_sel = 0;

	for (int i = 0; i < MAX_FEATURES_SEL; i++) {
		driver_features[i] = 0;
	}

	for (int i = 0; i < vqueues.size(); i++) {
		auto& q = vqueues[i];
		q.idx = i;
		// モニタの見栄えのため未設定時は無効値を入れておく
		q.desc   = 0xcccccccc;
		q.driver = 0xcccccccc;
		q.device = 0xcccccccc;
		q.num = 0;
		q.SetReady(0);
		q.last_avail_idx = 0;
	}
	vq = NULL;

	intr_status = 0;
	ChangeInterrupt();
}

// レジスタ部の値を返す。
uint32
VirtIODevice::GetReg(uint32 addr) const
{
	switch (addr) {
	 case VirtIO::MAGIC_VALUE:
		return VirtIO::MAGIC_STRING;

	 case VirtIO::VERSION:
		return VirtIO::DEVICE_VERSION;

	 case VirtIO::DEVICE_ID:
		return device_id;

	 case VirtIO::VENDOR_ID:
		return vendor_id;

	 case VirtIO::DEVICE_FEATURES:
		if (device_sel >= MAX_FEATURES_SEL) {
			return 0;
		}
		return device_features[device_sel];

	 case VirtIO::STATUS:
		return device_status;

	 case VirtIO::QUEUE_NUM_MAX:
		if (vq) {
			return vq->num_max;
		} else {
			return 0;
		}

	 case VirtIO::QUEUE_READY:
		if (vq) {
			return vq->GetReady();
		} else {
			return 0;
		}

	 case VirtIO::INTERRUPT_STATUS:
		return intr_status;

	 case VirtIO::CONFIG_GENERATION:
		return 0;

	 default:
		return 0xffffffff;
	}
}

// QUEUE_READY が更新された時に呼ばれる。必要ならオーバーライドする。
// q->ready が 0 なら Not ready(初期状態)、1 なら Ready。
void
VirtIODevice::QueueReadyChanged(VirtQueue *q)
{
}

// QUEUE_NOTIFY の処理。(これは裏スレッドで実行される)
void
VirtIODevice::QueueNotify(uint idx)
{
	assert(idx < vqueues.size());
	VirtQueue *q = &vqueues[idx];

	// avail_idx に追いつくまで処理。
	for (;;) {
		uint16 avail_idx = ReadLE16(q->driver + 2);
		putlog(3, "last_avail_idx=$%04x avail_idx=$%04x",
			q->last_avail_idx, avail_idx);
		if (q->last_avail_idx == avail_idx) {
			break;
		}

		// last_avail_idx の指すディスクリプタを読み出す。
		VirtIOReq req;
		req.q = q;
		StartDesc(req);

		// デバイス固有の処理。
		ProcessDesc(req);

		// 完了
		CommitDesc(req);

		// VM スレッドに完了通知。
		scheduler->SendMessage(msgid, q->idx);
	}
}

// q->last_avail_idx の指すディスクリプタからの
// ディスクリプタチェイン req を構成する。
// req は初期化後 .q のみ代入した状態で呼ぶこと。
void
VirtIODevice::StartDesc(VirtIOReq& req)
{
	const VirtQueue *q = req.q;
	uint32 flag;
	uint32 idx;

	// 先頭のインデックス。
	idx = ReadLE16(q->driver + 4 + q->Mod(q->last_avail_idx) * 2);
	if ((int32)idx < 0) {
		return;
	}
	req.idx = idx;

	do {
		// desc[idx] を読み出す。
		uint32 descaddr = q->desc + idx * 16;
		uint32 addr;
		uint32 slen;
		addr = ReadLE32(descaddr + 0);
		slen = ReadLE32(descaddr + 8);
		flag = ReadLE16(descaddr + 12);
		idx  = ReadLE16(descaddr + 14);	// Next Index

		if ((flag & VIRTQ_DESC_F_INDIRECT)) {
			AddIndirectDesc(req, addr, slen);
		} else {
			if ((flag & VIRTQ_DESC_F_WRITE)) {
				putlog(3, "req.wbuf[%u]: addr=$%08x len=$%04x",
					(uint)req.wbuf.size(), addr, slen);
				req.wbuf.emplace_back(addr, slen);
				req.wlen += slen;
			} else {
				putlog(3, "req.rbuf[%u]: addr=$%08x len=$%04x",
					(uint)req.rbuf.size(), addr, slen);
				req.rbuf.emplace_back(addr, slen);
				req.rlen += slen;
			}
		}
	} while ((flag & VIRTQ_DESC_F_NEXT));
}

// 間接ディスクリプタを追加する。
void
VirtIODevice::AddIndirectDesc(VirtIOReq& req, uint32 table, uint32 tablelen)
{
	uint32 count = tablelen / 16;
	uint32 flag;
	uint32 idx;

	idx = 0;
	do {
		if (__predict_false(idx >= count)) {
			// 指定されたテーブル領域外を指していたらどうする?
			putlog(0, "Invalid indirect table index=%u (count=%u)",
				idx, count);
			return;
		}
		uint32 descaddr = table + idx * 16;
		uint32 addr;
		uint32 slen;
		addr = ReadLE32(descaddr + 0);
		slen = ReadLE32(descaddr + 8);
		flag = ReadLE16(descaddr + 12);
		idx  = ReadLE16(descaddr + 14);

		if ((flag & VIRTQ_DESC_F_WRITE)) {
			putlog(3, "req.wbuf[%u]: addr=$%08x len=$%04x (Indirect)",
				(uint)req.wbuf.size(), addr, slen);
			req.wbuf.emplace_back(addr, slen);
			req.wlen += slen;
		} else {
			putlog(3, "req.rbuf[%u]: addr=$%08x len=$%04x (Indirect)",
				(uint)req.rbuf.size(), addr, slen);
			req.rbuf.emplace_back(addr, slen);
			req.rlen += slen;
		}
	} while ((flag & VIRTQ_DESC_F_NEXT));
}

// ディスクリプタ処理完了。
void
VirtIODevice::CommitDesc(VirtIOReq& req)
{
	VirtQueue *q = req.q;

	// Used Ring に記録。
	uint16 used_idx = ReadLE16(q->device + 2);
	WriteLE32(q->device + 4 + q->Mod(used_idx) * 8 + 0, req.idx);
	WriteLE32(q->device + 4 + q->Mod(used_idx) * 8 + 4, req.pos());
	used_idx++;
	WriteLE16(q->device + 2, used_idx);

	// 最後に自身の last_avail_idx を更新。
	q->last_avail_idx = (uint16)(q->last_avail_idx + 1);
}

// この req から1バイト読み込む。
// 読み込めなければ -1 を返す。
uint32
VirtIODevice::ReqReadU8(VirtIOReq& req)
{
	if (req.reof()) {
		return -1;
	}
	assert(req.roff < req.rbuf[req.rseg].len);

	uint32 data = ReadU8(req.rbuf[req.rseg].addr + req.roff);
	req.roff++;
	if (req.roff >= req.rbuf[req.rseg].len) {
		req.rseg++;
		req.roff = 0;
	}
	return data;
}

// この req から LE16 を読み込む。
// 読み込めなければ -1 を返す。
uint32
VirtIODevice::ReqReadLE16(VirtIOReq& req)
{
	uint32 hi, lo;

	lo = ReqReadU8(req);
	if ((int32)lo < 0) {
		return lo;
	}
	hi = ReqReadU8(req);
	if ((int32)hi < 0) {
		return hi;
	}

	return lo | (hi << 8);
}

// この req から LE32 を読み込む。
// 読み込めなければ -1 を返す。
uint64
VirtIODevice::ReqReadLE32(VirtIOReq& req)
{
	uint32 hi, lo;

	lo = ReqReadLE16(req);
	if ((int32)lo < 0) {
		return lo;
	}
	hi = ReqReadLE16(req);
	if ((int32)hi < 0) {
		return hi;
	}

	return lo | (hi << 16);
}

// この req から LE32 を読み込んで *retp に格納する。
// 読み込めなければ false を返す。
bool
VirtIODevice::ReqReadLE32(VirtIOReq& req, uint32 *retp)
{
	uint64 val = ReqReadLE32(req);
	if ((int64)val < 0) {
		return false;
	}

	*retp = (uint32)val;
	return true;
}

// この req から LE64 を読み込んで *retp に格納する。
// 読み込めなければ false を返す。
bool
VirtIODevice::ReqReadLE64(VirtIOReq& req, uint64 *retp)
{
	uint64 hi, lo;

	lo = ReqReadLE32(req);
	if ((int64)lo < 0) {
		return false;
	}
	hi = ReqReadLE32(req);
	if ((int64)hi < 0) {
		return false;
	}

	*retp = lo | (hi << 32);
	return true;
}

// この req に1バイト書き出す。
bool
VirtIODevice::ReqWriteU8(VirtIOReq& req, uint32 data)
{
	if (req.weof()) {
		return false;
	}
	assert(req.woff < req.wbuf[req.wseg].len);

	WriteU8(req.wbuf[req.wseg].addr + req.woff, data);
	req.woff++;
	if (req.woff >= req.wbuf[req.wseg].len) {
		req.wseg++;
		req.woff = 0;
	}
	return true;
}

// この req に LE16 を書き出す。
bool
VirtIODevice::ReqWriteLE16(VirtIOReq& req, uint32 data)
{
	if (ReqWriteU8(req, data & 0xff) == false) {
		return false;
	}
	if (ReqWriteU8(req, (data >> 8)) == false) {
		return false;
	}
	return true;
}

// この req に LE32 を書き出す。
bool
VirtIODevice::ReqWriteLE32(VirtIOReq& req, uint32 data)
{
	if (ReqWriteLE16(req, data & 0xffff) == false) {
		return false;
	}
	if (ReqWriteLE16(req, (data >> 16)) == false) {
		return false;
	}
	return true;
}

// この req から dst に dstlen バイト読み込む。
// dstlen のうち読み込めなかったバイト数を返す (すべて読み込めたら 0 を返す)。
uint32
VirtIODevice::ReqReadRegion(VirtIOReq& req, uint8 *dst, uint32 dstlen)
{
	uint8 *d = dst;

	while (dstlen != 0) {
		if (req.reof()) {
			break;
		}

		// 今回読み込むバイト数と読み出し元 VM アドレス。
		uint32 len = std::min(req.rbuf[req.rseg].len - req.roff, dstlen);
		uint32 addr = req.rbuf[req.rseg].addr + req.roff;
		// 指定範囲は通常全域 RAM のはずなので mainram を直接呼ぶ。
		// 指定範囲が RAM からはみ出たりしていたら (通常まずありえない)
		// mainbus 経由でアクセスする。
		bool ok = mainram->ReadMem(addr, d, len);
		if (__predict_true(ok)) {
			d += len;
		} else {
			for (uint32 end = addr + len; addr < end; ) {
				*d++ = ReadU8(addr++);
			}
		}
		// この加算でセグメント長を超えることはない。
		req.roff += len;
		if (req.roff == req.rbuf[req.rseg].len) {
			req.rseg++;
			req.roff = 0;
		}
		dstlen -= len;
	}

	return dstlen;
}

// src から req に srclen バイト書き出す。
// srclen のうち書き出せなかったバイト数を返す (すべて書き出せたら 0 を返す)。
uint32
VirtIODevice::ReqWriteRegion(VirtIOReq& req, const uint8 *src, uint32 srclen)
{
	const uint8 *s = src;

	while (srclen != 0) {
		if (req.weof()) {
			break;
		}

		// 今回書き出すバイト数と書き込み先 VM アドレス。
		uint32 len = std::min(req.wbuf[req.wseg].len - req.woff, srclen);
		uint32 addr = req.wbuf[req.wseg].addr + req.woff;
		// 指定範囲は通常全域 RAM のはずなので mainram を直接呼ぶ。
		// 指定範囲が RAM からはみ出たりしていたら (通常まずありえない)
		// mainbus 経由でアクセスする。
		bool ok = mainram->WriteMem(addr, s, len);
		if (__predict_true(ok)) {
			s += len;
		} else {
			for (uint32 end = addr + len; addr < end; ) {
				WriteU8(addr++, *s);
			}
		}
		// この加算でセグメント長を超えることはない。
		req.woff += len;
		if (req.woff == req.wbuf[req.wseg].len) {
			req.wseg++;
			req.woff = 0;
		}
		srclen -= len;
	}

	return srclen;
}

// 裏スレッドからの処理完了メッセージ通知。(VM スレッドで実行される)
// arg はキュー番号。
void
VirtIODevice::DoneMessage(MessageID msgid_, uint32 arg)
{
	int idx = arg;
	VirtQueue *q = &vqueues[idx];

	Done(q);
}

// ディスクリプタ処理が完了したので、必要なら割り込みを上げる。
// (VM スレッドで呼ぶこと)
void
VirtIODevice::Done(VirtQueue *q)
{
	// (必要なら) 割り込みを上げる。
	uint16 flag = ReadLE16(q->driver + 0);
	if ((flag & VIRTQ_AVAIL_F_NO_INTERRUPT) == 0) {
		intr_status |= 0x01;
		ChangeInterrupt();
	}
}

// 割り込み信号線の状態を変える。
void
VirtIODevice::ChangeInterrupt()
{
	assert(interrupt);
	interrupt->ChangeINT(this, (bool)intr_status);
}

// メインバスから U8 を読み込む。
uint32
VirtIODevice::ReadU8(uint32 addr)
{
	busdata data = mainbus->HVRead1(addr);
	return (uint8)data.Data();
}

// メインバスから LE16 を読み込む。
uint32
VirtIODevice::ReadLE16(uint32 addr)
{
	busdata data = mainbus->HVRead2(addr);
	return be16toh(data.Data());
}

// メインバスから LE32 を読み込む。
uint32
VirtIODevice::ReadLE32(uint32 addr)
{
	busdata data = mainbus->HVRead4(addr);
	return be32toh(data.Data());
}

// メインバスから LE64 を読み込む。
uint64
VirtIODevice::ReadLE64(uint32 addr)
{
	uint64 data;
	data  = (uint64)ReadLE32(addr + 0);
	data |= (uint64)ReadLE32(addr + 4) << 32;

	return data;
}

// メインバスに U8 を書き出す。
void
VirtIODevice::WriteU8(uint32 addr, uint32 data)
{
	mainbus->HVWrite1(addr, data);
}

// メインバスに LE16 を書き出す。
void
VirtIODevice::WriteLE16(uint32 addr, uint32 data)
{
	mainbus->HVWrite2(addr, htobe16(data));
}

// メインバスに LE32 を書き出す。
void
VirtIODevice::WriteLE32(uint32 addr, uint32 data)
{
	mainbus->HVWrite4(addr, htobe32(data));
}

// メインバスに LE64 を書き出す。
void
VirtIODevice::WriteLE64(uint32 addr, uint64 data)
{
	WriteLE32(addr + 0, data & 0xffffffff);
	WriteLE32(addr + 4, data >> 32);
}

// メインバスから副作用なく LE16 を読み出す。
uint32
VirtIODevice::PeekLE16(uint32 addr) const
{
	uint32 data;
	data  = mainbus->Peek1(addr + 0);
	data |= mainbus->Peek1(addr + 1) << 8;
	return data;
}

// メインバスから副作用なく LE32 を読み出す。
uint32
VirtIODevice::PeekLE32(uint32 addr) const
{
	uint32 data;
	data  = mainbus->Peek1(addr + 0);
	data |= mainbus->Peek1(addr + 1) << 8;
	data |= mainbus->Peek1(addr + 2) << 16;
	data |= mainbus->Peek1(addr + 3) << 24;
	return data;
}

// DEVICE_FEATURES にビットを立てる。初期化用。
void
VirtIODevice::SetDeviceFeatures(int feature)
{
	uint sel = feature / 32;
	uint bit = feature % 32;
	assert(sel < MAX_FEATURES_SEL);
	device_features[sel] |= (1U << bit);
}

// DRIVER_FEATURES のビットが立っていれば true を返す。
bool
VirtIODevice::GetDriverFeatures(int feature) const
{
	uint sel = feature / 32;
	uint bit = feature % 32;
	assert(sel < MAX_FEATURES_SEL);
	return (driver_features[sel] & (1U << bit));
}

// FEATURES 名を返す。(共通部分)
const char *
VirtIODevice::GetFeatureName(uint feature) const
{
	static std::pair<uint, const char *> names[] = {
		{ VIRTIO_F_RING_INDIRECT_DESC,	"INDIRECT" },
		{ VIRTIO_F_RING_EVENT_IDX,		"RING_EVENT" },
		{ VIRTIO_F_VERSION_1,			"VER1" },
		{ VIRTIO_F_ACCESS_PLATFORM,		"ACCESS_PLATFORM" },
		{ VIRTIO_F_RING_PACKED,			"RING_PACK" },
		{ VIRTIO_F_IN_ORDER,			"IN_ORDER" },
		{ VIRTIO_F_ORDER_PLATFORM,		"ORDER_PLATFORM" },
		{ VIRTIO_F_SF_IOV,				"SF_IOV" },
		{ VIRTIO_F_NOTIFICATION_DATA,	"NOTIFICATION_DATA" },
	};

	for (auto& p : names) {
		if (feature == p.first) {
			return p.second;
		}
	}
	return NULL;
}


//
// VirtIONone (空きスロット用)
//

// コンストラクタ
VirtIONoneDevice::VirtIONoneDevice()
	: inherited(OBJ_NONE)
{
	SetName("VirtIONone");
}

// デストラクタ
VirtIONoneDevice::~VirtIONoneDevice()
{
}

busdata
VirtIONoneDevice::ReadPort(uint32 offset)
{
	busdata data = PeekPort(offset);

	if (__predict_false(loglevel >= 1)) {
		uint32 addr = offset << 2;
		switch (addr) {
		 case VirtIO::MAGIC_VALUE:
			putlogn("MAGIC_VALUE -> $%08x", data.Data());
			break;

		 case VirtIO::DEVICE_ID:
			putlogn("DEVICE_ID -> $%08x", data.Data());
			break;

		 default:
			putlogn("REG%03x -> $%08x", addr, data.Data());
			break;
		}
	}

	data |= BusData::Size4;
	return data;
}

busdata
VirtIONoneDevice::WritePort(uint32 offset, uint32 data)
{
	return BusData::BusErr;
}

busdata
VirtIONoneDevice::PeekPort(uint32 offset)
{
	uint32 addr = offset << 2;

	switch (addr) {
	 case VirtIO::MAGIC_VALUE:
		return VirtIO::MAGIC_STRING;

	 case VirtIO::DEVICE_ID:
		return VirtIO::DEVICE_ID_INVALID;

	 default:
		return 0xffffffff;
	}
}


//
// VirtIOThread
//

// コンストラクタ
VirtIOThread::VirtIOThread(VirtIODevice *parent_)
	: inherited(OBJ_NONE)
{
	parent = parent_;
	SetName(parent->GetName());

	// コンストラクト後ただちにログ出力できるようここで一度追従しておく。
	// 以降は親クラス(表スレッド)の SetLogLevel() で変更する。
	loglevel = parent->loglevel;
}

// デストラクタ
VirtIOThread::~VirtIOThread()
{
	TerminateThread();
}

// 本当は ResetHard() で処理中のものをとめたほうがいいんだろうけど。

// スレッドの終了を指示
void
VirtIOThread::Terminate()
{
	std::lock_guard<std::mutex> lock(mtx);
	request |= REQ_TERMINATE;
	cv.notify_one();
}

// ゲストからの QUEUE_NOTIFY 要求。(VM スレッドで呼ばれる)
void
VirtIOThread::RequestQueueNotify(uint idx)
{
	std::lock_guard<std::mutex> lock(mtx);
	request |= REQ_QUEUE(idx);
	cv.notify_one();
}

// スレッド
void
VirtIOThread::ThreadRun()
{
	// どっちだろうか
	SetThreadAffinityHint(AffinityClass::Light);

	for (;;) {
		uint32 req;

		// 何か起きるまで待つ。
		{
			std::unique_lock<std::mutex> lock(mtx);
			cv.wait(lock, [&] { return (request != 0); });
			req = request;
			request = 0;
		}

		if ((req & REQ_TERMINATE)) {
			// 終了するので、他のフラグは無視。
			break;
		}

		// idx 番目のキューに QUEUE_NOTIFY が指示された。
		for (; req != 0; ) {
			int idx = __builtin_ctz(req);
			// 裏スレッドで Device クラスの関数を実行する。ややこしいので注意。
			parent->QueueNotify(idx);
			req &= ~(1U << idx);
		}
	}
}


//
// VirtQueue
//

// コンストラクタ
VirtQueue::VirtQueue(VirtIODevice *parent_, int idx_, const std::string& name_,
	int num_max_)
{
	parent = parent_;
	idx = idx_;
	name = name_;
	num_max = num_max_;
}

// QUEUE_READY の代入。
void
VirtQueue::SetReady(uint32 val)
{
	if (val != ready) {
		ready = val;
		parent->QueueReadyChanged(this);
	}
}


//
// VirtIOReq
//

// 読み込みセグメントの先頭からの現在位置を返す。
uint32
VirtIOReq::rpos() const
{
	uint32 totalpos = 0;

	for (int i = 0; i < rseg; i++) {
		totalpos += rbuf[i].len;
	}
	if (rseg < rbuf.size()) {
		totalpos += roff;
	}
	return totalpos;
}

// 書き込みセグメントの先頭からの現在位置を返す。
uint32
VirtIOReq::wpos() const
{
	uint32 totalpos = 0;

	for (int i = 0; i < wseg; i++) {
		totalpos += wbuf[i].len;
	}
	if (wseg < wbuf.size()) {
		totalpos += woff;
	}
	return totalpos;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.