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

//
// nono
// Copyright (C) 2018 [email protected]
//

#include <algorithm>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "scsitarget.h"
#include "scsi.h"
#include "configfile.h"
#include "mystring.h"

//
// SCSI コマンドクラス
//
class SCSICmd
{
 public:
	SCSICmd(SCSITarget *p) {
		parent = p;
	}
	virtual ~SCSICmd() { }

	// コマンドフェーズを開始する。
	// コマンドバイト列が揃ったところでコールされるので、
	// 必要な処理を行い、次のフェーズ (SCSI::Phase::*) を返す。
	virtual uint8 Command(const std::vector<uint8>& cmds) {
		// 何もしない系コマンドなら次はステータスフェーズ
		return SCSI::Phase::Status;
	}

	// データインフェーズを開始する。
	// データインフェーズ開始時にコールされるので、
	// ホストに転送するデータを用意し、
	// *ptr にデータの先頭、戻り値にデータ長を返す。
	virtual uint DataInBegin(const uint8 **ptr) {
		return 0;
	}

	// データアウトフェーズを開始する。
	// データアウトフェーズ開始時にコールされるので、
	// ホストからのデータ受信用バッファを用意し、
	// *ptr にバッファ、戻り値にバッファ長を返す。
	virtual uint DataOutBegin(uint8 **ptr) {
		return 0;
	}

	// データイン、データアウトフェーズ完了時にコールされる。
	virtual void DataDone() { }

	// ステータスフェーズを開始する。
	// ステータス (SCSI::StatusByte::*) を返す。
	virtual uint Status() {
		return SCSI::StatusByte::Good;
	}

	// メッセージインフェーズを開始する。
	// メッセージ (SCSI::MsgByte::*) を返す。
	virtual uint MsgIn() {
		return SCSI::MsgByte::CommandComplete;
	}

 protected:
	// このコマンドを呼び出したデバイス
	SCSITarget *parent = NULL;
};

// $00 TestUnitReady コマンド (共通)
// とりあえず何もせず正常を返すだけでいい。
class SCSICmdTestUnitReady : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	SCSICmdTestUnitReady(SCSITarget *p) : inherited(p) { }
	virtual ~SCSICmdTestUnitReady() { }
};

// $03 RequestSense コマンド (共通)
class SCSICmdRequestSense: public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	SCSICmdRequestSense(SCSITarget *p) : inherited(p) { }
	virtual ~SCSICmdRequestSense() { }

	virtual uint8 Command(const std::vector<uint8>& cmds) {
		parent->putlog(1, "RequestSense %d", cmds[4]);

		// この内容は全く不正。でっちあげである。

		len = (uint)cmds[4];
		mem[0] = 0x80 | 0x70;
		mem[1] = 0;
		mem[2] = 0;	// No Sense
		mem[3] = 0;
		mem[4] = 0;
		mem[5] = 0;
		mem[6] = 0;
		mem[7] = 0;	// 追加センスデータ長

		return SCSI::Phase::DataIn;
	}

	virtual uint DataInBegin(const uint8 **ptr) {
		*ptr = mem;
		return len;
	}

 private:
	uint len = 0;
	uint8 mem[256] {};
};

// $08 Read(6) コマンド (ダイレクトアクセスデバイス)
class SCSICmdRead6 : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	SCSICmdRead6(SCSITarget *p)
		: inherited(p)
	{
		hdparent = dynamic_cast<SCSIHD*>(p);
	}
	virtual ~SCSICmdRead6() { }

	virtual uint8 Command(const std::vector<uint8>& cmds) {
		uint32 lba;
		uint32 blks;

		lba  = (cmds[1] & 0x1f) << 16;
		lba |= cmds[2] << 8;
		lba |= cmds[3];
		blks = cmds[4];

		// XXX 範囲チェック
		// READ(6) の blks == 0 は 256 を意味する。
		// なお READ(10) ではこの変換は無い。
		if (blks == 0) {
			blks = 256;
		}
		dataptr = hdparent->mem + (lba * hdparent->blocksize);
		datalen = blks * parent->blocksize;

		parent->putlog(1, "READ6 $%x %d", lba, blks);

		return SCSI::Phase::DataIn;
	}

	virtual uint DataInBegin(const uint8 **ptr) {
		*ptr = dataptr;
		return datalen;
	}

 private:
	SCSIHD *hdparent = NULL;
	uint8 *dataptr = NULL;	// 今回の Read の先頭
	uint datalen = 0;		// 今回の Read のバイト数
};

