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

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

//
// SCSI デバイス
//

// ログレベルは
// 1: SCSI コマンドの概要。
//    デバイスの動作のうち大きな変更のもの
// 2: SCSI コマンドの詳細、ダンプ。
//    デバイスの動作のうち些細なもの、あるいは頻繁なもの
// 3: フェーズ遷移
// 4: データも含める

#include "scsicmd.h"
#include "scsidev.h"
#include "scsidomain.h"
#include "config.h"
#include "event.h"
#include "mainapp.h"
#include "scheduler.h"
#include "uimessage.h"

//
// SCSI デバイス共通部分
//

// コンストラクタ
SCSIDevice::SCSIDevice(uint objid_)
	: inherited(objid_)
{
	myid = -1;
}

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

// バスに接続する。
void
SCSIDevice::Attach(SCSIBus *bus_)
{
	bus = bus_;
}


//
// SCSI ホストデバイス (イニシエータ)
//

// コンストラクタ
SCSIHostDevice::SCSIHostDevice(SCSIDomain *domain_, IODevice *parent_)
	: inherited(OBJ_NONE)
{
	domain = domain_;
	parent = parent_;
	// イニシエータの ID は実行時にコントローラによって決まる。
	devtype = SCSI::DevType::Initiator;

	SetName(string_format("(SCSIHost.%s)", domain_->GetDomainName()));
	SetLogLevel(parent->loglevel);
}

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

// SCSI ID を設定する。
// (SCSIDomain からのみ呼ぶこと)
void
SCSIHostDevice::SetID(uint id_)
{
	myid = id_;
}

// SCSIBus からのコールバック関数を設定する。
void
SCSIHostDevice::SetBusCallback(
	BusFreeCallback_t busfree_,
	SCSIHostCallback_t selack_,
	SCSIHostCallback_t xferreq_)
{
	BusFreeCallback = busfree_;
	SelectionAckCallback = selack_;
	TransferReqCallback = xferreq_;
}

// バスフリーになった (SCSIBus から呼ばれる)
void
SCSIHostDevice::OnBusFree(uint id)
{
	// 実際にはバスフリーを検出したデバイスは各自自身が送出している信号を
	// 下げるのだが、色々面倒なので、ここではホストのバスフリーコールバックが
	// 一括して全部の線を下げる。
	// ただしさすがにログがうるさいので、上がってる線だけ下げる。

	uint32 data;
	while ((data = GetBSY()) != 0) {
		bus->NegateBSY(SCSIBus::DecodeID(data));
	}
	while ((data = GetSEL()) != 0) {
		bus->NegateSEL(SCSIBus::DecodeID(data));
	}
	if (GetREQ()) {
		NegateREQ();
	}
	if (GetACK()) {
		NegateACK();
	}
	if (GetATN()) {
		NegateATN();
	}
	SetXfer(0);
	SetData(0);

	// 親のバスフリーコールバックを呼ぶ
	(parent->*(BusFreeCallback))(id);

	// RST が見えてる状態で親に通知はしたので、ここで下ろす。
	if (GetRST()) {
		NegateRST();
	}
}

// ターゲットがセレクションに応答した (SCSIBus から呼ばれる)
void
SCSIHostDevice::OnSelectionAck()
{
	// ターゲットが BSY を立てて応答したので、
	// こちらはデータバスをクリアして SEL を下げる。
	// これによりセレクションフェーズは成功で完了する。
	SetData(0);
	NegateSEL();

	// 親のバスフリーコールバックを呼ぶ
	(parent->*(SelectionAckCallback))();
}

// ターゲットが REQ を立てた (SCSIBus から呼ばれる)
void
SCSIHostDevice::OnTransferReq()
{
	(parent->*(TransferReqCallback))();
}


//
// SCSI ターゲットデバイスの共通部分
//

// コンストラクタ。
SCSITarget::SCSITarget(SCSIDomain *domain_, uint id)
	: inherited(OBJ_NONE)	// オブジェクト ID で検索することはない
{
	domain = domain_;
	myid = id;

	// ログは出すので名前はつけるがそれは継承先で SetName() している。
}

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

// 電源オン
void
SCSITarget::ResetHard(bool poweron)
{
	if (poweron) {
		ClearSense();
		cmdseq.clear();
		cmdlen = 0;
		cmd.reset();
		msgseq.clear();
		bytecount = 0;
		lastbyte = false;
	}
}

