File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / scsidomain.cpp
Revision 1.1.1.3 (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) 2025 nono project
// Licensed under nono-license.txt
//

//
// SCSI ドメイン
//

// SCSI ドメインは、1台のイニシエータとそれに繋がるターゲットからなるシステム
// 一式を指すことにする。ここには SCSIBus は含まない。
//
// HBA (SPCとか)
//  |
//  +----> .domain ................................................
//  |     (SCSIDomain)                                            :
//  |      :  +----------------+  +------------+  +------------+  :
//  | .parent |.initiator      |  |.target[0]  |  |.target[6]  |  :
//  |<--------|(SCSIHostDevice)|  |(SCSITarget)|  |(SCSITarget)|  :
//  |      :  +----------------+  +------------+  +------------+  :
//  |      :         ^ |               ^ |             ^ |        :
//  |      ..........|.|...............|.|.............|.|.........
//  |                | |               | |             | |
//  |                | |.bus           | |.bus         | |.bus
//  +----> .bus .....|.|...............|.|.............|.|.........
//        (SCSIBus)  | v               | v             | v        :
//         :     .device[7]        .device[0]      .device[6]     :
//         :     (SCSIDevice)      (SCSIDevice)    (SCSIDevice)   :
//         ........................................................
//
// 初期化 (Create) 時に SCSIDomain は設定ファイルから自分のドメインの
// 各 .target[] と .initiator を生成する。
// HBA が SCSIBus を持つタイプであれば Init 時に
// SCSIDomain::AttachTargets(bus)、SCSIBus::Refresh() ですべてのターゲットを
// バスに参加させる。
// HBA が SCSIBus を持たないタイプであれば GetDevices() でターゲット情報を
// 手元に持っておく。
//
// 電源オン後、イニシエータの SCSI ID が確定したら HBA は
// SCSIDomain::AttachInitiator(bus, id)、SCSIBus::Refresh() でイニシエータを
// バスに参加させる。

#include "scsidomain.h"
#include "scsi.h"
#include "scsibus.h"
#include "scsidev.h"
#include "config.h"
#include "monitor.h"

// コンストラクタ
SCSIDomain::SCSIDomain(IODevice *parent_,
		const char *domain_name_, const char *config_name_)
	: inherited(OBJ_SCSI)
{
	parent = parent_;
	domain_name = domain_name_;
	config_name = config_name_;

	monitor = gMonitorManager->Regist(ID_MONITOR_SCSIDEVS, this);
	monitor->SetCallback(&SCSIDomain::MonitorScreen);
	// サイズは Create で決まる
}

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

// 動的なコンストラクション
bool
SCSIDomain::Create()
{
	// 設定を読み込む。
	for (int id = 0; id < MaxDevices; id++) {
		const std::string key = string_format("%s-id%u-image",
			config_name, id);
		const ConfigItem& item = gConfig->Find(key);
		const std::string& image = item.AsString();

		// 空ならデバイスなし
		if (image.empty()) {
			// 未接続部分のパラメータは減らしておくか。
			// --show-config するとさすがに無駄に多くて邪魔なので。
			const std::string wikey = string_format("%s-id%u-writeignore",
				config_name, id);
			gConfig->Delete(wikey);

			// -seektime は virtio-scsi にはない。
			if (strncmp(config_name, "virtio", 6) != 0) {
				const std::string stkey = string_format("%s-id%u-seektime",
					config_name, id);
				gConfig->Delete(stkey);
			}
			continue;
		}

		// ',' より前がデバイス種別 (ここではパスは不要)
		auto v = string_split(image, ',');
		// 比較のため前後の空白を取り除いて小文字にする
		std::string type = string_trim(v[0]);
		type = string_tolower(type);

		SCSI::DevType target_devtype;
		if (type == "hd") {
			target_devtype = SCSI::DevType::HD;
		} else if (type == "cd") {
			target_devtype = SCSI::DevType::CD;
		} else if (type == "mo") {
			target_devtype = SCSI::DevType::MO;
		} else {
			item.Err("Invaild device type '%s'", type.c_str());
			return false;
		}

		try {
			target[id].reset(new SCSIDisk(this, id, target_devtype));
		} catch (...) { }
		if ((bool)target[id] == false) {
			warnx("Failed to initialize target[%u] at %s", id, __method__);
			return false;
		}
		putlog(3, "Attach #%u", id);
	}

	// イニシエータを生成。
	try {
		initiator.reset(new SCSIHostDevice(this, parent));
	} catch (...) { }
	if ((bool)initiator == false) {
		warnx("Failed to initialize SCSHostDevice at %s", __method__);
		return false;
	}

	// この時点ではイニシエータに ID がないため失敗しないはず。
	bool r = RefreshDevices();
	assert(r == true);

	// イニシエータだけ必要な行数が異なるが、この時点の
	// connected_devices はイニシエータを含まないので都合がいい。
	monitor->SetSize(75, connected_devices.size() * 4 + 1);

	return true;
}