// $0a Write(6) コマンド (ダイレクトアクセスデバイス)
class SCSICmdWrite6 : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	SCSICmdWrite6(SCSITarget *p)
		: inherited(p)
	{
		hdparent = dynamic_cast<SCSIHD*>(p);
		databuf = NULL;
		datalen = 0;
	}
	virtual ~SCSICmdWrite6() {
		if (databuf) {
			delete[] databuf;
		}
	}

	virtual uint8 Command(const std::vector<uint8>& cmds) {
		uint32 blks;

		lba  = (cmds[1] & 0x1f) << 16;
		lba |= cmds[2] << 8;
		lba |= cmds[3];
		blks = cmds[4];

		// XXX 範囲チェック
		// WRITE(6) の blks == 0 は 256 を意味する。
		// なお WRITE(10) ではこの変換は無い。
		if (blks == 0) {
			blks = 256;
		}
		datalen = blks * parent->blocksize;
		databuf = new uint8[datalen];

		parent->putlog(1, "WRITE6 $%x %d", lba, blks);

		return SCSI::Phase::DataOut;
	}

	virtual uint DataOutBegin(uint8 **ptr) {
		*ptr = databuf;
		return datalen;
	}

	virtual void DataDone() {
		if (hdparent->writeprotect == false)
			memcpy(hdparent->mem + (lba * hdparent->blocksize),
				databuf, datalen);
	}

 private:
	SCSIHD *hdparent = NULL;
	uint8 *databuf = NULL;	// 今回の Write バッファ
	uint datalen = 0;		// 今回の Write のバイト数
	uint32 lba = 0;			// 今回の Write の LBA
};

// $12 Inquiry コマンド (共通)
class SCSICmdInquiry : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	// inq, len は INQUIRY 応答データ
	SCSICmdInquiry(SCSITarget *p, const uint8 *inq, uint len)
		: inherited(p)
	{
		inquiry_buf = inq;
		inquiry_len = len;
	}
	virtual ~SCSICmdInquiry() { }

	virtual uint8 Command(const std::vector<uint8>& cmds) {
		inquiry_len = std::min(inquiry_len, (uint)cmds[4]);

		return SCSI::Phase::DataIn;
	}

	virtual uint DataInBegin(const uint8 **ptr) {
		*ptr = inquiry_buf;
		return inquiry_len;
	}

 private:
	// Inquiry データは SCSI 種別で異なる
	const uint8 *inquiry_buf = NULL;
	uint inquiry_len = 0;
};

// $15 ModeSelect(6) コマンド (共通)
class SCSICmdModeSelect6 : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	// ModeSelect コマンドではホストからブロック長が指定される。
	// blocksizep はその指定されたブロック長を書き戻す場所。
	SCSICmdModeSelect6(SCSITarget *p)
		: inherited(p)
	{
		modebuf = NULL;
		len = 0;
	}
	virtual ~SCSICmdModeSelect6() {
		if (modebuf) {
			delete[] modebuf;
		}
	}

	virtual uint8 Command(const std::vector<uint8>& cmds) {
		// 指定されたバッファ長のメモリを確保。
		len = cmds[4];
		modebuf = new uint8[len];
		return SCSI::Phase::DataOut;
	}

	virtual uint DataOutBegin(uint8 **ptr) {
		*ptr = modebuf;
		return len;
	}

	virtual void DataDone() {
		// Mode Select(6) のデータは以下の構造で並んでいる。
		//  ヘッダ
		//  ブロックディスクリプタ#1
		//   :
		//  ブロックディスクリプタ#n
		//  パラメータページ #1
		//   :
		//  パラメータページ #m

		const SCSI::ModeParamHdr *hdr = (const SCSI::ModeParamHdr *)modebuf;

		if (hdr->block_desc_len > 0) {
			const SCSI::BlockDesc *desc =
				(const SCSI::BlockDesc *)(modebuf + sizeof(*hdr));
			uint blocksize;
			blocksize  = (desc->block_size[0]) << 16;
			blocksize |= (desc->block_size[1]) << 8;
			blocksize |= (desc->block_size[2]);

			// ブロック長を書き戻す
			parent->blocksize = blocksize;
		}
	};

 private:
	uint8 *modebuf = NULL;
	uint len = 0;
};