// バスリセットされた (SCSIBus から呼ばれる)
void
SCSITarget::OnBusReset()
{
	// センスキーをクリア
	ClearSense();

	// リセットされたので持ってる信号を全部解放。
	// 一応自分が変えていい信号線だけを落とす。
	switch (GetPhase()) {
	 case SCSI::Phase::BusFree:
		break;
	 case SCSI::Phase::Arbitration:
	 case SCSI::Phase::Selection:
	 case SCSI::Phase::Reselection:
		NegateSEL();
		NegateBSY();
		break;
	 case SCSI::Phase::Transfer:
		NegateBSY();
		break;
	}
}

// セレクションで選択された (SCSIBus から呼ばれる)
void
SCSITarget::OnSelected()
{
	// ターゲットが、自身が選択されていることを認識してから BSY を
	// 立てるまでの時間はもうバス側でカウントしてあるので、ここでは
	// ただちに BSY を上げてよい。
	putlog(4, "OnSelected");
	AssertBSY();
}

// 情報転送フェーズを開始する (SCSIBus から呼ばれる)
void
SCSITarget::OnStartTransfer()
{
	// ATN が立っていればメッセージアウトフェーズ。
	// そうでなければコマンドフェーズ。
	cmdseq.clear();
	cmdlen = 0;
	msgseq.clear();
	if (GetATN()) {
		putlog(4, "Start MsgOut Phase by ATN");
		SetXfer(SCSI::XferPhase::MsgOut);
	} else {
		putlog(4, "Start Command Phase");
		SetXfer(SCSI::XferPhase::Command);
	}
	AssertREQ();
}

// 情報転送フェーズ間の遷移について
//
// 情報転送フェーズは OnTransfer() で1バイトずつ転送を行い、所定のバイト数
// の転送が完了すればフェーズを遷移する。遷移方法は入出力方向別で6通り。
//  o IN  -> IN
//  o IN  -> OUT
//  o IN  -> END
//  o OUT -> OUT
//  o OUT -> IN
//  o OUT -> END
//
// IN -> IN の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを
// 変更して REQ を立てるところまで。
//
// IN -> OUT の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを
// 変更して (おそらく {Phase}Begin() を発行してから)、データをセットし REQ
// を立てる?
//
// IN -> END の遷移。OnTransfer() で前フェーズの最終バイト受信後、BSY を
// 下げて終了。
//
// OUT -> OUT の遷移。前フェーズの最終バイトをイニシエータが受信後に
// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin()
// を発行してから)、データをセットし REQ を立てる?
//
// OUT -> IN の遷移。前フェーズの最終バイトをイニシエータが受信後に
// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin()
// を発行してから)、REQ を立てる?
//
// OUT -> END の遷移。前フェーズの最終バイトをイニシエータが受信後に
// OnTransfer() が呼ばれるので、BSY を下げる?

