File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / virtio_net.cpp
Revision 1.1.1.8 (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_net.h"
#include "virtio_def.h"
#include "config.h"
#include "ethernet.h"
#include "event.h"
#include "macaddr.h"
#include "memorystream.h"
#include "monitor.h"
#include "scheduler.h"

// デバイス構成レイアウト
class VirtIONetConfigWriter
{
 public:
	std::array<u8, 6> mac {};
	le16 status {};
	le16 max_virtqueue_pairs {};
	le16 mtu {};

 public:
	void WriteTo(uint8 *dst) const;
};

// コンストラクタ
VirtIONetDevice::VirtIONetDevice(uint slot_)
	: inherited(OBJ_VIRTIO_NET, slot_)
{
	// 割り込み名(とログ名)
	strlcpy(intrname, "VIONet", sizeof(intrname));
	SetName(intrname);
	ClearAlias();
	AddAlias(intrname);

	device_id = VirtIO::DEVICE_ID_NETWORK;
	vqueues.emplace_back(this, 0, "ReceiveQ1", 8);
	vqueues.emplace_back(this, 1, "TransmitQ1", 16);

	// 完了通知メッセージ
	msgid = MessageID::VIRTIO_NET_DONE;

	// モニタの行数。
	int vqlines = 0;
	for (const auto& q : vqueues) {
		vqlines += 7 + q.num_max;
	}

	monitor = gMonitorManager->Regist(ID_MONITOR_VIRTIO_NET, this);
	monitor->SetCallback(&VirtIONetDevice::MonitorScreen);
	monitor->SetSize(MONITOR_WIDTH, 3 + vqlines);
}

// デストラクタ
VirtIONetDevice::~VirtIONetDevice()
{
	// EthernetDevice と同じ…。
	if ((bool)hostnet) {
		hostnet->ResetRxCallback();
	}
}

// 動的なコンストラクション
bool
VirtIONetDevice::Create()
{
	if (inherited::Create() == false) {
		return false;
	}

	// EthernetDevice とほぼ同じ…。
	try {
		hostnet.reset(new HostNetDevice(this, 0, "VirtIO Network"));
	} catch (...) { }
	if ((bool)hostnet == false) {
		warnx("Failed to initialize HostNetDevice at %s", __method__);
		return false;
	}

	auto func = ToDeviceCallback(&VirtIONetDevice::HostRxCallback);
	hostnet->SetRxCallback(func, 0);

	return true;
}

// 初期化
bool
VirtIONetDevice::Init()
{
	if (inherited::Init() == false) {
		return false;
	}

	// MAC アドレスを取得。
	if (EthernetDevice::GetConfigMacAddr(0, &macaddr, false) == false) {
		// エラーメッセージは表示済み。
		return false;
	}
	// 表示用。
	macaddr_str = macaddr.ToString(':');

	// DEVICE_FEATURES と構成レイアウトを用意。
	VirtIONetConfigWriter cfg;
	SetDeviceFeatures(VIRTIO_NET_F_CSUM);
	SetDeviceFeatures(VIRTIO_NET_F_GUEST_CSUM);
	SetDeviceFeatures(VIRTIO_NET_F_MTU), cfg.mtu = 1500;
	SetDeviceFeatures(VIRTIO_NET_F_MAC), macaddr.ExportTo(&cfg.mac[0]);
	SetDeviceFeatures(VIRTIO_NET_F_MRG_RXBUF);
	cfg.WriteTo(&device_config[0]);

	// ホストからの受信コールバックを登録
	scheduler->ConnectMessage(MessageID::HOSTNET_RX(0), this,
		ToMessageCallback(&VirtIONetDevice::RxMessage));

	auto evman = GetEventManager();
	event = evman->Regist(this,
		ToEventCallback(&VirtIONetDevice::RxEvent),
		"VirtIONet RX");

	return true;
}

void
VirtIONetDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	int y = 0;

	screen.Clear();

	y = MonitorScreenDev(screen, y);

	screen.Puts(0, y, "MACAddress:");
	screen.Puts(12, y, macaddr_str.c_str());
	y++;

	y++;
	for (const auto& q : vqueues) {
		y = MonitorScreenVirtQueue(screen, y, q);
		y++;
		y = MonitorScreenVirtQDesc(screen, y, q);
		y++;
	}
}