// $25 ReadCapacity コマンド (ダイレクトアクセスデバイス)
class SCSICmdReadCapacity : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	SCSICmdReadCapacity(SCSITarget *p)
		: inherited(p)
	{
		hdparent = dynamic_cast<SCSIHD*>(p);

		// 応答データは
		// +00.L 最終論理ブロックアドレス
		// +04.L ブロック長
		uint32 blksize = hdparent->blocksize;
		uint32 lastblk = (hdparent->memlen / blksize) - 1;
		databuf[0] = lastblk >> 24;
		databuf[1] = lastblk >> 16;
		databuf[2] = lastblk >> 8;
		databuf[3] = lastblk;
		databuf[4] = blksize >> 24;
		databuf[5] = blksize >> 16;
		databuf[6] = blksize >> 8;
		databuf[7] = blksize;
	}
	virtual ~SCSICmdReadCapacity() { }

	virtual uint8 Command(const std::vector<uint8>& cmds) {
		uint8 pmi;
		// PMI=1 は未サポート
		pmi = cmds[8] & 0x01;
		if (pmi != 0) {
			PANIC("SCSI ReadCapacity PMI=1 not supported");
		}

		return SCSI::Phase::DataIn;
	}

	virtual uint DataInBegin(const uint8 **ptr) {
		*ptr = databuf;
		return sizeof(databuf);
	}

 private:
	SCSIHD *hdparent = NULL;
	uint8 databuf[8] {};
};

// $28 Read(10) コマンド (ダイレクトアクセスデバイス)
class SCSICmdRead10 : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	SCSICmdRead10(SCSITarget *p)
		: inherited(p)
	{
		hdparent = dynamic_cast<SCSIHD*>(p);
	}
	virtual ~SCSICmdRead10() { }

	virtual uint8 Command(const std::vector<uint8>& cmds) {
		uint32 lba;
		uint32 blks;

		if ((cmds[1] & 0x01)) {
			PANIC("SCSI Read(10) RelAdr not supported");
		}

		lba  = cmds[2] << 24;
		lba |= cmds[3] << 16;
		lba |= cmds[4] << 8;
		lba |= cmds[5];

		blks  = cmds[7] << 8;
		blks |= cmds[8];

		// XXX 範囲チェック
		dataptr = hdparent->mem + (lba * hdparent->blocksize);
		datalen = blks * parent->blocksize;

		parent->putlog(1, "READ10 $%x %d", lba, blks);

		return SCSI::Phase::DataIn;
	}

	virtual uint DataInBegin(const uint8 **ptr) {
		*ptr = dataptr;
		return datalen;
	}

 private:
	SCSIHD *hdparent = NULL;
	uint8 *dataptr = 0;		// 今回の Read の先頭
	uint datalen = 0;		// 今回の Read のバイト数
};

// サポートしていないコマンドを受け取った時の処理。
class SCSICmdNotSupportedCommand : public SCSICmd
{
	typedef SCSICmd inherited;
 public:
	SCSICmdNotSupportedCommand(SCSITarget *p) : inherited(p) { }
	virtual ~SCSICmdNotSupportedCommand() { }