// 情報転送フェーズでイニシエータが ACK を上げた。(SCSIBus から呼ばれる)
// OUT (イニシエータ → ターゲット方向) なら、ターゲットが上げた REQ に応答
// してイニシエータがデータをデータバスに乗せてからこれをコールしてくる。
// IN (ターゲット → イニシエータ方向) なら、ターゲットがデータをデータバスに
// 乗せて REQ を上げたのをイニシエータが受信した後でこれをコールしてくる。
void
SCSITarget::OnTransfer()
{
	uint8 data;
	SCSI::XferPhase xfer;

	// このフェーズの最終バイトか
	lastbyte = false;

	// このフェーズの1バイト処理
	xfer = GetXfer();
	if (xfer == SCSI::XferPhase::Command) {
		// コマンドフェーズだけいろいろ特殊なので別処理。
		// 受信バッファが異なる、1バイト目で受信長が決まるなど。
		data = GetData();
		putlog(4, "%s: cmdseq[%02u] = $%02x", __func__,
			(uint)cmdseq.size(), data);
		cmdseq.push_back(data);
		if (cmdseq.size() == 1) {
			// 1バイト目ならこのコマンドが何バイトか調べる
			switch (cmdseq[0] >> 5) {
			 case 0:	cmdlen =  6;	break;
			 case 1:	cmdlen = 10;	break;
			 case 2:	cmdlen = 10;	break;
			 case 5:	cmdlen = 12;	break;
			 default:
				// 3,4 はリサーブ、6,7 はベンダー定義。XXX 来たらどうする?
				cmdlen = 6;
				break;
			}
		} else if (cmdseq.size() >= cmdlen) {
			if (loglevel >= 2
#if defined(NO_READWRITE_LOG)
				&& (cmdseq[0] != SCSI::Command::Read6 &&
				    cmdseq[0] != SCSI::Command::Read10 &&
				    cmdseq[0] != SCSI::Command::Write6 &&
				    cmdseq[0] != SCSI::Command::Write10)
#endif
			) {
				std::string cmdbuf;
				const char *name = SCSI::GetCommandName(cmdseq[0]);
				cmdbuf += string_format("Command \"%s\"", name ?: "?");
				for (const auto& v : cmdseq) {
					cmdbuf += string_format(" %02x", v);
				}
				putlogn("%s", cmdbuf.c_str());
			}
			// 最終バイトを受信したらコマンドを選択。
			lastbyte = true;
			SelectCommand();
			if ((bool)cmd == false) {
				std::string namebuf;
				const char *name = SCSI::GetCommandName(cmdseq[0]);
				if (name) {
					namebuf = std::string(name);
				} else {
					namebuf = string_format("$%02x", cmdseq[0]);
				}
				putlog(0, "Failed to initialize command %s", namebuf.c_str());
				AssertRST();
			}
		}
	} else if (xfer == SCSI::XferPhase::MsgOut) {
		data = GetData();
		putlog(4, "%s: msgseq[%02u] = $%02x", __func__,
			(uint)msgseq.size(), data);
		msgseq.push_back(data);
		bytecount--;
		if (bytecount == 0) {
			lastbyte = true;
		}

	} else if (GetIO() == false) {
		// OUT 方向 (ターゲットが受信側)
		// bytecount は減算方向。
		cmd->buf.push_back(GetData());
		bytecount--;
		if (bytecount == 0) {
			lastbyte = true;
		}
	} else {
		// IN 方向 (ターゲットが送信側)
		// bytecount は加算方向。
		// データは前のループの終わり (OnTransferAck()の下のほう) で
		// セットしているのでここでは引き取られた分をカウントする。
		bytecount++;
		if (bytecount >= cmd->buf.size()) {
			lastbyte = true;
		}
	}

	NegateREQ();
}

// 情報転送フェーズでイニシエータが ACK を下げた。(SCSIBus から呼ばれる)
void
SCSITarget::OnTransferAck()
{
	SCSI::XferPhase xfer = GetXfer();

	// これが最終バイトだったら、フェーズ完了 → フェーズ切り替え。
	if (lastbyte) {
		SCSI::XferPhase next;
		// フェーズ完了
		switch (xfer) {
		 case SCSI::XferPhase::Command:
			next = cmd->ExecCommand(cmdseq);
			break;
		 case SCSI::XferPhase::DataOut:
			next = cmd->DoneDataOut();
			break;
		 case SCSI::XferPhase::DataIn:
			next = cmd->DoneDataIn();
			break;
		 case SCSI::XferPhase::MsgOut:
			if (cmd) {
				next = cmd->DoneMsgOut();
			} else {
				// コマンド前のメッセージアウト。
				if (msgseq.size() == 1 && (msgseq[0] & 0x80)) {
					putlog(1, "%s: MsgOut $%02x Identify (NOT SUPPORTED)",
						__func__, msgseq[0]);
				} else {
					std::string buf;
					for (auto m : msgseq) {
						buf += string_format(" %02x", m);
					}
					putlog(0, "%s: MsgOut%s (NOT SUPPORTED)",
						__func__, buf.c_str());
				}
				next = SCSI::XferPhase::Command;
			}
			break;
		 case SCSI::XferPhase::MsgIn:
			next = cmd->DoneMsgIn();
			break;
		 case SCSI::XferPhase::Status:
			next = cmd->DoneStatus();
			break;
		 default:
			PANIC("%s unknown xfer done %d", __func__, (int)xfer);
		}
		putlog(3, "%s done", SCSI::GetXferPhaseName(xfer));

		// 簡略化のため、フェーズ完了時だけ REQ を引き伸ばすことができる
		if (cmd && cmd->optime != 0) {
			bus->SetOneshotReqWait(cmd->optime);
			cmd->optime = 0;
		}

		if (next != xfer) {
			// End ならバスフリーにして終了。
			if (next == SCSI::XferPhase::End) {
				cmd.reset();
				NegateBSY();
				return;
			}

			// (必要なら)フェーズを更新
			SetXfer(next);
			xfer = next;

			// 新しいフェーズの開始処理
			// XXX ここで bytecount とかをリセットできればいいのだが
			// Command フェーズで Data フェーズの準備をすることが多いので
			// 悩ましい。
			switch (xfer) {
			 case SCSI::XferPhase::Command:
				break;
			 case SCSI::XferPhase::DataOut:
				cmd->buf.clear();
				bytecount = cmd->recvbytes;
				putlog(3, "DataOut begin (%u bytes)", bytecount);
				break;
			 case SCSI::XferPhase::DataIn:
				bytecount = 0;
				putlog(3, "DataIn begin (%u bytes)", (uint)cmd->buf.size());
				break;
			 case SCSI::XferPhase::MsgOut:
				cmd->BeginMsgOut();
				cmd->buf.clear();
				bytecount = cmd->recvbytes;
				putlog(3, "MsgOut begin (%u bytes)", bytecount);
				break;
			 case SCSI::XferPhase::MsgIn:
				cmd->BeginMsgIn();
				bytecount = 0;
				putlog(3, "MsgIn begin (%u bytes)", (uint)cmd->buf.size());
				break;
			 case SCSI::XferPhase::Status:
				cmd->BeginStatus();
				bytecount = 0;
				putlog(3, "Status begin (%u bytes)", (uint)cmd->buf.size());
				break;
			 default:
				PANIC("%s unknown xfer begin %d", __func__, (int)xfer);
			}
		}
	}

	// IN 方向 (ターゲットが送信側) ならここでデータをバスにセット。
	if (GetIO()) {
		SetData(cmd->buf[bytecount]);
		putlog(4, "SetData $%02x", GetData());
	}

	// 次のバイト転送を行うために REQ を上げる。
	AssertREQ();
}

