File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / goldfish_tty.cpp
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//

//
// Goldfish TTY
//

// レジスタ仕様は次の通り。
//  +$00.L PUT_CHAR		-W: 1バイト出力
//  +$04.L BYTES_READY	R-: 受信キューにあるバイト数を取得
//  +$08.L COMMAND		-W: コマンド
//  +$0c.L -
//  +$10.L DATA_PTR		-W: DMA 開始アドレス
//  +$14.L DATA_LEN		-W: DMA データ長
//  +$18.L DATA_PTR_HIGH W: DMA 開始アドレスの上位 32 ビット
//  +$1c.L -
//  +$20.L VERSION		R-: このデバイスのバージョン ($1)
//
// コマンドは
//  $00 INT_DISABLE
//  $01 INT_ENABLE
//  $02 WRITE_BUFFER
//  $03 READ_BUFFER
//
// 受信キューにデータがあれば割り込みを上げる。
// 受信キューが空になると割り込みを下げる。
//
// WRITE_BUFFER, READ_BUFFER はたぶんバスマスタ的転送。
// 読み出し時 (READ_BUFFER) は、DATA_PTR で指定したアドレスにたぶん最大
// DATA_LEN バイトを GFTTY デバイスが書き込む。
// 書き込み時 (WRITE_BUFFER) は、DATA_PTR で指定したアドレスから DATA_LEN
// バイトを GFTTY デバイスが読み込む。
//
// nono では DATA_PTR_HIGH は実装しない。

#include "goldfish_tty.h"
#include "goldfish_pic.h"
#include "hostcom.h"
#include "mainbus.h"
#include "scheduler.h"
#include "syncer.h"

// コンストラクタ
GFTTYDevice::GFTTYDevice()
	: inherited(OBJ_GFTTY)
{
}

// デストラクタ
GFTTYDevice::~GFTTYDevice()
{
	if ((bool)hostcom) {
		hostcom->ResetRxCallback();
	}
}

bool
GFTTYDevice::Create()
{
	// 対応するホストドライバを作成
	try {
		hostcom.reset(new HostCOMDevice(this, 0, "GoldfishTTY"));
	} catch (...) { }
	if ((bool)hostcom == false) {
		warnx("Failed to initialize HostCOMDevice at %s", __method__);
		return false;
	}
	hostcom->SetRxCallback(ToDeviceCallback(&GFTTYDevice::HostRxCallback), 0);

	return true;
}

void
GFTTYDevice::SetLogLevel(int loglevel_)
{
	inherited::SetLogLevel(loglevel_);

	if ((bool)hostcom) {
		hostcom->SetLogLevel(loglevel_);
	}
}

// 初期化
bool
GFTTYDevice::Init()
{
	mainbus = GetMainbusDevice();

	gfpic = GetGFPICDevice(1);
	gfpic->RegistIRQ(32, "GFTTY", this);

	scheduler->ConnectMessage(MessageID::HOSTCOM0_RX, this,
		ToMessageCallback(&GFTTYDevice::RxMessage));
	return true;
}

// 電源オン/リセット
void
GFTTYDevice::ResetHard(bool poweron)
{
	sesame_idx = 0;
	rxq.clear();
	intr_enable = false;
	ChangeInterrupt();
}

busdata
GFTTYDevice::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case BYTES_READY:
		data = GetREADY();
		putlog(2, "BYTES_READY -> $%08x", data.Data());
		break;

	 case VERSION:
		data = GFTTYVersion;
		putlog(2, "VERSION -> $%08x", data.Data());
		break;

	 default:
		putlog(1, "Read $%08x.L", mainbus->GetPaddr());
		data.SetBusErr();
		break;
	}

	data |= BusData::Size4;
	return data;
}

busdata
GFTTYDevice::WritePort(uint32 offset, uint32 data)
{
	busdata r;

	switch (offset) {
	 case PUT_CHAR:
		putlog(2, "PUT_CHAR <- $%02x", data);
		Tx(data);
		break;

	 case COMMAND:
		Command(data);
		break;

	 case DATA_PTR:
		putlog(2, "DATA_PTR <- $%08x", data);
		dma_ptr = data;
		break;

	 case DATA_LEN:
		putlog(2, "DATA_LEN <- $%08x", data);
		dma_len = data;
		break;

	 default:
		putlog(1, "Write $%08x.L", mainbus->GetPaddr());
		r.SetBusErr();
		break;
	}

	r |= BusData::Size4;
	return r;
}

