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

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

//
// X68030 のメイン空間 ($000000-$FFFFFF) 担当
//

// IODevice
//   +-- MainbusBaseDevice (InitMainbus() と FC アクセスを持つ)
//   |    +-- MainbusDevice (これがメインバス、システムに1つ)
//   |    |    +-- Mainbus24Device (上位8ビットがテーブルで表せるメインバス)
//   |    |    |    +-- LunaMainbus
//   |    |    |    |    +-- Luna1Mainbus
//   |    |    |    |    +-- Luna88kMainbus
//   |    |    |    +-- NewsMainbus
//   |    |    +-- X68kMainbus
//   |    |
//   |    |  +--------------+
//   |    +--| X68kIODevice | (FC を受け取るため)
//   |       +--------------+
//   |
//   +-- XPbusDevice

#include "x68kio.h"
#include "adpcm.h"
#include "areaset.h"
#include "bankram.h"
#include "buserr.h"
#include "busio.h"
#include "config.h"
#include "crtc.h"
#include "dmac.h"
#include "extarea.h"
#include "fdc.h"
#include "gvram.h"
#include "mainbus.h"
#include "mainram.h"
#include "mfp.h"
#include "monitor.h"
#include "mpu.h"
#include "nereid.h"
#include "opm.h"
#include "pedec.h"
#include "pio.h"
#include "pluto.h"
#include "printer.h"
#include "romemu_x68k.h"
#include "romimg_x68k.h"
#include "rp5c15.h"
#include "scc.h"
#include "spc.h"
#include "sprite.h"
#include "sram.h"
#include "sysport.h"
#include "tvram.h"
#include "videoctlr.h"
#include "windrv.h"
#include <algorithm>

// コンストラクタ
X68kIODevice::X68kIODevice()
	: inherited(OBJ_X68KIO)
{
	map_monitor = gMonitorManager->Regist(ID_SUBWIN_X68KIO, this);
	map_monitor->SetCallback(&X68kIODevice::MonitorScreen);
	map_monitor->SetSize(73, 1 + 32);
	map_monitor->SetMaxHeight(1 + 256);

	// エリアセットは実質ここが担当しているので、モニタもここ。
	area_monitor = gMonitorManager->Regist(ID_MONITOR_AREASET, this);
	area_monitor->SetCallback(&X68kIODevice::MonitorScreenAreaset);
	area_monitor->SetSize(32, 6);

	cut_fc2 = gConfig->Find("x68k-cut-fc2").AsBool();

#define ADD_DEVICE(NAME, ADDR, LEN)	\
	AddDevice(__CONCAT(p,NAME).get(), ADDR, LEN)

#define N(NAME, NEW_CTOR, ADDR, LEN)	do {	\
		NEWDV(NAME, NEW_CTOR);	\
		ADD_DEVICE(NAME, ADDR, LEN);	\
	} while (0)

	NEWDV(BusErr, new BusErrDevice());
	NEWDV(MainRAM, new MainRAMDevice());
	NEWDV(IPLROM0, new IPLROM0Device());

	// デバイスクラス作成。
	// 初期化順の制約がない限りアドレス順。
	//                                      	開始アドレス 長さ
	N(GVRAM, new GVRAMDevice(),					0xc00000,0x200000);
	N(PlaneVRAM, new TVRAMDevice(),				0xe00000, 0x80000);
	N(CRTC, new CRTCDevice(),					0xe80000,  0x2000);
	N(VideoCtlr, new VideoCtlrDevice(),			0xe82000,  0x2000);
	N(DMAC, new DMACDevice(),					0xe84000,  0x2000);
	N(AreaSet, new BusIO_EB<AreaSetDevice>(),	0xe86000,  0x2000);
	N(MFP, new BusIO_EB<MFPDevice>(),			0xe88000,  0x2000);
	N(RTC, new BusIO_EB<RP5C15Device>(),		0xe8a000,  0x2000);
	N(Printer, new BusIO_EB<PrinterDevice>(),	0xe8c000,  0x2000);
	N(Sysport,new BusIO_FB<SysportDevice>(),	0xe8e000,  0x2000);
	N(OPM, new BusIO_EB<OPMDevice>(),			0xe90000,  0x2000);
	N(ADPCM, new BusIO_EB<ADPCMDevice>(),		0xe92000,  0x2000);
	N(FDC, new BusIO_EB<FDCDevice>(),			0xe94000,  0x2000);
	N(SPC, new BusIO_X68kSPC<SPCDevice>(),		0xe96000,  0x2000);
	N(SCC, new BusIO_EB<SCCDevice>(),			0xe98000,  0x2000);
	N(PPI, new BusIO_EB<PPIDevice>(),			0xe9a000,  0x2000);
	N(PEDEC, new BusIO_EB<PEDECDevice>(),		0xe9c000,  0x2000);
	// Windrv はデバイスは常に作成し、マップするかどうかは設定による。
	NEWDV(Windrv, new BusIO_B<WindrvDevice>());
	if (WindrvDevice::IsWindrv()) {
		ADD_DEVICE(Windrv,						0xe9e000,  0x2000);
	}
	N(Pluto, new PlutoDevice(),					0xeac000,  0x2000);
	N(ExtArea, new ExtAreaDevice(),				0xeae000,  0x2000);
	N(Sprite, new SpriteDevice(),				0xeb0000, 0x10000);
	// Nereid はこの時点ではデバイスの追加のみ。Init() 参照。
	NEWDV(Nereid, new NereidDevice());		//	0xece000,  0x2000
	N(SRAM, new SRAMDevice(),					0xed0000,  0x4000);
	N(CGROM, new CGROMDevice(),					0xf00000, 0xc0000);
	if (gConfig->Find("iplrom2-image").AsString().empty()) {
		NEWDV(IPLROM2, new ROM30EmuDevice());
	} else {
		NEWDV(IPLROM2, new IPLROM2Device());
	}
	ADD_DEVICE(IPLROM2,							0xfc0000, 0x20000);
	N(IPLROM1, new IPLROM1Device(),				0xfe0000, 0x20000);
}

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

