File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / newsctlr.cpp
Revision 1.1.1.8 (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) 2022 nono project
// Licensed under nono-license.txt
//

//
// NEWS の各種制御ポート(?)
//

// NWS-1750 の $e100'0000..$e1ff'ffff
//
//            +0 +1 +2 +3 +4 +5 +6 +7  +8 +9 +A +B +C +D +E +F'0000
// e100'0000 |TIC                      PE
// e110'0000 |                         I2D
// e120'0000 |                         ASD
// e130'0000 |L2E                      POW
// e140'0000 |
// e150'0000 |
// e160'0000 |
// e170'0000 |
// e180'0000 |
// e190'0000 |L2D
// e1a0'0000 |PD
// e1b0'0000 |
// e1c0'0000 |(*)
// e1d0'0000 |
// e1e0'0000 |
// e1f0'0000 |
//
// TIC: Timer Interrupt Control
// PE/PD: Parity Enable/Disable
// I2D: Lv2 Interrupt Disable
// ASD: AST Disable
// L2E/L2D: L2 Cache Enable/Disable
// POW: Power Control
// (*): IDROM, DIP-SW, Parity Vector, Interupt Status
//
// また、ここに書いてない $e000'0000 ブロックにあるデバイスも一部担当している。
// newsio.cpp 冒頭のメモリマップも参照。

#include "newsctlr.h"
#include "dipsw.h"
#include "ethernet.h"
#include "event.h"
#include "interrupt.h"
#include "scheduler.h"
#include "syncer.h"
#include "textscreen.h"

// コンストラクタ
NewsCtlrDevice::NewsCtlrDevice()
	: inherited(OBJ_NEWSCTLR)
{
	// ROM のうち固定のもの
	oidrom[0] = 0x0d;	// ModelID = NWS-1750
}

// デストラクタ
NewsCtlrDevice::~NewsCtlrDevice()
{
}

// 初期化
bool
NewsCtlrDevice::Init()
{
	dipsw = GetDipswDevice();

	// クロックの同期方法。
	sync_rt = GetSyncer()->GetSyncRealtime();

	// ステータス読み出しのためにキャストしたのを持っておく
	interrupt = dynamic_cast<NewsInterrupt*>(GetInterruptDevice());
	assert(interrupt);

	// MAC アドレスを ROM にセット。
	MacAddr macaddr;
	if (EthernetDevice::GetConfigMacAddr(0, &macaddr, false) == false) {
		// エラーメッセージは表示済み
		return false;
	}
	putmsg(1, "macaddr=%s", macaddr.ToString(':').c_str());
	memcpy(&oidrom[8], &macaddr, sizeof(macaddr));

	auto evman = GetEventManager();
	timer_event = evman->Regist(this,
		ToEventCallback(&NewsCtlrDevice::TimerCallback),
		"SystemTimer");
	timer_event->time = TIMER_PERIOD;

	return true;
}

void
NewsCtlrDevice::ResetHard(bool poweron)
{
	// たぶん止まるだろう。知らんけど。
	timer_intr = false;
	timer_enable = false;
	ChangeInterrupt();

	// たぶん消すだろう。知らんけど。
	led = 0;

	// たぶん電源オンで即起動? 知らんけど。
	if (poweron) {
		if (sync_rt) {
			stime = GetSyncer()->GetRealTime();
		}
		scheduler->StartEvent(timer_event);
	}
}

busdata
NewsCtlrDevice::Read(busaddr addr)
{
	uint32 paddr = addr.Addr() | 0xc0000000;

	if ((paddr >> 16) == 0xe1c0) {
		uint idx = (paddr >> 8) & 3;
		busdata data;
		switch (idx) {
		 case 0:		// e1c0'00xx
			data = GetIDROM(paddr);
			putlog(2, "$%08x.B (IDROM) -> $%02x", paddr, data.Data());
			break;
		 case 2:		// e1c0'02xx
			// 割り込みステータス
			data = interrupt->PeekStatus();
			putlog(1, "$%08x.B (IntStat) -> $%02x", paddr, data.Data());
			break;
		 default:		// e1c0'01xx
			// DIP-SW
			data = GetDIPSW();
			putlog(1, "$%08x.B (DIPSW) -> $%02x", paddr, data.Data());
			break;
		}
		data |= BusData::Size1;
		return data;
	}
	VMPANIC("Read($%08x)", paddr);
}