// SCSI コマンドのディスパッチャ。
// C++ の例外は呼び出し側で捕捉している。
void
SCSITarget::DispatchCmd()
{
	switch (cmdseq[0]) {
	 case SCSI::Command::TestUnitReady:	// 0x00
		cmd.reset(new SCSICmd(this));
		break;

	 case SCSI::Command::RezeroUnit:	// 0x01
		cmd.reset(new SCSICmd(this));
		break;

	 case SCSI::Command::RequestSense:	// 0x03
		cmd.reset(new SCSICmdRequestSense(this));
		break;

	 case SCSI::Command::Inquiry:		// 0x12
		cmd.reset(new SCSICmdInquiry(this));
		break;

	 case SCSI::Command::ModeSelect6:	// 0x15
	 case SCSI::Command::ModeSelect10:	// 0x55
		cmd.reset(new SCSICmdModeSelect(this));
		break;

	 case SCSI::Command::ModeSense6:	// 0x1a
	 case SCSI::Command::ModeSense10:	// 0x5a
		cmd.reset(new SCSICmdModeSense(this));
		break;

	 default:
		// サポートしていないコマンド
		const char *cname = SCSI::GetCommandName(cmdseq[0]);
		std::string name;
		if (cname) {
			name = string_format(" %s", cname);
		}
		putlog(0, "SCSI Command $%02x%s len=%u not supported",
			cmdseq[0], name.c_str(), (uint)cmdseq.size());

		cmd.reset(new SCSICmdNotSupportedCommand(this));
		break;
	}
}

// コマンド列を指定して対応するコマンドを選択する。
// 知らないコマンドなら SCSICmdNotSupportedCommand という仮想のコマンドを返す。
// コマンドインスタンスが生成できなければ NULL を返す。
// 外部向け。
SCSICmd *
SCSITarget::SelectCommand(const std::vector<uint8>& cmdseq_)
{
	cmdseq = cmdseq_;
	SelectCommand();
	return cmd.get();
}

// 内部版。
// 入力コマンド列はメンバ変数 cmdseq だし、生成した cmd もメンバ変数。
void
SCSITarget::SelectCommand()
{
	cmd.reset();
	// DispatchCmd() の中は new が多いので個別に捕捉せずこっちでやる。
	try {
		DispatchCmd();
	} catch (...) { }
}


//
// SCSI Disk
//

