|
|
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();
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.