|
|
nono 0.0.1
//
// nono
// Copyright (C) 2018 [email protected]
//
#include <algorithm>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "scsitarget.h"
#include "scsi.h"
#include "configfile.h"
#include "mystring.h"
//
// SCSI コマンドクラス
//
class SCSICmd
{
public:
SCSICmd(SCSITarget *p) {
parent = p;
}
virtual ~SCSICmd() { }
// コマンドフェーズを開始する。
// コマンドバイト列が揃ったところでコールされるので、
// 必要な処理を行い、次のフェーズ (SCSI::Phase::*) を返す。
virtual uint8 Command(const std::vector<uint8>& cmds) {
// 何もしない系コマンドなら次はステータスフェーズ
return SCSI::Phase::Status;
}
// データインフェーズを開始する。
// データインフェーズ開始時にコールされるので、
// ホストに転送するデータを用意し、
// *ptr にデータの先頭、戻り値にデータ長を返す。
virtual uint DataInBegin(const uint8 **ptr) {
return 0;
}
// データアウトフェーズを開始する。
// データアウトフェーズ開始時にコールされるので、
// ホストからのデータ受信用バッファを用意し、
// *ptr にバッファ、戻り値にバッファ長を返す。
virtual uint DataOutBegin(uint8 **ptr) {
return 0;
}
// データイン、データアウトフェーズ完了時にコールされる。
virtual void DataDone() { }
// ステータスフェーズを開始する。
// ステータス (SCSI::StatusByte::*) を返す。
virtual uint Status() {
return SCSI::StatusByte::Good;
}
// メッセージインフェーズを開始する。
// メッセージ (SCSI::MsgByte::*) を返す。
virtual uint MsgIn() {
return SCSI::MsgByte::CommandComplete;
}
protected:
// このコマンドを呼び出したデバイス
SCSITarget *parent = NULL;
};
// $00 TestUnitReady コマンド (共通)
// とりあえず何もせず正常を返すだけでいい。
class SCSICmdTestUnitReady : public SCSICmd
{
typedef SCSICmd inherited;
public:
SCSICmdTestUnitReady(SCSITarget *p) : inherited(p) { }
virtual ~SCSICmdTestUnitReady() { }
};
// $03 RequestSense コマンド (共通)
class SCSICmdRequestSense: public SCSICmd
{
typedef SCSICmd inherited;
public:
SCSICmdRequestSense(SCSITarget *p) : inherited(p) { }
virtual ~SCSICmdRequestSense() { }
virtual uint8 Command(const std::vector<uint8>& cmds) {
parent->putlog(1, "RequestSense %d", cmds[4]);
// この内容は全く不正。でっちあげである。
len = (uint)cmds[4];
mem[0] = 0x80 | 0x70;
mem[1] = 0;
mem[2] = 0; // No Sense
mem[3] = 0;
mem[4] = 0;
mem[5] = 0;
mem[6] = 0;
mem[7] = 0; // 追加センスデータ長
return SCSI::Phase::DataIn;
}
virtual uint DataInBegin(const uint8 **ptr) {
*ptr = mem;
return len;
}
private:
uint len = 0;
uint8 mem[256] {};
};
// $08 Read(6) コマンド (ダイレクトアクセスデバイス)
class SCSICmdRead6 : public SCSICmd
{
typedef SCSICmd inherited;
public:
SCSICmdRead6(SCSITarget *p)
: inherited(p)
{
hdparent = dynamic_cast<SCSIHD*>(p);
}
virtual ~SCSICmdRead6() { }
virtual uint8 Command(const std::vector<uint8>& cmds) {
uint32 lba;
uint32 blks;
lba = (cmds[1] & 0x1f) << 16;
lba |= cmds[2] << 8;
lba |= cmds[3];
blks = cmds[4];
// XXX 範囲チェック
// READ(6) の blks == 0 は 256 を意味する。
// なお READ(10) ではこの変換は無い。
if (blks == 0) {
blks = 256;
}
dataptr = hdparent->mem + (lba * hdparent->blocksize);
datalen = blks * parent->blocksize;
parent->putlog(1, "READ6 $%x %d", lba, blks);
return SCSI::Phase::DataIn;
}
virtual uint DataInBegin(const uint8 **ptr) {
*ptr = dataptr;
return datalen;
}
private:
SCSIHD *hdparent = NULL;
uint8 *dataptr = NULL; // 今回の Read の先頭
uint datalen = 0; // 今回の Read のバイト数
};
// $0a Write(6) コマンド (ダイレクトアクセスデバイス)
class SCSICmdWrite6 : public SCSICmd
{
typedef SCSICmd inherited;
public:
SCSICmdWrite6(SCSITarget *p)
: inherited(p)
{
hdparent = dynamic_cast<SCSIHD*>(p);
databuf = NULL;
datalen = 0;
}
virtual ~SCSICmdWrite6() {
if (databuf) {
delete[] databuf;
}
}
virtual uint8 Command(const std::vector<uint8>& cmds) {
uint32 blks;
lba = (cmds[1] & 0x1f) << 16;
lba |= cmds[2] << 8;
lba |= cmds[3];
blks = cmds[4];
// XXX 範囲チェック
// WRITE(6) の blks == 0 は 256 を意味する。
// なお WRITE(10) ではこの変換は無い。
if (blks == 0) {
blks = 256;
}
datalen = blks * parent->blocksize;
databuf = new uint8[datalen];
parent->putlog(1, "WRITE6 $%x %d", lba, blks);
return SCSI::Phase::DataOut;
}
virtual uint DataOutBegin(uint8 **ptr) {
*ptr = databuf;
return datalen;
}
virtual void DataDone() {
if (hdparent->writeprotect == false)
memcpy(hdparent->mem + (lba * hdparent->blocksize),
databuf, datalen);
}
private:
SCSIHD *hdparent = NULL;
uint8 *databuf = NULL; // 今回の Write バッファ
uint datalen = 0; // 今回の Write のバイト数
uint32 lba = 0; // 今回の Write の LBA
};
// $12 Inquiry コマンド (共通)
class SCSICmdInquiry : public SCSICmd
{
typedef SCSICmd inherited;
public:
// inq, len は INQUIRY 応答データ
SCSICmdInquiry(SCSITarget *p, const uint8 *inq, uint len)
: inherited(p)
{
inquiry_buf = inq;
inquiry_len = len;
}
virtual ~SCSICmdInquiry() { }
virtual uint8 Command(const std::vector<uint8>& cmds) {
inquiry_len = std::min(inquiry_len, (uint)cmds[4]);
return SCSI::Phase::DataIn;
}
virtual uint DataInBegin(const uint8 **ptr) {
*ptr = inquiry_buf;
return inquiry_len;
}
private:
// Inquiry データは SCSI 種別で異なる
const uint8 *inquiry_buf = NULL;
uint inquiry_len = 0;
};
// $15 ModeSelect(6) コマンド (共通)
class SCSICmdModeSelect6 : public SCSICmd
{
typedef SCSICmd inherited;
public:
// ModeSelect コマンドではホストからブロック長が指定される。
// blocksizep はその指定されたブロック長を書き戻す場所。
SCSICmdModeSelect6(SCSITarget *p)
: inherited(p)
{
modebuf = NULL;
len = 0;
}
virtual ~SCSICmdModeSelect6() {
if (modebuf) {
delete[] modebuf;
}
}
virtual uint8 Command(const std::vector<uint8>& cmds) {
// 指定されたバッファ長のメモリを確保。
len = cmds[4];
modebuf = new uint8[len];
return SCSI::Phase::DataOut;
}
virtual uint DataOutBegin(uint8 **ptr) {
*ptr = modebuf;
return len;
}
virtual void DataDone() {
// Mode Select(6) のデータは以下の構造で並んでいる。
// ヘッダ
// ブロックディスクリプタ#1
// :
// ブロックディスクリプタ#n
// パラメータページ #1
// :
// パラメータページ #m
const SCSI::ModeParamHdr *hdr = (const SCSI::ModeParamHdr *)modebuf;
if (hdr->block_desc_len > 0) {
const SCSI::BlockDesc *desc =
(const SCSI::BlockDesc *)(modebuf + sizeof(*hdr));
uint blocksize;
blocksize = (desc->block_size[0]) << 16;
blocksize |= (desc->block_size[1]) << 8;
blocksize |= (desc->block_size[2]);
// ブロック長を書き戻す
parent->blocksize = blocksize;
}
};
private:
uint8 *modebuf = NULL;
uint len = 0;
};
// $25 ReadCapacity コマンド (ダイレクトアクセスデバイス)
class SCSICmdReadCapacity : public SCSICmd
{
typedef SCSICmd inherited;
public:
SCSICmdReadCapacity(SCSITarget *p)
: inherited(p)
{
hdparent = dynamic_cast<SCSIHD*>(p);
// 応答データは
// +00.L 最終論理ブロックアドレス
// +04.L ブロック長
uint32 blksize = hdparent->blocksize;
uint32 lastblk = (hdparent->memlen / blksize) - 1;
databuf[0] = lastblk >> 24;
databuf[1] = lastblk >> 16;
databuf[2] = lastblk >> 8;
databuf[3] = lastblk;
databuf[4] = blksize >> 24;
databuf[5] = blksize >> 16;
databuf[6] = blksize >> 8;
databuf[7] = blksize;
}
virtual ~SCSICmdReadCapacity() { }
virtual uint8 Command(const std::vector<uint8>& cmds) {
uint8 pmi;
// PMI=1 は未サポート
pmi = cmds[8] & 0x01;
if (pmi != 0) {
PANIC("SCSI ReadCapacity PMI=1 not supported");
}
return SCSI::Phase::DataIn;
}
virtual uint DataInBegin(const uint8 **ptr) {
*ptr = databuf;
return sizeof(databuf);
}
private:
SCSIHD *hdparent = NULL;
uint8 databuf[8] {};
};
// $28 Read(10) コマンド (ダイレクトアクセスデバイス)
class SCSICmdRead10 : public SCSICmd
{
typedef SCSICmd inherited;
public:
SCSICmdRead10(SCSITarget *p)
: inherited(p)
{
hdparent = dynamic_cast<SCSIHD*>(p);
}
virtual ~SCSICmdRead10() { }
virtual uint8 Command(const std::vector<uint8>& cmds) {
uint32 lba;
uint32 blks;
if ((cmds[1] & 0x01)) {
PANIC("SCSI Read(10) RelAdr not supported");
}
lba = cmds[2] << 24;
lba |= cmds[3] << 16;
lba |= cmds[4] << 8;
lba |= cmds[5];
blks = cmds[7] << 8;
blks |= cmds[8];
// XXX 範囲チェック
dataptr = hdparent->mem + (lba * hdparent->blocksize);
datalen = blks * parent->blocksize;
parent->putlog(1, "READ10 $%x %d", lba, blks);
return SCSI::Phase::DataIn;
}
virtual uint DataInBegin(const uint8 **ptr) {
*ptr = dataptr;
return datalen;
}
private:
SCSIHD *hdparent = NULL;
uint8 *dataptr = 0; // 今回の Read の先頭
uint datalen = 0; // 今回の Read のバイト数
};
// サポートしていないコマンドを受け取った時の処理。
class SCSICmdNotSupportedCommand : public SCSICmd
{
typedef SCSICmd inherited;
public:
SCSICmdNotSupportedCommand(SCSITarget *p) : inherited(p) { }
virtual ~SCSICmdNotSupportedCommand() { }
// Command は何もせずステータスに移るだけなので基底のやつでいい
virtual uint Status() {
return SCSI::StatusByte::CheckCondition;
}
};
//
// SCSI ターゲットデバイス基本クラス
//
// コンストラクタ
SCSITarget::SCSITarget(SCSIHostDevice *h, int id, SCSI::DevType type)
{
host = h;
logname = host->GetLogName();
devtype = type;
ID = id;
phase = SCSI::Phase::BusFree;
}
// デストラクタ
SCSITarget::~SCSITarget()
{
}
// フェーズを newphase に遷移する
void
SCSITarget::SetPhase(uint newphase)
{
// フェーズ遷移時に ATN をチェック。
// バスフリーフェーズ・アービトレーションフェーズでは ATN はセットできない。
// ので、それら以外で ATN が立ってる時だけ処理が必要。
if (phase != SCSI::Phase::BusFree &&
phase != SCSI::Phase::Arbitration &&
GetATN())
{
// lastmsg は CommandComplete に初期化してあり、CommandComplete には
// 解除義務はないので、1回目は必ず default に落ちるという寸法。
switch (lastmsg) {
case SCSI::MsgByte::Disconnect:
case SCSI::MsgByte::InitiatorDetectedError:
case SCSI::MsgByte::Abort:
case SCSI::MsgByte::MessageReject:
case SCSI::MsgByte::NoOperation:
case SCSI::MsgByte::MessageParityError:
case SCSI::MsgByte::BusDeviceReset:
case SCSI::MsgByte::AbortTag:
case SCSI::MsgByte::ClearQueue:
case SCSI::MsgByte::InitiateRecovery:
case SCSI::MsgByte::ReleaseRecovery:
case SCSI::MsgByte::TerminateIOProcess:
// 2バイトコードは未対応
// 解除義務があるので、継続してたらバスフリーへ移行。
newphase = SCSI::Phase::BusFree;
break;
default:
// 解除義務はないので、継続して受け付ける。
// (1回目もここに来る)
nextphase = newphase;
newphase = SCSI::Phase::MsgOut;
break;
}
}
// MsgOut フェーズを抜ける際には必ず lastmsg を CommandComplete に戻す。
// CommandComplete が1回目フラグも兼ねているので。
if (phase == SCSI::Phase::MsgOut && newphase != SCSI::Phase::MsgOut) {
lastmsg = SCSI::MsgByte::CommandComplete;
}
// フェーズ遷移
phase = newphase;
switch (newphase) {
case SCSI::Phase::BusFree:
// 自身の状態を変更
BusFreePhase();
// バスの状態を変更 (バスを管理してるのはここではホストの基本クラス)
host->B_BusFree(ID);
// ホストに状態変更を通知
host->BusRelease();
break;
case SCSI::Phase::Arbitration:
case SCSI::Phase::Selection:
case SCSI::Phase::Reselection:
// ?
break;
case SCSI::Phase::DataOut:
DataOutPhase();
break;
case SCSI::Phase::DataIn:
DataInPhase();
break;
case SCSI::Phase::Command:
CommandPhase();
break;
case SCSI::Phase::Status:
StatusPhase();
break;
case SCSI::Phase::MsgOut:
MsgOutPhase();
break;
case SCSI::Phase::MsgIn:
MsgInPhase();
break;
}
}
// セレクション成功。
void
SCSITarget::Selected()
{
SetPhase(SCSI::Phase::Command);
SetREQ(true);
}
void
SCSITarget::Ack()
{
lastdata = GetDATA();
SetREQ(false);
putlog(3, "#%d data=%02x", ID, lastdata);
}
// バスフリーフェーズ
void
SCSITarget::BusFreePhase()
{
lastdata = 0;
lastmsg = 0;
}
void
SCSITarget::DataOutPhase()
{
SetMSG(false);
SetCD(false);
SetIO(false);
putlog(3, "#%d Target DataOutPhase", ID);
}
void
SCSITarget::DataInPhase()
{
SetMSG(false);
SetCD(false);
SetIO(true);
putlog(3, "#%d Target DataInPhase", ID);
}
void
SCSITarget::CommandPhase()
{
SetMSG(false);
SetCD(true);
SetIO(false);
putlog(3, "#%d Target CommandPhase", ID);
}
void
SCSITarget::StatusPhase()
{
SetMSG(false);
SetCD(true);
SetIO(true);
putlog(3, "#%d Target StatusPhase", ID);
}
void
SCSITarget::MsgOutPhase()
{
SetMSG(true);
SetCD(true);
SetIO(false);
putlog(3, "#%d Target MsgOutPhase", ID);
}
void
SCSITarget::MsgInPhase()
{
SetMSG(true);
SetCD(true);
SetIO(true);
putlog(3, "#%d Target MsgInPhase", ID);
}
// データインフェーズの処理。
uint
SCSITarget::ExecDataIn(const uint8 **ptr)
{
// コマンド固有のデータインフェーズの処理
uint len = cmd->DataInBegin(ptr);
// ステータスフェーズに移行
SetPhase(SCSI::Phase::Status);
return len;
}
// データアウトフェーズの処理。
uint
SCSITarget::ExecDataOut(uint8 **ptr)
{
// コマンド固有のデータアウトフェーズの処理
uint len = cmd->DataOutBegin(ptr);
// ステータスフェーズに移行
SetPhase(SCSI::Phase::Status);
return len;
}
// コマンドフェーズの処理。
// イニシエータが必要な長さ分のコマンドをホスト OS から受信したところで
// イニシエータ側から呼ばれて、コマンドの処理を行う。
// 処理後は次のフェーズに遷移する。
void
SCSITarget::ExecCommand(const std::vector<uint8>& argcmds)
{
cmds = argcmds;
const char *cmdname = SCSI::GetCommandName(cmds[0]);
if (cmdname != NULL) {
putlog(1, "#%d $%02x %s", ID, cmds[0], cmdname);
} else {
putlog(1, "#%d $%02x unknown command", ID, cmds[0]);
}
// コマンドを選択。
// まずデバイス固有のコマンドをサーチ。
cmd = ExecDeviceCommand();
// 派生クラス側で選択されなければ、こちらでサーチ。
if (cmd == NULL) {
switch (cmds[0]) {
case SCSI::Command::TestUnitReady:
cmd = new SCSICmdTestUnitReady(this);
break;
case SCSI::Command::RequestSense:
cmd = new SCSICmdRequestSense(this);
break;
case SCSI::Command::ModeSelect6:
cmd = new SCSICmdModeSelect6(this);
break;
default:
// サポートしていないコマンド
const char *cname = SCSI::GetCommandName(cmds[0]);
std::string name;
if (cname) {
name = string_format(" %s", cname);
}
putlog(0, "未実装 SCSI コマンド $%02x%s len=%zd",
cmds[0], name.c_str(), cmds.size());
cmd = new SCSICmdNotSupportedCommand(this);
break;
}
}
uint newphase = cmd->Command(cmds);
SetPhase(newphase);
}
SCSICmd *
SCSITarget::ExecDeviceCommand()
{
PANIC("should not reached");
}
// ステータスバイトを取得およびデータアウトフェーズ完了処理。
uint8
SCSITarget::GetStatusByte()
{
// データフェーズの完了処理をここで行う。
cmd->DataDone();
// XXX まだコマンド側からログが出せないのでとりあえず
if (cmds[0] == SCSI::Command::ModeSelect6) {
putlog(2, "#%d Mode Select 論理ブロック長 = %d", ID, blocksize);
}
// ステータスバイトを取得。
uint statusbyte = cmd->Status();
// メッセージインフェーズに移行。
SetPhase(SCSI::Phase::MsgIn);
return statusbyte;
}
uint8
SCSITarget::GetMessageByte()
{
// メッセージを取得。
uint messagebyte = cmd->MsgIn();
// CommandComplete メッセージを正常に送信したらバスフリーフェーズに移行。
// 実際には正常に送信できたら、だけど。
if (messagebyte == SCSI::MsgByte::CommandComplete) {
SetPhase(SCSI::Phase::BusFree);
}
// コマンドクラスを解放
// ここでいいか。
delete cmd;
cmd = NULL;
return messagebyte;
}
//
// SCSI HD
//
// コンストラクタ
SCSIHD::SCSIHD(SCSIHostDevice *h, int id)
: inherited(h, id, SCSI::HD)
{
devname = "SCSIHD";
logname = "scsihd";
fd = -1;
}
// デストラクタ
SCSIHD::~SCSIHD()
{
}
// イメージファイルの読み込み
bool
SCSIHD::Init()
{
struct stat st;
const std::string myname = string_format("%s(%s,ID%d)",
devname, host->GetConfigName(), ID);
const std::string keybody = string_format("%s_id%d_",
host->GetConfigName(), ID);
const std::string imgkey = string_format("%s%s",
keybody.c_str(), "image");
const std::string wpkey = string_format("%s%s",
keybody.c_str(), "writeprotect");
// devtype があって image がないのは異常状態なのでエラー
const std::string& filename = gConfig->ReadStr(imgkey);
if (filename == "") {
gConfig->Err(imgkey, "not specified");
return false;
}
writeprotect = (bool)gConfig->ReadInt(wpkey);
if (writeprotect) {
putmsg(0, "%s: write protected", myname.c_str());
}
const std::string path = gConfig->GetConfigDir() + filename;
fd = open(path.c_str(), O_RDWR);
if (fd == -1) {
warn("%s cannot open %s", myname.c_str(), path.c_str());
return false;
}
if (fstat(fd, &st) == -1) {
warn("%s cannot stat %s", 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 cannot mmap %s", myname.c_str(), path.c_str());
close(fd);
return false;
}
return true;
}
// HD 固有のコマンドの処理。
// こちらで処理すれば次フェーズを返す。そうでなければ None を返す。
SCSICmd *
SCSIHD::ExecDeviceCommand()
{
switch (cmds[0]) {
case SCSI::Command::Read6:
return new SCSICmdRead6(this);
case SCSI::Command::Write6:
return new SCSICmdWrite6(this);
case SCSI::Command::Inquiry:
// これは共通コマンドだけど引数だけ違う
return new SCSICmdInquiry(this, inquiry_data, sizeof(inquiry_data));
case SCSI::Command::ReadCapacity:
return new SCSICmdReadCapacity(this);
case SCSI::Command::Read10:
return new SCSICmdRead10(this);
default:
// それ以外は基本クラス側で処理する。
return NULL;
}
}
// HD の inquiry データ
/*static*/ const uint8
SCSIHD::inquiry_data[36] = {
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
'V', 'E', 'N', 'D', 'E', 'R', ' ', ' ',
'P', 'R', 'O', 'D', 'U', 'C', 'T', ' ',
'N', 'A', 'M', 'E', ' ', ' ', ' ', ' ',
'0', '1', '2', '3',
};
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.