File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / virtio_block.cpp
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 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_block.h"
#include "virtio_def.h"
#include "config.h"
#include "mainram.h"
#include "memorystream.h"
#include "monitor.h"

// デバイス構成レイアウト
class VirtIOBlkConfigWriter
{
 public:
	le64 capacity {};
	le32 size_max {};
	le32 seg_max {};
	struct {
		le16 cylinders {};
		u8   heads {};
		u8   sectors {};
	} geometry;
	le32 blk_size {};
	struct {
		u8   physical_block_exp {};
		u8   alignment_offset {};
		le16 min_io_size {};
		le32 opt_io_size {};
	} topology;
	u8   writeback {};
	le32 max_discard_sectors {};
	le32 max_discard_seg {};
	le32 discard_sector_alignment {};
	le32 max_write_zeroes_sectors {};
	le32 max_write_zeroes_seg {};
	u8   write_zeroes_may_unmap {};

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

// コンストラクタ
VirtIOBlockDevice::VirtIOBlockDevice(uint slot_, uint id_)
	: inherited(OBJ_VIRTIO_BLOCK(id_), slot_)
{
	id = id_;

	// 割り込み名(とログ名)
	snprintf(intrname, sizeof(intrname), "VIOBlk%u", id);
	SetName(intrname);
	ClearAlias();
	AddAlias(intrname);

	image.SetName(intrname);

	device_id = VirtIO::DEVICE_ID_BLOCK;
	int num_max = 32;
	vqueues.emplace_back(this, 0, "RequestQ", num_max);
	blocksize = 512;

	// 完了通知メッセージ
	msgid = MessageID::VIRTIO_BLOCK_DONE(id);

	monitor = gMonitorManager->Regist(ID_MONITOR_VIRTIO_BLOCK(id), this);
	monitor->SetCallback(&VirtIOBlockDevice::MonitorScreen);
	monitor->SetSize(MONITOR_WIDTH, 2 + 4 * 1 + num_max + 3);
}

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

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

	const std::string imgkey = string_format("virtio-block%u-image", id);
	const std::string wikey = string_format("virtio-block%u-writeignore", id);

	// オープン前に writeignore をチェック。see scsidev.cpp
	write_ignore = gConfig->Find(wikey).AsBool();

	// オープン (値が空ならここには来ない)
	const ConfigItem& itemimg = gConfig->Find(imgkey);
	assert(itemimg.AsString().empty() == false);
	std::string path = gMainApp.NormalizePath(itemimg.AsString());
	if (LoadDisk(path) == false) {
		itemimg.Err("Could not open %s", path.c_str());
		return false;
	}

	uint seg_max = vqueues[0].num_max - 2;

	// DEVICE_FEATURES と構成レイアウトを用意。
	VirtIOBlkConfigWriter cfg;
	SetDeviceFeatures(VIRTIO_BLK_F_SEG_MAX), cfg.seg_max = seg_max;
	SetDeviceFeatures(VIRTIO_BLK_F_BLK_SIZE), cfg.blk_size = blocksize;
	if (GetWriteMode() >= SCSIDisk::RW::WriteProtect) {
		SetDeviceFeatures(VIRTIO_BLK_F_RO);
	}

	cfg.capacity = image.GetSize() / 512; // capacity は 512 バイト単位
	cfg.WriteTo(&device_config[0]);

	return true;
}

// バックエンドのディスクイメージを開く。
// 成功すれば true、失敗すれば false を返す。
bool
VirtIOBlockDevice::LoadDisk(const std::string& pathname_)
{
	off_t size;
	int w_ok;

	pathname = pathname_;
	if (image.CreateHandler(pathname) == false) {
		return false;
	}

	w_ok = image.IsWriteable();
	if (w_ok < 0) {
		return false;
	}

	// 書き込みモードをここで確定
	if (w_ok) {
		if (write_ignore) {
			write_mode = SCSIDisk::RW::WriteIgnore;
		} else {
			write_mode = SCSIDisk::RW::Writeable;
		}
	} else {
		write_mode = SCSIDisk::RW::WriteProtect;
	}

	// ここでオープン
	bool read_only = (GetWriteMode() != SCSIDisk::RW::Writeable);
	if (image.Open(read_only, write_ignore) == false) {
		return false;
	}

	// セクタ単位になっていること
	size = image.GetSize();
	if (size % blocksize != 0) {
		warnx("%s: Bad image size(%ju): Not a multiple of blocksize(%u)",
			pathname.c_str(), (uintmax_t)size, blocksize);
		image.Close();
		return false;
	}
	// 最大サイズの制約ある?

	// 書き込み無視なら一応ログ出力
	if (GetWriteMode() == SCSIDisk::RW::WriteIgnore) {
		putmsg(0, "write is ignored");
	}

	return true;
}

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

	screen.Clear();

	y = MonitorScreenDev(screen, y);
	y++;
	y = MonitorScreenVirtQueue(screen, y, vqueues[0]);
	y++;
	y = MonitorScreenVirtQDesc(screen, y, vqueues[0]);
}