// QUEUE_READY が変化したら呼ばれる。
void
VirtIONetDevice::QueueReadyChanged(VirtQueue *q)
{
	if (q->idx == 0) {
		// 受信開始/停止
		if (q->GetReady() != 0) {
			hostnet->EnableRx(true);
		} else {
			scheduler->StopEvent(event);
			hostnet->EnableRx(false);
		}
	}
}

// ディスクリプタを一つ処理する。
void
VirtIONetDevice::ProcessDesc(VirtIOReq& req)
{
	VirtQueue *q = req.q;
	uint8 flags;
	uint8 gso_type;
	uint16 hdr_len;
	uint16 gso_size;
	uint16 csum_start;
	uint16 csum_offset;
	uint16 num_buffers;

	if (q->idx == 0) {
		putlog(0, "QUEUE_NOTIFY on rx queue (VQ%u)?", q->idx);
		return;
	}

	flags       = ReqReadU8(req);
	gso_type    = ReqReadU8(req);
	hdr_len     = ReqReadLE16(req);
	gso_size    = ReqReadLE16(req);
	csum_start  = ReqReadLE16(req);
	csum_offset = ReqReadLE16(req);
	num_buffers = ReqReadLE16(req);
	putlog(2, "req.hdr $%08x: flags=%02x gso=%02x hdr_len=%04x gso_size=%04x "
		"csum_start=%04x offset=%04x num=%04x",
		req.rbuf[0].addr,
		flags,
		gso_type,
		hdr_len,
		gso_size,
		csum_start,
		csum_offset,
		num_buffers);

	NetPacket tx_packet;
	uint32 len = req.rremain();
	tx_packet.Resize(len);
	uint32 rest = ReqReadRegion(req, tx_packet.Data(), len);
	tx_packet.Resize(len - rest);

	hostnet->Tx(tx_packet);
}

// これは Host スレッドから呼ばれる
void
VirtIONetDevice::HostRxCallback(uint32 dummy)
{
	// スレッドを超えるためにメッセージを投げる
	scheduler->SendMessage(MessageID::HOSTNET_RX(0));
}

// パケット受信通知
void
VirtIONetDevice::RxMessage(MessageID msgid_, uint32 arg)
{
	VirtQueue *q = &vqueues[0];

	if (__predict_false(q->GetReady() == 0)) {
		return;
	}

	// 受信イベントが止まっていれば動かす。
	// 受信イベントがすでに動いていれば、1パケット受信完了後に
	// ホストキューを確認するので、ここでは何もしなくてよい。
	if (event->IsRunning() == false) {
		event->time = 0;
		scheduler->StartEvent(event);
	}
}

// 受信ループイベント。
void
VirtIONetDevice::RxEvent(Event *ev)
{
	VirtQueue *q = &vqueues[0];

	if (__predict_false(q->GetReady() == 0)) {
		return;
	}

	// 空きディスクリプタがなければ、空くまでポーリングで待ち続ける。
	uint16 avail_idx = ReadLE16(q->driver + 2);
	if (q->last_avail_idx == avail_idx) {
		event->time = 500_usec;
		putlog(2, "%s: wait %u usec", __func__,
			(uint)tsec_to_usec(event->time));
		scheduler->RestartEvent(event);
		return;
	}

	// ディスクリプタに空きが出来たので、ホストキューから1パケット取り出す。
	assert(rx_packet.Length() == 0);
	if (hostnet->Rx(&rx_packet) == false) {
		// 取り出せなくなったら、ここでイベントループを終了。
		rx_packet.Clear();
		putlog(2, "Rx no more rx_packet");
		return;
	}

	putlog(2, "%s: %u bytes from host queue", __func__, rx_packet.Length());

	// rx_packet が埋まって、空きディスクリプタがあるので受信処理へ。
	Rx(q);

	rx_packet.Clear();
	event->time = 500_usec;
	scheduler->RestartEvent(event);
}