// メインバスの初期化
bool
X68kIODevice::InitMainbus()
{
	auto buserr = pBusErr.get();
	auto mainram = dynamic_cast<MainRAMDevice*>(pMainRAM.get());

	// RAM は容量が確定した後のここで配置。
	for (int i = 0; i < mainram->GetSize() / 8192; i++) {
		sdevice[i] = mainram;
	}

	// Nereid は1枚でもボードがある時だけメモリ空間に追加する。
	// Nereid が1枚もない場合でもモニタやログ(この場合のログは起動時にどれが
	// 有効とか無効と判断したことを表示するもの)のためデバイス自体は常に存在。
	auto nereid = GetNereidDevice();
	if (nereid->IsInstalled()) {
		ADD_DEVICE(Nereid, 0xece000, 0x2000);

		// バンク領域もバンクメモリが有効な場合のみ追加する。
		// #0 が $ee0000、
		// #1 が $ef0000 固定にしている。
		for (int i = 0; i < 2; i++) {
			auto bank = gMainApp.FindObject<BankRAMDevice>(OBJ_BANKRAM(i));
			if (bank != NULL && bank->GetSize() > 0) {
				AddDevice(bank, 0xee0000 + i * 0x010000, 0x010000);
			}
		}
	}

	// 必要なデバイスは全部埋めたので、この時点で nullptr なエントリを
	// BusErr に差し替える。
	for (auto&& d : sdevice) {
		if (d == nullptr) {
			d = buserr;
		}
	}

	// ユーザ空間にコピー。
	// 00'0000 - BF'FFFF: S U : RAM
	// C0'0000 - E7'FFFF: S - : GVRAM/TVRAM
	// E8'0000 - EB'FFFF: S - : I/O 空間
	// EC'0000 - EC'FFFF: S U : ユーザ I/O 空間
	// ED'0000 - EF'FFFF: S - : I/O 空間
	// F0'0000 - FF'FFFF: S - : ROM
	SetUdevice(0x000000, 0xbfffff, true);
	SetUdevice(0xc00000, 0xebffff, false);
	SetUdevice(0xec0000, 0xecffff, true);
	SetUdevice(0xed0000, 0xffffff, false);

	// ユーザ空間も nullptr をチェック。
	assert(std::all_of(udevice.begin(), udevice.end(),
		[](IODevice *d) { return d != nullptr; }));

	// デバッグ表示
	if (0) {
		for (int i = 1536; i < sdevice.size(); i++) {
			printf("[%3u] $%06x ", i, i * 8192);
			assert(sdevice[i]);
			assert(udevice[i]);
			printf("S:%-8s U:%s\n",
				sdevice[i]->GetName().c_str(),
				udevice[i]->GetName().c_str());
		}
	}

	return true;
}

