File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / scsicmd.cpp
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:42 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, v024, v023, HEAD
nono 1.3.0

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

//
// SCSI コマンド
//

#include "scsicmd.h"
#include "scsidev.h"

#define LUN	(cmdseq[1] >> 5)

// LUN != 0 のリクエストをエラーにする (ExecCommand() 内で使う)。
#define CheckLUN	do {	\
	if (LUN != 0) {	\
		parent->sensekey = SCSI::SenseKey::NotReady;	\
		parent->senseasc = SCSI::ASC::LogicalUnitNotSupported;	\
		status_byte = SCSI::StatusByte::CheckCondition;	\
		return SCSI::XferPhase::Status;	\
	}	\
} while (0)
// メディアがなければエラーにする (ExecCommand() 内で使う)。
#define CheckMedium	do {	\
	if (disk->IsMediumLoaded() == false) {	\
		parent->sensekey = SCSI::SenseKey::NotReady;	\
		parent->senseasc = SCSI::ASC::MediumNotPresent;	\
		status_byte = SCSI::StatusByte::CheckCondition;	\
		return SCSI::XferPhase::Status;	\
	}	\
} while (0)


//
// SCSI コマンドの基本クラス
//

// コンストラクタ
SCSICmd::SCSICmd(SCSITarget *parent_)
	: parent(parent_)
{
}

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

// コマンドフェーズ
SCSI::XferPhase
SCSICmd::ExecCommand(std::vector<uint8>& cmdseq)
{
	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;

	return SCSI::XferPhase::Status;
}


//
// SCSI コマンド: サポートしていないコマンド
//

