--- nono/host/hostcom.cpp 2026/04/29 17:05:21 1.1.1.3 +++ nono/host/hostcom.cpp 2026/04/29 17:05:56 1.1.1.10 @@ -12,14 +12,12 @@ // // VM thread : Host thread // -// 各シリアルデバイス::Tx() : -// | -// SerialDevice::Tx() : +// 各デバイス::Tx() : // | // HostCOMDevice::Tx() : HostDevice::ThreadRun // | -// +-------------->| queue |----+ -// +-------------->| pipe |----+ +// +-------------->| queue |----+ … 送信キューに追加し +// +-------------->| pipe |----+ … パイプでホストスレッドに通知 // | | // <-+ : v // --- kevent @@ -43,12 +41,17 @@ // : | // HostCOMDevice::Read() // | -// +---------------| queue |<---+ -// +---------------| event |<---+ +// | queue |<---+ … 受信キューに追加 +// ‖ +// ‖ HostCOMDevice::*(rx_func)() +// | +// +-------------| Message |<---+ … メッセージで VM スレッドに通知 // v -// SerialDevice::Callback() -// | -// 各シリアルデバイス::Rx() +// 各デバイス::RxMessage() ‖ … メッセージコールバック +// | ‖ +// 各デバイス::Rx() ‖ … イベントコールバック +// | ‖ +// HostCOMDevice::Rx() <==++ … ここで queue から読み出す // ログ名 // MPSCC HostDevice COMDriver @@ -63,35 +66,43 @@ #include "hostcom.h" +#include "comdriver_cons.h" #include "comdriver_none.h" #include "comdriver_stdio.h" #include "comdriver_tcp.h" #include "config.h" #include "mainapp.h" +#include "monitor.h" +#include "uimessage.h" // コンストラクタ // -// objname_ は "HostCOM" とか (スレッド名も同じになる)。 -// 設定ファイルキーワードプレフィックスはこれを小文字にしたもの("hostcom")。 -HostCOMDevice::HostCOMDevice(Device *parent_, const std::string& objname_) - : inherited(parent_, OBJ_NONE) +// n は 0 以上なら本体 COM のいずれか。-1 ならデバッガ。 +HostCOMDevice::HostCOMDevice(Device *parent_, int n, + const std::string& portname_) + : inherited(parent_, (n >= 0 ? OBJ_HOSTCOM(n) : OBJ_HOSTCOM_DBG), portname_) { - SetName(objname_); + // SetName() で設定したオブジェクト名はスレッド名でも使う。 + // また設定ファイルキーワードプレフィックスはこれを小文字にしたもの。 // 親が hostcom* か debugger かによっていろいろ動作が違う。 - if (GetName() == "Debugger") { + if (n < 0) { + SetName("Debugger"); + // デバッガコンソールならログレベルは親に準じるため、こっちは不要。 ClearAlias(); // 送受信バイト数がメインのモニタはこっちにはなくていいだろう。 } else { + SetName(string_format("HostCOM%u", n)); + // hostcom* なら親(mpscc などのシリアルデバイス)とこっち(HostCOM*) // のログレベルは独立。 // モニタは hostcom* のみ必要。 - monitor.func = ToMonitorCallback(&HostCOMDevice::MonitorUpdate); - monitor.SetSize(30, 17); - monitor.Regist(ID_MONITOR_HOSTCOM); + monitor = gMonitorManager->Regist(ID_MONITOR_HOSTCOM(n), this); + monitor->func = ToMonitorCallback(&HostCOMDevice::MonitorUpdate); + monitor->SetSize(33, 17); } } @@ -106,133 +117,196 @@ HostCOMDevice::SetLogLevel(int loglevel_ { inherited::SetLogLevel(loglevel_); + RWLockGuard_Read guard(driverlock); if ((bool)driver) { driver->SetLogLevel(loglevel_); } } -// 初期化 +// 動的コンストラクションその2 bool -HostCOMDevice::Init() +HostCOMDevice::Create2() { - if (inherited::Init() == false) { + if (inherited::Create2() == false) { return false; } - if (SelectDriver() == false) { + if (SelectDriver(true) == false) { return false; } return true; } -// ドライバ(再)選択 +// ドライバ(再)選択。 +// エラーが起きた場合、 +// 起動時なら、warn() 等で表示して false を返す(終了する)。 +// 実行中なら、warn() 等で表示してもいいが、必ず None にフォールバックして +// true を返すこと。 bool -HostCOMDevice::SelectDriver() +HostCOMDevice::SelectDriver(bool startup) { + RWLockGuard_Write guard(driverlock); + + driver.reset(); + errmsg.clear(); + // 設定のキー名は親デバイスによって異なる std::string key = GetConfigKey(); const ConfigItem& item = gConfig->Find(key + "-driver"); const std::string& type = item.AsString(); - driver.reset(); + enum { + ERR_CONFIG, // 設定でのエラー + ERR_INVALID, // 知らないドライバ + } reason = ERR_CONFIG; + + if (type == "none") { + CreateNone(); + } else if (type == "stdio") { + CreateStdio(item, key); + } else if (type == "tcp") { + CreateTCP(); + } else if (type == "console" || type == "cons") { + if (key == "debugger") { + reason = ERR_INVALID; + } else { + CreateConsole(item); + } + } else { + // 知らないドライバ種別 + reason = ERR_INVALID; + } - if (type != "none") { - if (type == "stdio") { - // debugger-driver=stdio (または -D オプション) と - // hostcom*-driver=stdio とは共存できない。 - // 歴史的経緯でとりあえず debugger-driver=stdio 側を優先とする。 - // XXX これも後指定優先にするかどうか - // XXX hostcom が増えたらたぶんこうじゃなくなる - if (key != "debugger") { - const ConfigItem& dditem = gConfig->Find("debugger-driver"); - const std::string& ddtype = dditem.AsString(); - if (ddtype == "stdio") { - auto where = dditem.GetWhere(); - if (where == "-V") { - where += " debugger-driver=stdio"; - } - item.Err("stdio driver conflicts with " + where); - return false; - } + if ((bool)driver == false) { + // 指定のドライバが使えなかった場合、hostcom* でも debugger でも + // o 起動時ならエラー終了する。 + // o 実行中なら none にフォールバックする。 + if (startup) { + switch (reason) { + case ERR_INVALID: + item.Err("Invalid driver name"); + break; + case ERR_CONFIG: + item.Err("Could not configure the driver"); + warnx("(See details with option -C -L%s=1)", key.c_str()); + break; + default: + assert(false); } + return false; + } else { + // UI に通知してフォールバック。 + int n = (key == "debugger") ? -1 : GetId() - OBJ_HOSTCOM0; + gMainApp.GetUIMessage()->Post(UIMessage::HOSTCOM_FAILED, n); - driver.reset(new COMDriverStdio(this)); - if ((bool)driver && driver->InitDriver() == false) { - driver.reset(); - } + CreateNone(); + } + } + assert((bool)driver); - } else if (type == "tcp") { - driver.reset(new COMDriverTCP(this)); - if ((bool)driver && driver->InitDriver() == false) { - driver.reset(); - } + // ドライバ名は Capitalize だがログはほぼ小文字なので雰囲気を揃える… + putmsg(1, "selected host driver: %s", + string_tolower(driver->GetDriverName()).c_str()); - } else { - // 知らないドライバ種別 - item.Err(); - return false; - } + return true; +} - if ((bool)driver == false) { - bool fallback = false; +// None ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void +HostCOMDevice::CreateNone() +{ + try { + driver.reset(new COMDriverNone(this)); + } catch (...) { } + if ((bool)driver) { + if (driver->InitDriver()) { + // 成功。 + return; + } + errmsg = driver->errmsg; + driver.reset(); + } +} - // 指定のドライバが使えなかった場合、 - // hostcom*-driver なら fallback するかどうかは設定による。 - // debugger-driver なら常に fallback せず終了する。 - // - // 元々 hostnet が tap, bpf のように順に試してという流れだったので - // fallback の選択肢が用意されていたが、hostcom は今の所どれか1つ - // (stdio か tcp) か、そうでなければ none しかないので、fallback - // するかどうかを選ばせる意味があまりような気もするけど、 - // とりあえず形式だけ真似してみる…。 - // 一方、debugger-driver なら常に fallback せず、設定自体も用意 - // しない。デバッガを使いたいと言ってる時点でデバッガがなくても - // とりあえず起動してほしいとはならないだろうと思うので。 - if (key != "debugger") { - fallback = gConfig->Find(key + "-fallback").AsInt(); - putmsg(1, "%s-fallback=%d", key.c_str(), fallback ? 1 : 0); - } +// stdio ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void +HostCOMDevice::CreateStdio(const ConfigItem& item, const std::string& key) +{ + // セマフォの処理は InitDriver() 内で行ってある。 - if (fallback == false) { - // フォールバックしないならエラー終了 - std::string errmsg; - errmsg = string_format("No %s driver found", key.c_str()); - putmsg(1, "%s", errmsg.c_str()); - warnx("%s (See details with option -C -L%s=1)", - errmsg.c_str(), key.c_str()); - return false; - } + try { + driver.reset(new COMDriverStdio(this)); + } catch (...) { } + if ((bool)driver) { + if (driver->InitDriver()) { + // 成功。 + return; } + errmsg = driver->errmsg; + driver.reset(); } +} - if ((bool)driver == false) { - driver.reset(new COMDriverNone(this)); - if ((bool)driver && driver->InitDriver() == false) { - // 失敗したら出来ることはあまりなさげ - assert(false); +// TCP ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void +HostCOMDevice::CreateTCP() +{ + try { + driver.reset(new COMDriverTCP(this)); + } catch (...) { } + if ((bool)driver) { + if (driver->InitDriver()) { + // 成功。 + return; } + errmsg = driver->errmsg; + driver.reset(); } - assert((bool)driver); +} - // ドライバ名は Capitalize だがログはほぼ小文字なので雰囲気を揃える… - putmsg(1, "selected host driver: %s", - string_tolower(driver->GetDriverName()).c_str()); +// Console ドライバを生成する。 +// 戻り値はなく、成否は (bool)driver で判断する。 +void +HostCOMDevice::CreateConsole(const ConfigItem& item) +{ + // console デバイスのいない機種では指定出来ない。 + if (gMainApp.FindObject(OBJ_CONSOLE) == NULL) { + item.Err("cannot be specified on vmtype=%s", + gMainApp.GetVMTypeStr().c_str()); + return; + } - return true; + try { + driver.reset(new COMDriverConsole(this)); + } catch (...) { } + if ((bool)driver) { + if (driver->InitDriver()) { + // 成功。 + return; + } + errmsg = driver->errmsg; + driver.reset(); + } } void HostCOMDevice::Dispatch(int udata) { // driver の Dispatch は処理し終わったら DONE を返す - udata = driver->Dispatch(udata); + { + RWLockGuard_Read guard(driverlock); + udata = driver->Dispatch(udata); + } // ...のだが、Dispatch(LISTEN_SOCKET) は着信を受け付けた時には // LISTEN_SOCKET を返し、それを受けてここで通知処理を行う。 if (udata == LISTEN_SOCKET) { if (accept_func) { - (parent->*accept_func)(); + (parent->*accept_func)(0); } udata = DONE; } @@ -263,10 +337,10 @@ HostCOMDevice::Tx(uint32 data) stat.tx_bytes++; // ざっくりピーク値 - stat.txq_peak = std::max((int)txq.Length(), stat.txq_peak); + stat.txq_peak = std::max((uint)txq.Length(), stat.txq_peak); - // パイプに通知 (値はダミー) - return WritePipe(0); + // パイプで通知。 + return WritePipe(PIPE_TX); } // キューから取り出す (VM スレッドで呼ばれる) @@ -299,6 +373,7 @@ HostCOMDevice::Rx() int HostCOMDevice::Read() { + RWLockGuard_Read guard(driverlock); assert((bool)driver); int data = driver->Read(); @@ -313,17 +388,18 @@ HostCOMDevice::Read() } // ざっくりピーク値 - stat.rxq_peak = std::max((int)rxq.Length(), stat.rxq_peak); + stat.rxq_peak = std::max((uint)rxq.Length(), stat.rxq_peak); return 1; } // 外部への書き出し (ホストスレッドで呼ばれる) void -HostCOMDevice::Write(uint32 dummy) +HostCOMDevice::Write() { uint8 data; + RWLockGuard_Read guard(driverlock); assert(driver); // 送信キューを全部吐き出す @@ -333,39 +409,52 @@ HostCOMDevice::Write(uint32 dummy) } } +// ドライバ名を返す +const std::string& +HostCOMDevice::GetDriverName() +{ + RWLockGuard_Read guard(driverlock); + assert((bool)driver); + return driver->GetDriverName(); +} + // モニタ void HostCOMDevice::MonitorUpdate(Monitor *, TextScreen& screen) { screen.Clear(); - screen.Print(0, 0, "Parent Device : %s", parent->GetName().c_str()); - screen.Print(0, 1, "HostCOM Driver: %s", driver->GetDriverName()); - // 次の1行はドライバ依存情報 - driver->MonitorUpdateMD(screen, 2); + screen.Print(0, 0, "Device(Port) : %s", GetPortName().c_str()); + { + RWLockGuard_Read gurad(driverlock); + screen.Print(0, 1, "HostCOM Driver: %s", + driver->GetDriverName().c_str()); + // 次の1行はドライバ依存情報 + driver->MonitorUpdateMD(screen, 2); + } int y = 4; - screen.Print(0, y++, "%-17s%13s", "", "Bytes"); - screen.Print(0, y++, "%-17s%13s", "VM sends", + screen.Print(0, y++, "%-20s%13s", "", "Bytes"); + screen.Print(0, y++, "%-20s%13s", "VM sends", format_number(stat.tx_bytes).c_str()); - screen.Print(0, y++, "%-17s%13s", "Write to host", + screen.Print(0, y++, "%-20s%13s", "Write to host", format_number(stat.write_bytes).c_str()); - screen.Print(0, y++, "%-17s%13s", "Drop:TxQ Full", + screen.Print(0, y++, "%-20s%13s", "Drop:TxQ Full", format_number(stat.txqfull_bytes).c_str()); y++; - screen.Print(0, y++, "%-17s%13s", "", "Bytes"); - screen.Print(0, y++, "%-17s%13s", "Read from host", + screen.Print(0, y++, "%-20s%13s", "", "Bytes"); + screen.Print(0, y++, "%-20s%13s", "Read from host", format_number(stat.read_bytes).c_str()); - screen.Print(0, y++, "%-17s%13s", "VM receives", + screen.Print(0, y++, "%-20s%13s", "VM receives", format_number(stat.rx_bytes).c_str()); - screen.Print(0, y++, "%-17s%13s", "Drop:RxQ Full", + screen.Print(0, y++, "%-20s%13s", "Drop:RxQ Full", format_number(stat.rxqfull_bytes).c_str()); y++; screen.Print(5, y++, "Capacity Peak"); - screen.Print(0, y++, "TxQ %4d/%4d %4d", - (int)txq.Length(), (int)txq.Capacity(), stat.txq_peak); - screen.Print(0, y++, "RxQ %4d/%4d %4d", - (int)rxq.Length(), (int)rxq.Capacity(), stat.rxq_peak); + screen.Print(0, y++, "TxQ %4u/%4u %4u", + (uint)txq.Length(), (uint)txq.Capacity(), stat.txq_peak); + screen.Print(0, y++, "RxQ %4u/%4u %4u", + (uint)rxq.Length(), (uint)rxq.Capacity(), stat.rxq_peak); }