// コンストラクタ
SCSIDisk::SCSIDisk(SCSIDomain *domain_, uint id, SCSI::DevType devtype_)
	: inherited(domain_, id)
{
	devtype = devtype_;

	const char *devtypename = SCSI::GetDevTypeName(devtype);
	const char *domainname = domain->GetDomainName();

	// ドライブ自身のログ表示名は "HD(SPC0-6)" のような形式にする。
	// ログ指定はそれに合わせて "spc0-6" のような完全形と
	// "hd6" のような短縮形を用意してみる。
	// SCSI コントローラ2台目ではこの短縮形だけなくす?
	//
	// コンストラクタの初期化子では devtypename を組み込みにくいので、
	// コンストラクタ本文でログ名などを設定する。その時の作法に
	// 従っているのですこし面倒な手法が必要。
	// lib/object.h 参照。
	std::string name = string_format("%s-%u", domainname, id);
	SetName(string_format("%s(%s)", devtypename, name.c_str()));
	ClearAlias();
	AddAlias(name);
	AddAlias(string_format("%s%u", devtypename, id));

	// image はコンストラクタがないので、このあたりで代入する。
	image.SetName(name);

	switch (devtype) {
	 case SCSI::DevType::HD:
		removable_device = false;
		writeable_device = true;
		blocksize = 512;
		break;
	 case SCSI::DevType::CD:
		removable_device = true;
		writeable_device = false;
		blocksize = 2048;
		break;
	 case SCSI::DevType::MO:
		removable_device = true;
		writeable_device = true;
		blocksize = 512;
		break;
	 default:
		PANIC("corrupted devtype=%u", (uint)devtype);
	}

}

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

// 初期化
bool
SCSIDisk::Init()
{
	const std::string keybody = string_format("%s-id%u-",
		domain->GetConfigName(), myid);
	const std::string imgkey = keybody + "image";
	const std::string wikey  = keybody + "writeignore";
	const std::string stkey  = keybody + "seektime";

	// デバイス種別(v[0])とパス(v[1])に分解。
	// デバイス種別はすでにチェックしてあるのでここでは不要。
	const ConfigItem& itemimg = gConfig->Find(imgkey);
	const std::string& imageval = itemimg.AsString();
	auto v = string_split(imageval, ',', 2);
	std::string filename;
	if (v.size() > 1) {
		filename = string_trim(v[1]);
	}

	// 固定デバイス(HD)ならファイル名は省略不可
	if (!IsRemovableDevice() && filename.empty()) {
		itemimg.Err("imagefile not specified");
		return false;
	}

	// リムーバブルデバイスのみ UI からの通知を受け取るイベントを用意
	if (IsRemovableDevice()) {
		scheduler->ConnectMessage(MessageID::SCSIDEV_LOAD(myid), this,
			ToMessageCallback(&SCSIDisk::LoadMessage));
		scheduler->ConnectMessage(MessageID::SCSIDEV_UNLOAD(myid), this,
			ToMessageCallback(&SCSIDisk::UnloadMessage));
	}

	// オープン前に writeignore をチェック。
	// この後パス名を処理するとすぐに UI に通知を出すため、それより前で
	// 行わないといけない。うーん。
	write_ignore = gConfig->Find(wikey).AsBool();

	// writeignore が決まったので状態を初期化。
	Clear();

	// 設定時点でファイルが指定されていればオープン
	if (filename.empty() == false) {
		std::string path = gMainApp.NormalizePath(filename);
		if (LoadDisk(path) == false) {
			itemimg.Err("Could not open %s", path.c_str());
			return false;
		}
	}

	// 平均シークタイム。設定は [msec]、変数は [tsec]。
	// virtio-scsi にはない。
	if (strncmp(stkey.c_str(), "virtio", 6) != 0) {
		seektime = gConfig->Find(stkey).AsInt();
		seektime *= 1_msec;
	}

	return true;
}

// 電源オン
void
SCSIDisk::ResetHard(bool poweron)
{
	inherited::ResetHard(poweron);

	if (poweron) {
		// メディアの取り出し禁止を解除
		PreventMediumRemoval(false);
	}
}

// バスリセットされた (SCSIBus から呼ばれる)
void
SCSIDisk::OnBusReset()
{
	// メディアの取り出し禁止を解除
	PreventMediumRemoval(false);

	inherited::OnBusReset();
}