// アドレス start から end (を含む) までのユーザ空間を作成する。
// accessible true ならユーザアクセス可能 (スーパーバイザ空間をコピー)。
// false なら BusErr で埋める。
void
X68kIODevice::SetUdevice(uint32 startaddr, uint32 endaddr, bool accessible)
{
	assert(startaddr < endaddr);
	assert(((endaddr + 1) & 0x1fff) == 0);
	uint start = startaddr / 8192;
	uint end = (endaddr + 1) / 8192;

	// MPU の FC2 ピンカットの動作は本来 MPU 側を加工すべきだが、
	// MPU アクセスのほうが圧倒的に多いので影響の少ない DMAC 側で処理する。

	// FC2 ピンカットする前の情報を DMAC に展開。
	auto dmac = dynamic_cast<DMACDevice*>(pDMAC.get());
	dmac->SetUdevice(start, end, accessible);

	// FC2 ピンカットなら全域がユーザアクセス可能。
	if (cut_fc2) {
		accessible = true;
	}

	if (accessible) {
		memcpy(&udevice[start], &sdevice[start],
			(end - start) * sizeof(udevice[0]));
	} else {
		auto buserr = pBusErr.get();
		for (int i = start; i < end; i++) {
			udevice[i] = buserr;
		}
	}
}

// デバイステーブル sdevice に dev を追加する。
void
X68kIODevice::AddDevice(IODevice *dev, uint devaddr, uint devlen)
{
	uint idx = devaddr / 8192;
	for (int i = idx; i < idx + (devlen + 8191) /  8192; i++) {
		// 重複するケースは未対応
		assertmsg(sdevice[i] == nullptr, "X68kIODevice.AddDevice '%s'",
			sdevice[i]->GetName().c_str());

		sdevice[i] = dev;
	}
}

// アドレス super:addr に対応する IODevice* を返す。
// addr は事前に 24bit マスクしてあるもの。
inline IODevice *
X68kIODevice::Decoder(bool super, uint32 addr) const
{
	uint idx = addr / 8192;
	if (super) {
		return sdevice[idx];
	} else {
		return udevice[idx];
	}
}

// 回路図より CIIN は以下の通り。
//
// FC1 ---------|
// ______       |OR ---o|&      ____
// DSACK0 --o|>-|       |&o---- CIIN
// __                   |&
// AS -----------------o|&
//
// __     ___                 ____
// AS FC1 DS0 : DS0 FC1|DS0 : CIIN
//  L   0   H :  L     L    :  L   (データ空間 & 16ビットポート)
//  L   0   L :  H     H    :  H
//  L   1   H :  L     H    :  H
//  L   1   L :  H     H    :  H

inline void
X68kIODevice::CheckCI(busaddr addr)
{
	// 16ビットポートはメインメモリ、GVRAM、TVRAM、ROM を除いた部分なので
	// 0xe80000 .. 0xefffff かな。
	busaddr cimask = busaddr(0xf80000) | BusAddr::D;
	busaddr data16 = busaddr(0xe80000) | BusAddr::D;
	if ((addr.Get() & cimask.Get()) == data16.Get()) {
		mpu->SetCI();
	}
}

busdata
X68kIODevice::Read(busaddr addr)
{
	addr &= 0x00ffffff;
	CheckCI(addr);

	IODevice *d = Decoder(addr.IsSuper(), addr.Addr());
	busdata bd = d->Read(addr);
	putlog(2, "Read  $%06x -> $%0*x",
		addr.Addr(), addr.GetSize() * 2, bd.Data());
	return bd;
}

busdata
X68kIODevice::Write(busaddr addr, uint32 data)
{
	addr &= 0x00ffffff;
	CheckCI(addr);

	IODevice *d = Decoder(addr.IsSuper(), addr.Addr());
	putlog(2, "Write $%06x <- $%0*x", addr.Addr(), addr.GetSize() * 2, data);
	return d->Write(addr, data);
}

busdata
X68kIODevice::Peek1(uint32 addr)
{
	addr &= 0x00ffffff;

	IODevice *d = Decoder(true, addr);
	return d->Peek1(addr);
}

bool
X68kIODevice::Poke1(uint32 addr, uint32 data)
{
	addr &= 0x00ffffff;

	IODevice *d = Decoder(true, addr);
	return d->Poke1(addr, data);
}

void
X68kIODevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	screen.Clear();

	// pos が表示開始位置(0-)
	int pos = (int)screen.userdata;

	// ヘッダは常に描く
	screen.Clear();
	for (int i = 0; i < 8; i++) {
		screen.Print(10 + i * 8, 0, "+$%04x", i * 0x2000);
	}

	// 012345678901234567890
	// $00'0000: 1234567

	uint idx = pos * 8;
	for (int y = 1, end = screen.GetRow(); y < end; y++) {
		screen.Print(0, y, "$%02x'0000:", (idx * 0x2000) >> 16);
		for (int i = 0; i < 8; i++) {
			auto sdev = sdevice[idx];
			auto udev = udevice[idx];
			auto pair = MainbusBaseDevice::FormatDevName(sdev);
			TA attr;
			if (sdev != udev) {
				// スーパーバイザ空間は太字とかにして区別する。
				attr = TA::Bold;
			} else {
				// 両方からアクセス可能 (かそもそもバスエラー)。
				attr = pair.second;
			}
			const std::string& name = pair.first;
			screen.Print(10 + i * 8, y, attr, "%s", name.c_str());
			idx++;
		}
	}
}