	// Command は何もせずステータスに移るだけなので基底のやつでいい

	virtual uint Status() {
		return SCSI::StatusByte::CheckCondition;
	}
};

//
// SCSI ターゲットデバイス基本クラス
//

// コンストラクタ
SCSITarget::SCSITarget(SCSIHostDevice *h, int id, SCSI::DevType type)
{
	host = h;
	logname = host->GetLogName();
	devtype = type;
	ID = id;
	phase = SCSI::Phase::BusFree;
}

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

// フェーズを newphase に遷移する
void
SCSITarget::SetPhase(uint newphase)
{
	// フェーズ遷移時に ATN をチェック。
	// バスフリーフェーズ・アービトレーションフェーズでは ATN はセットできない。
	// ので、それら以外で ATN が立ってる時だけ処理が必要。
	if (phase != SCSI::Phase::BusFree &&
	    phase != SCSI::Phase::Arbitration &&
	    GetATN())
	{
		// lastmsg は CommandComplete に初期化してあり、CommandComplete には
		// 解除義務はないので、1回目は必ず default に落ちるという寸法。
		switch (lastmsg) {
		 case SCSI::MsgByte::Disconnect:
		 case SCSI::MsgByte::InitiatorDetectedError:
		 case SCSI::MsgByte::Abort:
		 case SCSI::MsgByte::MessageReject:
		 case SCSI::MsgByte::NoOperation:
		 case SCSI::MsgByte::MessageParityError:
		 case SCSI::MsgByte::BusDeviceReset:
		 case SCSI::MsgByte::AbortTag:
		 case SCSI::MsgByte::ClearQueue:
		 case SCSI::MsgByte::InitiateRecovery:
		 case SCSI::MsgByte::ReleaseRecovery:
		 case SCSI::MsgByte::TerminateIOProcess:
		 // 2バイトコードは未対応
			// 解除義務があるので、継続してたらバスフリーへ移行。
			newphase = SCSI::Phase::BusFree;
			break;
		 default:
			// 解除義務はないので、継続して受け付ける。
			// (1回目もここに来る)
			nextphase = newphase;
			newphase = SCSI::Phase::MsgOut;
			break;
		}
	}

	// MsgOut フェーズを抜ける際には必ず lastmsg を CommandComplete に戻す。
	// CommandComplete が1回目フラグも兼ねているので。
	if (phase == SCSI::Phase::MsgOut && newphase != SCSI::Phase::MsgOut) {
		lastmsg = SCSI::MsgByte::CommandComplete;
	}

	// フェーズ遷移
	phase = newphase;
	switch (newphase) {
	 case SCSI::Phase::BusFree:
		// 自身の状態を変更
		BusFreePhase();
		// バスの状態を変更 (バスを管理してるのはここではホストの基本クラス)
		host->B_BusFree(ID);
		// ホストに状態変更を通知
		host->BusRelease();
		break;
	 case SCSI::Phase::Arbitration:
	 case SCSI::Phase::Selection:
	 case SCSI::Phase::Reselection:
		// ?
		break;
	 case SCSI::Phase::DataOut:
		DataOutPhase();
		break;
	 case SCSI::Phase::DataIn:
		DataInPhase();
		break;
	 case SCSI::Phase::Command:
		CommandPhase();
		break;
	 case SCSI::Phase::Status:
		StatusPhase();
		break;
	 case SCSI::Phase::MsgOut:
		MsgOutPhase();
		break;
	 case SCSI::Phase::MsgIn:
		MsgInPhase();
		break;
	}
}

// セレクション成功。
void
SCSITarget::Selected()
{
	SetPhase(SCSI::Phase::Command);
	SetREQ(true);
}

void
SCSITarget::Ack()
{
	lastdata = GetDATA();
	SetREQ(false);
	putlog(3, "#%d data=%02x", ID, lastdata);
}

// バスフリーフェーズ
void
SCSITarget::BusFreePhase()
{
	lastdata = 0;
	lastmsg = 0;
}