// バックエンドのディスクイメージを開く。
// 成功すれば true、失敗すれば false を返す。(ロードされれば true を返す
// のではないが、medium_loaded が同じ値になるので使いまわしている)
bool
SCSIDisk::LoadDisk(const std::string& pathname_)
{
	bool unloaded;
	off_t size;
	bool read_only;

	// すでにあればクローズ (ここでは通知は行わない)
	unloaded = UnloadDisk_internal();

	// 新しいイメージをオープン(する準備)
	pathname = pathname_;
	if (image.CreateHandler(pathname) == false) {
		goto done;
	}

	// 書き込みモードをここで確定。
	//
	// デバイス特性	イメージ	write_ignore	write_mode
	// ------------	----------	------------ :	----------
	// 書込み可		書込み可	no				Writeable
	// 〃			〃			yes				WriteIgnore
	// 〃			書込み不可	no				WriteProtect (A)
	// 〃			〃			yes				WriteIgnore  (B)
	// 書込み不可	*			*				ReadOnly
	//
	// (A) は「ライトプロテクトした MO を入れた」状態。
	// (B) は「書き換えたくないので書き込み権を落としてあるイメージファイルを
	// あたかも書き込めるように見せたい」。(WI マークが出る)
	if (IsWriteableDevice()) {
		// 書き込み可能デバイスの場合
		if (write_ignore) {
			write_mode = RW::WriteIgnore;
		} else {
			int w_ok = image.IsWriteable();
			if (w_ok < 0) {
				goto done;
			} else if (w_ok) {
				write_mode = RW::Writeable;
			} else {
				write_mode = RW::WriteProtect;
			}
		}
	} else {
		// 読み込み専用デバイスの場合
		write_mode = RW::ReadOnly;
	}

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

	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);
		goto done;
	}
	// メディアごとの最大サイズを越えていないこと
	switch (GetDevType()) {
	 case SCSI::DevType::HD:
		// LBA (32bit) * 512 byte/sector
		if (size >= 0x200'0000'0000ULL) {
			warnx("%s: Bad image size(%ju): Too large",
				pathname.c_str(), (uintmax_t)size);
			goto done;
		}
		break;
	 case SCSI::DevType::CD:
		// https://cdrfaq.org/faq07.html#S7-6
		// 63min = 283,500 [sect] = 553.7MB CD-ROM = 635.9MB CD-DA
		// 74min = 333,000 [sect] = 650.3MB CD-ROM = 746.9MB CD-DA
		// 80min = 360,000 [sect] = 703.1MB CD-ROM = 807.4MB CD-DA
		// 90min = 405,000 [sect] = 791.0MB CD-ROM = 908.4MB CD-DA
		// 99min = 445,500 [sect] = 870.1MB CD-ROM = 999.3MB CD-DA
		if (size >= 445500 * 2048) {
			warnx("%s: Bad image size(%ju): Too Large",
				pathname.c_str(), (uintmax_t)size);
			goto done;
		}
		break;
	 case SCSI::DevType::MO:
		// 128M は 25 [sect] * 10,000 [sect/track] * 512 [byte/sect]
		// 230M は 25 [sect] * 17,853 [sect/track] * 512 [byte/sect]
		// 540M は 25 [sect] * 41,660 [sect/track] * 512 [byte/sect]
		// 640M は 17 [sect] * 18,256 [sect/track] * 2048 [byte/sect]
		// 1.3G は 17 [sect] * 35,638 [sect/track] * 2048 [byte/sect]
		// 2.3G は 17 [sect] * 62,538 [sect/track] * 2048 [byte/sect]
		//
		// 今の所 512 byte/sector フォーマットのみ対応しておく。
		if (size >= 25 * 41660 * 512) {
			warnx("%s: Bad image size(%ju): 640M or larger MO not supported",
				pathname.c_str(), (uintmax_t)size);
			goto done;
		}
		break;
	 default:
		PANIC("corrupted devtype=%u", (uint)devtype);
	}

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

	medium_loaded = true;
	// リムーバブルデバイスでだけログを出す
	if (IsRemovableDevice()) {
		putlog(1, "Medium loaded");
	}

 done:
	if (medium_loaded == false) {
		// ここで状態をクリアする
		Clear();
	}

	// 変化があれば通知
	if (unloaded || medium_loaded) {
		MediaChanged();
	}
	return medium_loaded;
}

// バックエンドのディスクイメージを閉じる。
// オープンされてなければ何もしない。
void
SCSIDisk::UnloadDisk(bool force)
{
	if (IsMediumRemovalAllowed() || force) {
		if (UnloadDisk_internal()) {
			// 実際に閉じたら通知
			MediaChanged();
		}
	}
}

// バックエンドのディスクイメージを閉じる (内部用)。
// 実際に閉じれば true を返す。オープンされてなければ何もせず false を返す。
// ここでは通知は行わない。
bool
SCSIDisk::UnloadDisk_internal()
{
	if ((bool)image == false) {
		return false;
	}

	Clear();

	// 取り出し禁止はされてても解除する
	if (IsMediumRemovalPrevented()) {
		// XXX PreventMediumRemoval() を呼ぶかどうするか
		medium_removal_prevented = false;
	}

	putlog(1, "Medium unloaded");
	return true;
}

// ディスク状態をクリアする
// (メディアを取り外した時など)
void
SCSIDisk::Clear()
{
	medium_loaded = false;
	image.Close();
	pathname.clear();

	if (IsWriteableDevice()) {
		if (write_ignore) {
			write_mode = RW::WriteIgnore;
		} else {
			write_mode = RW::Writeable;
		}
	} else {
		write_mode = RW::ReadOnly;
	}
}