// ブートページを切り替える。
// X68kMainbus から呼ばれるが実際はこっちがメイン。
void
X68kIODevice::SwitchBootPage(bool isrom)
{
	IODevice *ram;
	IODevice *rom;

	if (gMainApp.human_mode) {
		// Human モードではブートページを RAM に見せる。
		// 今のところ。
		return;
	}

	// 共通処理
	inherited::SwitchBootPage(isrom);

	if (isrom) {
		// 0x000000 からの  64KB ( 8エントリ)、
		// 0xfe0000 からの 128KB (16エントリ) のどちらも IPLROM0 が担当する。
		ram = GetIPLROM0Device();
		rom = GetIPLROM0Device();
	} else {
		// 0x000000 からの  64KB ( 8エントリ) に RAM を見せる。
		// 0xfe0000 からの 128KB (16エントリ) に IPLROM1 を見せる。
		ram = GetMainRAMDevice();
		rom = GetIPLROM1Device();
	}

	for (int i = 0; i < 8; i++) {
		sdevice[i] = ram;
	}
	for (int i = 0; i < 16; i++) {
		sdevice[sdevice.size() - 16 + i] = rom;
	}
}

// エリアセット設定
void
X68kIODevice::SetAreaset(uint8 arealimit_)
{
	arealimit = arealimit_;

	// $000000 から (設定値 + 1) * 8KB をスーパーバイザ領域にする。
	// $00 なら $000000 から $001FFF まで (8KB)。
	// $ff なら $000000 から $01FFFF まで (2MB)。
	// X68030 なのでメモリは最低でも 4MB あるので上限チェックは不要。
	uint end = (arealimit + 1);
	SetUdevice(0x00'0000, end * 8192 - 1, false);
	SetUdevice(end * 8192, 0x20'0000 - 1, true);
}

// 拡張エリアセット取得
uint8
X68kIODevice::GetExtarea(uint offset) const
{
	if (offset < extarea.size()) {
		return extarea[offset];
	}
	// ?
	return 0xff;
}

// 拡張エリアセット設定
void
X68kIODevice::SetExtarea(uint offset, uint8 data)
{
	if (offset < extarea.size()) {
		extarea[offset] = data;
	}

	// [0] $200000 .. $3fffff (2MB)
	// [1] $400000 .. $5fffff
	// [2] $600000 .. $7fffff
	// [3] $800000 .. $9fffff
	// [4] $a00000 .. $bfffff
	//
	// +----+----+----+----+----+----+----+----+
	// | S7 | S6 | S5 | S4 | S3 | S2 | S1 | S0 |
	// +----+----+----+----+----+----+----+----+
	//
	// S0 は +$000000 〜 +$03ffff (256KB、32エントリ)
	// S1 は +$040000 〜 +$07ffff
	// S2 は +$080000 〜 +$0bffff
	// S3 は +$0c0000 〜 +$0fffff
	// S4 は +$100000 〜 +$13ffff
	// S5 は +$140000 〜 +$17ffff
	// S6 は +$180000 〜 +$1bffff
	// S7 は +$1c0000 〜 +$1fffff
	//
	// %1 ならスーパーバイザ保護 (ユーザアクセス不可)。

	uint32 addr = (offset + 1) * 0x20'0000;
	data = ~data;
	for (int i = 0; i < 8; i++) {
		bool u = (data & 1);
		SetUdevice(addr, addr + 0x04'0000 - 1, u);

		data >>= 1;
		addr += 0x04'0000;
	}
}

// エリアセットと拡張エリアセットのモニタ。
void
X68kIODevice::MonitorScreenAreaset(Monitor *, TextScreen& screen)
{
	uint areaend;

	screen.Clear();

	// エリアセット
	areaend = (arealimit + 1) * 8192;
	screen.Print(0, 0, "Areaset    $%02x  $000000-$%06x",
		arealimit, areaend);

	// 拡張エリアセット
	for (int i = 0; i < extarea.size(); i++) {
		uint8 data = GetExtarea(i);
		screen.Print(0, i + 1, "Extarea[%u] $%02x  $%06x", i, data,
			(i + 1) * 0x20'0000);
		for (int j = 0; j < 8; j++) {
			screen.Putc(24 + j, i + 1, (((int8)data < 0) ? 'S' : '-'));
			data <<= 1;
		}
	}
}

unix.superglobalmegacorp.com

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