busdata
NewsCtlrDevice::Write(busaddr addr, uint32 data)
{
	uint32 paddr = addr.Addr() | 0xc0000000;
	uint32 reqsize = addr.GetSize();
	data >>= (reqsize - 1) * 8;

	if (paddr == 0xe0c8'0000) {
		// PWS-1560 FDC?
		putlog(0, "FDC <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe0dc'0000) {
		WriteLED(data);

	} else if (paddr == 0xe100'0000) {
		WriteTimer(data);

	} else if (paddr == 0xe108'0000) {
		// Parity enable?
		putlog(0, "Parity enable? <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe118'0000) {
		// Level2 Interrupt ?
		putlog(0, "Lv2 Interrupt disable? <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe128'0000) {
		putlog(0, "AST disable? <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe130'0000) {
		putlog(0, "L2 cache enable? <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe138'0000) {
		putlog(0, "Power Control <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe190'0000) {
		putlog(0, "L2 cache disable? <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe1a0'0000) {
		putlog(0, "Parity disable? <- $%02x (NOT IMPLEMENTED)", data);

	} else if (paddr == 0xe1c0'0200) {
		// XXX たぶん 0xe1c0'02ff まで全部同じかも
		putlog(0, "Parity Vector? <- $%02x (NOT IMPLEMENTED)", data);

	} else {
		VMPANIC("Write($%08x) <- $%02x", paddr, data);
	}

	busdata r = BusData::Size1;
	return r;
}

busdata
NewsCtlrDevice::Peek1(uint32 addr)
{
	addr |= 0xc0000000;

	if ((addr >> 16) == 0xe1c0) {
		uint idx = (addr >> 8) & 3;
		switch (idx) {
		 case 0:	// e1c0'00xx
			return GetIDROM(addr);
		 case 2:	// e1c0'02xx
			return interrupt->PeekStatus();
		 default:	// e1c0'01xx
			return GetDIPSW();
		}
	}

	return BusData::BusErr;
}

// NewsIO の下請けとして呼ばれる。
void
NewsCtlrDevice::MonitorScreenNewsCtlr(TextScreen& screen, int y)
{
	// テーブル方式ではないので、Read/Write と手動で揃える…。

	uint32 addr = 0xe100;
	for (int i = 0; i < 32; i++) {
		screen.Print(0, y, "$%04x'0000:", addr);

		const char *name = NULL;
		TA attr;
		for (int j = 0; j < 8; j++) {
			switch (addr) {
			 case 0xe100:	name = "Timer";		break;
			 case 0xe108:	name = "ParEn?";	break;
			 case 0xe118:	name = "Lv2Int?";	break;
			 case 0xe128:	name = "ASTDis?";	break;
			 case 0xe130:	name = "L2CE?";		break;
			 case 0xe138:	name = "Power";		break;
			 case 0xe190:	name = "L2CD?";		break;
			 case 0xe1a0:	name = "ParDis?";	break;
			 case 0xe1c0:	name = "Misc";		break;
			 default:
				name = NULL;
				break;
			}
			if (name) {
				attr = TA::Normal;
			} else {
				name = "NopIO";
				attr = TA::Disable;
			}
			screen.Puts(12 + j * 8, y, attr, name);
			addr++;
		}
		y++;
	}
}

// IDROM の値を返す。この関数は副作用を起こしてはいけない。
uint8
NewsCtlrDevice::GetIDROM(uint32 addr) const
{
	uint32 data;

	// $e1c0'00xx はたぶん 256x4bit ROM。
	//
	// 生データ oidrom[] はバイトで持っておき(つまり128バイト)、
	// アクセス時に分解する。
	// 実際に使われているのはこのうち先頭の 16バイト(32ニブル)分のようだ。

	int n = (addr & 0xff) / 2;
	if ((addr & 1) == 0) {
		data = oidrom[n] >> 4;
	} else {
		data = oidrom[n];
	}
	return data | 0xf0;
}

// DIPSW の状態を読み出す。この関数は副作用を起こしてはいけない。
// SW1 が LSB 側。
// オン(true)なら %0、オフ(false)なら %1。
uint8
NewsCtlrDevice::GetDIPSW() const
{
	uint8 data = 0;

	for (int i = 0; i < 8; i++) {
		if (dipsw->Get(i) == false) {
			data |= 1U << i;
		}
	}
	return data;
}

// LED 制御ポート書き込み
void
NewsCtlrDevice::WriteLED(uint32 data)
{
	// bit0 が緑 LED、bit1 がオレンジ LED のオンオフらしい。
	led = data & 0x03;
	putlog(1, "LED <- $%02x", led);
}

// システムタイマー
void
NewsCtlrDevice::WriteTimer(uint32 data)
{
	switch (data) {
	 case 0:
		timer_enable = false;
		timer_intr = false;
		break;
	 case 1:
		timer_enable = true;
		break;
	 default:
		VMPANIC("Unknown Timer Control $%02x", data);
		break;
	}
	ChangeInterrupt();
}

// システムタイマーイベント
void
NewsCtlrDevice::TimerCallback(Event *ev)
{
	timer_intr = true;
	ChangeInterrupt();

	// 足す前は前回の割り込み発生時刻なので period を足すと現在時刻。
	if (sync_rt) {
		stime += TIMER_PERIOD;
		scheduler->StartRealtimeEvent(ev, stime, TIMER_PERIOD);
	} else {
		scheduler->RestartEvent(ev);
	}
}

// 割り込み信号線の状態を変える
void
NewsCtlrDevice::ChangeInterrupt()
{
	interrupt->ChangeINT(this, (bool)(timer_enable && timer_intr));
}

unix.superglobalmegacorp.com

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