void
SCSITarget::DataOutPhase()
{
	SetMSG(false);
	SetCD(false);
	SetIO(false);
	putlog(3, "#%d Target DataOutPhase", ID);
}

void
SCSITarget::DataInPhase()
{
	SetMSG(false);
	SetCD(false);
	SetIO(true);
	putlog(3, "#%d Target DataInPhase", ID);
}

void
SCSITarget::CommandPhase()
{
	SetMSG(false);
	SetCD(true);
	SetIO(false);
	putlog(3, "#%d Target CommandPhase", ID);
}

void
SCSITarget::StatusPhase()
{
	SetMSG(false);
	SetCD(true);
	SetIO(true);
	putlog(3, "#%d Target StatusPhase", ID);
}

void
SCSITarget::MsgOutPhase()
{
	SetMSG(true);
	SetCD(true);
	SetIO(false);
	putlog(3, "#%d Target MsgOutPhase", ID);
}

void
SCSITarget::MsgInPhase()
{
	SetMSG(true);
	SetCD(true);
	SetIO(true);
	putlog(3, "#%d Target MsgInPhase", ID);
}

// データインフェーズの処理。
uint
SCSITarget::ExecDataIn(const uint8 **ptr)
{
	// コマンド固有のデータインフェーズの処理
	uint len = cmd->DataInBegin(ptr);

	// ステータスフェーズに移行
	SetPhase(SCSI::Phase::Status);

	return len;
}

// データアウトフェーズの処理。
uint
SCSITarget::ExecDataOut(uint8 **ptr)
{
	// コマンド固有のデータアウトフェーズの処理
	uint len = cmd->DataOutBegin(ptr);

	// ステータスフェーズに移行
	SetPhase(SCSI::Phase::Status);

	return len;
}

// コマンドフェーズの処理。
// イニシエータが必要な長さ分のコマンドをホスト OS から受信したところで
// イニシエータ側から呼ばれて、コマンドの処理を行う。
// 処理後は次のフェーズに遷移する。
void
SCSITarget::ExecCommand(const std::vector<uint8>& argcmds)
{
	cmds = argcmds;

	const char *cmdname = SCSI::GetCommandName(cmds[0]);
	if (cmdname != NULL) {
		putlog(1, "#%d $%02x %s", ID, cmds[0], cmdname);
	} else {
		putlog(1, "#%d $%02x unknown command", ID, cmds[0]);
	}

	// コマンドを選択。
	// まずデバイス固有のコマンドをサーチ。
	cmd = ExecDeviceCommand();
	// 派生クラス側で選択されなければ、こちらでサーチ。
	if (cmd == NULL) {
		switch (cmds[0]) {
		 case SCSI::Command::TestUnitReady:
			cmd = new SCSICmdTestUnitReady(this);
			break;

		 case SCSI::Command::RequestSense:
			cmd = new SCSICmdRequestSense(this);
			break;

		 case SCSI::Command::ModeSelect6:
			cmd = new SCSICmdModeSelect6(this);
			break;

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

			cmd = new SCSICmdNotSupportedCommand(this);
			break;
		}
	}

	uint newphase = cmd->Command(cmds);
	SetPhase(newphase);
}

SCSICmd *
SCSITarget::ExecDeviceCommand()
{
	PANIC("should not reached");
}

// ステータスバイトを取得およびデータアウトフェーズ完了処理。
uint8
SCSITarget::GetStatusByte()
{
	// データフェーズの完了処理をここで行う。
	cmd->DataDone();

	// XXX まだコマンド側からログが出せないのでとりあえず
	if (cmds[0] == SCSI::Command::ModeSelect6) {
		putlog(2, "#%d Mode Select 論理ブロック長 = %d", ID, blocksize);
	}

	// ステータスバイトを取得。
	uint statusbyte = cmd->Status();

	// メッセージインフェーズに移行。
	SetPhase(SCSI::Phase::MsgIn);
	return statusbyte;
}