busdata
GFTTYDevice::PeekPort(uint32 offset)
{
	switch (offset) {
	 case BYTES_READY:
		return GetREADY();
	 case VERSION:
		return GFTTYVersion;
	 default:
		return BusData::BusErr;
	}
}

// 着信しているバイト数を返す。
uint32
GFTTYDevice::GetREADY() const
{
	return rxq.size();
}

void
GFTTYDevice::Command(uint32 cmd)
{
	switch (cmd) {
	 case CMD_INT_DISABLE:
		putlog(1, "CMD_INT_DISABLE");
		intr_enable = false;
		ChangeInterrupt();
		break;

	 case CMD_INT_ENABLE:
		putlog(1, "CMD_INT_ENABLE");
		intr_enable = true;
		ChangeInterrupt();
		break;

	 case CMD_WRITE_BUFFER:
	 {
		// ゲストからの書き込み
		putlog(1, "CMD_WRITE_BUFFER($%08x, $%08x)", dma_ptr, dma_len);
		uint32 addr = dma_ptr;
		uint32 end = addr + dma_len;
		for (; addr < end; addr++) {
			busdata data = mainbus->HVRead1(addr);
			if (data.IsBusErr()) {
				putlog(1, "WRITE_BUFFER: BusError at $%08x", addr);
				break;
			}
			Tx(data);
		}
		break;
	 }

	 case CMD_READ_BUFFER:
	 {
		// ゲストからの読み込み (ゲスト空間へ書き出し)
		putlog(1, "CMD_READ_BUFFER($%08x, $%08x)", dma_ptr, dma_len);
		uint32 addr = dma_ptr;
		uint32 len = std::min(dma_len, (uint32)rxq.size());
		for (uint32 end = addr + len; addr < end; addr++) {
			uint32 data = rxq.front();
			rxq.pop_front();
			busdata bd = mainbus->HVWrite1(addr, data);
			if (bd.IsBusErr()) {
				putlog(1, "READ_BUFFER: BusError at $%08x", addr);
				break;
			}
		}
		ChangeInterrupt();
		break;
	 }

	 default:
		putlog(1, "Unknown command $%08x", cmd);
		break;
	}
}

// 1バイト送信。
void
GFTTYDevice::Tx(uint32 data)
{
	// --perf によるパフォーマンス測定用のギミック。
	// GFTTY に特定の文字列を書き込むと、
	// VM 電源オン時から現在までの実時間をログに出力する。NetBSD の
	// マルチユーザブートがあらかた終わってログインプロンプトが出る手前の
	// /etc/rc.local に
	//
	// printf '\e[?68008h\e[?68008l'
	//
	// と書いておくことで、ここまでにかかる時間を表示できる。
	// この値はホスト性能に依存するため、同一環境下での相対比較のみ可能。
	//
	// このエスケープシーケンスは DEC/xterm 拡張の設定と解除を続けて行う
	// もので、68008 は空いてそうな適当な値。知らない番号の拡張機能については
	// おそらく黙って何もしないだけだと思うので、どの端末エミュレータを通過
	// しても副作用は少ないと思われる。
	static const char sesame[] = "\x1b[?68008h\x1b[?68008l";
	if (__predict_false(data == sesame[sesame_idx])) {
		sesame_idx++;
		if (__predict_false(sesame_idx >= strlen(sesame))) {
			GetSyncer()->PutlogRealtime();
			sesame_idx = 0;
		}
	} else {
		sesame_idx = 0;
	}

	// 1バイト送信。
	hostcom->Tx(data);
}

void
GFTTYDevice::ChangeInterrupt()
{
	bool irq = (intr_enable && rxq.empty() == false);
	gfpic->ChangeINT(this, irq);
}

// ホストスレッドからの受信通知
// (HostCOM スレッドで呼ばれる)
void
GFTTYDevice::HostRxCallback(uint32 dummy)
{
	scheduler->SendMessage(MessageID::HOSTCOM0_RX);
}

// ホストシリアルからの1バイト(以上)受信メッセージ
// (VM スレッドで呼ばれる)
void
GFTTYDevice::RxMessage(MessageID msgid, uint32 arg)
{
	bool recv = false;

	for (;;) {
		uint32 data = hostcom->Rx();
		if ((int32)data < 0) {
			break;
		}
		rxq.push_back(data);
		recv = true;
	}

	if (recv) {
		ChangeInterrupt();
	}
}

unix.superglobalmegacorp.com

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