|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// SCSI デバイス
//
// ログレベルは
// 1: SCSI コマンドの概要。
// デバイスの動作のうち大きな変更のもの
// 2: SCSI コマンドの詳細、ダンプ。
// デバイスの動作のうち些細なもの、あるいは頻繁なもの
// 3: フェーズ遷移
// 4: データも含める
#include "scsicmd.h"
#include "scsidev.h"
#include "scsidomain.h"
#include "config.h"
#include "event.h"
#include "mainapp.h"
#include "scheduler.h"
#include "uimessage.h"
//
// SCSI デバイス共通部分
//
// コンストラクタ
SCSIDevice::SCSIDevice(uint objid_)
: inherited(objid_)
{
myid = -1;
}
// デストラクタ
SCSIDevice::~SCSIDevice()
{
}
// バスに接続する。
void
SCSIDevice::Attach(SCSIBus *bus_)
{
bus = bus_;
}
//
// SCSI ホストデバイス (イニシエータ)
//
// コンストラクタ
SCSIHostDevice::SCSIHostDevice(SCSIDomain *domain_, IODevice *parent_)
: inherited(OBJ_NONE)
{
domain = domain_;
parent = parent_;
// イニシエータの ID は実行時にコントローラによって決まる。
devtype = SCSI::DevType::Initiator;
SetName(string_format("(SCSIHost.%s)", domain_->GetDomainName()));
SetLogLevel(parent->loglevel);
}
// デストラクタ
SCSIHostDevice::~SCSIHostDevice()
{
}
// SCSI ID を設定する。
// (SCSIDomain からのみ呼ぶこと)
void
SCSIHostDevice::SetID(uint id_)
{
myid = id_;
}
// SCSIBus からのコールバック関数を設定する。
void
SCSIHostDevice::SetBusCallback(
BusFreeCallback_t busfree_,
SCSIHostCallback_t selack_,
SCSIHostCallback_t xferreq_)
{
BusFreeCallback = busfree_;
SelectionAckCallback = selack_;
TransferReqCallback = xferreq_;
}
// バスフリーになった (SCSIBus から呼ばれる)
void
SCSIHostDevice::OnBusFree(uint id)
{
// 実際にはバスフリーを検出したデバイスは各自自身が送出している信号を
// 下げるのだが、色々面倒なので、ここではホストのバスフリーコールバックが
// 一括して全部の線を下げる。
// ただしさすがにログがうるさいので、上がってる線だけ下げる。
uint32 data;
while ((data = GetBSY()) != 0) {
bus->NegateBSY(SCSIBus::DecodeID(data));
}
while ((data = GetSEL()) != 0) {
bus->NegateSEL(SCSIBus::DecodeID(data));
}
if (GetREQ()) {
NegateREQ();
}
if (GetACK()) {
NegateACK();
}
if (GetATN()) {
NegateATN();
}
SetXfer(0);
SetData(0);
// 親のバスフリーコールバックを呼ぶ
(parent->*(BusFreeCallback))(id);
// RST が見えてる状態で親に通知はしたので、ここで下ろす。
if (GetRST()) {
NegateRST();
}
}
// ターゲットがセレクションに応答した (SCSIBus から呼ばれる)
void
SCSIHostDevice::OnSelectionAck()
{
// ターゲットが BSY を立てて応答したので、
// こちらはデータバスをクリアして SEL を下げる。
// これによりセレクションフェーズは成功で完了する。
SetData(0);
NegateSEL();
// 親のバスフリーコールバックを呼ぶ
(parent->*(SelectionAckCallback))();
}
// ターゲットが REQ を立てた (SCSIBus から呼ばれる)
void
SCSIHostDevice::OnTransferReq()
{
(parent->*(TransferReqCallback))();
}
//
// SCSI ターゲットデバイスの共通部分
//
// コンストラクタ。
SCSITarget::SCSITarget(SCSIDomain *domain_, uint id)
: inherited(OBJ_NONE) // オブジェクト ID で検索することはない
{
domain = domain_;
myid = id;
// ログは出すので名前はつけるがそれは継承先で SetName() している。
}
// デストラクタ
SCSITarget::~SCSITarget()
{
}
// 電源オン
void
SCSITarget::ResetHard(bool poweron)
{
if (poweron) {
ClearSense();
cmdseq.clear();
cmdlen = 0;
cmd.reset();
msgseq.clear();
bytecount = 0;
lastbyte = false;
}
}
// バスリセットされた (SCSIBus から呼ばれる)
void
SCSITarget::OnBusReset()
{
// センスキーをクリア
ClearSense();
// リセットされたので持ってる信号を全部解放。
// 一応自分が変えていい信号線だけを落とす。
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;
}
}
// セレクションで選択された (SCSIBus から呼ばれる)
void
SCSITarget::OnSelected()
{
// ターゲットが、自身が選択されていることを認識してから BSY を
// 立てるまでの時間はもうバス側でカウントしてあるので、ここでは
// ただちに BSY を上げてよい。
putlog(4, "OnSelected");
AssertBSY();
}
// 情報転送フェーズを開始する (SCSIBus から呼ばれる)
void
SCSITarget::OnStartTransfer()
{
// ATN が立っていればメッセージアウトフェーズ。
// そうでなければコマンドフェーズ。
cmdseq.clear();
cmdlen = 0;
msgseq.clear();
if (GetATN()) {
putlog(4, "Start MsgOut Phase by ATN");
SetXfer(SCSI::XferPhase::MsgOut);
} else {
putlog(4, "Start Command Phase");
SetXfer(SCSI::XferPhase::Command);
}
AssertREQ();
}
// 情報転送フェーズ間の遷移について
//
// 情報転送フェーズは OnTransfer() で1バイトずつ転送を行い、所定のバイト数
// の転送が完了すればフェーズを遷移する。遷移方法は入出力方向別で6通り。
// o IN -> IN
// o IN -> OUT
// o IN -> END
// o OUT -> OUT
// o OUT -> IN
// o OUT -> END
//
// IN -> IN の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを
// 変更して REQ を立てるところまで。
//
// IN -> OUT の遷移。OnTransfer() で前フェーズの最終バイト受信後、フェーズを
// 変更して (おそらく {Phase}Begin() を発行してから)、データをセットし REQ
// を立てる?
//
// IN -> END の遷移。OnTransfer() で前フェーズの最終バイト受信後、BSY を
// 下げて終了。
//
// OUT -> OUT の遷移。前フェーズの最終バイトをイニシエータが受信後に
// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin()
// を発行してから)、データをセットし REQ を立てる?
//
// OUT -> IN の遷移。前フェーズの最終バイトをイニシエータが受信後に
// OnTransfer() が呼ばれるので、フェーズを変更して (おそらく {Phase}Begin()
// を発行してから)、REQ を立てる?
//
// OUT -> END の遷移。前フェーズの最終バイトをイニシエータが受信後に
// OnTransfer() が呼ばれるので、BSY を下げる?
// 情報転送フェーズでイニシエータが ACK を上げた。(SCSIBus から呼ばれる)
// OUT (イニシエータ → ターゲット方向) なら、ターゲットが上げた REQ に応答
// してイニシエータがデータをデータバスに乗せてからこれをコールしてくる。
// IN (ターゲット → イニシエータ方向) なら、ターゲットがデータをデータバスに
// 乗せて REQ を上げたのをイニシエータが受信した後でこれをコールしてくる。
void
SCSITarget::OnTransfer()
{
uint8 data;
SCSI::XferPhase xfer;
// このフェーズの最終バイトか
lastbyte = false;
// このフェーズの1バイト処理
xfer = GetXfer();
if (xfer == SCSI::XferPhase::Command) {
// コマンドフェーズだけいろいろ特殊なので別処理。
// 受信バッファが異なる、1バイト目で受信長が決まるなど。
data = GetData();
putlog(4, "%s: cmdseq[%02u] = $%02x", __func__,
(uint)cmdseq.size(), data);
cmdseq.push_back(data);
if (cmdseq.size() == 1) {
// 1バイト目ならこのコマンドが何バイトか調べる
switch (cmdseq[0] >> 5) {
case 0: cmdlen = 6; break;
case 1: cmdlen = 10; break;
case 2: cmdlen = 10; break;
case 5: cmdlen = 12; break;
default:
// 3,4 はリサーブ、6,7 はベンダー定義。XXX 来たらどうする?
cmdlen = 6;
break;
}
} else if (cmdseq.size() >= cmdlen) {
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);
}
putlogn("%s", cmdbuf.c_str());
}
// 最終バイトを受信したらコマンドを選択。
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 (xfer == SCSI::XferPhase::MsgOut) {
data = GetData();
putlog(4, "%s: msgseq[%02u] = $%02x", __func__,
(uint)msgseq.size(), data);
msgseq.push_back(data);
bytecount--;
if (bytecount == 0) {
lastbyte = true;
}
} else if (GetIO() == false) {
// OUT 方向 (ターゲットが受信側)
// bytecount は減算方向。
cmd->buf.push_back(GetData());
bytecount--;
if (bytecount == 0) {
lastbyte = true;
}
} else {
// IN 方向 (ターゲットが送信側)
// bytecount は加算方向。
// データは前のループの終わり (OnTransferAck()の下のほう) で
// セットしているのでここでは引き取られた分をカウントする。
bytecount++;
if (bytecount >= cmd->buf.size()) {
lastbyte = true;
}
}
NegateREQ();
}
// 情報転送フェーズでイニシエータが ACK を下げた。(SCSIBus から呼ばれる)
void
SCSITarget::OnTransferAck()
{
SCSI::XferPhase xfer = GetXfer();
// これが最終バイトだったら、フェーズ完了 → フェーズ切り替え。
if (lastbyte) {
SCSI::XferPhase next;
// フェーズ完了
switch (xfer) {
case SCSI::XferPhase::Command:
next = cmd->ExecCommand(cmdseq);
break;
case SCSI::XferPhase::DataOut:
next = cmd->DoneDataOut();
break;
case SCSI::XferPhase::DataIn:
next = cmd->DoneDataIn();
break;
case SCSI::XferPhase::MsgOut:
if (cmd) {
next = cmd->DoneMsgOut();
} else {
// コマンド前のメッセージアウト。
if (msgseq.size() == 1 && (msgseq[0] & 0x80)) {
putlog(1, "%s: MsgOut $%02x Identify (NOT SUPPORTED)",
__func__, msgseq[0]);
} else {
std::string buf;
for (auto m : msgseq) {
buf += string_format(" %02x", m);
}
putlog(0, "%s: MsgOut%s (NOT SUPPORTED)",
__func__, buf.c_str());
}
next = SCSI::XferPhase::Command;
}
break;
case SCSI::XferPhase::MsgIn:
next = cmd->DoneMsgIn();
break;
case SCSI::XferPhase::Status:
next = cmd->DoneStatus();
break;
default:
PANIC("%s unknown xfer done %d", __func__, (int)xfer);
}
putlog(3, "%s done", SCSI::GetXferPhaseName(xfer));
// 簡略化のため、フェーズ完了時だけ REQ を引き伸ばすことができる
if (cmd && cmd->optime != 0) {
bus->SetOneshotReqWait(cmd->optime);
cmd->optime = 0;
}
if (next != xfer) {
// End ならバスフリーにして終了。
if (next == SCSI::XferPhase::End) {
cmd.reset();
NegateBSY();
return;
}
// (必要なら)フェーズを更新
SetXfer(next);
xfer = next;
// 新しいフェーズの開始処理
// XXX ここで bytecount とかをリセットできればいいのだが
// Command フェーズで Data フェーズの準備をすることが多いので
// 悩ましい。
switch (xfer) {
case SCSI::XferPhase::Command:
break;
case SCSI::XferPhase::DataOut:
cmd->buf.clear();
bytecount = cmd->recvbytes;
putlog(3, "DataOut begin (%u bytes)", bytecount);
break;
case SCSI::XferPhase::DataIn:
bytecount = 0;
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 (%u bytes)", bytecount);
break;
case SCSI::XferPhase::MsgIn:
cmd->BeginMsgIn();
bytecount = 0;
putlog(3, "MsgIn begin (%u bytes)", (uint)cmd->buf.size());
break;
case SCSI::XferPhase::Status:
cmd->BeginStatus();
bytecount = 0;
putlog(3, "Status begin (%u bytes)", (uint)cmd->buf.size());
break;
default:
PANIC("%s unknown xfer begin %d", __func__, (int)xfer);
}
}
}
// IN 方向 (ターゲットが送信側) ならここでデータをバスにセット。
if (GetIO()) {
SetData(cmd->buf[bytecount]);
putlog(4, "SetData $%02x", GetData());
}
// 次のバイト転送を行うために REQ を上げる。
AssertREQ();
}
// SCSI コマンドのディスパッチャ。
// C++ の例外は呼び出し側で捕捉している。
void
SCSITarget::DispatchCmd()
{
switch (cmdseq[0]) {
case SCSI::Command::TestUnitReady: // 0x00
cmd.reset(new SCSICmd(this));
break;
case SCSI::Command::RezeroUnit: // 0x01
cmd.reset(new SCSICmd(this));
break;
case SCSI::Command::RequestSense: // 0x03
cmd.reset(new SCSICmdRequestSense(this));
break;
case SCSI::Command::Inquiry: // 0x12
cmd.reset(new SCSICmdInquiry(this));
break;
case SCSI::Command::ModeSelect6: // 0x15
case SCSI::Command::ModeSelect10: // 0x55
cmd.reset(new SCSICmdModeSelect(this));
break;
case SCSI::Command::ModeSense6: // 0x1a
case SCSI::Command::ModeSense10: // 0x5a
cmd.reset(new SCSICmdModeSense(this));
break;
default:
// サポートしていないコマンド
const char *cname = SCSI::GetCommandName(cmdseq[0]);
std::string name;
if (cname) {
name = string_format(" %s", cname);
}
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<uint8>& cmdseq_)
{
cmdseq = cmdseq_;
SelectCommand();
return cmd.get();
}
// 内部版。
// 入力コマンド列はメンバ変数 cmdseq だし、生成した cmd もメンバ変数。
void
SCSITarget::SelectCommand()
{
cmd.reset();
// DispatchCmd() の中は new が多いので個別に捕捉せずこっちでやる。
try {
DispatchCmd();
} catch (...) { }
}
//
// SCSI Disk
//
// コンストラクタ
SCSIDisk::SCSIDisk(SCSIDomain *domain_, uint id, SCSI::DevType devtype_)
: inherited(domain_, id)
{
devtype = devtype_;
const char *devtypename = SCSI::GetDevTypeName(devtype);
const char *domainname = domain->GetDomainName();
// ドライブ自身のログ表示名は "HD(SPC0-6)" のような形式にする。
// ログ指定はそれに合わせて "spc0-6" のような完全形と
// "hd6" のような短縮形を用意してみる。
// SCSI コントローラ2台目ではこの短縮形だけなくす?
//
// コンストラクタの初期化子では devtypename を組み込みにくいので、
// コンストラクタ本文でログ名などを設定する。その時の作法に
// 従っているのですこし面倒な手法が必要。
// lib/object.h 参照。
std::string name = string_format("%s-%u", domainname, id);
SetName(string_format("%s(%s)", devtypename, name.c_str()));
ClearAlias();
AddAlias(name);
AddAlias(string_format("%s%u", devtypename, id));
// image はコンストラクタがないので、このあたりで代入する。
image.SetName(name);
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);
}
}
// デストラクタ
SCSIDisk::~SCSIDisk()
{
}
// 初期化
bool
SCSIDisk::Init()
{
const std::string keybody = string_format("%s-id%u-",
domain->GetConfigName(), myid);
const std::string imgkey = keybody + "image";
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& 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;
}
// リムーバブルデバイスのみ 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).AsBool();
// writeignore が決まったので状態を初期化。
Clear();
// 設定時点でファイルが指定されていればオープン
if (filename.empty() == false) {
std::string path = gMainApp.NormalizePath(filename);
if (LoadDisk(path) == false) {
itemimg.Err("Could not open %s", path.c_str());
return false;
}
}
// 平均シークタイム。設定は [msec]、変数は [tsec]。
// virtio-scsi にはない。
if (strncmp(stkey.c_str(), "virtio", 6) != 0) {
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;
bool read_only;
// すでにあればクローズ (ここでは通知は行わない)
unloaded = UnloadDisk_internal();
// 新しいイメージをオープン(する準備)
pathname = pathname_;
if (image.CreateHandler(pathname) == false) {
goto done;
}
// 書き込みモードをここで確定。
//
// デバイス特性 イメージ write_ignore write_mode
// ------------ ---------- ------------ : ----------
// 書込み可 書込み可 no Writeable
// 〃 〃 yes WriteIgnore
// 〃 書込み不可 no WriteProtect (A)
// 〃 〃 yes WriteIgnore (B)
// 書込み不可 * * ReadOnly
//
// (A) は「ライトプロテクトした MO を入れた」状態。
// (B) は「書き換えたくないので書き込み権を落としてあるイメージファイルを
// あたかも書き込めるように見せたい」。(WI マークが出る)
if (IsWriteableDevice()) {
// 書き込み可能デバイスの場合
if (write_ignore) {
write_mode = RW::WriteIgnore;
} else {
int w_ok = image.IsWriteable();
if (w_ok < 0) {
goto done;
} else if (w_ok) {
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;
}
// ディスク状態をクリアする
// (メディアを取り外した時など)
void
SCSIDisk::Clear()
{
medium_loaded = false;
image.Close();
pathname.clear();
if (IsWriteableDevice()) {
if (write_ignore) {
write_mode = RW::WriteIgnore;
} else {
write_mode = RW::Writeable;
}
} else {
write_mode = RW::ReadOnly;
}
}
// メディア状態変更を UI に通知する
void
SCSIDisk::MediaChanged() const
{
gMainApp.GetUIMessage()->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 に通知
gMainApp.GetUIMessage()->Post(UIMessage::SCSI_MEDIA_FAILED);
}
}
// メディア排出メッセージコールバック
void
SCSIDisk::UnloadMessage(MessageID msgid, uint32 arg)
{
bool force = arg;
UnloadDisk(force);
}
// SCSI コマンドのディスパッチャ。
// C++ の例外は呼び出し側で捕捉している。
void
SCSIDisk::DispatchCmd()
{
switch (cmdseq[0]) {
case SCSI::Command::Read6: // 0x08
case SCSI::Command::Read10: // 0x28
cmd.reset(new SCSICmdRead(this));
break;
case SCSI::Command::Write6: // 0x0a
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::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:
// 見付からなければ基本クラス側に任せる
inherited::DispatchCmd();
break;
}
}
// Inquiry データを返す
bool
SCSIDisk::Inquiry(std::vector<uint8>& buf, uint lun)
{
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<uint8>& 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<uint8>& buf, uint64 start)
{
if (__predict_false((bool)image == false)) {
return false;
}
if (GetWriteMode() >= RW::WriteProtect) {
// 書き込み不可
return false;
}
return image.Write(buf.data(), start, buf.size());
}
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);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.