File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / virtio_base.h
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 2026 UTC (3 months 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
//

#pragma once

#include "device.h"
#include "message.h"
#include "missing_bswap.h"
#include "thread.h"
#include <array>
#include <condition_variable>

class InterruptDevice;
class MainbusDevice;
class MainRAMDevice;
class TextScreen;
class VirtIODevice;
class VirtIOThread;
class VirtQDesc;

class VirtQueue
{
 public:
	VirtQueue(VirtIODevice *parent_, int idx_, const std::string& name_,
		int num_max_);

	// idx_ % num を返す。
	// num が 0 なら何でもいいので死なないように 0 を返す。
	uint Mod(uint idx_) const {
		if (__predict_false(num == 0)) {
			return 0;
		}
		return idx_ % num;
	}

	uint32 last_avail_idx {};

	uint idx {};			// 自身が何番目か
	std::string name {};	// キュー名(仕様から決まる)

	uint32 desc {};			// QUEUE_DESC (下位32ビットのみ)
	uint32 driver {};		// QUEUE_DRIVER (下位32ビットのみ)
	uint32 device {};		// QUEUE_DEVICE (下位32ビットのみ)

	uint num {};			// QUEUE_NUM (合意できたディスクリプタ数)
	uint num_max {};		// QUEUE_NUM の上限

	// QUEUE_READY の設定と取得。
	// Get は副作用を起こしてはいけない。
	void SetReady(uint32 val);
	uint32 GetReady() const	noexcept { return ready; }

 private:
	VirtIODevice *parent {};

	uint32 ready {};		// QUEUE_READY
};

class VirtIOSeg
{
 public:
	uint32 addr {};			// セグメントアドレス (ゲストアドレス)
	uint32 len {};			// このセグメント長

	VirtIOSeg(uint32 addr_, uint32 len_)
		: addr(addr_), len(len_)
	{
	}
};

// ディスクリプタチェイン。
// ファイルハンドルのようなものも兼ねている。
// 元の仕様からシーケンシャルアクセスを想定しているようなので、ここでも
// シーケンシャルアクセスを前提とする。そのため pos() が UsedRing に書き込む
// バイト数になる。
class VirtIOReq
{
 public:
	VirtQueue *q {};
	uint32 idx {};			// このディスクリプタチェインの先頭インデックス

	int    rseg {};			// 現在の読み込みセグメントインデックス
	uint32 roff {};			// 読み込み位置(セグメント内の相対位置)
	std::vector<VirtIOSeg> rbuf {};	// 読み込み用セグメント
	uint32 rlen {};			// 読み込みセグメントの総バイト数

	int    wseg {};			// 現在の書き込みセグメントインデックス
	uint32 woff {};			// 書き込み位置(セグメント内の相対位置)
	std::vector<VirtIOSeg> wbuf {};	// 書き込み用セグメント
	uint32 wlen {};			// 書き込みセグメントの総バイト数

	// このディスクリプタチェインの総バイト数。
	uint32 totallen() const noexcept { return rlen + wlen; }

	// 読み込みセグメントの残りバイト数。
	uint32 rremain() const { return rlen - rpos(); }

	// 読み込みセグメントの残りをすべて読み捨てる。
	void rskip() {
		rseg = rbuf.size();
		roff = 0;
	}

	// 読み込みセグメントの先頭からの現在位置を返す。
	uint32 rpos() const;
	// 書き込みセグメントの先頭からの現在位置を返す。
	uint32 wpos() const;
	// 処理した総バイト数を返す。
	uint32 pos() const { return rpos() + wpos(); }

	// 読み込みまたは書き込みが末尾に到達していれば true を返す。
	bool reof() const { return rseg >= rbuf.size(); }
	bool weof() const { return wseg >= wbuf.size(); }
};

// VirtIO 基本クラス
class VirtIODevice : public IODevice
{
	using inherited = IODevice;
	friend class VirtQueue;

 protected:
	// 仕様書には名前が出てこないけど上限
	static const uint MAX_FEATURES_SEL = 2;

	// モニタ幅
	static const int MONITOR_WIDTH = 75;

	// 動作には不要だがモニタに表示したいので…。
	static const uint32 baseaddr = 0xff070000;

 public:
	VirtIODevice(uint objid_, uint slot_);
	~VirtIODevice() override;

	bool Create() override;
	void SetLogLevel(int loglevel_) override;
	bool Init() override;
	void ResetHard(bool poweron) override;

	// QUEUE_NOTIFY の処理 (裏スレッドから呼ばれる)
	void QueueNotify(uint idx);

 protected:
	// BusIO インタフェース
	static const uint32 NPORT = 0x200 >> 2;
	busdata ReadPort(uint32 offset);
	busdata WritePort(uint32 offset, uint32 data);
	busdata PeekPort(uint32 offset);

	int MonitorScreenDev(TextScreen&, int y) const;
	int MonitorScreenVirtQueue(TextScreen&, int y, const VirtQueue&) const;
	int MonitorScreenVirtQDesc(TextScreen&, int y, const VirtQueue&) const;
	void Reset();
	virtual uint32 GetReg(uint32 addr) const;
	virtual void QueueReadyChanged(VirtQueue *);

	// ディスクリプタを一つ処理する。
	virtual void ProcessDesc(VirtIOReq&) = 0;

	// ディスクリプタを読んでチェインを用意する。
	void StartDesc(VirtIOReq&);
	// ディスクリプタを更新する。
	void CommitDesc(VirtIOReq&);

	// ディスクリプタアクセス
	uint32 ReqReadU8(VirtIOReq&);
	uint32 ReqReadLE16(VirtIOReq&);
	uint64 ReqReadLE32(VirtIOReq&);
	uint32 ReqReadBE16(VirtIOReq& req) { return bswap16(ReqReadLE16(req)); }
	uint32 ReqReadBE32(VirtIOReq& req) { return bswap32(ReqReadLE32(req)); }
	bool ReqReadLE32(VirtIOReq&, uint32 *);
	bool ReqReadLE64(VirtIOReq&, uint64 *);
	bool ReqWriteU8(VirtIOReq&, uint32 data);
	bool ReqWriteLE16(VirtIOReq&, uint32 data);
	bool ReqWriteLE32(VirtIOReq&, uint32 data);
	uint32 ReqReadRegion(VirtIOReq&, uint8 *dst, uint32 dstlen);
	uint32 ReqWriteRegion(VirtIOReq&, const uint8 *src, uint32 srclen);

	// 裏スレッドからの処理完了メッセージ
	void DoneMessage(MessageID, uint32);
	// 処理完了
	void Done(VirtQueue *);

	void ChangeInterrupt();

	// メインバスアクセス
	uint32 ReadU8(uint32 addr);
	uint32 ReadLE16(uint32 addr);
	uint32 ReadLE32(uint32 addr);
	uint64 ReadLE64(uint32 addr);
	void WriteU8(uint32 addr, uint32 data);
	void WriteLE16(uint32 addr, uint32 data);
	void WriteLE32(uint32 addr, uint32 data);
	void WriteLE64(uint32 addr, uint64 data);
	uint32 PeekLE16(uint32 addr) const;
	uint32 PeekLE32(uint32 addr) const;

	// DEVICE_FEATURES の feature 番目のビットを立てる。初期化用。
	void SetDeviceFeatures(int feature);
	// DEVICE_FEATURES の feature 番目の名前を返す。
	virtual const char *GetFeatureName(uint feature) const;

	// DRIVER_FEATURES の feature 番目のビットが立っていれば true。
	bool GetDriverFeatures(int feature) const;

 private:
	// 間接ディスクリプタを追加する。
	void AddIndirectDesc(VirtIOReq&, uint32 addr, uint32 len);

 protected:
	uint slot {};				// VirtIO のスロット番号 (0-127)

	uint32 device_id {};		// DEVICE_ID
	uint32 vendor_id {};		// VENDOR_ID
	uint32 device_status {};	// STATUS

	uint32 device_sel {};		// DEVICE_FEATURES_SEL
	uint32 driver_sel {};		// DRIVER_FEATURES_SEL
	std::array<uint32, 2> device_features {};	// DEVICE_FEATURES
	std::array<uint32, 2> driver_features {};	// DRIVER_FEATURES

	// VirtQueue。継承先で個数が決まっている。
	std::vector<VirtQueue> vqueues {};
	// QUEUE_SEL の値。
	uint32 queue_sel {};
	// QUEUE_SEL で選択されている現在の VirtQueue。選択されてなければ NULL。
	VirtQueue *vq {};

	uint32 intr_status {};			// INTERRUPT_STATUS

	// 0x100 以降の領域
	std::array<uint8, 0x100> device_config {};

	char intrname[10] {};			// 割り込み名

	MessageID msgid {};				// 完了通知のメッセージ ID

	std::unique_ptr<VirtIOThread> backend /*{}*/;

	InterruptDevice *interrupt {};
	MainbusDevice *mainbus {};
	MainRAMDevice *mainram {};

	Monitor *monitor {};
};

// VirtIO 空きスロット用
class VirtIONoneDevice : public IODevice
{
	using inherited = IODevice;
 public:
	VirtIONoneDevice();
	~VirtIONoneDevice() override;

 protected:
	// BusIO インタフェース
	static const uint32 NPORT = 0x200 >> 2;
	busdata ReadPort(uint32 offset);
	busdata WritePort(uint32 offset, uint32 data);
	busdata PeekPort(uint32 offset);
};

// 裏スレッド
class VirtIOThread : public ThreadDevice
{
	using inherited = ThreadDevice;

 protected:
	// REQ_QUEUE はキュー1本に1ビットずつ割り当てているので現状最大31まで。
	static const uint32 REQ_TERMINATE	= 0x80000000;	// スレッド終了指示
	static constexpr uint32 REQ_QUEUE(uint idx) {		// QUEUE_NOTIFY 指示
		return (1U << idx);
	}

 public:
	explicit VirtIOThread(VirtIODevice *parent_);
	~VirtIOThread() override;

	void Terminate() override;

	// ゲストからの QUEUE_NOTIFY 要求
	void RequestQueueNotify(uint);

 private:
	void ThreadRun() override;

 protected:
	// リクエストフラグ
	uint32 request {};
	std::mutex mtx {};
	std::condition_variable cv {};

	VirtIODevice *parent {};
};

unix.superglobalmegacorp.com

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