--- nono/vm/scsidev.h 2026/04/29 17:04:29 1.1 +++ nono/vm/scsidev.h 2026/04/29 17:05:30 1.1.1.17 @@ -1,43 +1,54 @@ // // nono -// Copyright (C) 2020 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -// SCSI デバイス共通部分 +// +// SCSI デバイス +// + +// Device Device +// | | +// v v +// IODevice SCSIDevice (SCSI デバイスとしての共通部分) +// | | +// v +-----+------+ +// SPCDevice v v +// .scsi -> SCSIHostDevice SCSITarget (SCSI ホスト/ターゲットの共通部分) +// | +// v +// SCSIDisk (各デバイス、各ターゲット固有部分) #pragma once +#include "diskimage.h" +#include "message.h" #include "scsibus.h" - -// IODevice (SPCDevice の親になるため) -// | -// v -// SCSIDevice (SCSI デバイスとしての共通部分) -// | -// +------+---------+ -// v v -// SCSIHostDevice SCSITarget (SCSI ホスト/ターゲットとしての共通部分) -// | | -// v v -// SPCDevice SCSIHD (各デバイス、各ターゲット固有部分) - -class SCSICmd; +#include "scsicmd.h" // // SCSI デバイスとしての共通部分。 -// これ自体は IODevice ではないが SCSI ホストアダプタがこれを継承するため。 // -class SCSIDevice : public IODevice +class SCSIDevice : public Device { + using inherited = Device; public: - SCSIDevice(); - virtual ~SCSIDevice(); + explicit SCSIDevice(uint objid_); + ~SCSIDevice() override; - // バスに接続 (SCSIBus::Attach から呼ばれる) - void Attach(SCSIBus *bus_, int id) { - bus = bus_; - myid = id; - } + // バスに接続された (SCSIBus から呼ばれる) + void OnAttached(SCSIBus *bus_, uint myid_); + + // SCSI ID を返す + uint GetMyID() const { return myid; } + + // デバイス種別を返す (主にデバッグ表示用) + SCSI::DevType GetDevType() const { return devtype; } + + // 自身が接続しているバスを返す。 + // イニシエータがバスに接続するのは VM 実行後なことに注意。 + SCSIBus *GetBus() const { return bus; } // // 信号線制御 @@ -71,71 +82,105 @@ class SCSIDevice : public IODevice void SetData(uint8 val) const { bus->SetData(val); } // - // SCSIBus コールバック + // SCSIBus から呼ばれる // // バスリセットされた (全員に通知される) - virtual void BusResetCB() { } + virtual void OnBusReset() { } // バスフリーになった (全員に通知される) - virtual void BusFreeCB(int id) { } + virtual void OnBusFree(uint id) { } // セレクションで選択された (ターゲットのみ通知される) - virtual void SelectionCB() { } + virtual void OnSelected() { } // ターゲットがセレクションに応答した (イニシエータのみ通知される) - virtual void SelectionAckCB() { } + virtual void OnSelectionAck() { } // 情報転送フェーズを開始する (ターゲットのみ通知される) - virtual void StartTransferCB() { } + virtual void OnStartTransfer() { } // 情報転送フェーズで REQ を立てた (イニシエータのみ通知される) - virtual void TransferReqCB() { } + virtual void OnTransferReq() { } // 情報転送フェーズで1バイト送受信が進んだ (ターゲットのみ通知される) - virtual void TransferCB() { } + virtual void OnTransfer() { } // 情報転送フェーズ(マニュアル転送)で ACK を下げた (ターゲットのみ通知) - virtual void TransferAckCB() { } + virtual void OnTransferAck() { } protected: // 自身の ID - uint myid; + uint myid {}; + + // デバイス種別 + SCSI::DevType devtype {}; // 自身が繋がっているバスへのポインタ。 // (イニシエータ自身も含めて) 所有していない。 - SCSIBus *bus; + SCSIBus *bus {}; }; // // ホストデバイスの共通部分。 +// イニシエータもしくは HBA (ホストバスアダプタ) に相当する部分。 // class SCSIHostDevice : public SCSIDevice { + using inherited = SCSIDevice; + public: - SCSIHostDevice(); - virtual ~SCSIHostDevice(); + using SCSIHostCallback_t = void (Device::*)(); + using BusFreeCallback_t = void (Device::*)(uint); +#define ToSCSIHostCallback(f) \ + static_cast(f) +#define ToBusFreeCallback(f) \ + static_cast(f) - virtual bool Create(); - virtual bool Init(); + public: + SCSIHostDevice(IODevice *parent_, const char *config_name_); + ~SCSIHostDevice() override; - // 設定ファイル上のこのホストデバイスを示す接頭語を返す - const char *GetConfigName() const { return config_name; } + bool Init() override; - // バスフリーになった (全員に通知される) - virtual void BusFreeCB(int id); + // コールバック関数を設定する。 + void SetCallback( + BusFreeCallback_t busfree_, + SCSIHostCallback_t selack_, + SCSIHostCallback_t xferreq_); - protected: - // 設定ファイルに使われるこのホストデバイスを示す接頭語。 - // ("spc0" とか) - const char *config_name = NULL; + // 所有している SCSIBus ポインタを返す。こっちは接続前でも使える。 + SCSIBus *GetOwnedBus() const { return scsibus.get(); } + + // ターゲットデバイスを返す。(ショートカット) + SCSITarget *GetTarget(uint id) const { return scsibus->GetTarget(id); } + + private: + // SCSIBus から呼ばれる + + // バスフリーになった (SCSIBus から全員に通知される) + void OnBusFree(uint id) override; + // ターゲットがセレクションに応答した (SCSIBus から呼ばれる) + void OnSelectionAck() override; + // 情報転送フェーズで REQ を立てた (イニシエータのみ通知される) + void OnTransferReq() override; + + // 親クラス(コントローラ) へのコールバック。 + + // バスフリーを通知する + BusFreeCallback_t BusFreeCallback {}; + // ターゲットがセレクションに応答したことを通知する + SCSIHostCallback_t SelectionAckCallback {}; + // 転送フェーズでターゲットが REQ を立てたことを通知する + SCSIHostCallback_t TransferReqCallback {}; // SCSI バス (とターゲットオブジェクト) // // ホストデバイスなのでバスオブジェクト(SCSIBus *scsibus)と、このバスに // 繋がるターゲットデバイスのオブジェクト(SCSITarget *target[]) を所有 // (unique_ptr) している。これはオブジェクト同士の所有関係の話。 - // ホストは SCSIDevice を継承しているので target[] にホスト自身は含めない。 + // ホストデバイスは SCSIDevice を継承しているので target[] にホスト自身は + // 含めない。 // // それとは別に SCSI バスと SCSI デバイスの仮想的な接続関係があり、 // こっちはすべて所有権のないただのポインタで相互にリンクする。 @@ -143,20 +188,22 @@ class SCSIHostDevice : public SCSIDevice // // こんな感じ。 // SCSIHostDevice - // +-------------------------------------+ - // | scsibus target[0] | - // +--- || ----+ +---------- | -+ - // || |SCSIDevice| | - // ID7 ||<----> bus | | - // || +----------+ | - // || | - // || SCSIHD(SCSIDevice) | - // || +----------+ | - // ID0 ||<----> bus |<----------+ - // || +----------+ own + // +----------------------+ + // | .scsibus | + // +--- || ----+ | + // || |SCSIDevice| + // ID7 ||<----> .bus | + // || +----------+ + // || + // || SCSIDisk(SCSIDevice) + // || +----------+ + // ID0 ||<----> .bus | + // || +----------+ // - std::unique_ptr scsibus; - std::unique_ptr target[8]; + std::unique_ptr scsibus {}; + + // コントローラデバイス。 + IODevice *parent {}; }; // @@ -164,88 +211,204 @@ class SCSIHostDevice : public SCSIDevice // class SCSITarget : public SCSIDevice { - typedef SCSIDevice inherited; - friend class SCSICmdModeSelect6; + using inherited = SCSIDevice; public: - SCSITarget(SCSIHostDevice *h, int id, std::vector& inq); - virtual ~SCSITarget(); + SCSITarget(SCSIBus *, uint id); + ~SCSITarget() override; - // デバイス種別を返す (主にデバッグ表示用) - SCSI::DevType GetDevType() const { return devtype; } + void ResetHard(bool poweron) override; - // Inquiry 応答データ - std::vector inquiry_data; + // Inquiry データを返す (Cmd から呼ばれる) + virtual bool Inquiry(std::vector& buf, uint lun) = 0; // 現在のコマンド列を返す (モニタ用) std::vector GetCmdSeq() const { return cmdseq; } - // SCSIBus コールバック + // コマンドを選択する (ROM30 エミュレーション用) + SCSICmd *SelectCommand(const std::vector& cmdseq_); + + // 論理ブロック長 + uint32 GetBlocksize() const { return blocksize; } + void SetBlocksize(uint32 val) { blocksize = val; } + + // ModeSense コマンドのデバイス固有パラメータを返す + virtual uint8 GetDeviceSpecificParam() const { return 0; } + + // センスキーと ASC/ASCQ + uint8 sensekey {}; + uint16 senseasc {}; + + // センスキーをクリア + void ClearSense() { + sensekey = 0; + senseasc = 0; + } + + // SCSIBus から呼ばれる + + // バスリセットされた (全員に通知される) + void OnBusReset() override; // セレクションで選択された (ターゲットのみ通知される) - virtual void SelectionCB(); + void OnSelected() override; // 情報転送フェーズを開始する (ターゲットのみ通知される) - virtual void StartTransferCB(); + void OnStartTransfer() override; // 情報転送フェーズで1バイト送受信が進んだ (ターゲットのみ通知される) - virtual void TransferCB(); + void OnTransfer() override; // 情報転送フェーズ(マニュアル転送)で ACK を下げた (ターゲットのみ通知) - virtual void TransferAckCB(); + void OnTransferAck() override; protected: - // デバイス種別 - SCSI::DevType devtype; - // ホスト - SCSIHostDevice *host; + // SCSI コマンドのディスパッチャ + virtual void DispatchCmd(); // コマンド std::vector cmdseq {}; + // このコマンドの長さ。 // cmdseq の1バイト目で決まるやつのことで、受信目標バイト数。 // 今何バイト受信しているかは cmdseq.size()。 - int cmdlen; - - // SCSI コマンドのディスパッチャ - virtual void DispatchCmd(); + uint cmdlen {}; // 現在実行中の SCSI コマンド - std::unique_ptr cmd; + std::unique_ptr cmd {}; // 現在のフェーズでの転送カウント。 // IN と OUT でカウント方向が違うことに注意。 - uint32 bytecount = 0; + uint32 bytecount {}; // 現在処理中のバイトが(各)フェーズの最終バイトなら true - bool lastbyte = false; + bool lastbyte {}; - // 論理ブロック長 (ModeSelect コマンドでホストから指定される) - // XXX: LUNA88K で ROM がたぶんやる。。。 - uint32 blocksize = 512; + // 論理ブロック長 + uint32 blocksize {}; }; // -// SCSI HD +// SCSI Disk (HD,CD,...) // -class SCSIHD : public SCSITarget +class SCSIDisk : public SCSITarget { - typedef SCSITarget inherited; - friend class SCSICmdRead6; - friend class SCSICmdWrite6; - friend class SCSICmdReadCapacity; - friend class SCSICmdRead10; + using inherited = SCSITarget; public: - SCSIHD(SCSIHostDevice *h, int id); - virtual ~SCSIHD(); + // メディアへの書き込みモード + // + // Device Media Ignore + // RO (RO) * ReadOnly + // RW RO * WriteProtect + // RW RW no Writeable + // RW RW yes WriteIgnore + enum class RW { + ReadOnly = 2, // 読み込み専用デバイス + WriteProtect = 1, // 書き込み禁止メディア + Writeable = 0, // 書き込み可能 + WriteIgnore = -1, // 書き込み可能メディアで書き込みを無視 + }; - virtual bool Init(); - virtual void DispatchCmd(); + public: + SCSIDisk(SCSIBus *, uint id, SCSI::DevType type); + ~SCSIDisk() override; + + bool Init() override; + void ResetHard(bool poweron) override; + + void DispatchCmd() override; + bool Inquiry(std::vector& buf, uint lun) override; + + // ModeSense コマンドのデバイス固有パラメータを返す + uint8 GetDeviceSpecificParam() const override; + + // buf にデータを読み込む。 + // 単位はセクタではなくバイトなことに注意。 + bool Read(std::vector& buf, uint64 start); + + // buf のデータを書き込む。 + // 単位はセクタではなくバイトなことに注意。 + bool Write(const std::vector& buf, uint64 start); + + // このデバイスのバイトサイズ + off_t GetSize() const; + + // このデバイスをディスクイメージだと思うとして (いや実際そうだけど) + // 先頭から start バイト目から len バイト読み出して返す。 + // 単位はセクタではなくバイトなことに注意。 + // (エミュレータ特権アクセス) + bool Peek(void *buf, uint64 start, uint32 len) const; + + // シーク時間[nsec] + uint64 GetSeekTime() const { return seektime; } + + // リムーバブルデバイスなら true + bool IsRemovableDevice() const { return removable_device; } + + // 書き込み可能デバイスなら true + bool IsWriteableDevice() const { return writeable_device; } + + // メディアが入っていれば true + bool IsMediumLoaded() const { return medium_loaded; } + + // メディアの書き込みモードを返す + RW GetWriteMode() const { return write_mode; } + // メディアの書き込みモードを文字列で返す (モニタ用) + const char *GetWriteModeStr() const; + + // メディア(ディスク)イメージを開く/閉じる。(同一スレッドから呼ぶこと) + bool LoadDisk(const std::string& path_); + void UnloadDisk(bool force = false); + + // メディア(ディスク)イメージを開く/閉じる。(UI スレッドから呼ぶ) + void LoadDiskUI(const std::string& path_); + void UnloadDiskUI(bool force); + + // メディアの取り出し可否 + bool IsMediumRemovalPrevented() const { return medium_removal_prevented; } + bool IsMediumRemovalAllowed() const { return !medium_removal_prevented; } + void PreventMediumRemoval(bool val); + + // イメージファイルのフルパスを返す + const std::string& GetPathName() const { return pathname; } + + // SCSIBus から呼ばれる + + // バスリセットされた (全員に通知される) + void OnBusReset() override; private: - int fd = 0; - uint8 *mem = NULL; - size_t memlen = 0; - bool writeprotect = false; + // ディスクイメージを閉じる (内部用) + bool UnloadDisk_internal(); + + // ディスク状態をクリアする + void Clear(); + + // メディア状態変更を UI に通知する + void MediaChanged() const; + + // メディア挿入/排出コールバック + void LoadMessage(MessageID, uint32); + void UnloadMessage(MessageID, uint32); + + // イメージファイルのフルパス + std::string pathname {}; + // 新しいイメージファイルのフルパス (UIから) + std::string new_pathname {}; + + // イメージファイル + DiskImage image {}; + + bool removable_device {}; // リムーバブルデバイスなら true + bool writeable_device {}; // 書き込み可能なデバイスなら true + bool write_ignore {}; // 書き込み無視オプション + bool medium_loaded {}; // メディアが挿入されていれば true + + // メディアの書き込みモード + RW write_mode {}; + + // メディアの取り出しを禁止するなら true。 + // XXX Bus Device Reset メッセージでも解除できるがメッセージは未実装 + bool medium_removal_prevented {}; - static std::vector inquiry_data; + uint64 seektime {}; };