File:  [Isaki's NoNo m68k/m88k emulator] / nono / host / hostcom.cpp
Revision 1.1.1.11 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:02 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
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);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.