// コンストラクタ
SCSICmdNotSupportedCommand::SCSICmdNotSupportedCommand(SCSITarget *parent_)
	: inherited(parent_)
{
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdNotSupportedCommand::ExecCommand(std::vector<uint8>& cmdseq)
{
	// XXX センスキーよく分からんけどとりあえず
	parent->sensekey = SCSI::SenseKey::IllegalRequest;
	parent->senseasc = SCSI::ASC::InvalidCommandOperationCode;
	status_byte = SCSI::StatusByte::CheckCondition;

	return SCSI::XferPhase::Status;
}


//
// SCSI コマンド 0x03 RequestSense (共通)
//
//    +---+---+---+---+---+---+---+---+
//  0 |         Operation Code        |
//    +-----------+-------------------+
//  1 |    LUN    |         0         |
//    +-------+---+-------------------+
//  2 |               0               |
//  3 |               0               |
//    +-------------------------------+
//  4 |        アロケーション長       |
//    +-------------------------------+
//  5 |          Control Byte         |
//    +-------------------------------+

// コンストラクタ
SCSICmdRequestSense::SCSICmdRequestSense(SCSITarget *parent_)
	: inherited(parent_)
{
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdRequestSense::ExecCommand(std::vector<uint8>& cmdseq)
{
	CheckLUN;

	// 応答のセンスデータは SCSI-2 形式(最小18バイト) を実装。
	// 今の所、センスキー、ASC、ASCQ だけで返せる内容にだけ対応している。
	// これ以外のフィールドを返す必要が出来たらまた考えること。
	uint sensekey = parent->sensekey;
	uint senseasc = parent->senseasc;
	parent->putlogf(1, lstr("%s -> %s asc=$%04x",
		SCSI::GetCommandName(cmdseq[0]),
		SCSI::GetSenseKeyDisp(sensekey).c_str(),
		senseasc));

	// 送信用に取り出した後でセンスデータをクリア
	// (reqlen によって実際に送信したかどうかは問わない)
	parent->ClearSense();

	buf.clear();
	buf.resize(18);
	buf[0]  = 0x70;				// Valid=0、エラーコード 0x70
	buf[1]  = 0;				// セグメント番号(?)
	buf[2]  = sensekey;			// センスキー (上位4ビットは今の所 0)
	buf[3]  = 0;				// Information
	buf[4]  = 0;				// Information
	buf[5]  = 0;				// Information
	buf[6]  = 0;				// Information
	buf[7]  = 0;				// 追加センスデータ長 0
	buf[8]  = 0;				// Command specific Information
	buf[9]  = 0;				// Command specific Information
	buf[10] = 0;				// Command specific Information
	buf[11] = 0;				// Command specific Information
	buf[12] = senseasc >> 8;
	buf[13] = senseasc & 0xff;
	buf[14] = 0;				// FRU
	buf[15] = 0;				// センスキー固有情報
	buf[16] = 0;				// センスキー固有情報
	buf[17] = 0;				// センスキー固有情報
	// 追加センスデータなし

	// アロケーション長が 0 なら送信せずに終了。
	// XXX 送信せずにってステータスフェーズに移るってことだろうか?
	uint reqlen = cmdseq[4];
	if (reqlen == 0) {
		return SCSI::XferPhase::Status;
	}
	// 本来の応答データ長か reqlen の短いほうまで。
	if (reqlen < buf.size()) {
		buf.resize(reqlen);
	}
	return SCSI::XferPhase::DataIn;
}


//
// SCSI コマンド 0x08 Read(6)  (DA / CD-ROM)
//               0x28 Read(10) (DA / CD-ROM)
//
// Read(6):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x08     |
//    +-----------+-------------------+
//  1 |    LUN    |                   |
//    +-----------+                   |
//  2 |      論理ブロックアドレス     |
//  3 |                               |
//    +-------------------------------+
//  4 |    転送データ長 (0 なら 256)  |
//    +-------------------------------+
//  5 |          Control Byte         |
//    +-------------------------------+
//
// Read(10):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x28     |
//    +-----------+-----------+---+---+
//  1 |    LUN    |DPO|FUA|   0   |Rel|
//    +-----------+-----------+---+---+
//  2 |                               |
//  3 |      論理ブロックアドレス     |
//  4 |                               |
//  5 |                               |
//    +-------------------------------+
//  6 |               0               |
//    +-------------------------------+
//  7 |          転送データ長         |
//  8 |       (0 なら転送しない)      |
//    +-------------------------------+
//  9 |          Control Byte         |
//    +-------------------------------+

// コンストラクタ
SCSICmdRead::SCSICmdRead(SCSIDisk *parent_)
	: inherited(parent_)
{
	disk = parent_;
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdRead::ExecCommand(std::vector<uint8>& cmdseq)
{
	uint32 lba;
	uint32 blks;

	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;
	CheckMedium;

	const uint8 cmd = cmdseq[0];
	if (cmd == SCSI::Command::Read10) {
		if ((cmdseq[1] & 0x01)) {
			parent->putlogf(0, lstr("%s RelAdr not supported",
				SCSI::GetCommandName(cmd)));
		}
		lba  = cmdseq[2] << 24;
		lba |= cmdseq[3] << 16;
		lba |= cmdseq[4] << 8;
		lba |= cmdseq[5];
		blks  = cmdseq[7] << 8;
		blks |= cmdseq[8];
	} else {
		lba  = (cmdseq[1] & 0x1f) << 16;
		lba |=  cmdseq[2] << 8;
		lba |=  cmdseq[3];
		blks =  cmdseq[4];
		// Read(6) の blks == 0 は 256 ブロック転送を意味する。
		if (blks == 0) {
			blks = 256;
		}
	}
#if !defined(NO_READWRITE_LOG)
	parent->putlogf(1, lstr("%s LBA=0x%x blks=0x%x",
		SCSI::GetCommandName(cmd), lba, blks));
#endif

	uint64 start = (uint64)lba * parent->GetBlocksize();
	uint64 len = (uint64)blks * parent->GetBlocksize();
	uint64 end = start + len;

	if (end > disk->GetSize()) {
		parent->sensekey = SCSI::SenseKey::IllegalRequest;
		parent->senseasc = SCSI::ASC::InvalidFieldInCDB;
		status_byte = SCSI::StatusByte::CheckCondition;
		return SCSI::XferPhase::Status;
	}
	buf.resize(len);
	disk->Read(buf, start);

	// 平均シークタイム
	optime = disk->GetSeekTime();

	return SCSI::XferPhase::DataIn;
}


//
// SCSI コマンド 0x0a Write(6)  (DA / CD-ROM)
//               0x2a Write(10) (DA / CD-ROM)
//
// Write(6):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x0a     |
//    +-----------+-------------------+
//  1 |    LUN    |                   |
//    +-----------+                   |
//  2 |      論理ブロックアドレス     |
//  3 |                               |
//    +-------------------------------+
//  4 |    転送データ長 (0 なら 256)  |
//    +-------------------------------+
//  5 |          Control Byte         |
//    +-------------------------------+
//
// Write(10):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x2a     |
//    +-----------+-----------+---+---+
//  1 |    LUN    |DPO|FUA|   0   |Rel|
//    +-----------+-----------+---+---+
//  2 |                               |
//  3 |      論理ブロックアドレス     |
//  4 |                               |
//  5 |                               |
//    +-------------------------------+
//  6 |               0               |
//    +-------------------------------+
//  7 |          転送データ長         |
//  8 |       (0 なら転送しない)      |
//    +-------------------------------+
//  9 |          Control Byte         |
//    +-------------------------------+

// コンストラクタ
SCSICmdWrite::SCSICmdWrite(SCSIDisk *parent_)
	: inherited(parent_)
{
	disk = parent_;
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdWrite::ExecCommand(std::vector<uint8>& cmdseq)
{
	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;
	CheckMedium;

	cmd = cmdseq[0];
	if (cmd == SCSI::Command::Write10) {
		lba  = cmdseq[2] << 24;
		lba |= cmdseq[3] << 16;
		lba |= cmdseq[4] << 8;
		lba |= cmdseq[5];
		blks  = cmdseq[7] << 8;
		blks |= cmdseq[8];
	} else {
		lba  = (cmdseq[1] & 0x1f) << 16;
		lba |=  cmdseq[2] << 8;
		lba |=  cmdseq[3];
		blks =  cmdseq[4];
		// Write(6) の blks == 0 は 256 ブロック転送を意味する。
		if (blks == 0) {
			blks = 256;
		}
	}

	if (((uint64)lba + blks) * parent->GetBlocksize() > disk->GetSize()) {
		parent->sensekey = SCSI::SenseKey::IllegalRequest;
		parent->senseasc = SCSI::ASC::InvalidFieldInCDB;
		status_byte = SCSI::StatusByte::CheckCondition;
		return SCSI::XferPhase::Status;
	}

	// 平均シークタイム
	optime = disk->GetSeekTime();

	recvbytes = blks * parent->GetBlocksize();
	return SCSI::XferPhase::DataOut;
}

// データアウトフェーズ完了
SCSI::XferPhase
SCSICmdWrite::DoneDataOut()
{
#if !defined(NO_READWRITE_LOG)
	parent->putlogf(1, lstr("%s LBA=0x%x blks=0x%x",
		SCSI::GetCommandName(cmd), lba, blks));
#endif
	uint64 start = (uint64)lba * parent->GetBlocksize();
	if (disk->Write(buf, start) == false) {
		parent->sensekey = SCSI::SenseKey::HardwareError;
		parent->senseasc = SCSI::ASC::PeripheralDeviceWriteFault;
		status_byte = SCSI::StatusByte::CheckCondition;
	}
	return SCSI::XferPhase::Status;
}


//
// SCSI コマンド 0x12 Inquiry (共通)
//
//    +---+---+---+---+---+---+---+---+
//  0 |         Operation Code        |
//    +-----------+---------------+---+
//  1 |    LUN    |       0       | E | E:Enable Vital Product Data
//    +-----------+---------------+---+
//  2 |          ページコード         |
//    +-------------------------------+
//  3 |               0               |
//    +-------------------------------+
//  4 |        アロケーション長       |
//    +-------------------------------+
//  5 |          Control Byte         |
//    +-------------------------------+
//
// 応答
//    +---+---+---+---+---+---+---+---+
//  0 | Qualifier | Device Type Code  |
//    +---+-------+-------------------+
//  1 |RMB|    Device Type Modifier   |
//    +---+---+-----------+-----------+
//  2 |ISO ver| ECMA ver  | ANSI ver  |
//    +---+---+-------+---------------+
//  3 | A | T |   0   |ResponseFormat |
//    +---+---+-------+---------------+
//  4 |       追加データ長 (n-4)      |
//    +-------------------------------+
//  5 |               0               |
//  6 |               0               |
//    +---+---+---+---+---+---+---+---+
//  7 |Rel|W32|W16|Syn|Lnk| 0 |CQ |SR |
//    +---+---+---+---+---+---+---+---+
//  8 |      ベンダ文字列 (8Byte)     |
//    :                               :
// 15 |                               |
//    +-------------------------------+
// 16 |   プロダクト文字列 (16Byte)   |
//    :                               :
// 31 |                               |
//    +-------------------------------+
// 32 | プロダクト版数文字列 (4Byte)  |
//    :                               :
// 35 |                               |
//    +-------------------------------+
// 36         以下ベンダ固有

// コンストラクタ
SCSICmdInquiry::SCSICmdInquiry(SCSITarget *parent_)
	: inherited(parent_)
{
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdInquiry::ExecCommand(std::vector<uint8>& cmdseq)
{
	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	uint reqlen = (uint)cmdseq[4];
	parent->putlogf(1, lstr("%s LUN=%u reqlen=$%02x",
		SCSI::GetCommandName(cmdseq[0]), LUN, reqlen));

	// 基本の 36バイト分を用意した上でデバイスに問い合わせ。
	// これ以上必要ならデバイス側で伸ばしてもよい。
	buf.resize(36);
	std::fill(buf.begin(), buf.end(), 0);
	if (parent->Inquiry(buf, LUN) == false) {
		status_byte = SCSI::StatusByte::CheckCondition;
		return SCSI::XferPhase::Status;
	}

	// 本来の応答データ長か reqlen の短いほうまで。
	uint origlen = buf.size();
	if (reqlen < origlen) {
		buf.resize(reqlen);
	}

	if (parent->loglevel >= 2) {
		// 応答をダンプ、ここで表示する長さは本来の応答データ長
		std::string str = string_format("%s Response len=$%02x:",
			SCSI::GetCommandName(cmdseq[0]), origlen);
		// 一方ダンプは実際の送信長
		for (int i = 0; i < buf.size(); i++) {
			if (i % 8 == 0 && i != 0) {
				str += " ";
			}
			str += string_format(" %02x", buf[i]);
		}
		parent->putlogn("%s", str.c_str());
	}

	return SCSI::XferPhase::DataIn;
}


//
// SCSI コマンド 0x15 ModeSelect(6)  (共通)
//               0x55 ModeSelect(10) (共通)
//
// ModeSelect(6):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x15     |
//    +-----------+---+-----------+---+
//  1 |    LUN    |PF |     0     |SP | PF:PageFormat, SP:SavePages
//    +-----------+---+-----------+---+
//  2 |               0               |
//  3 |               0               |
//    +-------------------------------+
//  4 |      パラメータリスト長       |
//    +-------------------------------+
//  5 |          Control Byte         |
//    +-------------------------------+
//
// ModeSelect(10):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x55     |
//    +-----------+---+-----------+---+
//  1 |    LUN    |PF |     0     |SP | PF:PageFormat, SP:SavePages
//    +-----------+---+-----------+---+
//  2 |                               |
//  3 |                               |
//  4 |               0               |
//  5 |                               |
//  6 |                               |
//    +-------------------------------+
//  7 |      パラメータリスト長       |
//  8 |               0               |
//    +-------------------------------+
//  9 |          Control Byte         |
//    +-------------------------------+
//
// 応答: (モードパラメータヘッダ、6の時)
//    +-------------------------------+
//  0 |      モードパラメータ長       | (このバイトを含まずこれ以降)
//    +-------------------------------+
//  1 |        メディアタイプ         |
//    +-------------------------------+
//  2 |     デバイス固有パラメータ    |
//    +-------------------------------+
//  3 |   ブロックディスクリプタ長 m  |
//    +-------------------------------+
//
//    (モードパラメータヘッダ、10の時)
//    +-------------------------------+
//  0 |      モードパラメータ長       | (このフィールドを含まずこれ以降?)
//  1 |                               |
//    +-------------------------------+
//  2 |        メディアタイプ         |
//    +-------------------------------+
//  3 |     デバイス固有パラメータ    |
//    +-------------------------------+
//  4 |               0               |
//  5 |               0               |
//    +-------------------------------+
//  6 |   ブロックディスクリプタ長 m  |
//  7 |                               |
//    +-------------------------------+
//
//    (ブロックディスクリプタ) * m 個
//    +-------------------------------+
//  0 |        デンシティコード       | (デバイスタイプごとに固有)
//    +-------------------------------+
//  1 |                               |
//  2 |          ブロック数           |
//  3 |                               |
//    +-------------------------------+
//  4 |               0               |
//    +-------------------------------+
//  5 |                               |
//  6 |          ブロック長           |
//  7 |                               |
//    +-------------------------------+ ブロックディスクリプタここまで
//
//    (パラメータページ) * ? 個
//    +---+---+-----------------------+
//  0 |PS | 0 |     ページコード      | PS:Parameter Savable
//    +---+---+-----------------------+
//  1 |           ページ長            | (このバイトを含まずこれ以降の長さ)
//    +-------------------------------+
//  2 |  ページコード固有のパラメータ |
//    :                               :
//    |                               |
//    +-------------------------------+

// コンストラクタ
SCSICmdModeSelect::SCSICmdModeSelect(SCSITarget *parent_)
	: inherited(parent_)
{
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdModeSelect::ExecCommand(std::vector<uint8>& cmdseq)
{
	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;

	cmd = cmdseq[0];
	if (cmd == SCSI::Command::ModeSelect10) {
		recvbytes  = cmdseq[7] << 8;
		recvbytes |= cmdseq[8];
	} else {
		recvbytes = cmdseq[4];
	}

	parent->putlogf(1, lstr("%s len=$%02x",
		SCSI::GetCommandName(cmd), recvbytes));

	return SCSI::XferPhase::DataOut;
}

// データアウトフェーズ完了
SCSI::XferPhase
SCSICmdModeSelect::DoneDataOut()
{
	uint block_desc_len;
	const uint8 *desc;

	// ヘッダの構造と長さが ModeSelect(6)/(10) で違う
	if (cmd == SCSI::Command::ModeSelect10) {
		block_desc_len  = buf[6] << 8;
		block_desc_len |= buf[7];
		desc = &buf[8];
	} else {
		block_desc_len = buf[3];
		desc = &buf[4];
	}

	if (block_desc_len > 0) {
		// ブロックディスクリプタの構造は ModeSelect(6)/(10) で共通
		uint blocksize;
		blocksize  = desc[5] << 16;
		blocksize |= desc[6] << 8;
		blocksize |= desc[7];

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

	return SCSI::XferPhase::Status;
}


//
// SCSI コマンド 0x1a ModeSense(6)  (共通)
//               0x5a ModeSense(10) (共通)
//
// ModeSense(6):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x1a     |
//    +-----------+---+---+-----------+
//  1 |    LUN    | 0 |DBD|     0     | DBD:Disable Block Descriptors
//    +-------+---+---+---+-----------+
//  2 |   PC  |       Page Code       | PC:Page Control
//    +-------+-----------------------+
//  3 |               0               |
//    +-------------------------------+
//  4 |        アロケーション長       |
//    +-------------------------------+
//  5 |          Control Byte         |
//    +-------------------------------+
//
// ModeSense(10):
//    +---+---+---+---+---+---+---+---+
//  0 |     Operation Code = 0x5a     |
//    +-----------+---+---+-----------+
//  1 |    LUN    |LLA|DBD|     0     | DBD:Disable Block Descriptors
//    +-------+---+---+---+-----------+ LLA:Long LBA Accepted
//  2 |   PC  |       Page Code       | PC:Page Control
//    +-------+-----------------------+
//  3 |           SubPageCode         |
//    +-------------------------------+
//  4 |                               |
//  5 |               0               |
//  6 |                               |
//    +-------------------------------+
//  7 |        アロケーション長       |
//  8 |                               |
//    +-------------------------------+
//  9 |          Control Byte         |
//    +-------------------------------+

// コンストラクタ
SCSICmdModeSense::SCSICmdModeSense(SCSITarget *parent_)
	: inherited(parent_)
{
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdModeSense::ExecCommand(std::vector<uint8>& cmdseq)
{
	uint reqlen;

	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;

	uint cmd = cmdseq[0];
	pc = (PC)(cmdseq[2] >> 6);
	pagecode = (cmdseq[2] & 0x3f);
	if (cmd == SCSI::Command::ModeSense10) {
		reqlen  = cmdseq[7] << 8;
		reqlen |= cmdseq[8];
	} else {
		reqlen = cmdseq[4];
	}
	bool dbd = (cmdseq[1] & 0x08);

	if (parent->loglevel >= 1) {
		log = string_format("%s %s",
			SCSI::GetCommandName(cmd),
			SCSI::GetModePageDisp(pagecode).c_str());
		if (pc != PC::Current) {
			log += string_format(" PC=%u", (uint)pc);
		}
	}

	// ヘッダを用意。

	// メディアタイプ。
	// XXX 実際にはデバイス種別ごとに定義があるが、とりあえず 0 でよさげ
	const uint8 mediatype = 0;
	// デバイス固有パラメータを取得
	uint8 devspec = parent->GetDeviceSpecificParam();
	// ブロックディスクリプタ長 (バイト数)
	uint8 bdlen = dbd ? 0 : 8;

	// ヘッダは (6) と (10) で形式が違う。
	// モードパラメータ長(全長)は最後に代入する。
	buf.clear();
	if (cmd == SCSI::Command::ModeSense10) {
		buf.resize(8);
		buf[2] = mediatype;
		buf[3] = devspec;
		buf[6] = bdlen >> 8;
		buf[7] = bdlen;
	} else {
		buf.resize(4);
		buf[1] = mediatype;
		buf[2] = devspec;
		buf[3] = bdlen;
	}

	// ブロックディスクリプタ
	if (dbd == false) {
		// ブロック数とブロックサイズ
		uint32 blocks = 0;
		uint32 blocklen = parent->GetBlocksize();
		const SCSIDisk *disk = dynamic_cast<const SCSIDisk*>(parent);
		if (disk) {
			blocks = disk->GetSize() / blocklen;
		}

		// 1バイトずつ追加していくほうが楽…。
		// デンシティコードは実際にはデバイスごとだが、だいたい 0 でよさげ。
		buf.emplace_back(0);						// +0 デンシティコード
		buf.emplace_back((blocks >> 16) & 0xff);	// +1 ブロック数
		buf.emplace_back((blocks >>  8) & 0xff);	// +2 ブロック数
		buf.emplace_back( blocks        & 0xff);	// +3 ブロック数
		buf.emplace_back(0);						// +4 reserved
		buf.emplace_back((blocklen >> 16) & 0xff);	// +5 ブロック長
		buf.emplace_back((blocklen >>  8) & 0xff);	// +6 ブロック長
		buf.emplace_back( blocklen        & 0xff);	// +7 ブロック長
	}

	// モードページ
	if (pagecode == SCSI::ModePage::AllPages) {
		// サポートしているのを全部送る。
		if (parent->loglevel >= 1) {
			log += " " + SCSI::GetModePageDisp(pagecode);
			log += "->";
		}
		for (auto& pair : Pagelist) {
			auto code = pair.first;
			auto func = pair.second;

			if ((this->*(func))() != ModePageResult::Failed) {
				if (parent->loglevel >= 1) {
					log += " " + SCSI::GetModePageDisp(code);
				}
			}
		}
	} else {
		// 指定されたページコードを送る。
		ModePageResult r = ModePageResult::NotSupported;
		for (auto& pair : Pagelist) {
			auto code = pair.first;
			auto func = pair.second;

			if (pagecode == code) {
				r = (this->*(func))();
				break;
			}
		}

		// Failed は対応しない/できないと分かっているページ、
		// NotSupported は未実装や知らないページ。
		if (r <= ModePageResult::Failed) {
			if (parent->loglevel >= 1) {
				if (r == ModePageResult::NotSupported) {
					parent->putlogn("%s not supported", log.c_str());
				} else {
					parent->putlogn("%s", log.c_str());
				}
			}
			parent->sensekey = SCSI::SenseKey::IllegalRequest;
			parent->senseasc = SCSI::ASC::InvalidFieldInCDB;
			status_byte = SCSI::StatusByte::CheckCondition;
			return SCSI::XferPhase::Status;
		}
	}

	parent->putlogf(1, [&] { return log; });

	// モードパラメータ長は自フィールドを含まない長さ
	if (cmd == SCSI::Command::ModeSelect10) {
		uint buflen = buf.size() - 2;
		buf[0] = buflen >> 8;
		buf[1] = buflen;
	} else {
		uint buflen = buf.size() - 1;
		buf[0] = buflen;
	}

	// 本来の応答データ長か reqlen の短いほうまで
	if (reqlen < buf.size()) {
		buf.resize(reqlen);
	}
	return SCSI::XferPhase::DataIn;
}

// ページを送信する順(=昇順)に (手動で) 並べること。
// ただし 0x00 (VendorSpecific) は最後。
#define PAGE(code, func)	\
	{ SCSI::ModePage::code, &SCSICmdModeSense::func }
/*static*/ const std::vector<std::pair<uint8, SCSICmdModeSense::PageFunc>>
SCSICmdModeSense::Pagelist {
	PAGE(/*04*/RigidDiskGeometry,	AddNotSupportedPage),
	PAGE(/*05*/FlexibleDisk,		AddNotSupportedPage),
	PAGE(/*08*/CachingPage,			AddCachingPage),
	PAGE(/*00*/VendorSpecific,		AddEmptyPage),
};

// サポートしないページ
//
// ページコード 04 RigidDiskGeometry (DA)
//  -> 生イメージからジオメトリを求めるのは意味ないので実装しない。
// ページコード 05 FlexibleDisk (DA)
//  -> ジオメトリなどのパラメータがメインっぽいので実装しない。
SCSICmdModeSense::ModePageResult
SCSICmdModeSense::AddNotSupportedPage()
{
	return ModePageResult::Failed;
}

// 空のページ
SCSICmdModeSense::ModePageResult
SCSICmdModeSense::AddEmptyPage()
{
	std::vector<uint8> page(2);
	page[0] = pagecode;				// +0 ページコード
	page[1] = page.size() - 2;		// +1 このバイト以降のページ長

	buf.insert(buf.end(), page.begin(), page.end());
	buf[0] += page.size();
	return ModePageResult::Success;
}

// ページコード 08 CachingPage (DA, MO, CD-ROM)
SCSICmdModeSense::ModePageResult
SCSICmdModeSense::AddCachingPage()
{
	// XXX この assert いる?
	assert(dynamic_cast<const SCSIDisk*>(parent) != NULL);

	// とりあえずパラメータはなんもなし。
	// PC::Changeable でも全部 0 なのでこのままでいい。
	std::vector<uint8> page(12);
	page[0] = pagecode;				// +0 ページコード
	page[1] = page.size() - 2;		// +1 このバイト以降のページ長
	// +2 WCE, MF, RCD フラグ
	// +3 データの保持優先度
	// +4 プリフェッチ抑止ブロック数
	// +6 最小プリフェッチ
	// +8 最大プリフェッチ
	// +10 最大プリフェッチ制限ブロック数

	buf.insert(buf.end(), page.begin(), page.end());
	buf[0] += page.size();
	return ModePageResult::Success;
}


//
// SCSI コマンド 0x1b StartStopUnit (DA / CD-ROM)
//
//    +---+---+---+---+---+---+---+---+
//  0 |         Operation Code        |
//    +-----------+---------------+---+
//  1 |    LUN    |         0     | I | I:Immediate
//    +-----------+---------------+---+
//  2 |               0               |
//  3 |               0               |
//    +-----------------------+---+---+
//  4 |               0       |L/E| S | L/E:Load/Eject, S:Start
//    +-----------------------+---+---+
//  5 |          Control Byte         |
//    +-------------------------------+

// コンストラクタ
SCSICmdStartStopUnit::SCSICmdStartStopUnit(SCSIDisk *parent_)
	: inherited(parent_)
{
	disk = parent_;
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdStartStopUnit::ExecCommand(std::vector<uint8>& cmdseq)
{
	bool immed = (cmdseq[1] & 0x01);
	if (immed) {
		parent->putlogf(1, lstr("%s Immed=1 not supported",
			SCSI::GetCommandName(cmdseq[0])));
	}

	bool loej  = (cmdseq[4] & 0x02);
	bool start = (cmdseq[4] & 0x01);
	parent->putlogf(1, lstr("%s LoEj=%u Start=%u",
		SCSI::GetCommandName(cmdseq[0]), (loej ? 1 : 0), (start ? 1 : 0)));

	if (disk->IsRemovableDevice()) {
		// LoEj Start
		// x	0		媒体へのアクセスを不可にする?
		// x	1		媒体へのアクセスを可にする?
		// 1	0		媒体のアンロード
		// 1	1		媒体のロード
		//
		// らしいけど、イジェクトのみ対応
		if (loej) {
			if (disk->IsMediumLoaded()) {
				disk->UnloadDisk();
			} else {
				// メディアがロードされてない時にトレイを開ける動作に
				// 相当するのは「ファイルダイアログを出す」だが伝わるだろうか
				parent->putlogf(0, lstr("%s Loading not supported",
					SCSI::GetCommandName(cmdseq[0])));
			}
		} else {
			parent->putlogf(0, lstr("%s LoEj=0 not supported",
				SCSI::GetCommandName(cmdseq[0])));
		}
	} else {
		// 固定デバイスなら黙って成功しておく?
	}
	return inherited::ExecCommand(cmdseq);
}


//
// SCSI コマンド 0x1e PreventAllowMediumRemoval (DA / CD-ROM)
//
//    +---+---+---+---+---+---+---+---+
//  0 |         Operation Code        |
//    +-----------+-------------------+
//  1 |    LUN    |         0         |
//    +-----------+-------------------+
//  2 |               0               |
//  3 |               0               |
//    +---------------------------+---+
//  4 |               0           | P | P:Prevent
//    +---------------------------+---+
//  5 |          Control Byte         |
//    +-------------------------------+

// XXX コマンドとしてはダイレクトアクセスデバイスに対しても発行できるが、
// その時どう応答すべきかは要調査

// コンストラクタ
SCSICmdPreventAllowMediumRemoval::SCSICmdPreventAllowMediumRemoval(
	SCSIDisk *parent_)
	: inherited(parent_)
{
	disk = parent_;
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdPreventAllowMediumRemoval::ExecCommand(std::vector<uint8>& cmdseq)
{
	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;
	CheckMedium;

	// PreventMediumRemoval() は状態変化でログレベル 1 で表示してるので、
	// ここではログレベル 2 で常に表示にしておく。
	uint prevent = cmdseq[4] & 1;
	parent->putlogf(2, lstr("%s prevent=%u",
		SCSI::GetCommandName(cmdseq[0]), prevent));

	disk->PreventMediumRemoval(prevent);
	return SCSI::XferPhase::Status;
}


//
// SCSI コマンド 0x25 ReadCapacity      (DA)
//               0x25 ReadCDROMCapacity (CD-ROM)
//
//    +---+---+---+---+---+---+---+---+
//  0 |         Operation Code        |
//    +-----------+---------------+---+
//  1 |    LUN    |       0       |Rel|
//    +-----------+---------------+---+
//  2 |                               |
//  3 |      論理ブロックアドレス     |
//  4 |      (PMI=0 なら 0)           |
//  5 |                               |
//    +-------------------------------+
//  6 |               0               |
//  7 |               0               |
//    +---------------------------+---+
//  8 |               0           |PMI|
//    +---------------------------+---+
//  9 |          Control Byte         |
//    +-------------------------------+
//
// CD-ROM デバイスでは同じ 0x25 で Read CDROM Capacity というコマンド名。
// Read CDROM Capacity のほうはオーディオ CD の場合の意味分けが追加されて
// いるが、たぶんデータ CD だけを扱ううちはダイレクトアクセスデバイスの
// Read Capacity と同じでいいはず。

// コンストラクタ
SCSICmdReadCapacity::SCSICmdReadCapacity(SCSIDisk *parent_)
	: inherited(parent_)
{
	disk = parent_;
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdReadCapacity::ExecCommand(std::vector<uint8>& cmdseq)
{
	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;
	CheckMedium;

	// PMI=1 は未サポート
	if ((cmdseq[8] & 0x01) != 0) {
		parent->putlogf(0, lstr("%s PMI=1 not supported",
			SCSI::GetCommandName(cmdseq[0])));
	}

	// 応答データは
	// +00.L 最終論理ブロックアドレス
	// +04.L ブロック長
	uint32 blksize = parent->GetBlocksize();
	uint32 lastblk = (disk->GetSize() / blksize) - 1;
	parent->putlogf(1, lstr("%s -> lastblk=$%x blksize=$%x",
		SCSI::GetCommandName(cmdseq[0]), lastblk, blksize));
	buf.resize(8);
	buf[0] = lastblk >> 24;
	buf[1] = lastblk >> 16;
	buf[2] = lastblk >> 8;
	buf[3] = lastblk;
	buf[4] = blksize >> 24;
	buf[5] = blksize >> 16;
	buf[6] = blksize >> 8;
	buf[7] = blksize;

	return SCSI::XferPhase::DataIn;
}


//
// SCSI コマンド 0x43 ReadTOC (CD-ROM)
//
//    +---+---+---+---+---+---+---+---+
//  0 |         Operation Code        |
//    +-----------+-----------+---+---+
//  1 |    LUN    |     0     |MSF| 0 |
//    +-----------+-----------+---+---+
//  2 |               0               |
//  3 |               0               |
//  4 |               0               |
//  5 |               0               |
//    +-------------------------------+
//  6 |          開始トラック         |
//    +-------------------------------+
//  7 |        アロケーション長       |
//  8 |                               |
//    +-------------------------------+
//  9 |          Control Byte         |
//    +-------------------------------+

// コンストラクタ
SCSICmdReadTOC::SCSICmdReadTOC(SCSIDisk *parent_)
	: inherited(parent_)
{
	disk = parent_;
}

// コマンドフェーズ
SCSI::XferPhase
SCSICmdReadTOC::ExecCommand(std::vector<uint8>& cmdseq)
{
	// コマンド開始前に直前のセンスデータをクリア
	parent->ClearSense();

	CheckLUN;
	CheckMedium;

	// MSF 形式は未実装
	if ((cmdseq[1] & 0x02)) {
		parent->putlogf(0, lstr("ReadTOC MSF=1 (NOT IMPLEMENTED)"));
	}

	// 開始トラックが 0 なら、媒体上の先頭トラックからを意味する。
	// ここでは1トラック目から始まることにしてみる。
	const uint TrackStart = 1;
	const uint TrackMax = 1;

	uint start = cmdseq[6];
	if (start == 0) {
		start = TrackStart;
	}

	// イニシエータが用意しているバッファ長
	uint reqlen = (cmdseq[7] << 8) | cmdseq[8];

	// start は送られてきた情報が知りたいだろう
	parent->putlogf(1, lstr("%s start=$%x len=$%x",
		SCSI::GetCommandName(cmdseq[0]), cmdseq[6], reqlen));

	// ヘッダ4バイト + トラックディスクリプタ(8バイト) * トラック数で、
	// ここではトラックは常に1つ。
	buf.clear();
	buf.resize(4 + 8 * 1);

	// TOC データ長はこの2バイトのフィールドを含まないバイト数で、
	// 開始トラック以降の有効な全トラック数。
	uint tocdatalen = 2;
	if (start <= TrackMax) {
		tocdatalen += (TrackMax - start) * 8;
	}
	// 開始論理アドレス
	uint32 address = 0;

	// ヘッダ
	buf[0]  = tocdatalen >> 8;	// TOC データ長
	buf[1]  = tocdatalen;		// TOC データ長
	buf[2]  = start;			// 先頭トラック番号
	buf[3]  = TrackMax;			// 最終トラック番号
	// トラック1
	buf[4]  = 0;				// Reserved
	buf[5]  = 0x06;				// ADR=0(?)、データトラック
	buf[6]  = 1;				// トラック番号
	buf[7]  = 0;				// Reserved
	buf[8]  = address >> 24;	// 論理アドレス
	buf[9]  = address >> 16;	// 論理アドレス
	buf[10] = address >> 8;		// 論理アドレス
	buf[11] = address;			// 論理アドレス

	// 本来の応答データ長か reqlen の短いほうまで
	if (reqlen < buf.size()) {
		buf.resize(reqlen);
	}

	return SCSI::XferPhase::DataIn;
}

//
// SCSI コマンド 0xa0 ReportLUNs
//
//    +---+---+---+---+---+---+---+---+
//  0 |         Operation Code        |
//    +-----------+-------------------+
//  1 |  Ignored  |         0         |
//    +-------------------------------+
//  2 |          Select Report        |
//    +-------------------------------+
//  3 |               0               |
//  4 |               0               |
//  5 |               0               |
//    +-------------------------------+
//  6 |                               |
//  7 |        アロケーション長       |
//  8 |                               |
//  9 |                               |
//    +-------------------------------+
// 10 |               0               |
//    +-------------------------------+
// 11 |          Control Byte         |
//    +-------------------------------+

// Response:
//    +-------------------------------+
//  0 |                               |
//  1 |         LUN リスト長          |
//  2 | (先頭8バイト以降のバイト数?)  |
//  3 |                               |
//    +-------------------------------+
//  4 |                               |
//  5 |               0               |
//  6 |                               |
//  7 |                               |
//    +-------------------------------+
//    +-------+-----------------------+ [0]
//  8 |   0   |        Bus ID         |
//    +-------+-----------------------+
//  9 |    Single Level LUN Address   |
//    +-------------------------------+
// 10 |    Second Level LUN Address   |
// 11 |                               |
//    +-------------------------------+
// 12 |    Third Level LUN Address    |
// 13 |                               |
//    +-------------------------------+
// 14 |    Fourth Level LUN Address   |
// 15 |                               |
//    +-------------------------------+ [1]
//                   :

unix.superglobalmegacorp.com

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