uint8
SCSITarget::GetMessageByte()
{
	// メッセージを取得。
	uint messagebyte = cmd->MsgIn();

	// CommandComplete メッセージを正常に送信したらバスフリーフェーズに移行。
	// 実際には正常に送信できたら、だけど。
	if (messagebyte == SCSI::MsgByte::CommandComplete) {
		SetPhase(SCSI::Phase::BusFree);
	}

	// コマンドクラスを解放
	// ここでいいか。
	delete cmd;
	cmd = NULL;

	return messagebyte;
}


//
// SCSI HD
//

// コンストラクタ
SCSIHD::SCSIHD(SCSIHostDevice *h, int id)
	: inherited(h, id, SCSI::HD)
{
	devname = "SCSIHD";
	logname = "scsihd";

	fd = -1;
}

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

// イメージファイルの読み込み
bool
SCSIHD::Init()
{
	struct stat st;

	const std::string myname = string_format("%s(%s,ID%d)",
		devname, host->GetConfigName(), ID);
	const std::string keybody = string_format("%s_id%d_",
		host->GetConfigName(), ID);
	const std::string imgkey = string_format("%s%s",
		keybody.c_str(), "image");
	const std::string wpkey  = string_format("%s%s",
		keybody.c_str(), "writeprotect");

	// devtype があって image がないのは異常状態なのでエラー
	const std::string& filename = gConfig->ReadStr(imgkey);
	if (filename == "") {
		gConfig->Err(imgkey, "not specified");
		return false;
	}

	writeprotect = (bool)gConfig->ReadInt(wpkey);
	if (writeprotect) {
		putmsg(0, "%s: write protected", myname.c_str());
	}

	const std::string path = gConfig->GetConfigDir() + filename;
	fd = open(path.c_str(), O_RDWR);
	if (fd == -1) {
		warn("%s cannot open %s", myname.c_str(), path.c_str());
		return false;
	}

	if (fstat(fd, &st) == -1) {
		warn("%s cannot stat %s", myname.c_str(), path.c_str());
		close(fd);
		return false;
	}

	memlen = st.st_size;
	mem = (uint8 *)mmap(NULL, memlen, PROT_READ | PROT_WRITE,
		MAP_SHARED, fd, 0);
	if (mem == MAP_FAILED) {
		warn("%s cannot mmap %s", myname.c_str(), path.c_str());
		close(fd);
		return false;
	}

	return true;
}

// HD 固有のコマンドの処理。
// こちらで処理すれば次フェーズを返す。そうでなければ None を返す。
SCSICmd *
SCSIHD::ExecDeviceCommand()
{
	switch (cmds[0]) {
	 case SCSI::Command::Read6:
		return new SCSICmdRead6(this);

	 case SCSI::Command::Write6:
		return new SCSICmdWrite6(this);

	 case SCSI::Command::Inquiry:
		// これは共通コマンドだけど引数だけ違う
		return new SCSICmdInquiry(this, inquiry_data, sizeof(inquiry_data));

	 case SCSI::Command::ReadCapacity:
		return new SCSICmdReadCapacity(this);

	 case SCSI::Command::Read10:
		return new SCSICmdRead10(this);

	 default:
		// それ以外は基本クラス側で処理する。
		return NULL;
	}
}

// HD の inquiry データ
/*static*/ const uint8
SCSIHD::inquiry_data[36] = {
	0x00,	// 00 Qualifier + DeviceType
	0x00,	// 01 RMB
	0x40,	// 02 ISO version
	0x00,	// 03 Response Data Format?
	36 - 4,	// 04 追加データ長
	0,		// 05 Reserved
	0,		// 06 Reserved
	0x00,	// 07
	'V', 'E', 'N', 'D', 'E', 'R', ' ', ' ',
	'P', 'R', 'O', 'D', 'U', 'C', 'T', ' ',
	'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ',
	'0', '1', '2', '3',
};

unix.superglobalmegacorp.com

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