|
|
nono 1.7.0
//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//
//
// SCSI ドメイン
//
// SCSI ドメインは、1台のイニシエータとそれに繋がるターゲットからなるシステム
// 一式を指すことにする。ここには SCSIBus は含まない。
//
// HBA (SPCとか)
// |
// +----> .domain ................................................
// | (SCSIDomain) :
// | : +----------------+ +------------+ +------------+ :
// | .parent |.initiator | |.target[0] | |.target[6] | :
// |<--------|(SCSIHostDevice)| |(SCSITarget)| |(SCSITarget)| :
// | : +----------------+ +------------+ +------------+ :
// | : ^ | ^ | ^ | :
// | ..........|.|...............|.|.............|.|.........
// | | | | | | |
// | | |.bus | |.bus | |.bus
// +----> .bus .....|.|...............|.|.............|.|.........
// (SCSIBus) | v | v | v :
// : .device[7] .device[0] .device[6] :
// : (SCSIDevice) (SCSIDevice) (SCSIDevice) :
// ........................................................
//
// 初期化 (Create) 時に SCSIDomain は設定ファイルから自分のドメインの
// 各 .target[] と .initiator を生成する。
// HBA が SCSIBus を持つタイプであれば Init 時に
// SCSIDomain::AttachTargets(bus)、SCSIBus::Refresh() ですべてのターゲットを
// バスに参加させる。
// HBA が SCSIBus を持たないタイプであれば GetDevices() でターゲット情報を
// 手元に持っておく。
//
// 電源オン後、イニシエータの SCSI ID が確定したら HBA は
// SCSIDomain::AttachInitiator(bus, id)、SCSIBus::Refresh() でイニシエータを
// バスに参加させる。
#include "scsidomain.h"
#include "scsi.h"
#include "scsibus.h"
#include "scsidev.h"
#include "config.h"
#include "monitor.h"
// コンストラクタ
SCSIDomain::SCSIDomain(IODevice *parent_,
const char *domain_name_, const char *config_name_)
: inherited(OBJ_SCSI)
{
parent = parent_;
domain_name = domain_name_;
config_name = config_name_;
monitor = gMonitorManager->Regist(ID_MONITOR_SCSIDEVS, this);
monitor->SetCallback(&SCSIDomain::MonitorScreen);
// サイズは Create で決まる
}
// デストラクタ
SCSIDomain::~SCSIDomain()
{
}
// 動的なコンストラクション
bool
SCSIDomain::Create()
{
// 設定を読み込む。
for (int id = 0; id < MaxDevices; id++) {
const std::string key = string_format("%s-id%u-image",
config_name, id);
const ConfigItem& item = gConfig->Find(key);
const std::string& image = item.AsString();
// 空ならデバイスなし
if (image.empty()) {
// 未接続部分のパラメータは減らしておくか。
// --show-config するとさすがに無駄に多くて邪魔なので。
const std::string wikey = string_format("%s-id%u-writeignore",
config_name, id);
gConfig->Delete(wikey);
// -seektime は virtio-scsi にはない。
if (strncmp(config_name, "virtio", 6) != 0) {
const std::string stkey = string_format("%s-id%u-seektime",
config_name, id);
gConfig->Delete(stkey);
}
continue;
}
// ',' より前がデバイス種別 (ここではパスは不要)
auto v = string_split(image, ',');
// 比較のため前後の空白を取り除いて小文字にする
std::string type = string_trim(v[0]);
type = string_tolower(type);
SCSI::DevType target_devtype;
if (type == "hd") {
target_devtype = SCSI::DevType::HD;
} else if (type == "cd") {
target_devtype = SCSI::DevType::CD;
} else if (type == "mo") {
target_devtype = SCSI::DevType::MO;
} else {
item.Err("Invaild device type '%s'", type.c_str());
return false;
}
try {
target[id].reset(new SCSIDisk(this, id, target_devtype));
} catch (...) { }
if ((bool)target[id] == false) {
warnx("Failed to initialize target[%u] at %s", id, __method__);
return false;
}
putlog(3, "Attach #%u", id);
}
// イニシエータを生成。
try {
initiator.reset(new SCSIHostDevice(this, parent));
} catch (...) { }
if ((bool)initiator == false) {
warnx("Failed to initialize SCSHostDevice at %s", __method__);
return false;
}
// この時点ではイニシエータに ID がないため失敗しないはず。
bool r = RefreshDevices();
assert(r == true);
// イニシエータだけ必要な行数が異なるが、この時点の
// connected_devices はイニシエータを含まないので都合がいい。
monitor->SetSize(75, connected_devices.size() * 4 + 1);
return true;
}
// 初期化。
bool
SCSIDomain::Init()
{
// 初期化ではないけど、ログが使えるようになったここでデバッグ表示。
if (loglevel >= 1) {
const auto& conn = GetConnectedDevices();
for (const auto dev : conn) {
putmsgn("%s target #%u %s", domain_name, dev->GetMyID(),
SCSI::GetDevTypeName(dev->GetDevType()));
}
}
return true;
}
// すべてのターゲットをバスに参加させる。
// バスを持つホストデバイスが Init() 時に呼ぶ。
void
SCSIDomain::AttachTargets(SCSIBus *bus)
{
for (uint id = 0; id < MaxDevices; id++) {
if ((bool)target[id]) {
target[id]->Attach(bus);
}
}
}
// イニシエータに ID を付与し、(あれば)バスに参加させる。
// bus は NULL でもよい。
// 成功すれば true を返す。
// 失敗すればイニシエータの ID を -1 に設定して false を返す。
bool
SCSIDomain::AttachInitiator(SCSIBus *bus, uint id_)
{
initiator->SetID(id_);
// ドメインに反映させる。
if (RefreshDevices() == false) {
// 失敗したらイニシエータを切り離しておく。
initiator->SetID(-1);
RefreshDevices();
return false;
}
if (bus) {
initiator->Attach(bus);
}
return true;
}
// initiator と target[] から device[] と connected_devices を作り直す。
// target[] はイニシエータを含まないターゲットデバイス。
// device[] はターゲットと ID の確定したイニシエータを指す穴あきの配列。
// connected_devices はイニシエータも含む ID 順の可変長リスト。
// initiator か target[] に増減があるか、initiator の ID が確定した時に
// 呼ぶこと。
// initiator の ID が他と衝突すれば false を返す。この場合 device[] と
// connected_devices の状態は不定。
bool
SCSIDomain::RefreshDevices()
{
// initiator と taget[] から device[] を作る。
device.fill(NULL);
for (int id = 0; id < MaxDevices; id++) {
if ((bool)target[id]) {
device[id] = target[id].get();
}
if (initiator->GetMyID() == id) {
if (device[id]) {
return false;
}
device[id] = initiator.get();
}
}
// device[] から connected_devices を作る。
connected_devices.clear();
for (uint id = 0; id < MaxDevices; id++) {
if (device[id]) {
connected_devices.push_back(device[id]);
}
}
return true;
}
// (イニシエータを除く) デバイス数を返す。
uint
SCSIDomain::GetTargetCount() const
{
uint count = 0;
for (const auto& t : target) {
if ((bool)t) {
count++;
}
}
return count;
}
// SCSI デバイスモニタ
// (どこでやるのがいいか分からんけど、SCSI デバイスを一括して表示したいので
// とりあえずここで)
void
SCSIDomain::MonitorScreen(Monitor *, TextScreen& screen)
{
int y;
// 012345678901234567890123456789012345678901234567890123456789012345678901234
// ID6: HD(2048MB)
// MediumLoaded Off LogicalBlock 2048 ImageSize 2,147,483,648
// RemovalPrevent Off WriteProtected
screen.Clear();
y = 0;
for (const auto dev : connected_devices) {
std::string desc = SCSI::GetDevTypeName(dev->GetDevType());
std::string pathname;
const SCSIDisk *disk = dynamic_cast<const SCSIDisk*>(dev);
if (disk && disk->IsMediumLoaded()) {
desc += string_format("(%uMB)",
(uint)(disk->GetSize() / 1024 / 1024));
pathname = disk->GetPathName();
}
screen.Print(0, y, "ID%c: %s", '0' + dev->GetMyID(), desc.c_str());
#if 1
(void)pathname;
#else
// パス名の非ASCIIどうするか
if (!pathname.empty()) {
if (pathname.size() > screen.GetCol() - 16 - 3) {
pathname = pathname.substr(0, screen.GetCol() - 16 - 3);
pathname += "...";
}
screen.Puts(16, y, pathname.c_str());
}
#endif
y++;
// イニシエータならここまで。(今の所イニシエータとディスクしかない)
if (disk == NULL) {
continue;
}
const int x1 = 5;
const int x2 = 29;
const int x3 = 50;
// 1行目
screen.Print(x1, y,
(disk->IsRemovableDevice() ? TA::Normal : TA::Disable),
"MediumLoaded %s",
disk->IsMediumLoaded() ? "On" : "Off");
screen.Print(x2, y, "LogicalBlock %u", disk->GetBlocksize());
std::string imagesize = format_number(disk->GetSize());
screen.Print(x3, y, "ImageSize %15s", imagesize.c_str());
y++;
// 2行目
screen.Print(x1, y,
(disk->IsRemovableDevice() ? TA::Normal : TA::Disable),
"RemovalPrevent %s",
disk->IsMediumRemovalPrevented() ? "On" : "Off");
screen.Puts(x2, y,
(disk->IsMediumLoaded() ? TA::Normal : TA::Disable),
disk->GetWriteModeStr());
y++;
y++;
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.