--- nono/vm/scsidev.cpp 2026/04/29 17:04:32 1.1.1.2 +++ nono/vm/scsidev.cpp 2026/04/29 17:04:47 1.1.1.7 @@ -1,6 +1,7 @@ // // nono -// Copyright (C) 2020 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // // SCSI デバイス共通部分 @@ -9,9 +10,6 @@ #include "config.h" #include "mainapp.h" #include "mystring.h" -#include -#include -#include // // SCSI デバイス @@ -34,6 +32,8 @@ SCSIDevice::~SCSIDevice() // コンストラクタ SCSIHostDevice::SCSIHostDevice() { + devtype = SCSI::DevType::Initiator; + // バスデバイスを作成する。 // ただし自身がバスに参加するのは継承側で行う // (例えば SPC なら BDID 書き込みで ID が決まった後、など)。 @@ -60,11 +60,19 @@ 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->Get(key); + 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; } @@ -98,9 +106,9 @@ SCSIHostDevice::Init() std::string typestr; if (target[id]) { switch (target[id]->GetDevType()) { - case SCSI::None: + default: break; - case SCSI::HD: + case SCSI::DevType::HD: typestr = "HD"; break; } @@ -144,6 +152,12 @@ SCSIHostDevice::BusFreeCB(int id) SetData(0); } +#if defined(__clang__) +// デストラクタがインラインで書けないのは面倒なのでとりあえず +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + // // SCSI コマンドの共通部分 // @@ -165,11 +179,22 @@ class SCSICmd 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; } @@ -180,10 +205,9 @@ class SCSICmd virtual SCSI::XferPhase DoneDataOut() { return SCSI::XferPhase::Status; } // ステータスフェーズを開始する。 - // デフォルトでは Good を返す実装を用意しておく。 - virtual void BeginStatus() { + void BeginStatus() { buf.clear(); - buf.push_back((uint8)SCSI::StatusByte::Good); + buf.push_back(status_byte); } // ステータスフェーズを完了する。 @@ -208,6 +232,9 @@ class SCSICmd protected: // このコマンドを実行しているターゲットデバイス。 SCSITarget *parent; + + // ステータスバイト (デフォルトは 0 = Good) + uint8 status_byte {}; }; // @@ -215,10 +242,10 @@ class SCSICmd // class SCSICmdTestUnitReady : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdTestUnitReady(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdTestUnitReady() { } + virtual ~SCSICmdTestUnitReady() override { } }; // @@ -226,10 +253,10 @@ class SCSICmdTestUnitReady : public SCSI // class SCSICmdRezeroUnit : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdRezeroUnit(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRezeroUnit() { } + virtual ~SCSICmdRezeroUnit() override { } // とりあえず黙って成功するだけでいいか }; @@ -239,28 +266,44 @@ class SCSICmdRezeroUnit : public SCSICmd // class SCSICmdRequestSense : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdRequestSense(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRequestSense() { } + virtual ~SCSICmdRequestSense() override { } + + SCSI::XferPhase Command(std::vector& cmdseq) override { + // 応答のセンスデータは SCSI-2 形式(最小18バイト) を実装。 + // 今の所、センスキー、ASC、ASCQ だけで返せる内容にだけ対応している。 + // これ以外のフィールドを返す必要が出来たらまた考えること。 - virtual SCSI::XferPhase Command(std::vector& cmdseq) { - // この内容は全く不正。でっちあげである。 - // まず 8バイトのバッファに応答を作成 buf.clear(); - buf.resize(8); - buf[0] = 0xf0; // Valid, エラークラス7, エラーコード0 - buf[1] = 0; - buf[2] = 0; // No Sense - buf[3] = 0; - buf[4] = 0; - buf[5] = 0; - buf[6] = 0; - buf[7] = 0; // 追加センスデータ長 0 + 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; } }; @@ -270,30 +313,45 @@ class SCSICmdRequestSense : public SCSIC // class SCSICmdRead6 : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdRead6(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRead6() { } + virtual ~SCSICmdRead6() override { } - virtual SCSI::XferPhase Command(std::vector& cmdseq) { + 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); - parent->putlog(1, "READ6 LBA=0x%x blks=0x%x", lba, blks); + // 平均シークタイム + optime = hdparent->seektime; + return SCSI::XferPhase::DataIn; } }; @@ -303,29 +361,43 @@ class SCSICmdRead6 : public SCSICmd // class SCSICmdWrite6 : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdWrite6(SCSITarget *p) : inherited(p) { hdparent = dynamic_cast(parent); } - virtual ~SCSICmdWrite6() { } + virtual ~SCSICmdWrite6() override { } + + SCSI::XferPhase Command(std::vector& cmdseq) override { + // センスデータをクリア + parent->ClearSense(); - virtual SCSI::XferPhase Command(std::vector& cmdseq) { lba = (cmdseq[1] & 0x1f) << 16; lba |= cmdseq[2] << 8; lba |= cmdseq[3]; blks = cmdseq[4]; - // blks == 0 は 256 ブロック転送を意味する。 if (blks == 0) { blks = 256; } - recvbytes = blks * parent->GetBlocksize(); + 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; } - virtual SCSI::XferPhase DoneDataOut() { + 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()), @@ -345,12 +417,15 @@ class SCSICmdWrite6 : public SCSICmd // class SCSICmdInquiry : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdInquiry(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdInquiry() { } + virtual ~SCSICmdInquiry() override { } + + SCSI::XferPhase Command(std::vector& cmdseq) override { + // センスデータをクリア + parent->ClearSense(); - virtual SCSI::XferPhase Command(std::vector& cmdseq) { // 応答データはターゲットデバイスが持っている buf = parent->inquiry_data; @@ -368,17 +443,20 @@ class SCSICmdInquiry : public SCSICmd // class SCSICmdModeSelect6 : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdModeSelect6(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdModeSelect6() { } + virtual ~SCSICmdModeSelect6() override { } + + SCSI::XferPhase Command(std::vector& cmdseq) override { + // センスデータをクリア + parent->ClearSense(); - virtual SCSI::XferPhase Command(std::vector& cmdseq) { recvbytes = cmdseq[4]; return SCSI::XferPhase::DataOut; } - virtual SCSI::XferPhase DoneDataOut() { + SCSI::XferPhase DoneDataOut() override { // Mode Select(6) のデータは以下の構造で並んでいる。 // ヘッダ // ブロックディスクリプタ#1 @@ -410,21 +488,44 @@ class SCSICmdModeSelect6 : public SCSICm // class SCSICmdModeSense6 : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdModeSense6(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdModeSense6() { } + virtual ~SCSICmdModeSense6() override { } - virtual SCSI::XferPhase Command(std::vector& cmdseq) { + 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; } +}; - virtual void BeginStatus() { - buf.clear(); - buf.push_back((uint8)SCSI::StatusByte::CheckCondition); +// +// 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); } }; @@ -433,14 +534,17 @@ class SCSICmdModeSense6 : public SCSICmd // class SCSICmdReadCapacity : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdReadCapacity(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdReadCapacity() { } + virtual ~SCSICmdReadCapacity() override { } - virtual SCSI::XferPhase Command(std::vector& cmdseq) { + 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"); @@ -450,7 +554,7 @@ class SCSICmdReadCapacity : public SCSIC // +00.L 最終論理ブロックアドレス // +04.L ブロック長 uint32 blksize = parent->GetBlocksize(); - uint32 lastblk = (hdparent->memlen / blksize) - 1; + uint32 lastblk = (hdparent->GetSize() / blksize) - 1; buf.resize(8); buf[0] = lastblk >> 24; buf[1] = lastblk >> 16; @@ -470,16 +574,19 @@ class SCSICmdReadCapacity : public SCSIC // class SCSICmdRead10 : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdRead10(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdRead10() { } + virtual ~SCSICmdRead10() override { } - virtual SCSI::XferPhase Command(std::vector& cmdseq) { + 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"); } @@ -490,35 +597,104 @@ class SCSICmdRead10 : public SCSICmd 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; + } - // XXX 範囲チェック uint8 *start = hdparent->mem + lba * parent->GetBlocksize(); uint8 *end = start + blks * parent->GetBlocksize(); buf.assign(start, end); - parent->putlog(1, "READ10 LBA=0x%x blks=0x%x", lba, blks); + // 平均シークタイム + optime = hdparent->seektime; + return SCSI::XferPhase::DataIn; } }; // +// SCSI コマンド 0x2a Write(10) (ダイレクトアクセスデバイス) +// +class SCSICmdWrite10 : public SCSICmd +{ + using inherited = SCSICmd; + public: + SCSICmdWrite10(SCSITarget *p) : inherited(p) { + hdparent = dynamic_cast(parent); + } + virtual ~SCSICmdWrite10() override { } + + SCSI::XferPhase Command(std::vector& cmdseq) override { + // センスデータをクリア + parent->ClearSense(); + + lba = cmdseq[2] << 24; + lba |= cmdseq[3] << 16; + lba |= cmdseq[4] << 8; + lba |= cmdseq[5]; + blks = cmdseq[7] << 8; + blks |= cmdseq[8]; + + 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, "WRITE10 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 コマンド サポートしていないコマンド // class SCSICmdNotSupportedCommand : public SCSICmd { - typedef SCSICmd inherited; + using inherited = SCSICmd; public: SCSICmdNotSupportedCommand(SCSITarget *p) : inherited(p) { } - virtual ~SCSICmdNotSupportedCommand() { } + virtual ~SCSICmdNotSupportedCommand() override { } - // Command は何もせずステータスに移るだけなので基底のやつでいい - - virtual void BeginStatus() { - buf.clear(); - buf.push_back((uint8)SCSI::StatusByte::CheckCondition); + 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; } }; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif // // SCSI ターゲットデバイスの共通部分 @@ -541,6 +717,30 @@ SCSITarget::~SCSITarget() { } +// バスリセットされた (SCSIBus から呼ばれる) +void +SCSITarget::BusResetCB() +{ + // リセットされたので持ってる信号を全部解放。 + // 一応自分が変えていい信号線だけを落とす。 + 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; + } + + // センスキーをクリア + ClearSense(); +} + // セレクションで選択された (SCSIBus から呼ばれる) void SCSITarget::SelectionCB() @@ -701,6 +901,12 @@ SCSITarget::TransferAckCB() } putlog(2, "%s done", SCSI::GetXferPhaseName(xfer)); + // 簡略化のため、フェーズ完了時だけ REQ を引き伸ばすことができる + if (cmd->optime != 0) { + bus->SetOneshotReqWait(cmd->optime); + cmd->optime = 0; + } + if (next != xfer) { // End ならバスフリーにして終了。 if (next == SCSI::XferPhase::End) { @@ -815,38 +1021,27 @@ SCSIHD::SCSIHD(SCSIHostDevice *h, int id : inherited(h, id, inquiry_data) { devname = "SCSIHD"; - devtype = SCSI::HD; - fd = -1; - mem = NULL; + devtype = SCSI::DevType::HD; } // デストラクタ SCSIHD::~SCSIHD() { - if (mem) { - munmap(mem, memlen); - mem = NULL; - } - if (fd >= 0) { - close(fd); - fd = -1; - } } // 初期化 bool SCSIHD::Init() { - struct stat st; - 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 imgkey = keybody + "image"; const std::string wpkey = keybody + "writeprotect"; + const std::string stkey = keybody + "seektime"; - const ConfigItem& itemimg = gConfig->Get(imgkey); + const ConfigItem& itemimg = gConfig->Find(imgkey); const std::string& image = itemimg.AsString(); // ここに来る時にチェックしているので ',' はあるはず auto pos = image.find(',') + 1; @@ -856,33 +1051,24 @@ SCSIHD::Init() return false; } + // ファイルをオープン const std::string path = gMainApp.GetVMDir() + filename; - fd = open(path.c_str(), O_RDWR); - if (fd == -1) { - warn("%s \"%s\" open failed", myname.c_str(), path.c_str()); - return false; - } - - if (fstat(fd, &st) == -1) { - warn("%s \"%s\" fstat failed", 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 \"%s\" mmap failed", myname.c_str(), path.c_str()); - close(fd); + file.SetFilename(path); + file.SetDispname(string_format("%s \"%s\"", myname.c_str(), path.c_str())); + mem = file.OpenFile(); + if (mem == NULL) { return false; } - writeprotect = gConfig->Get(wpkey).AsInt(); + writeprotect = gConfig->Find(wpkey).AsInt(); if (writeprotect) { putmsg(0, "%s: write protected", myname.c_str()); } + // 設定は [msec]、変数は [nsec]。 + seektime = gConfig->Find(stkey).AsInt(); + seektime *= 1_msec; + return true; } @@ -899,6 +1085,10 @@ SCSIHD::DispatchCmd() cmd.reset(new SCSICmdWrite6(this)); break; + case SCSI::Command::StartStopUnit: // 0x1b + cmd.reset(new SCSICmdStartStopUnit(this)); + break; + case SCSI::Command::ReadCapacity: // 0x25 cmd.reset(new SCSICmdReadCapacity(this)); break; @@ -907,6 +1097,10 @@ SCSIHD::DispatchCmd() cmd.reset(new SCSICmdRead10(this)); break; + case SCSI::Command::Write10: // 0x2a + cmd.reset(new SCSICmdWrite10(this)); + break; + default: // 見付からなければ基本クラス側に任せる inherited::DispatchCmd(); @@ -922,7 +1116,7 @@ SCSIHD::PeekImage(void *buf, uint32 star if (mem == NULL) { return false; } - if ((uint64)start + (uint64)len > memlen) { + if ((uint64)start + (uint64)len > GetSize()) { return false; } memcpy(buf, mem + start, len);