--- nono/vm/x68kio.cpp 2026/04/29 17:05:20 1.1.1.2 +++ nono/vm/x68kio.cpp 2026/04/29 17:05:24 1.1.1.3 @@ -8,18 +8,37 @@ // 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 "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 "mpu.h" #include "nereid.h" #include "opm.h" #include "pedec.h" @@ -36,7 +55,8 @@ #include "sysport.h" #include "tvram.h" #include "videoctlr.h" -#include "vm.h" +#include "windrv.h" +#include // コンストラクタ X68kIODevice::X68kIODevice() @@ -47,6 +67,13 @@ X68kIODevice::X68kIODevice() monitor.SetMaxHeight(1 + 256); monitor.Regist(ID_SUBWIN_X68KIO); + // エリアセットは実質ここが担当しているので、モニタもここ。 + monitor_area.func = ToMonitorCallback(&X68kIODevice::MonitorUpdateAreaset); + monitor_area.SetSize(32, 6); + monitor_area.Regist(ID_MONITOR_AREASET); + + cut_fc2 = (gConfig->Find("x68k-cut-fc2").AsInt() != 0); + #define ADD_DEVICE(NAME, ADDR, LEN) \ AddDevice(__CONCAT(p,NAME).get(), ADDR, LEN) @@ -55,12 +82,16 @@ X68kIODevice::X68kIODevice() 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 BusIO_W(), 0xe80000, 0x2000); + N(CRTC, new CRTCDevice(), 0xe80000, 0x2000); N(VideoCtlr, new VideoCtlrDevice(), 0xe82000, 0x2000); N(DMAC, new DMACDevice(), 0xe84000, 0x2000); N(AreaSet, new AreaSetDevice(), 0xe86000, 0x2000); @@ -75,7 +106,13 @@ X68kIODevice::X68kIODevice() N(SCC, new BusIO_EB(), 0xe98000, 0x2000); N(PPI, new BusIO_EB(), 0xe9a000, 0x2000); N(PEDEC, new BusIO_EB(), 0xe9c000, 0x2000); + // Windrv はデバイスは常に作成し、マップするかどうかは設定による。 + NEWDV(Windrv, new 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 @@ -95,20 +132,16 @@ X68kIODevice::~X68kIODevice() { } -// 初期化 +// メインバスの初期化 bool -X68kIODevice::Init() +X68kIODevice::InitMainbus() { - if (inherited::Init() == false) { - return false; - } + auto buserr = pBusErr.get(); + auto mainram = dynamic_cast(pMainRAM.get()); - auto buserr = GetBusErrDevice(); - auto mainram = GetMainRAMDevice(); - - // RAM は RamDevice::Init() で ram_size が決定してから行う。 + // RAM は容量が確定した後のここで配置。 for (int i = 0; i < mainram->GetSize() / 8192; i++) { - table[i] = mainram; + sdevice[i] = mainram; } // Nereid は1枚でもボードがある時だけメモリ空間に追加する。 @@ -117,113 +150,224 @@ X68kIODevice::Init() 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(OBJ_BANKRAM(i)); + if (bank != NULL && bank->GetSize() > 0) { + AddDevice(bank, 0xee0000 + i * 0x010000, 0x010000); + } + } } // 必要なデバイスは全部埋めたので、この時点で nullptr なエントリを // BusErr に差し替える。 - for (auto&& d : table) { + 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 < table.size(); i++) { + for (int i = 1536; i < sdevice.size(); i++) { printf("[%3d] $%06x ", i, i * 8192); - assert(table[i]); - printf("%s\n", table[i]->GetName().c_str()); + assert(sdevice[i]); + assert(udevice[i]); + printf("S:%-8s U:%s\n", + sdevice[i]->GetName().c_str(), + udevice[i]->GetName().c_str()); } } return true; } -// デバイステーブルに dev を追加する。 +// アドレス start から end (を含む) までのユーザ空間を作成する。 +// accessible true ならユーザアクセス可能 (スーパーバイザ空間をコピー)。 +// false なら BusErr で埋める。 +void +X68kIODevice::SetUdevice(uint32 startaddr, uint32 endaddr, bool accessible) +{ + assert(startaddr < endaddr); + assert(((endaddr + 1) & 0x1fff) == 0); + int start = startaddr / 8192; + int end = (endaddr + 1) / 8192; + + // MPU の FC2 ピンカットの動作は本来 MPU 側を加工すべきだが、 + // MPU アクセスのほうが圧倒的に多いので影響の少ない DMAC 側で処理する。 + + // FC2 ピンカットする前の情報を DMAC に展開。 + auto dmac = dynamic_cast(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) { int idx = devaddr / 8192; for (int i = idx; i < idx + (devlen + 8191) / 8192; i++) { // 重複するケースは未対応 - assertmsg(table[i] == nullptr, "X68kIODevice.AddDevice '%s'", - table[i]->GetName().c_str()); + assertmsg(sdevice[i] == nullptr, "X68kIODevice.AddDevice '%s'", + sdevice[i]->GetName().c_str()); - table[i] = dev; + sdevice[i] = dev; } } -// アドレス addr に対応する IODevice* を返す。 +// アドレス super:addr に対応する IODevice* を返す。 // addr は事前に 24bit マスクしてあるもの。 inline IODevice * -X68kIODevice::SearchDevice(uint32 addr) const +X68kIODevice::Decoder(bool super, uint32 addr) const { uint idx = addr / 8192; - __assume(idx < 2048); - return table[idx]; + if (super) { + return sdevice[idx]; + } else { + return udevice[idx]; + } } -uint64 -X68kIODevice::Read8(uint32 addr) +busdata +X68kIODevice::Peek8(uint32 addr) { addr &= 0x00ffffff; - IODevice *d = SearchDevice(addr); - return d->Read8(addr); + + IODevice *d = Decoder(true, addr); + return d->Peek8(addr); } -uint64 -X68kIODevice::Read16(uint32 addr) +bool +X68kIODevice::Poke8(uint32 addr, uint32 data) { addr &= 0x00ffffff; - IODevice *d = SearchDevice(addr); - return d->Read16(addr); + + IODevice *d = Decoder(true, addr); + return d->Poke8(addr, data); } -uint64 -X68kIODevice::Read32(uint32 addr) +// 回路図より 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) { - addr &= 0x00ffffff; - IODevice *d = SearchDevice(addr); - return d->Read32(addr); + // 16ビットポートはメインメモリ、GVRAM、TVRAM、ROM を除いた部分なので + // 0xe80000 .. 0xefffff かな。 + busaddr cimask(0xf80000, busaddr::D); + busaddr data16(0xe80000, busaddr::D); + if ((addr.Get() & cimask.Get()) == data16.Get()) { + mpu->SetCI(); + } } -uint64 -X68kIODevice::Write8(uint32 addr, uint32 data) +// Read*/Write* 全部の共通部分。副作用のあるマクロ。 +#define PREPARE() \ + CheckCI(addr); \ + uint32 addr24 = addr.Addr() & 0x00ffffff; \ + IODevice *d = Decoder(addr.IsSuper(), addr24); + +busdata +X68kIODevice::Read8(busaddr addr) { - addr &= 0x00ffffff; - IODevice *d = SearchDevice(addr); - return d->Write8(addr, data); + PREPARE(); + + busdata bd = d->Read8(addr24); + putlog(2, "Read $%06x -> $%02x", addr24, bd.Data()); + return bd; } -uint64 -X68kIODevice::Write16(uint32 addr, uint32 data) +busdata +X68kIODevice::Read16(busaddr addr) { - addr &= 0x00ffffff; - IODevice *d = SearchDevice(addr); - return d->Write16(addr, data); + PREPARE(); + + busdata bd = d->Read16(addr24); + putlog(2, "Read $%06x -> $%04x", addr24, bd.Data()); + return bd; } -uint64 -X68kIODevice::Write32(uint32 addr, uint32 data) +busdata +X68kIODevice::Read32(busaddr addr) { - addr &= 0x00ffffff; - IODevice *d = SearchDevice(addr); - return d->Write32(addr, data); + PREPARE(); + + busdata bd = d->Read32(addr24); + putlog(2, "Read $%06x -> $%08x", addr24, bd.Data()); + return bd; } -uint64 -X68kIODevice::Peek8(uint32 addr) +busdata +X68kIODevice::Write8(busaddr addr, uint32 data) { - addr &= 0x00ffffff; - IODevice *d = SearchDevice(addr); - return d->Peek8(addr); + PREPARE(); + + putlog(1, "Write $%06x <- $%02x", addr24, data); + return d->Write8(addr24, data); +} + +busdata +X68kIODevice::Write16(busaddr addr, uint32 data) +{ + PREPARE(); + + putlog(1, "Write $%06x <- $%04x", addr24, data); + return d->Write16(addr24, data); } -// このアドレスを担当する次段の IODevice を返す (IOContainerDevice 用) -IODevice * -X68kIODevice::GetDevice(uint32 addr) const +busdata +X68kIODevice::Write32(busaddr addr, uint32 data) { - return SearchDevice(addr & 0x00ffffff); + PREPARE(); + + putlog(1, "Write $%06x <- $%08x", addr24, data); + return d->Write32(addr24, data); } void @@ -231,26 +375,34 @@ X68kIODevice::MonitorUpdate(Monitor *, T { screen.Clear(); - // 012345678901234567890 - // $00'0000: 1234567 - // pos が表示開始位置(0-) int pos = (int)screen.userdata; - int row = screen.GetRow(); + // ヘッダは常に描く + screen.Clear(); for (int i = 0; i < 8; i++) { screen.Print(10 + i * 8, 0, "+$%04x", i * 0x2000); } + // 012345678901234567890 + // $00'0000: 1234567 + int idx = pos * 8; - for (int y = 1; y < row; y++) { - uint32 addr = idx * 0x2000; - std::string addrstr = '$' + strhex(addr >> 16, 2) + "'0000:"; - screen.Puts(0, y, addrstr.c_str()); + 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 pair = MainbusDevice::FormatDevName(table[idx]); + 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; - TA attr = pair.second; screen.Print(10 + i * 8, y, attr, "%s", name.c_str()); idx++; } @@ -258,13 +410,22 @@ X68kIODevice::MonitorUpdate(Monitor *, T } // ブートページを切り替える。 -// (VM_X68030::SwitchBootPage() の下請け) +// 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 が担当する。 @@ -278,9 +439,100 @@ X68kIODevice::SwitchBootPage(bool isrom) } for (int i = 0; i < 8; i++) { - table[i] = ram; + sdevice[i] = ram; } for (int i = 0; i < 16; i++) { - table[table.size() - 16 + i] = rom; + 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 あるので上限チェックは不要。 + int end = (arealimit + 1); + SetUdevice(0x00'0000, end * 8192 - 1, false); + SetUdevice(end * 8192, 0x20'0000 - 1, true); +} + +// 拡張エリアセット取得 +uint8 +X68kIODevice::GetExtarea(int offset) const +{ + if (0 <= offset && offset < extarea.size()) { + return extarea[offset]; + } + // ? + return 0xff; +} + +// 拡張エリアセット設定 +void +X68kIODevice::SetExtarea(int offset, uint8 data) +{ + if (0 <= offset && 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::MonitorUpdateAreaset(Monitor *, TextScreen& screen) +{ + int 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[%d] $%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; + } } }