// ディスクリプタを一つ処理する。
void
VirtIOBlockDevice::ProcessDesc(VirtIOReq& req)
{
	VirtQueue *q = req.q;
	uint32 type;
	uint64 sector;
	uint32 status;

	// ヘッダ部は 16 バイト。
	// +0.L: type (コマンド)
	// +4.L: (reserved)
	// +8.L: 開始セクタ番号

	if (ReqReadLE32(req, &type) == false) {
		putlog(0, "Cannot read type in header");
		return;
	}
	ReqReadLE32(req); // skip
	if (ReqReadLE64(req, &sector) == false) {
		putlog(0, "Cannot read sector in header");
		return;
	}
	putlog(3, "%s req.idx=%u type=%x sec=$%x'%08x",
		__func__, req.idx, type, (uint32)(sector >> 32), (uint32)sector);

	// 転送データ長は、ディスクリプタの全長から
	// ヘッダ(16バイト) とステータスバイト(1バイト) を引いた部分。
	// という求め方しかない。
	uint32 datalen;
	if (req.totallen() >= 16 + 1) {
		datalen = req.totallen() - (16 + 1);
	} else {
		datalen = 0;
	}

	switch (type) {
	 case VIRTIO_BLK_T_IN:
		status = CmdRead(req, sector, datalen);
		access_read = q->last_avail_idx;
		break;

	 case VIRTIO_BLK_T_OUT:
		status = CmdWrite(req, sector, datalen);
		access_write = q->last_avail_idx;
		break;

	 case VIRTIO_BLK_T_GET_ID:
		status = CmdGetID(req, datalen);
		break;

	 default:
		putlog(0, "%s req.type=%u (NOT IMPLEMENTED)", __func__, type);
		status = VIRTIO_BLK_S_UNSUPP;
		break;
	}
	ReqWriteU8(req, status);
}

// 読み込みコマンド (BLK_T_IN) 実行。
uint32
VirtIOBlockDevice::CmdRead(VirtIOReq& req, uint64 sector, uint32 datalen)
{
	std::vector<uint8> databuf(datalen);

	if (image.Read(databuf.data(), sector * 512, databuf.size()) == false) {
		return VIRTIO_BLK_S_IOERR;
	}
	if (ReqWriteRegion(req, databuf.data(), datalen) != 0) {
		return VIRTIO_BLK_S_IOERR;
	}

	return VIRTIO_BLK_S_OK;
}

// 書き込みコマンド (BLK_T_OUT) 実行。
uint32
VirtIOBlockDevice::CmdWrite(VirtIOReq& req, uint64 sector, uint32 datalen)
{
	std::vector<uint8> databuf(datalen);

	if (ReqReadRegion(req, databuf.data(), datalen) != 0) {
		return VIRTIO_BLK_S_IOERR;
	}
	if (image.Write(databuf.data(), sector * 512, databuf.size()) == false) {
		return VIRTIO_BLK_S_IOERR;
	}

	return VIRTIO_BLK_S_OK;
}

// ID 取得コマンド (BLK_T_GET_ID) 実行。
uint32
VirtIOBlockDevice::CmdGetID(VirtIOReq& req, uint32 datalen)
{
	std::vector<uint8> databuf(datalen);

	// Device ID string を返すらしいが、どういう文字列か分からん。
	// 20バイトで余りはゼロ埋め。20文字なら終端文字なしで 20文字有効。
	snprintf((char *)databuf.data(), datalen, "virtio-block%u", id);

	if (ReqWriteRegion(req, databuf.data(), datalen) != 0) {
		return VIRTIO_BLK_S_IOERR;
	}

	return VIRTIO_BLK_S_OK;
}

const char *
VirtIOBlockDevice::GetFeatureName(uint feature) const
{
	static std::pair<uint, const char *> names[] = {
		{ VIRTIO_BLK_F_SIZE_MAX,	"SIZE_MAX" },
		{ VIRTIO_BLK_F_SEG_MAX,		"SEG_MAX" },
		{ VIRTIO_BLK_F_GEOMETRY,	"GEOMETRY" },
		{ VIRTIO_BLK_F_RO,			"RO" },
		{ VIRTIO_BLK_F_BLK_SIZE,	"BLK_SIZE" },
		{ VIRTIO_BLK_F_FLUSH,		"FLUSH" },
		{ VIRTIO_BLK_F_TOPOLOGY,	"TOPOLOGY" },
		{ VIRTIO_BLK_F_CONFIG_WCE,	"CFG_WCE" },
		{ VIRTIO_BLK_F_DISCARD,		"DISCARD" },
		{ VIRTIO_BLK_F_WRITE_ZEROES,"WZEROES" },
	};

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

// virtio_blk_config 構造体を書き出す。
void
VirtIOBlkConfigWriter::WriteTo(uint8 *dst) const
{
	MemoryStreamLE mem(dst);

	mem.Write8(capacity);
	mem.Write4(size_max);
	mem.Write4(seg_max);
	mem.Write2(geometry.cylinders);
	mem.Write1(geometry.heads);
	mem.Write1(geometry.sectors);
	mem.Write4(blk_size);
	mem.Write1(topology.physical_block_exp);
	mem.Write1(topology.alignment_offset);
	mem.Write2(topology.min_io_size);
	mem.Write4(topology.opt_io_size);
	mem.Write1(writeback);
	mem.Write1(0);
	mem.Write1(0);
	mem.Write1(0);
	mem.Write4(max_discard_sectors);
	mem.Write4(max_discard_seg);
	mem.Write4(discard_sector_alignment);
	mem.Write4(max_write_zeroes_sectors);
	mem.Write4(max_write_zeroes_seg);
	mem.Write1(write_zeroes_may_unmap);
	mem.Write1(0);
	mem.Write1(0);
	mem.Write1(0);
}

unix.superglobalmegacorp.com

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