--- nono/vm/scsidev.cpp 2026/04/29 17:04:45 1.1.1.6 +++ nono/vm/scsidev.cpp 2026/04/29 17:05:43 1.1.1.20 @@ -4,20 +4,35 @@ // Licensed under nono-license.txt // -// SCSI デバイス共通部分 +// +// SCSI デバイス +// + +// ログレベルは +// 1: SCSI コマンドの概要。 +// デバイスの動作のうち大きな変更のもの +// 2: SCSI コマンドの詳細、ダンプ。 +// デバイスの動作のうち些細なもの、あるいは頻繁なもの +// 3: フェーズ遷移 +// 4: データも含める +#include "scsicmd.h" #include "scsidev.h" +#include "scsidomain.h" #include "config.h" #include "mainapp.h" -#include "mystring.h" +#include "scheduler.h" +#include "uimessage.h" // -// SCSI デバイス +// SCSI デバイス共通部分 // // コンストラクタ -SCSIDevice::SCSIDevice() +SCSIDevice::SCSIDevice(uint objid_) + : inherited(objid_) { + myid = -1; } // デストラクタ @@ -25,19 +40,28 @@ SCSIDevice::~SCSIDevice() { } +// バスに接続する。 +void +SCSIDevice::Attach(SCSIBus *bus_) +{ + bus = bus_; +} + + // -// SCSI ホストデバイス +// SCSI ホストデバイス (イニシエータ) // // コンストラクタ -SCSIHostDevice::SCSIHostDevice() +SCSIHostDevice::SCSIHostDevice(SCSIDomain *domain_, IODevice *parent_) + : inherited(OBJ_NONE) { + domain = domain_; + parent = parent_; + // イニシエータの ID は実行時にコントローラによって決まる。 devtype = SCSI::DevType::Initiator; - // バスデバイスを作成する。 - // ただし自身がバスに参加するのは継承側で行う - // (例えば SPC なら BDID 書き込みで ID が決まった後、など)。 - scsibus.reset(new SCSIBus()); + SetLogLevel(parent->loglevel); } // デストラクタ @@ -45,87 +69,29 @@ SCSIHostDevice::~SCSIHostDevice() { } -// 設定ファイルに使われるこのホストデバイスを示す接頭語を設定する -// (継承クラス側のコンストラクタで config_name を設定後に呼ぶこと) +// SCSI ID を設定する。 +// (SCSIDomain からのみ呼ぶこと) void -SCSIHostDevice::SetConfigName(const char *name_) +SCSIHostDevice::SetID(uint id_) { - config_name = name_; + myid = id_; } -bool -SCSIHostDevice::Create() -{ - // 設定を読み込む。 - for (int id = 0; id < 8; id++) { - const std::string key = string_format("%s-id%d-image", - GetConfigName(), id); - const ConfigItem& item = gConfig->Find(key); - const std::string& image = item.AsString(); - - // 空ならデバイスなし - if (image.length() == 0) { - // 未接続部分のパラメータは減らしておくか。 - // --show-config するとさすがに無駄に多くて邪魔なので。 - const std::string wpkey = string_format("%s-id%d-writeprotect", - GetConfigName(), id); - const std::string stkey = string_format("%s-id%d-seektime", - GetConfigName(), id); - gConfig->Delete(wpkey); - gConfig->Delete(stkey); - continue; - } - - // ',' より前がデバイス種別 (ここではパスは不要) - auto pos = image.find(','); - if (pos == std::string::npos) { - item.Err("no device type specified"); - return false; - } - std::string type = string_tolower(image.substr(0, pos)); - - if (type == "hd") { - target[id].reset(new SCSIHD(this, id)); - scsibus->Attach(target[id].get(), id); - } else { - item.Err("invaild device type '%s'", type.c_str()); - } - } - - return true; -} - -bool -SCSIHostDevice::Init() +// SCSIBus からのコールバック関数を設定する。 +void +SCSIHostDevice::SetBusCallback( + BusFreeCallback_t busfree_, + SCSIHostCallback_t selack_, + SCSIHostCallback_t xferreq_) { - // 初期化ではないけどログレベルがセットできたここでデバッグ表示。 - // -L オプションの処理は Create() の後なので。 - - if (loglevel >= 1) { - for (int id = 0; id < 8; id++) { - std::string typestr; - if (target[id]) { - switch (target[id]->GetDevType()) { - default: - break; - case SCSI::DevType::HD: - typestr = "HD"; - break; - } - } - if (!typestr.empty()) { - putmsg("%s target #%d %s", GetConfigName(), id, - typestr.c_str()); - } - } - } - - return true; + BusFreeCallback = busfree_; + SelectionAckCallback = selack_; + TransferReqCallback = xferreq_; } -// バスフリーコールバック (SCSIBus から呼ばれる) +// バスフリーになった (SCSIBus から呼ばれる) void -SCSIHostDevice::BusFreeCB(int id) +SCSIHostDevice::OnBusFree(uint id) { // 実際にはバスフリーを検出したデバイスは各自自身が送出している信号を // 下げるのだが、色々面倒なので、ここではホストのバスフリーコールバックが @@ -134,10 +100,10 @@ SCSIHostDevice::BusFreeCB(int id) uint32 data; while ((data = GetBSY()) != 0) { - bus->NegateBSY(DecodeID(data)); + bus->NegateBSY(SCSIBus::DecodeID(data)); } while ((data = GetSEL()) != 0) { - bus->NegateSEL(DecodeID(data)); + bus->NegateSEL(SCSIBus::DecodeID(data)); } if (GetREQ()) { NegateREQ(); @@ -150,511 +116,47 @@ SCSIHostDevice::BusFreeCB(int id) } SetXfer(0); SetData(0); -} - -#if defined(__clang__) -// デストラクタがインラインで書けないのは面倒なのでとりあえず -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -// -// SCSI コマンドの共通部分 -// -class SCSICmd -{ - public: - SCSICmd(SCSITarget *p) { - parent = p; - } - virtual ~SCSICmd() { } - - // 転送バッファと転送バイト数。 - // IN (ターゲットが送出側)なら、各フェーズ開始までに送出バイト数分の - // バッファを buf に用意すること。recvbytes は操作不要。 - // - // OUT(ターゲットが受信側)なら、各フェーズ開始前までに受信バイト数を - // recvbytes にセットすること。buf は操作不要。 - // なお parent は OUT(受信)フェーズ中にこの recvbytes を変更しない。 - std::vector buf; - uint32 recvbytes; - - // このコマンドの動作時間 [nsec] - // シークタイムとかを簡易的に再現するため、次フェーズ遷移時の最初の - // REQ アサートを引き延ばすために使う。 - // 主にコマンドフェーズで使うことになると思う。 - uint64 optime {}; - - // 以下いずれも戻り値は遷移先のフェーズ。 - - // コマンドフェーズを処理する。 - // コマンドが確定したところで呼ばれる。戻り値に遷移先フェーズを返すこと。 - // また、コマンド開始時に各自で ClearSense() を呼ぶこと。センスというか - // CA 状態は次のコマンド実行でクリアされるが (ネクサスはとりあえず無視)、 - // RequestSense の場合は現在保持しているセンス情報をホストに返すので、 - // つまり RequestSense 以外のコマンドが全員各自でクリアする必要がある。 - virtual SCSI::XferPhase Command(std::vector& cmdseq) { - parent->ClearSense(); - return SCSI::XferPhase::Status; - } - - // データインフェーズを完了する。 - virtual SCSI::XferPhase DoneDataIn() { return SCSI::XferPhase::Status; } - - // データアウトフェーズを完了する。 - virtual SCSI::XferPhase DoneDataOut() { return SCSI::XferPhase::Status; } - - // ステータスフェーズを開始する。 - void BeginStatus() { - buf.clear(); - buf.push_back(status_byte); - } - - // ステータスフェーズを完了する。 - virtual SCSI::XferPhase DoneStatus() { return SCSI::XferPhase::MsgIn; } - - // メッセージインフェーズを開始する。 - // デフォルトでは Command Complete を返す実装を用意しておく。 - virtual void BeginMsgIn() { - buf.clear(); - buf.push_back((uint8)SCSI::MsgByte::CommandComplete); - } - - // メッセージインフェーズを完了する。 - virtual SCSI::XferPhase DoneMsgIn() { return SCSI::XferPhase::End; } - - // メッセージアウトフェーズを開始する。 - virtual void BeginMsgOut() { } - - // メッセージアウトフェーズを完了する。 - virtual SCSI::XferPhase DoneMsgOut() { return SCSI::XferPhase::End; } - - protected: - // このコマンドを実行しているターゲットデバイス。 - SCSITarget *parent; - - // ステータスバイト (デフォルトは 0 = Good) - uint8 status_byte {}; -}; - -// -// SCSI コマンド 0x00: TestUnitReady (共通) -// -class SCSICmdTestUnitReady : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdTestUnitReady(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdTestUnitReady() override { } -}; - -// -// SCSI コマンド 0x01: RezeroUnit (共通) -// -class SCSICmdRezeroUnit : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdRezeroUnit(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRezeroUnit() override { } - - // とりあえず黙って成功するだけでいいか -}; - -// -// SCSI コマンド 0x03: RequestSense (共通) -// -class SCSICmdRequestSense : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdRequestSense(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRequestSense() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - // 応答のセンスデータは SCSI-2 形式(最小18バイト) を実装。 - // 今の所、センスキー、ASC、ASCQ だけで返せる内容にだけ対応している。 - // これ以外のフィールドを返す必要が出来たらまた考えること。 - - buf.clear(); - buf.resize(18); - buf[0] = 0xf0; // Valid、エラーコード 0x70 - buf[1] = 0; // セグメント番号(?) - buf[2] = parent->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] = parent->senseasc >> 8; - buf[13] = parent->senseasc & 0xff; - buf[14] = 0; // FRU - buf[15] = 0; // センスキー固有情報 - buf[16] = 0; // センスキー固有情報 - buf[17] = 0; // センスキー固有情報 - // 追加センスデータなし - - // それからバッファを指定の長さに変更 - buf.resize((uint)cmdseq[4]); - - // 転送した後にセンスデータをクリア - parent->ClearSense(); - - return SCSI::XferPhase::DataIn; - } -}; - -// -// SCSI コマンド 0x08: Read(6) (共通) -// -class SCSICmdRead6 : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdRead6(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRead6() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - SCSIHD *hdparent = dynamic_cast(parent); - uint32 lba; - uint32 blks; - - // センスデータをクリア - parent->ClearSense(); - - lba = (cmdseq[1] & 0x1f) << 16; - lba |= cmdseq[2] << 8; - lba |= cmdseq[3]; - blks = cmdseq[4]; - // blks == 0 は 256 ブロック転送を意味する。 - if (blks == 0) { - blks = 256; - } - parent->putlog(1, "READ6 LBA=0x%x blks=0x%x", lba, blks); - - if (((uint64)lba + (uint64)blks) * parent->GetBlocksize() > - hdparent->GetSize()) - { - parent->sensekey = SCSI::SenseKey::IllegalRequest; - parent->senseasc = SCSI::ASC::InvalidFieldInCDB; - status_byte = SCSI::StatusByte::CheckCondition; - return SCSI::XferPhase::Status; - } - - uint8 *start = hdparent->mem + lba * parent->GetBlocksize(); - uint8 *end = start + blks * parent->GetBlocksize(); - buf.assign(start, end); - // 平均シークタイム - optime = hdparent->seektime; + // 親のバスフリーコールバックを呼ぶ + (parent->*(BusFreeCallback))(id); - return SCSI::XferPhase::DataIn; + // RST が見えてる状態で親に通知はしたので、ここで下ろす。 + if (GetRST()) { + NegateRST(); } -}; - -// -// SCSI コマンド 0x0a: Write(6) (共通) -// -class SCSICmdWrite6 : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdWrite6(SCSITarget *p) : inherited(p) { - hdparent = dynamic_cast(parent); - } - virtual ~SCSICmdWrite6() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - // センスデータをクリア - parent->ClearSense(); - - lba = (cmdseq[1] & 0x1f) << 16; - lba |= cmdseq[2] << 8; - lba |= cmdseq[3]; - blks = cmdseq[4]; - // blks == 0 は 256 ブロック転送を意味する。 - if (blks == 0) { - blks = 256; - } - - if (((uint64)lba + (uint64)blks) * parent->GetBlocksize() > - hdparent->GetSize()) - { - parent->sensekey = SCSI::SenseKey::IllegalRequest; - parent->senseasc = SCSI::ASC::InvalidFieldInCDB; - status_byte = SCSI::StatusByte::CheckCondition; - return SCSI::XferPhase::Status; - } - - // 平均シークタイム - optime = hdparent->seektime; - - recvbytes = blks * parent->GetBlocksize(); - return SCSI::XferPhase::DataOut; - } - - SCSI::XferPhase DoneDataOut() override { - parent->putlog(1, "WRITE6 LBA=0x%x blks=0x%x", lba, blks); - if (hdparent->writeprotect == false) { - memcpy(hdparent->mem + (lba * parent->GetBlocksize()), - buf.data(), buf.size()); - } - return SCSI::XferPhase::Status; - } - - private: - SCSIHD *hdparent; - uint32 lba; - uint32 blks; -}; - -// -// SCSI コマンド 0x12: Inquiry (共通) -// -class SCSICmdInquiry : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdInquiry(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdInquiry() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - // センスデータをクリア - parent->ClearSense(); - - // 応答データはターゲットデバイスが持っている - buf = parent->inquiry_data; - - // 本来の応答データ長か reqlen の短いほうまで。 - uint reqlen = (uint)cmdseq[4]; - if (reqlen < buf.size()) { - buf.resize(reqlen); - } - return SCSI::XferPhase::DataIn; - } -}; - -// -// SCSI コマンド 0x15 ModeSelect(6) (共通) -// -class SCSICmdModeSelect6 : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdModeSelect6(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdModeSelect6() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - // センスデータをクリア - parent->ClearSense(); - - recvbytes = cmdseq[4]; - return SCSI::XferPhase::DataOut; - } - - SCSI::XferPhase DoneDataOut() override { - // Mode Select(6) のデータは以下の構造で並んでいる。 - // ヘッダ - // ブロックディスクリプタ#1 - // : - // ブロックディスクリプタ#n - // パラメータページ #1 - // : - // パラメータページ #m - - const SCSI::ModeParamHdr *hdr = (const SCSI::ModeParamHdr *)buf.data(); - - if (hdr->block_desc_len > 0) { - const SCSI::BlockDesc *desc = - (const SCSI::BlockDesc *)(buf.data() + sizeof(*hdr)); - uint blocksize; - blocksize = (desc->block_size[0]) << 16; - blocksize |= (desc->block_size[1]) << 8; - blocksize |= (desc->block_size[2]); - - // ブロック長を書き戻す - parent->SetBlocksize(blocksize); - } - return SCSI::XferPhase::Status; - } -}; - -// -// SCSI コマンド 0x1a ModeSense(6) (共通) -// -class SCSICmdModeSense6 : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdModeSense6(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdModeSense6() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - uint8 pagecode = (cmdseq[2] & 0x3f); - - // センスデータをクリア - parent->ClearSense(); - - // 未実装 (XXX センスデータは適当) - parent->putlog(1, "ModeSense(6) PageCode $%02x 未実装", pagecode); - parent->sensekey = SCSI::SenseKey::IllegalRequest; - parent->senseasc = SCSI::ASC::InvalidFieldInCDB; - status_byte = SCSI::StatusByte::CheckCondition; - - return SCSI::XferPhase::Status; - } -}; - -// -// SCSI コマンド 0x1b StartStopUnit (ダイレクトアクセスデバイス) -// -class SCSICmdStartStopUnit : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdStartStopUnit(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdStartStopUnit() override { } - - // スピンアップ/ダウンやメディアのロード/アンロードを指示する。 - // ので今の所エミュレーション上は特にすることはない。 - // XXX それっぽい時間待つくらいはしてもいいかも知れない - - SCSI::XferPhase Command(std::vector& cmdseq) override { - parent->putlog(1, "StartStopUnit"); - return inherited::Command(cmdseq); - } -}; - -// -// SCSI コマンド 0x25 ReadCapacity (ダイレクトアクセスデバイス) -// -class SCSICmdReadCapacity : public SCSICmd -{ - using inherited = SCSICmd; - public: - SCSICmdReadCapacity(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdReadCapacity() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - SCSIHD *hdparent = dynamic_cast(parent); - - // センスデータをクリア - parent->ClearSense(); - - // PMI=1 は未サポート - if ((cmdseq[8] & 0x01) != 0) { - parent->putmsg("SCSI ReadCapacity PMI=1 not supported"); - } - - // 応答データは - // +00.L 最終論理ブロックアドレス - // +04.L ブロック長 - uint32 blksize = parent->GetBlocksize(); - uint32 lastblk = (hdparent->GetSize() / blksize) - 1; - 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 コマンド 0x28 Read(10) (ダイレクトアクセスデバイス) -// -class SCSICmdRead10 : public SCSICmd +// ターゲットがセレクションに応答した (SCSIBus から呼ばれる) +void +SCSIHostDevice::OnSelectionAck() { - using inherited = SCSICmd; - public: - SCSICmdRead10(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRead10() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - SCSIHD *hdparent = dynamic_cast(parent); - uint32 lba; - uint32 blks; - - // センスデータをクリア - parent->ClearSense(); - - if ((cmdseq[1] & 0x01)) { - parent->putmsg("SCSI Read(10) RelAdr not supported"); - } - - lba = cmdseq[2] << 24; - lba |= cmdseq[3] << 16; - lba |= cmdseq[4] << 8; - lba |= cmdseq[5]; - blks = cmdseq[7] << 8; - blks |= cmdseq[8]; - parent->putlog(1, "READ10 LBA=0x%x blks=0x%x", lba, blks); - - if (((uint64)lba + (uint64)blks) * parent->GetBlocksize() > - hdparent->GetSize()) - { - parent->sensekey = SCSI::SenseKey::IllegalRequest; - parent->senseasc = SCSI::ASC::InvalidFieldInCDB; - status_byte = SCSI::StatusByte::CheckCondition; - return SCSI::XferPhase::Status; - } - - uint8 *start = hdparent->mem + lba * parent->GetBlocksize(); - uint8 *end = start + blks * parent->GetBlocksize(); - buf.assign(start, end); - - // 平均シークタイム - optime = hdparent->seektime; + // ターゲットが BSY を立てて応答したので、 + // こちらはデータバスをクリアして SEL を下げる。 + // これによりセレクションフェーズは成功で完了する。 + SetData(0); + NegateSEL(); - return SCSI::XferPhase::DataIn; - } -}; + // 親のバスフリーコールバックを呼ぶ + (parent->*(SelectionAckCallback))(); +} -// -// SCSI コマンド サポートしていないコマンド -// -class SCSICmdNotSupportedCommand : public SCSICmd +// ターゲットが REQ を立てた (SCSIBus から呼ばれる) +void +SCSIHostDevice::OnTransferReq() { - using inherited = SCSICmd; - public: - SCSICmdNotSupportedCommand(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdNotSupportedCommand() override { } - - SCSI::XferPhase Command(std::vector& cmdseq) override { - // XXX センスキーよく分からんけどとりあえず - parent->sensekey = SCSI::SenseKey::IllegalRequest; - parent->senseasc = SCSI::ASC::InvalidCommandOperationCode; - status_byte = SCSI::StatusByte::CheckCondition; - return SCSI::XferPhase::Status; - } -}; + (parent->*(TransferReqCallback))(); +} -#if defined(__clang__) -#pragma clang diagnostic pop -#endif // // SCSI ターゲットデバイスの共通部分 // -// コンストラクタ -SCSITarget::SCSITarget(SCSIHostDevice *h, int id, std::vector& inq) +// コンストラクタ。 +SCSITarget::SCSITarget(SCSIDomain *domain_, uint id) + : inherited(OBJ_NONE) // オブジェクト ID で検索することはない { - logname = "scsitarget"; - devname = "SCSITarget"; - - inquiry_data = inq; - // ホストへのリンク (所有関係のないポインタ) - host = h; + domain = domain_; myid = id; } @@ -663,10 +165,27 @@ SCSITarget::~SCSITarget() { } +// 電源オン +void +SCSITarget::ResetHard(bool poweron) +{ + if (poweron) { + ClearSense(); + cmdseq.clear(); + cmdlen = 0; + cmd.reset(); + bytecount = 0; + lastbyte = false; + } +} + // バスリセットされた (SCSIBus から呼ばれる) void -SCSITarget::BusResetCB() +SCSITarget::OnBusReset() { + // センスキーをクリア + ClearSense(); + // リセットされたので持ってる信号を全部解放。 // 一応自分が変えていい信号線だけを落とす。 switch (GetPhase()) { @@ -682,25 +201,22 @@ SCSITarget::BusResetCB() NegateBSY(); break; } - - // センスキーをクリア - ClearSense(); } // セレクションで選択された (SCSIBus から呼ばれる) void -SCSITarget::SelectionCB() +SCSITarget::OnSelected() { // ターゲットが、自身が選択されていることを認識してから BSY を // 立てるまでの時間はもうバス側でカウントしてあるので、ここでは // ただちに BSY を上げてよい。 - putlog(4, "Selection Callback"); + putlog(4, "OnSelected"); AssertBSY(); } // 情報転送フェーズを開始する (SCSIBus から呼ばれる) void -SCSITarget::StartTransferCB() +SCSITarget::OnStartTransfer() { // 最初はコマンドフェーズ。(ATN はとりあえず放置) putlog(4, "Start Command Phase"); @@ -712,7 +228,7 @@ SCSITarget::StartTransferCB() // 情報転送フェーズ間の遷移について // -// 情報転送フェーズは TransferCB() で1バイトずつ転送を行い、所定のバイト数 +// 情報転送フェーズは OnTransfer() で1バイトずつ転送を行い、所定のバイト数 // の転送が完了すればフェーズを遷移する。遷移方法は入出力方向別で6通り。 // o IN -> IN // o IN -> OUT @@ -721,26 +237,26 @@ SCSITarget::StartTransferCB() // o OUT -> IN // o OUT -> END // -// IN -> IN の遷移。TransferCB() で前フェーズの最終バイト受信後、フェーズを +// IN -> IN の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを // 変更して REQ を立てるところまで。 // -// IN -> OUT の遷移。TransferCB() で前フェーズの最終バイト受信後、フェーズを +// IN -> OUT の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを // 変更して (おそらく {Phase}Begin() を発行してから)、データをセットし REQ // を立てる? // -// IN -> END の遷移。TransferCB() で前フェーズの最終バイト受信後、BSY を +// IN -> END の遷移。OnTransfer() で前フェーズの最終バイト受信後、BSY を // 下げて終了。 // // OUT -> OUT の遷移。前フェーズの最終バイトをイニシエータが受信後に -// TransferCB() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() +// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() // を発行してから)、データをセットし REQ を立てる? // // OUT -> IN の遷移。前フェーズの最終バイトをイニシエータが受信後に -// TransferCB() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() +// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin() // を発行してから)、REQ を立てる? // // OUT -> END の遷移。前フェーズの最終バイトをイニシエータが受信後に -// TransferCB() が呼ばれるので、BSY を下げる? +// OnTransfer() が呼ばれるので、BSY を下げる? // 情報転送フェーズでイニシエータが ACK を上げた。(SCSIBus から呼ばれる) // OUT (イニシエータ → ターゲット方向) なら、ターゲットが上げた REQ に応答 @@ -748,7 +264,7 @@ SCSITarget::StartTransferCB() // IN (ターゲット → イニシエータ方向) なら、ターゲットがデータをデータバスに // 乗せて REQ を上げたのをイニシエータが受信した後でこれをコールしてくる。 void -SCSITarget::TransferCB() +SCSITarget::OnTransfer() { uint8 data; SCSI::XferPhase xfer; @@ -762,8 +278,8 @@ SCSITarget::TransferCB() // コマンドフェーズだけいろいろ特殊なので別処理。 // 受信バッファが異なる、1バイト目で受信長が決まるなど。 data = GetData(); - putlog(4, "TransferCB: cmdseq[%02d] = $%02x", - (int)cmdseq.size(), data); + putlog(4, "%s: cmdseq[%02u] = $%02x", __func__, + (uint)cmdseq.size(), data); cmdseq.push_back(data); if (cmdseq.size() == 1) { // 1バイト目ならこのコマンドが何バイトか調べる @@ -778,18 +294,36 @@ SCSITarget::TransferCB() break; } } else if (cmdseq.size() >= cmdlen) { - if (loglevel >= 1) { + 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); + cmdbuf += string_format(" %02x", v); } - putlog("%s", cmdbuf.c_str()); + putlogn("%s", cmdbuf.c_str()); } - // 最終バイトを受信したらコマンドを選択 - DispatchCmd(); + // 最終バイトを受信したらコマンドを選択。 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 (GetIO() == false) { // OUT 方向 (ターゲットが受信側) @@ -802,7 +336,7 @@ SCSITarget::TransferCB() } else { // IN 方向 (ターゲットが送信側) // bytecount は加算方向。 - // データは前のループの終わり (TransferAckCB()の下のほう) で + // データは前のループの終わり (OnTransferAck()の下のほう) で // セットしているのでここでは引き取られた分をカウントする。 bytecount++; if (bytecount >= cmd->buf.size()) { @@ -815,7 +349,7 @@ SCSITarget::TransferCB() // 情報転送フェーズでイニシエータが ACK を下げた。(SCSIBus から呼ばれる) void -SCSITarget::TransferAckCB() +SCSITarget::OnTransferAck() { SCSI::XferPhase xfer = GetXfer(); @@ -825,7 +359,7 @@ SCSITarget::TransferAckCB() // フェーズ完了 switch (xfer) { case SCSI::XferPhase::Command: - next = cmd->Command(cmdseq); + next = cmd->ExecCommand(cmdseq); break; case SCSI::XferPhase::DataOut: next = cmd->DoneDataOut(); @@ -843,9 +377,9 @@ SCSITarget::TransferAckCB() next = cmd->DoneStatus(); break; default: - PANIC("TransferCB unknown xfer done %d", (int)xfer); + PANIC("%s unknown xfer done %d", __func__, (int)xfer); } - putlog(2, "%s done", SCSI::GetXferPhaseName(xfer)); + putlog(3, "%s done", SCSI::GetXferPhaseName(xfer)); // 簡略化のため、フェーズ完了時だけ REQ を引き伸ばすことができる if (cmd->optime != 0) { @@ -856,6 +390,7 @@ SCSITarget::TransferAckCB() if (next != xfer) { // End ならバスフリーにして終了。 if (next == SCSI::XferPhase::End) { + cmd.reset(); NegateBSY(); return; } @@ -872,30 +407,30 @@ SCSITarget::TransferAckCB() case SCSI::XferPhase::DataOut: cmd->buf.clear(); bytecount = cmd->recvbytes; - putlog(3, "DataOut begin (%d bytes)", bytecount); + putlog(3, "DataOut begin (%u bytes)", bytecount); break; case SCSI::XferPhase::DataIn: bytecount = 0; - putlog(3, "DataIn begin (%d bytes)", (int)cmd->buf.size()); + 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 (%d bytes)", bytecount); + putlog(3, "MsgOut begin (%u bytes)", bytecount); break; case SCSI::XferPhase::MsgIn: cmd->BeginMsgIn(); bytecount = 0; - putlog(3, "MsgIn begin (%d bytes)", (int)cmd->buf.size()); + putlog(3, "MsgIn begin (%u bytes)", (uint)cmd->buf.size()); break; case SCSI::XferPhase::Status: cmd->BeginStatus(); bytecount = 0; - putlog(3, "Status begin (%d bytes)", (int)cmd->buf.size()); + putlog(3, "Status begin (%u bytes)", (uint)cmd->buf.size()); break; default: - PANIC("TransferCB unknown xfer begin %d", (int)xfer); + PANIC("%s unknown xfer begin %d", __func__, (int)xfer); } } } @@ -910,17 +445,18 @@ SCSITarget::TransferAckCB() AssertREQ(); } -// SCSI コマンドのディスパッチャ +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 void SCSITarget::DispatchCmd() { switch (cmdseq[0]) { case SCSI::Command::TestUnitReady: // 0x00 - cmd.reset(new SCSICmdTestUnitReady(this)); + cmd.reset(new SCSICmd(this)); break; case SCSI::Command::RezeroUnit: // 0x01 - cmd.reset(new SCSICmdRezeroUnit(this)); + cmd.reset(new SCSICmd(this)); break; case SCSI::Command::RequestSense: // 0x03 @@ -932,15 +468,13 @@ SCSITarget::DispatchCmd() break; case SCSI::Command::ModeSelect6: // 0x15 - cmd.reset(new SCSICmdModeSelect6(this)); + case SCSI::Command::ModeSelect10: // 0x55 + cmd.reset(new SCSICmdModeSelect(this)); break; case SCSI::Command::ModeSense6: // 0x1a - cmd.reset(new SCSICmdModeSense6(this)); - break; - - case SCSI::Command::ReadCapacity: // 0x25 - cmd.reset(new SCSICmdTestUnitReady(this)); + case SCSI::Command::ModeSense10: // 0x5a + cmd.reset(new SCSICmdModeSense(this)); break; default: @@ -950,97 +484,448 @@ SCSITarget::DispatchCmd() if (cname) { name = string_format(" %s", cname); } - putlog(0, "未実装 SCSI コマンド $%02x%s len=%d", - cmdseq[0], name.c_str(), (int)cmdseq.size()); + 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& cmdseq_) +{ + cmdseq = cmdseq_; + SelectCommand(); + return cmd.get(); +} + +// 内部版。 +// 入力コマンド列はメンバ変数 cmdseq だし、生成した cmd もメンバ変数。 +void +SCSITarget::SelectCommand() +{ + cmd.reset(); + // DispatchCmd() の中は new が多いので個別に捕捉せずこっちでやる。 + try { + DispatchCmd(); + } catch (...) { } +} + + // -// SCSI HD +// SCSI Disk // // コンストラクタ -SCSIHD::SCSIHD(SCSIHostDevice *h, int id) - : inherited(h, id, inquiry_data) +SCSIDisk::SCSIDisk(SCSIDomain *domain_, uint id, SCSI::DevType devtype_) + : inherited(domain_, id) { - devname = "SCSIHD"; - devtype = SCSI::DevType::HD; + devtype = devtype_; + + const char *devtypename = SCSI::GetDevTypeName(devtype); + + // コンストラクタの初期化子では devtypename を組み込みにくいので、 + // コンストラクタ本文でログ名などを設定する。その時の作法に + // 従っているのですこし面倒な手法が必要。 + // lib/object.h 参照。 + SetName(string_format("SCSI%s%u", devtypename, id)); + ClearAlias(); + AddAlias(string_format("SCSI%s%u", devtypename, id)); + AddAlias(string_format("SCSI%u", id)); + AddAlias(string_format("%s%u", devtypename, id)); + + 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); + } + } // デストラクタ -SCSIHD::~SCSIHD() +SCSIDisk::~SCSIDisk() { } // 初期化 bool -SCSIHD::Init() +SCSIDisk::Init() { - const std::string myname = string_format("%s:ID%d", - host->GetConfigName(), myid); - const std::string keybody = string_format("%s-id%d-", - host->GetConfigName(), myid); + const std::string keybody = string_format("%s-id%u-", + domain->GetConfigName(), myid); const std::string imgkey = keybody + "image"; - const std::string wpkey = keybody + "writeprotect"; + 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& image = itemimg.AsString(); - // ここに来る時にチェックしているので ',' はあるはず - auto pos = image.find(',') + 1; - std::string filename = string_ltrim(image.substr(pos, image.size() - pos)); - if (filename.length() == 0) { + 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; } - // ファイルをオープン - const std::string path = gMainApp.GetVMDir() + filename; - file.SetFilename(path); - file.SetDispname(string_format("%s \"%s\"", myname.c_str(), path.c_str())); - mem = file.OpenFile(); - if (mem == NULL) { - 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).AsInt(); + + // 設定時点でファイルが指定されていればオープン + if (filename.empty() == false) { + std::string path = gMainApp.NormalizePath(filename); + if (LoadDisk(path) == false) { + return false; + } + } else { + // メディアがロードされないままでも起動時は必ず通知を投げる。 + // ステータスパネルはこの UIMessage でのみ状態を更新するため。 + MediaChanged(); } - writeprotect = gConfig->Find(wpkey).AsInt(); - if (writeprotect) { - putmsg(0, "%s: write protected", myname.c_str()); + // 平均シークタイム。設定は [msec]、変数は [nsec]。 + // virtio-scsi にはない。 + if (strncmp(stkey.c_str(), "virtio", 6) != 0) { + seektime = gConfig->Find(stkey).AsInt(); + seektime *= 1_msec; } - // 設定は [msec]、変数は [nsec]。 - 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; + int w_ok; + bool read_only; + + // すでにあればクローズ (ここでは通知は行わない) + unloaded = UnloadDisk_internal(); + + // 新しいイメージをオープン(する準備) + pathname = pathname_; + if (image.CreateHandler(pathname) == false) { + goto done; + } + + w_ok = image.IsWriteable(); + if (w_ok < 0) { + goto done; + } + + // 書き込みモードをここで確定 + if (IsWriteableDevice()) { + // 書き込み可能デバイスの場合 + if (w_ok) { + if (write_ignore) { + write_mode = RW::WriteIgnore; + } else { + 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; } -// SCSI コマンドのディスパッチャ +// ディスク状態をクリアする +// (メディアを取り外した時など) +void +SCSIDisk::Clear() +{ + medium_loaded = false; + image.Close(); + pathname.clear(); + write_mode = RW::Writeable; +} + +// メディア状態変更を UI に通知する +void +SCSIDisk::MediaChanged() const +{ + UIMessage::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 に通知 + UIMessage::Post(UIMessage::SCSI_MEDIA_FAILED); + } +} + +// メディア排出メッセージコールバック void -SCSIHD::DispatchCmd() +SCSIDisk::UnloadMessage(MessageID msgid, uint32 arg) +{ + bool force = arg; + UnloadDisk(force); +} + +// SCSI コマンドのディスパッチャ。 +// C++ の例外は呼び出し側で捕捉している。 +void +SCSIDisk::DispatchCmd() { switch (cmdseq[0]) { case SCSI::Command::Read6: // 0x08 - cmd.reset(new SCSICmdRead6(this)); + case SCSI::Command::Read10: // 0x28 + cmd.reset(new SCSICmdRead(this)); break; case SCSI::Command::Write6: // 0x0a - cmd.reset(new SCSICmdWrite6(this)); + 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::Read10: // 0x28 - cmd.reset(new SCSICmdRead10(this)); + 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: @@ -1050,34 +935,151 @@ SCSIHD::DispatchCmd() } } -// 指定の場所を自由に読み出す -// (エミュレータ特権アクセス) +// Inquiry データを返す bool -SCSIHD::PeekImage(void *buf, uint32 start, uint32 len) const +SCSIDisk::Inquiry(std::vector& buf, uint lun) { - if (mem == NULL) { + 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& 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& buf, uint64 start) +{ + if (__predict_false((bool)image == false)) { return false; } - if ((uint64)start + (uint64)len > GetSize()) { + + if (GetWriteMode() >= RW::WriteProtect) { + // 書き込み不可 return false; } - memcpy(buf, mem + start, len); - return true; + return image.Write(buf.data(), start, buf.size()); } -// HD の inquiry データ -/*static*/ std::vector -SCSIHD::inquiry_data = { - 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 - 'N', 'O', 'N', 'O', ' ', ' ', ' ', ' ', - 'S', 'C', 'S', 'I', 'H', 'D', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - '0', ' ', ' ', ' ', -}; +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); + } +}