|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
//
// VirtIO SCSI デバイス
//
#include "virtio_scsi.h"
#include "virtio_def.h"
#include "mainbus.h"
#include "memorystream.h"
#include "monitor.h"
#include "scsidev.h"
#include "scsidomain.h"
// デバイス構成レイアウト
class VirtIOSCSIConfigWriter
{
public:
le32 num_queues {};
le32 seg_max {};
le32 max_sectors {};
le32 cmd_per_lun {};
le32 event_info_size {};
le32 sense_size {};
le32 cdb_size {};
le16 max_channel {};
le16 max_target {};
le32 max_lun {};
public:
void WriteTo(uint8 *dst) const;
};
// コンストラクタ
VirtIOSCSIDevice::VirtIOSCSIDevice(uint slot_)
: inherited(OBJ_VIRTIO_SCSI, slot_)
{
// 割り込み名(とログ名)
strlcpy(intrname, "VIOSCSI", sizeof(intrname));
SetName(intrname);
ClearAlias();
AddAlias(intrname);
device_id = VirtIO::DEVICE_ID_SCSI;
vqueues.emplace_back(this, 0, "ControlQ", 8);
vqueues.emplace_back(this, 1, "EventQ", 8);
vqueues.emplace_back(this, 2, "RequestQ", 8);
// 完了通知メッセージ
msgid = MessageID::VIRTIO_SCSI_DONE;
// モニタの行数。
int vqlines = 0;
for (const auto& q : vqueues) {
vqlines += 7 + q.num_max;
}
monitor = gMonitorManager->Regist(ID_MONITOR_VIRTIO_SCSI, this);
monitor->SetCallback(&VirtIOSCSIDevice::MonitorScreen);
monitor->SetSize(MONITOR_WIDTH, 3 + vqlines);
}
// デストラクタ
VirtIOSCSIDevice::~VirtIOSCSIDevice()
{
}
// 動的なコンストラクション
bool
VirtIOSCSIDevice::Create()
{
if (inherited::Create() == false) {
return false;
}
try {
domain.reset(new SCSIDomain(this, "VIOSCSI", "virtio-scsi"));
} catch (...) { }
if ((bool)domain == false) {
warnx("Failed to initialize SCSIDomain at %s", __method__);
return false;
}
return true;
}
bool
VirtIOSCSIDevice::Init()
{
if (inherited::Init() == false) {
return false;
}
host = domain->GetInitiator();
return true;
}
void
VirtIOSCSIDevice::ResetHard(bool poweron)
{
// sense_size, cdb_size はリセットで元に戻る (5.6.4)。
// XXX 書き換えに対応?
sense_size = 96;
cdb_size = 32;
// DEVICE_FEATURES と構成レイアウトを用意。
VirtIOSCSIConfigWriter cfg;
cfg.num_queues = 1;
cfg.seg_max = 6; // XXX ?
cfg.max_sectors = 256;
cfg.cmd_per_lun = 1;
cfg.sense_size = sense_size;
cfg.cdb_size = cdb_size;
cfg.max_channel = 0;
cfg.max_target = 7;
cfg.max_lun = 0;
cfg.WriteTo(&device_config[0]);
}
void
VirtIOSCSIDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
int y = 0;
screen.Clear();
y = MonitorScreenDev(screen, y);
y++;
for (const auto& q : vqueues) {
y = MonitorScreenVirtQueue(screen, y, q);
y++;
y = MonitorScreenVirtQDesc(screen, y, q);
y++;
}
}
// ディスクリプタを一つ処理する。
void
VirtIOSCSIDevice::ProcessDesc(VirtIOReq& req)
{
VirtQueue *q = req.q;
switch (q->idx) {
case 0:
ProcessDescControl(req);
break;
case 1:
ProcessDescEvent(req);
break;
case 2:
ProcessDescRequest(req);
break;
default:
assert(q->idx < 3);
}
}
// Control キューのディスクリプタを一つ処理する。
void
VirtIOSCSIDevice::ProcessDescControl(VirtIOReq& req)
{
putlog(0, "Control Queue (NOT IMPLEMENTED)");
}
// Event キューのディスクリプタを一つ処理する。
void
VirtIOSCSIDevice::ProcessDescEvent(VirtIOReq& req)
{
putlog(0, "Event Queue (NOT IMPLEMENTED)");
}
// Request キューのディスクリプタを一つ処理する。
void
VirtIOSCSIDevice::ProcessDescRequest(VirtIOReq& req)
{
// 8バイトの lun はこういう構成。
// bus は 1 から数え始める。
// ID が SCSI ID に相当するところ (0-255)。
// LUN は 0-16387。0x4000 が立っているのは何?
// 下位 32 ビットは論理ユニット固有のアドレスフィールド (0)。
// 複数バイトのフィールドは BE 順にメモリに置かれている。
//
// +0 +1 +2 +3 +4 +5 +6 +7
// +-----+-----+-----+-----+-----+-----+-----+-----+
// | bus | ID | LUN | 0 |
// +-----+-----+-----+-----+-----+-----+-----+-----+
uint32 bus = ReqReadU8(req);
uint32 id = ReqReadU8(req);
uint32 lun = ReqReadBE16(req);
uint32 addr = ReqReadBE32(req);
putlog(2, "Bus=$%02x ID=$%02x LUN=$%04x Addr=$%08x", bus, id, lun, addr);
// 仕様ではこっちが id という名前だが紛らわしいし使わないので tag にする。
// QoS Tag みたいなもの?
uint64 tag;
ReqReadLE64(req, &tag);
putlog(3, "tagID=%08x'%08x", (uint32)(tag >> 32), (uint32)tag);
uint32 task_attr = ReqReadU8(req);
uint32 prio = ReqReadU8(req);
uint32 crn = ReqReadU8(req);
putlog(3, "task_attr=%02x prio=%02x crn=%02x",
task_attr, prio, crn);
// コマンド(CDB) を取り出す。
std::vector<uint8> cdb(cdb_size);
for (int i = 0; i < cdb_size; i++) {
cdb[i] = ReqReadU8(req);
}
if (loglevel >= 2
#if defined(NO_READWRITE_LOG)
&& (cdb[0] != SCSI::Command::Read6 &&
cdb[0] != SCSI::Command::Read10 &&
cdb[0] != SCSI::Command::Write6 &&
cdb[0] != SCSI::Command::Write10)
#endif
) {
std::string cmdbuf;
const char *name = SCSI::GetCommandName(cdb[0]);
cmdbuf += string_format("Command \"%s\"", name ?: "?");
for (const auto& v : cdb) {
cmdbuf += string_format(" %02x", v);
}
putlogn("%s", cmdbuf.c_str());
}
// 読み込み側は、ここから後ろがあれば dataout[]。
uint32 dataout_len = req.rremain();
// datain[] も計算。12 は .sense_len から .response までのバイト数。
uint32 datain_len = req.wlen - 12 - sense_size;
// ターゲット ID が存在するか。
auto *target = domain->GetTarget(id);
if (target == NULL) {
ProcessDescRequestError(req, VIRTIO_SCSI_S_BAD_TARGET,
dataout_len + datain_len);
return;
}
// 両方向データ (F_INOUT) はサポートしていない
if (dataout_len > 0 && datain_len > 0) {
ProcessDescRequestError(req, VIRTIO_SCSI_S_FAILURE,
dataout_len + datain_len);
return;
}
// ID と読み書き方向が分かったので、アクセスインジケータ用に記録。
// 少なくとも dataout[] があれば書き込み、それ以外を読み込みとする。
if (dataout_len > 0) {
access[id * 2 + 1] = req.q->last_avail_idx;
} else {
access[id * 2 + 0] = req.q->last_avail_idx;
}
// ターゲットでコマンドを起動してみる。
SCSICmd *cmd = target->SelectCommand(cdb);
if (cmd == NULL) {
// インスタンスの生成に失敗した (未実装コマンドではない)
ProcessDescRequestError(req, VIRTIO_SCSI_S_FAILURE,
dataout_len + datain_len);
return;
}
std::vector<uint8> databuf;
std::vector<uint8> statusbuf;
std::vector<uint8> msgbuf;
// 初手は必ずコマンドフェース。
// コマンドフェーズの次からは動的に遷移。
auto phase = cmd->ExecCommand(cdb);
for (; phase != SCSI::XferPhase::End; ) {
SCSI::XferPhase next;
switch (phase) {
case SCSI::XferPhase::DataOut: // イニシエータ → ターゲット
cmd->buf.clear();
cmd->buf.resize(req.rremain());
ReqReadRegion(req, cmd->buf.data(), cmd->buf.size());
putlog(3, "DataOut begin (%zu bytes)", cmd->buf.size());
next = cmd->DoneDataOut();
break;
case SCSI::XferPhase::DataIn: // イニシエータ ← ターゲット
next = cmd->DoneDataIn();
databuf = cmd->buf;
putlog(3, "DataIn begin (%u bytes)", (uint)databuf.size());
break;
case SCSI::XferPhase::MsgOut:
cmd->buf.clear();
cmd->BeginMsgOut();
next = cmd->DoneMsgOut();
putlog(3, "MsgOut begin (%u bytes)", (uint)cmd->buf.size());
break;
case SCSI::XferPhase::MsgIn:
cmd->BeginMsgIn();
next = cmd->DoneMsgIn();
msgbuf = cmd->buf;
putlog(3, "MsgIn begin (%u bytes)", (uint)msgbuf.size());
break;
case SCSI::XferPhase::Status:
cmd->BeginStatus();
next = cmd->DoneStatus();
statusbuf = cmd->buf;
putlog(3, "Status begin (%u bytes)", (uint)statusbuf.size());
break;
default:
PANIC("%s unknown phase done %d", __func__, (int)phase);
}
putlog(3, "%s done", SCSI::GetXferPhaseName(phase));
phase = next;
}
// dataout が残っていれば読み飛ばす?
req.rskip();
// sense バッファを作成。
// 内容は ScSICmdRequestSense::ExecCommand() のコード参照。
uint32 sense_len = 18;
std::vector<uint8> sensebuf(sense_len);
uint32 sensekey = cmd->GetTarget()->sensekey;
uint32 senseasc = cmd->GetTarget()->senseasc;
sensebuf[0] = 0x70; // Valid(?)
sensebuf[2] = sensekey;
sensebuf[12] = senseasc >> 8;
sensebuf[13] = senseasc & 0xff;
if (sense_size < sense_len) {
sense_len = sense_size;
}
sensebuf.resize(sense_size);
// residual は未処理の"データ"バイト数?
uint32 residual = 0; // XXX よく分からん
ReqWriteLE32(req, sense_len);
ReqWriteLE32(req, residual);
ReqWriteLE16(req, 0); // status_qualifier
if (statusbuf.size() == 0) {
// ステータスフェーズが来なかった?
ReqWriteU8(req, 0);
ReqWriteU8(req, VIRTIO_SCSI_S_FAILURE);
return;
}
ReqWriteU8(req, statusbuf[0]);
ReqWriteU8(req, VIRTIO_SCSI_S_OK);
// sense[sense_size] は常に sense_size バイト分。
ReqWriteRegion(req, sensebuf.data(), sensebuf.size());
// datain ならコマンドが作成したバッファをメモリに書き出す。
if (databuf.empty() == false) {
ReqWriteRegion(req, databuf.data(), databuf.size());
}
}
// Request キューのエラー応答を作成する。
// wpos は書き込みセグメントの先頭を指していること。
void
VirtIOSCSIDevice::ProcessDescRequestError(VirtIOReq& req,
uint32 response_, uint32 residual_)
{
// よく分からんけど readable part は読み捨てておく?
req.rskip();
// writable part の先頭にいるはず。
assert(req.wpos() == 0);
ReqWriteLE32(req, 0); // sense_len
ReqWriteLE32(req, residual_);
ReqWriteLE16(req, 0); // status_qualifier
ReqWriteU8(req, 0); // status
ReqWriteU8(req, response_);
}
const char *
VirtIOSCSIDevice::GetFeatureName(uint feature) const
{
static std::pair<uint, const char *> names[] = {
{ VIRTIO_SCSI_F_INOUT, "INOUT" },
{ VIRTIO_SCSI_F_HOTPLUG, "HOTPLUG" },
{ VIRTIO_SCSI_F_CHANGE, "CHANGE" },
{ VIRTIO_SCSI_F_T10_PI, "T10_PI" },
};
for (auto& p : names) {
if (feature == p.first) {
return p.second;
}
}
return inherited::GetFeatureName(feature);
}
// デバイス構成レイアウトを書き出す。
void
VirtIOSCSIConfigWriter::WriteTo(uint8 *dst) const
{
MemoryStreamLE mem(dst);
mem.Write4(num_queues);
mem.Write4(seg_max);
mem.Write4(max_sectors);
mem.Write4(cmd_per_lun);
mem.Write4(event_info_size);
mem.Write4(sense_size);
mem.Write4(cdb_size);
mem.Write2(max_channel);
mem.Write2(max_target);
mem.Write4(max_lun);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.