// メディア状態変更を UI に通知する
void
SCSIDisk::MediaChanged() const
{
	gMainApp.GetUIMessage()->Post(UIMessage::SCSI_MEDIA_CHANGE, GetMyID());
}

// メディアを挿入する。
// UI スレッドで実行されるので、VM スレッドに通知するだけ。
void
SCSIDisk::LoadDiskUI(const std::string& pathname_)
{
	new_pathname = pathname_;
	scheduler->SendMessage(MessageID::SCSIDEV_LOAD(GetMyID()));
}

// メディアを排出する。
// UI スレッドで実行されるので、VM スレッドに通知するだけ。
void
SCSIDisk::UnloadDiskUI(bool force)
{
	scheduler->SendMessage(MessageID::SCSIDEV_UNLOAD(GetMyID()), force);
}

// メディア挿入メッセージコールバック
void
SCSIDisk::LoadMessage(MessageID msgid, uint32 arg)
{
	if (LoadDisk(new_pathname) == false) {
		// 失敗したら UI に通知
		gMainApp.GetUIMessage()->Post(UIMessage::SCSI_MEDIA_FAILED);
	}
}

// メディア排出メッセージコールバック
void
SCSIDisk::UnloadMessage(MessageID msgid, uint32 arg)
{
	bool force = arg;
	UnloadDisk(force);
}

// SCSI コマンドのディスパッチャ。
// C++ の例外は呼び出し側で捕捉している。
void
SCSIDisk::DispatchCmd()
{
	switch (cmdseq[0]) {
	 case SCSI::Command::Read6:			// 0x08
	 case SCSI::Command::Read10:		// 0x28
		cmd.reset(new SCSICmdRead(this));
		break;

	 case SCSI::Command::Write6:		// 0x0a
	 case SCSI::Command::Write10:		// 0x2a
		if (IsWriteableDevice()) {
			cmd.reset(new SCSICmdWrite(this));
		} else {
			cmd.reset(new SCSICmdNotSupportedCommand(this));
		}
		break;

	 case SCSI::Command::StartStopUnit:	// 0x1b
		cmd.reset(new SCSICmdStartStopUnit(this));
		break;

	 case SCSI::Command::PreventAllowMediumRemoval:	// 0x1e
		if (IsRemovableDevice() == false) {
			// HD は取り出せないので、とりあえず。
			// XXX 要調査
			cmd.reset(new SCSICmdNotSupportedCommand(this));
			return;
		}
		cmd.reset(new SCSICmdPreventAllowMediumRemoval(this));
		break;

	 case SCSI::Command::ReadCapacity:	// 0x25
		// DA デバイスでは Read Capacity (0x25)、
		// CD-ROM デバイスでは Read CDROM Capacity (0x25) だが
		// 今の所同じ動作なので区別せず処理する。
		cmd.reset(new SCSICmdReadCapacity(this));
		break;

	 case SCSI::Command::SynchronizeCache:	// 0x35
		// このクラスは、物理ドライブにあるようなディスクとの間の
		// キャッシュ機構は持っていないので、そのキャッシュをフラッシュ
		// しろという指示に対しては、黙って成功するだけでいい気がする。
		// このコマンドはオプションだが NetBSD カーネルが使う。
		//
		// CD-ROM でキャッシュの同期とは読み出し側のことだろうか?
		// とりあえず黙って成功しておく。
		cmd.reset(new SCSICmd(this));
		break;

	 case SCSI::Command::ReadTOC:		// 0x43
		if (devtype == SCSI::DevType::CD) {
			cmd.reset(new SCSICmdReadTOC(this));
		} else {
			cmd.reset(new SCSICmdNotSupportedCommand(this));
		}
		break;

	 case SCSI::Command::ReadFormatCapacities:	// 0x23
		// SCSI-2 規格でないので詳細不明。
		// ディスク用か共通かも分からないけど、とりあえず
	 case SCSI::Command::ReadDiscInformation:	// 0x51
		// CD-ROM デバイスのオプションコマンドで
		// CD-R ライター用(?)らしいので無視する。
		cmd.reset(new SCSICmdNotSupportedCommand(this));
		break;

	 default:
		// 見付からなければ基本クラス側に任せる
		inherited::DispatchCmd();
		break;
	}
}