// 受信。(rx_packet が埋まっていてかつ空きディスクリプタがある状態で呼ぶこと)
void
VirtIONetDevice::Rx(VirtQueue *q)
{
	assert(rx_packet.Length() != 0);
	assert(q->last_avail_idx != ReadLE16(q->driver + 2));

	// XXX どこでやるのがいいか
	// VirtIO は FCS を含まない 1514 バイトが上限と決まっているが、
	// HostNet の下の受信ドライバが FCS を含めているかどうかが分からない。
	// 仕方ないので 1514 バイトを超える時だけ取り除く…。
	if (rx_packet.Length() > 1514) {
		rx_packet.Resize(1514);
	}

	VirtIOReq req;
	req.q = q;
	StartDesc(req);

	// ヘッダを用意。
	virtio_net_hdr hdr;
	memset(&hdr, 0, sizeof(hdr));
	hdr.hdr_len = htole16(sizeof(hdr));
	// num_buffers は実際に使ったセグメント数らしい。
	uint nseg = 0;
	uint32 len = 0;
	for (; nseg < req.wbuf.size(); nseg++) {
		len += req.wbuf[nseg].len;
		if (len >= sizeof(hdr) + rx_packet.Length()) {
			break;
		}
	}
	hdr.num_buffers = htole16(nseg);

	// ヘッダを書き込む。
	ReqWriteRegion(req, (const uint8 *)&hdr, sizeof(hdr));

	// パケット本体を書き込む。
	ReqWriteRegion(req, rx_packet.Data(), rx_packet.Length());

	CommitDesc(req);

	// ここは VM スレッドなのでそのまま割り込みを上げる。
	Done(q);
}

// この宛先アドレスを受信するかどうか。
// これは HostNet スレッドで呼ばれる。
int
VirtIONetDevice::HWAddrFilter(const MacAddr& dstaddr) const
{
	if (dstaddr.IsUnicast()) {
		if (dstaddr != macaddr) {
			return HPF_DROP_UNICAST;
		}
	}
	// マルチキャストをホストデバイス側でフィルタする機構は
	// CTRLQ 内にあるが、NetBSD のドライバは CTRLQ を実装していない。

	return HPF_PASS;
}

const char *
VirtIONetDevice::GetFeatureName(uint feature) const
{
	static std::pair<uint, const char *> names[] = {
		{ VIRTIO_NET_F_CSUM,			"CSUM" },
		{ VIRTIO_NET_F_GUEST_CSUM,		"G_CSUM" },
		{ VIRTIO_NET_F_CTRL_GUEST_OFFLOADS,	"C_G_OFFL" },
		{ VIRTIO_NET_F_MTU,				"MTU" },
		{ VIRTIO_NET_F_MAC,				"MAC" },
		{ VIRTIO_NET_F_GUEST_TSO4,		"G_TSO4" },
		{ VIRTIO_NET_F_GUEST_TSO6,		"G_TSO6" },
		{ VIRTIO_NET_F_GUEST_ECN,		"G_ECN" },
		{ VIRTIO_NET_F_GUEST_UFO,		"G_UFO" },
		{ VIRTIO_NET_F_HOST_TSO4,		"H_TSO4" },
		{ VIRTIO_NET_F_HOST_TSO6,		"H_TSO6" },
		{ VIRTIO_NET_F_HOST_ECN,		"H_ECN" },
		{ VIRTIO_NET_F_HOST_UFO,		"H_UFO" },
		{ VIRTIO_NET_F_MRG_RXBUF,		"MRG_RX" },
		{ VIRTIO_NET_F_STATUS,			"STATUS" },
		{ VIRTIO_NET_F_CTRL_VQ,			"CTL_VQ" },
		{ VIRTIO_NET_F_CTRL_RX,			"CTL_RX" },
		{ VIRTIO_NET_F_CTRL_VLAN,		"CTL_VLAN" },
		{ VIRTIO_NET_F_GUEST_ANNOUNCE,	"G_ANN" },
		{ VIRTIO_NET_F_MQ,				"MQ" },
		{ VIRTIO_NET_F_CTRL_MAC_ADDR,	"CTL_MAC" },
		{ VIRTIO_NET_F_RSC_EXT,			"RSC_EXT" },
		{ VIRTIO_NET_F_STANDBY,			"STANDBY" },
	};

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

// デバイス構成レイアウトを書き出す。
void
VirtIONetConfigWriter::WriteTo(uint8 *dst) const
{
	MemoryStreamLE mem(dst);

	for (auto c : mac) {
		mem.Write1(c);
	}
	mem.Write2(status);
	mem.Write2(max_virtqueue_pairs);
	mem.Write2(mtu);
}

unix.superglobalmegacorp.com

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