// 初期化。
bool
SCSIDomain::Init()
{
	// 初期化ではないけど、ログが使えるようになったここでデバッグ表示。
	if (loglevel >= 1) {
		const auto& conn = GetConnectedDevices();
		for (const auto dev : conn) {
			putmsgn("%s target #%u %s", domain_name, dev->GetMyID(),
				SCSI::GetDevTypeName(dev->GetDevType()));
		}
	}

	return true;
}

// すべてのターゲットをバスに参加させる。
// バスを持つホストデバイスが Init() 時に呼ぶ。
void
SCSIDomain::AttachTargets(SCSIBus *bus)
{
	for (uint id = 0; id < MaxDevices; id++) {
		if ((bool)target[id]) {
			target[id]->Attach(bus);
		}
	}
}

// イニシエータに ID を付与し、(あれば)バスに参加させる。
// bus は NULL でもよい。
// 成功すれば true を返す。
// 失敗すればイニシエータの ID を -1 に設定して false を返す。
bool
SCSIDomain::AttachInitiator(SCSIBus *bus, uint id_)
{
	initiator->SetID(id_);

	// ドメインに反映させる。
	if (RefreshDevices() == false) {
		// 失敗したらイニシエータを切り離しておく。
		initiator->SetID(-1);
		RefreshDevices();
		return false;
	}

	if (bus) {
		initiator->Attach(bus);
	}

	return true;
}

// initiator と target[] から device[] と connected_devices を作り直す。
// target[] はイニシエータを含まないターゲットデバイス。
// device[] はターゲットと ID の確定したイニシエータを指す穴あきの配列。
// connected_devices はイニシエータも含む ID 順の可変長リスト。
// initiator か target[] に増減があるか、initiator の ID が確定した時に
// 呼ぶこと。
// initiator の ID が他と衝突すれば false を返す。この場合 device[] と
// connected_devices の状態は不定。
bool
SCSIDomain::RefreshDevices()
{
	// initiator と taget[] から device[] を作る。
	device.fill(NULL);
	for (int id = 0; id < MaxDevices; id++) {
		if ((bool)target[id]) {
			device[id] = target[id].get();
		}
		if (initiator->GetMyID() == id) {
			if (device[id]) {
				return false;
			}
			device[id] = initiator.get();
		}
	}

	// device[] から connected_devices を作る。
	connected_devices.clear();
	for (uint id = 0; id < MaxDevices; id++) {
		if (device[id]) {
			connected_devices.push_back(device[id]);
		}
	}

	return true;
}

// (イニシエータを除く) デバイス数を返す。
uint
SCSIDomain::GetTargetCount() const
{
	uint count = 0;

	for (const auto& t : target) {
		if ((bool)t) {
			count++;
		}
	}

	return count;
}

// SCSI デバイスモニタ
// (どこでやるのがいいか分からんけど、SCSI デバイスを一括して表示したいので
// とりあえずここで)
void
SCSIDomain::MonitorScreen(Monitor *, TextScreen& screen)
{
	int y;

// 012345678901234567890123456789012345678901234567890123456789012345678901234
// ID6: HD(2048MB)
//      MediumLoaded   Off      LogicalBlock 2048    ImageSize   2,147,483,648
//      RemovalPrevent Off      WriteProtected


	screen.Clear();

	y = 0;
	for (const auto dev : connected_devices) {
		std::string desc = SCSI::GetDevTypeName(dev->GetDevType());
		std::string pathname;

		const SCSIDisk *disk = dynamic_cast<const SCSIDisk*>(dev);
		if (disk && disk->IsMediumLoaded()) {
			desc += string_format("(%uMB)",
				(uint)(disk->GetSize() / 1024 / 1024));
			pathname = disk->GetPathName();
		}
		screen.Print(0, y, "ID%c: %s", '0' + dev->GetMyID(), desc.c_str());

#if 1
		(void)pathname;
#else
		// パス名の非ASCIIどうするか
		if (!pathname.empty()) {
			if (pathname.size() > screen.GetCol() - 16 - 3) {
				pathname = pathname.substr(0, screen.GetCol() - 16 - 3);
				pathname += "...";
			}
			screen.Puts(16, y, pathname.c_str());
		}
#endif
		y++;

		// イニシエータならここまで。(今の所イニシエータとディスクしかない)
		if (disk == NULL) {
			continue;
		}

		const int x1 = 5;
		const int x2 = 29;
		const int x3 = 50;

		// 1行目
		screen.Print(x1, y,
			(disk->IsRemovableDevice() ? TA::Normal : TA::Disable),
			"MediumLoaded   %s",
			disk->IsMediumLoaded() ? "On" : "Off");
		screen.Print(x2, y, "LogicalBlock %u", disk->GetBlocksize());
		std::string imagesize = format_number(disk->GetSize());
		screen.Print(x3, y, "ImageSize %15s", imagesize.c_str());
		y++;

		// 2行目
		screen.Print(x1, y,
			(disk->IsRemovableDevice() ? TA::Normal : TA::Disable),
			"RemovalPrevent %s",
			disk->IsMediumRemovalPrevented() ? "On" : "Off");

		screen.Puts(x2, y,
			(disk->IsMediumLoaded() ? TA::Normal : TA::Disable),
			disk->GetWriteModeStr());
		y++;

		y++;
	}
}

unix.superglobalmegacorp.com

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