// Inquiry データを返す
bool
SCSIDisk::Inquiry(std::vector<uint8>& buf, uint lun)
{
	uint8 inqtype;
	const char *prodname;

	switch (devtype) {
	 case SCSI::DevType::HD:
		inqtype = SCSI::InquiryDeviceType::DirectAccess;
		prodname = "SCSIHD";
		break;
	 case SCSI::DevType::CD:
		inqtype = SCSI::InquiryDeviceType::CDROM;
		prodname = "SCSICD";
		break;
	 case SCSI::DevType::MO:
		inqtype = SCSI::InquiryDeviceType::MO;
		prodname = "SCSIMO";
		break;
	 default:
		PANIC("Unexpected devtype=%u", (uint)devtype);
	}

	buf[0] = inqtype;
	if (lun != 0) {
		buf[0] |= SCSI::InquiryQualifier::LU_NotPreseted;
	}
	buf[1] = 0;
	if (IsRemovableDevice()) {
		buf[1] |= 0x80;
	}
	buf[2] = 2;		// SCSI-2
	buf[3] = 2;		// Response Data Format = SCSI-2
	buf[4] = buf.size() - 4;	// 追加データ長

	// 文字列はゼロ終端ではなく、余りは空白で埋める
	memset(&buf[8], ' ', 36 - 8);
	memcpy(&buf[8],  "NONO", 4);
	memcpy(&buf[16], prodname, strlen(prodname));
	memcpy(&buf[32], "0", 1);

	return true;
}

// ModeSense コマンドのデバイス固有パラメータを返す
uint8
SCSIDisk::GetDeviceSpecificParam() const
{
	uint8 param = 0;

	// DA, MO, CD のデバイス固有パラメータは以下のようになっている。
	// DPOFUA はキャッシュ制御フラグのサポート(%1)かどうかなので %0 でいい。
	//
	//      7   6   5     4    3   2   1   0
	//    +---+---+---+------+---+---+---+---+
	// DA |WP |   0   |DPOFUA|       0       | WP: Write Protect
	//    +---+---+---+------+---+---+---+---+
	//
	//    +---+---+---+------+---+---+---+---+
	// MO |WP |   0   |DPOFUA|     0     |EBC| EBC: Enable Blank Check
	//    +---+---+---+------+---+---+---+---+
	//
	//    +---+---+---+------+---+---+---+---+
	// CD |     0     |DPOFUA|       0       |
	//    +---+---+---+------+---+---+---+---+

	if (GetDevType() == SCSI::DevType::CD) {
		// CD には WP ビットの定義がない
	} else {
		// DA, MO は WP ビットの定義がある。
		if (IsMediumLoaded() && GetWriteMode() == RW::WriteProtect) {
			param |= 0x80;
		}
	}

	return param;
}

bool
SCSIDisk::Read(std::vector<uint8>& buf, uint64 start)
{
	if (__predict_false((bool)image == false)) {
		return false;
	}
	return image.Read(buf.data(), start, buf.size());
}

bool
SCSIDisk::Write(const std::vector<uint8>& buf, uint64 start)
{
	if (__predict_false((bool)image == false)) {
		return false;
	}

	if (GetWriteMode() >= RW::WriteProtect) {
		// 書き込み不可
		return false;
	}
	return image.Write(buf.data(), start, buf.size());
}

off_t
SCSIDisk::GetSize() const
{
	if (__predict_false((bool)image == false)) {
		return 0;
	}
	return image.GetSize();
}

// 指定の場所を自由に読み出す
// (エミュレータ特権アクセス)
bool
SCSIDisk::Peek(void *buf, uint64 start, uint32 len) const
{
	if (__predict_false((bool)image == false)) {
		return false;
	}
	return image.Read(buf, start, len);
}

// メディアの取り出し可否(取り出し禁止)を設定する
void
SCSIDisk::PreventMediumRemoval(bool val)
{
	// 状態が変わったところでログを表示
	if (medium_removal_prevented == false && val == true) {
		putlog(1, "Medium removal prevented");
	} else if (medium_removal_prevented == true && val == false) {
		putlog(1, "Medium removal allowed");
	}
	medium_removal_prevented = val;
}

// メディアの書き込みモードを文字列で返す
const char *
SCSIDisk::GetWriteModeStr() const
{
	switch (write_mode) {
	 case RW::ReadOnly:		return "ReadOnly";
	 case RW::WriteProtect:	return "WriteProtected";
	 case RW::Writeable:	return "Writeable";
	 case RW::WriteIgnore:	return "WriteIgnore";
	 default:
		PANIC("corrupted write_mode=%d", (int)write_mode);
	}
}

unix.superglobalmegacorp.com

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