|
|
nono 1.7.0
//
// nono
// Copyright (C) 2021 nono project
// Licensed under nono-license.txt
//
//
// シリアルポートのホストデバイス
//
// 送信のフロー
//
// VM thread : Host thread
//
// 各デバイス::Tx() :
// |
// HostCOMDevice::Tx() : HostDevice::ThreadRun
// |
// +-------------->| queue |----+ … 送信キューに追加し
// +-------------->| pipe |----+ … パイプでホストスレッドに通知
// | |
// <-+ : v
// --- kevent
// : |
// HostCOMDevice::Write()
// : |
// COMDriver*::Write()
// : |
// +-----> Send to the real world
// 受信のフロー
//
// VM thread : Host thread
//
// : HostDevice::ThreadRun
//
// : +-----< Recv from the real world
// |
// : v
// --- kevent
// : |
// HostCOMDevice::Read()
// |
// | queue |<---+ … 受信キューに追加
// ‖
// ‖ HostCOMDevice::*(rx_func)()
// |
// +-------------| Message |<---+ … メッセージで VM スレッドに通知
// v
// 各デバイス::RxMessage() ‖ … メッセージコールバック
// | ‖
// 各デバイス::Rx() ‖ … イベントコールバック
// | ‖
// HostCOMDevice::Rx() <==++ … ここで queue から読み出す
// ログ名
// MPSCC HostDevice COMDriver
// | | |
// v v v
// SIODevice -> HostCOMDevice -> COMDriver***
// 表示名 (sio) (HostCOMx) (HostCOMx.***)
// 識別名 "sio" "hostcomx" "hostcomx"
//
// HostCOM は 1VM あたり 1個とは限らないので、親デバイスがそれぞれ対応する
// HostCOM に名前を付ける。
#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"
// コンストラクタ
//
// 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() で設定したオブジェクト名はスレッド名でも使う。
// また設定ファイルキーワードプレフィックスはこれを小文字にしたもの。
// 親が hostcom* か debugger かによっていろいろ動作が違う。
if (n < 0) {
SetName("Debugger");
// デバッガコンソールならログレベルは親に準じるため、こっちは不要。
ClearAlias();
// 送受信バイト数がメインのモニタはこっちにはなくていいだろう。
} else {
SetName(string_format("HostCOM%u", n));
// hostcom* なら親(mpscc などのシリアルデバイス)とこっち(HostCOM*)
// のログレベルは独立。
// モニタは hostcom* のみ必要。
monitor = gMonitorManager->Regist(ID_MONITOR_HOSTCOM(n), this);
monitor->SetCallback(&HostCOMDevice::MonitorScreen);
monitor->SetSize(33, 17);
}
}
// デストラクタ
HostCOMDevice::~HostCOMDevice()
{
}
// ログレベル設定
void
HostCOMDevice::SetLogLevel(int loglevel_)
{
inherited::SetLogLevel(loglevel_);
RWLockGuard_Read guard(driverlock);
if ((bool)driver) {
driver->SetLogLevel(loglevel_);
}
}
// 動的コンストラクションその2
bool
HostCOMDevice::Create2()
{
if (inherited::Create2() == false) {
return false;
}
if (SelectDriver(true) == false) {
return false;
}
return true;
}
// ドライバ(再)選択。
// エラーが起きた場合、
// 起動時なら、warn() 等で表示して false を返す(終了する)。
// 実行中なら、warn() 等で表示してもいいが、必ず None にフォールバックして
// true を返すこと。
bool
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();
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 ((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);
CreateNone();
}
}
assert((bool)driver);
// ドライバ名は Capitalize だがログはほぼ小文字なので雰囲気を揃える…
putmsg(1, "selected host driver: %s",
string_tolower(driver->GetDriverName()).c_str());
return true;
}
// 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();
}
}
// stdio ドライバを生成する。
// 戻り値はなく、成否は (bool)driver で判断する。
void
HostCOMDevice::CreateStdio(const ConfigItem& item, const std::string& key)
{
// セマフォの処理は InitDriver() 内で行ってある。
try {
driver.reset(new COMDriverStdio(this));
} catch (...) { }
if ((bool)driver) {
if (driver->InitDriver()) {
// 成功。
return;
}
errmsg = driver->errmsg;
driver.reset();
}
}
// 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();
}
}
// 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;
}
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 を返す
{
RWLockGuard_Read guard(driverlock);
udata = driver->Dispatch(udata);
}
// ...のだが、Dispatch(LISTEN_SOCKET) は着信を受け付けた時には
// LISTEN_SOCKET を返し、それを受けてここで通知処理を行う。
if (udata == LISTEN_SOCKET) {
if (accept_func) {
(parent->*accept_func)(0);
}
udata = DONE;
}
inherited::Dispatch(udata);
}
// VM からの送信 (VM スレッドで呼ばれる)
bool
HostCOMDevice::Tx(uint32 data)
{
if (loglevel >= 2) {
char buf[8];
buf[0] = '\0';
if (0x20 <= data && data < 0x7f) {
snprintf(buf, sizeof(buf), " '%c'", data);
}
putlog(2, "Send $%02x%s", data, buf);
}
// 送信キューに入れて..
if (txq.Enqueue(data) == false) {
putlog(2, "txq exhausted");
stat.txqfull_bytes++;
return false;
}
stat.tx_bytes++;
// ざっくりピーク値
stat.txq_peak = std::max((uint)txq.Length(), stat.txq_peak);
// パイプで通知。
return WritePipe(PIPE_TX);
}
// キューから取り出す (VM スレッドで呼ばれる)
uint32
HostCOMDevice::Rx()
{
uint8 data;
if (rxq.Dequeue(&data) == false) {
// キューが空
return -1;
}
stat.rx_bytes++;
if (loglevel >= 2) {
char buf[8];
buf[0] = '\0';
if (0x20 <= data && data < 0x7f) {
snprintf(buf, sizeof(buf), " '%c'", data);
}
putlog(2, "Recv $%02x%s", data, buf);
}
return data;
}
// ドライバから読み込む。
// 戻り値はキューに投入したデータ数。
int
HostCOMDevice::Read()
{
RWLockGuard_Read guard(driverlock);
assert((bool)driver);
int data = driver->Read();
if (data < 0) {
return 0;
}
stat.read_bytes++;
if (rxq.Enqueue(data) == false) {
stat.rxqfull_bytes++;
return 0;
}
// ざっくりピーク値
stat.rxq_peak = std::max((uint)rxq.Length(), stat.rxq_peak);
return 1;
}
// 外部への書き出し (ホストスレッドで呼ばれる)
void
HostCOMDevice::Write()
{
uint8 data;
RWLockGuard_Read guard(driverlock);
assert(driver);
// 送信キューを全部吐き出す
while (txq.Dequeue(&data)) {
driver->Write(data);
stat.write_bytes++;
}
}
// ドライバ名を返す
const std::string&
HostCOMDevice::GetDriverName()
{
RWLockGuard_Read guard(driverlock);
assert((bool)driver);
return driver->GetDriverName();
}
// モニタ
void
HostCOMDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
screen.Clear();
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->MonitorScreenMD(screen, 2);
}
int y = 4;
screen.Print(0, y++, "%-20s%13s", "<Tx>", "Bytes");
screen.Print(0, y++, "%-20s%13s", "VM sends",
format_number(stat.tx_bytes).c_str());
screen.Print(0, y++, "%-20s%13s", "Write to host",
format_number(stat.write_bytes).c_str());
screen.Print(0, y++, "%-20s%13s", "Drop:TxQ Full",
format_number(stat.txqfull_bytes).c_str());
y++;
screen.Print(0, y++, "%-20s%13s", "<Rx>", "Bytes");
screen.Print(0, y++, "%-20s%13s", "Read from host",
format_number(stat.read_bytes).c_str());
screen.Print(0, y++, "%-20s%13s", "VM receives",
format_number(stat.rx_bytes).c_str());
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 %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);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.