mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-22 13:55:38 +00:00
9ffce8b92a
Making changes to a header file should no longer force the entire project to recompile.
370 lines
11 KiB
C++
370 lines
11 KiB
C++
/**
|
|
** Supermodel
|
|
** A Sega Model 3 Arcade Emulator.
|
|
** Copyright 2011-2019 Bart Trzynadlowski, Nik Henson, Ian Curtis
|
|
**
|
|
** This file is part of Supermodel.
|
|
**
|
|
** Supermodel is free software: you can redistribute it and/or modify it under
|
|
** the terms of the GNU General Public License as published by the Free
|
|
** Software Foundation, either version 3 of the License, or (at your option)
|
|
** any later version.
|
|
**
|
|
** Supermodel is distributed in the hope that it will be useful, but WITHOUT
|
|
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
** more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License along
|
|
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
/*
|
|
* MPC10x.cpp
|
|
*
|
|
* Implementation of the CMPC10x class: Motorola MPC105 and MPC106 PCI/bridge
|
|
* controllers. For now, only a single PCI bus can be attached and all requests
|
|
* are forwarded to it.
|
|
*
|
|
* This is a minimal implementation designed with the Model 3 in mind. It does
|
|
* not properly emulate the device and there are numerous inaccuracies in this
|
|
* code.
|
|
*
|
|
* Important Problems To Be Aware Of
|
|
* ---------------------------------
|
|
*
|
|
* The handling of accesses smaller than 32 bits is funky and probably very
|
|
* incorrect, particularly for non-32-bit-aligned register numbers. This
|
|
* problem extends to the entire PCI emulation.
|
|
*
|
|
* Little endian mode is not supported (the internal registers, however, seem
|
|
* to be stored in little endian format). The registers are implemented as byte
|
|
* read/writeable but this is not accurate. In fact, some are read only, and
|
|
* some can only be accessed as 16-bit or 32-bit words.
|
|
*
|
|
* This code assumes we are running on a little endian machine and that the
|
|
* PowerPC is a big endian machine. Therefore, bytes can be written to the
|
|
* register space directly but multi-byte words must be flipped before being
|
|
* interpreted internally.
|
|
*
|
|
* External configuration registers are not implemented.
|
|
*
|
|
* Multiple PCI buses are not yet supported (everything is assumed to be on
|
|
* bus 0).
|
|
*
|
|
* ... And lots more!
|
|
*
|
|
* References
|
|
* ----------
|
|
* 1. "MPC106 PCI/Bridge/Memory Controller User's Manual" (MPC106UM/D Rev.1)
|
|
*
|
|
* To-Do List
|
|
* ----------
|
|
* - The whole PCI system desperately needs a re-write to deal with different
|
|
* bit-width accesses in a cleaner manner.
|
|
* - Fix endian confusion. Should assume everything written is in the correct
|
|
* endian, pushing the responsibility onto the caller.
|
|
*/
|
|
|
|
#include "MPC10x.h"
|
|
|
|
#include <cstring>
|
|
#include "Supermodel.h"
|
|
|
|
|
|
/******************************************************************************
|
|
Save States
|
|
******************************************************************************/
|
|
|
|
void CMPC10x::SaveState(CBlockFile *SaveState)
|
|
{
|
|
SaveState->NewBlock("MPC10x", __FILE__);
|
|
SaveState->Write(regs, sizeof(regs));
|
|
SaveState->Write(&pciBus, sizeof(pciBus));
|
|
SaveState->Write(&pciDevice, sizeof(pciDevice));
|
|
SaveState->Write(&pciFunction, sizeof(pciFunction));
|
|
SaveState->Write(&pciReg, sizeof(pciReg));
|
|
}
|
|
|
|
void CMPC10x::LoadState(CBlockFile *SaveState)
|
|
{
|
|
if (OKAY != SaveState->FindBlock("MPC10x"))
|
|
{
|
|
ErrorLog("Unable to load MPC%X state. Save state file is corrupt.", model);
|
|
return;
|
|
}
|
|
|
|
SaveState->Read(regs, sizeof(regs));
|
|
SaveState->Read(&pciBus, sizeof(pciBus));
|
|
SaveState->Read(&pciDevice, sizeof(pciDevice));
|
|
SaveState->Read(&pciFunction, sizeof(pciFunction));
|
|
SaveState->Read(&pciReg, sizeof(pciReg));
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Emulation Functions
|
|
******************************************************************************/
|
|
|
|
/*
|
|
* CMPC10x::WritePCIConfigAddress(data):
|
|
*
|
|
* Writes to the PCI configuration space address (CONFIG_ADDR) register, which
|
|
* selects a PCI device and configuration space register.
|
|
*/
|
|
void CMPC10x::WritePCIConfigAddress(UINT32 data)
|
|
{
|
|
UINT32 d = FLIPENDIAN32(data);
|
|
|
|
pciBus = (d>>16)&0xFF;
|
|
pciDevice = (d>>11)&0x1F;
|
|
pciFunction = (d>>8)&7;
|
|
pciReg = d&0xFF; // NOTE: for actual PCI devices (device>0), register must be shifted right by 2 and clamped to 0x3F
|
|
|
|
// The manual is unclear as to whether device 0 (MPC10x) register #s are clamped to 0x3F or not. Pay attention to this!
|
|
#ifdef DEBUG
|
|
if (pciDevice == 0)
|
|
{
|
|
DebugLog("MPC10x: Device 0 configuration access: %08X\n", d);
|
|
if ((d&3))
|
|
ErrorLog("MPC10x: Device 0 configuration address with low bits set: %08X\n", d);
|
|
}
|
|
//DebugLog("MPC10x: Bus=%X, Device=%X, Function=%X, Reg=%X\n", pciBus, pciDevice, pciFunction, pciReg);
|
|
#endif
|
|
|
|
if (pciBus != 0)
|
|
{
|
|
//printf("Multiple PCI buses detected!\n");
|
|
DebugLog("Multiple PCI buses detected!\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::ReadPCIConfigData(bits, offset):
|
|
*
|
|
* Reads from the PCI configuration space data (CONFIG_DATA) register, which in
|
|
* turn calls upon the selected PCI device to return the data.
|
|
*/
|
|
UINT32 CMPC10x::ReadPCIConfigData(unsigned bits, unsigned offset)
|
|
{
|
|
// Handle self-access first
|
|
if (pciDevice == 0)
|
|
{
|
|
// Alignment check
|
|
#ifdef DEBUG
|
|
if (((bits==16)&&(offset&1)) || ((bits==32)&&(offset&3)))
|
|
ErrorLog("Misaligned MPC10x read request (bits=%d,reg=%X,offset=%d)\n", bits, pciReg, offset);
|
|
#endif
|
|
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
return regs[pciReg+offset];
|
|
case 16:
|
|
return (regs[pciReg+offset+0]<<8) | regs[pciReg+offset+1];
|
|
case 32:
|
|
return (regs[pciReg+offset+0]<<24) |
|
|
(regs[pciReg+offset+1]<<16) |
|
|
(regs[pciReg+offset+2]<<8) |
|
|
regs[pciReg+offset+3];
|
|
default:
|
|
ErrorLog("MPC10x internal error: invalid access size (%d-bits)", bits);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// All other PCI devices passed to PCI bus
|
|
return PCIBus->ReadConfigSpace(pciDevice, (pciReg>>2)&0x3C, bits, offset);
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::WritePCIConfigData(bits, offset, data):
|
|
*
|
|
* Writes to the PCI configuration space data (CONFIG_DATA) register, which in
|
|
* turn passes the data to the selected PCI device.
|
|
*/
|
|
void CMPC10x::WritePCIConfigData(unsigned bits, unsigned offset, UINT32 data)
|
|
{
|
|
// Handle self-access first
|
|
if (pciDevice == 0)
|
|
{
|
|
// Alignment check
|
|
#ifdef DEBUG
|
|
if (((bits==16)&&(offset&1)) || ((bits==32)&&(offset&3)))
|
|
ErrorLog("Misaligned MPC10x read request (bits=%d,reg=%X,offset=%d)\n", bits, pciReg, offset);
|
|
#endif
|
|
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
regs[pciReg+offset] = data&0xFF;
|
|
break;
|
|
case 16:
|
|
regs[pciReg+offset+0] = (data>>8)&0xFF;
|
|
regs[pciReg+offset+1] = data&0xFF;
|
|
break;
|
|
case 32:
|
|
regs[pciReg+offset+0] = (data>>24)&0xFF;
|
|
regs[pciReg+offset+1] = (data>>16)&0xFF;
|
|
regs[pciReg+offset+2] = (data>>8)&0xFF;
|
|
regs[pciReg+offset+3] = data&0xFF;
|
|
break;
|
|
default:
|
|
ErrorLog("MPC10x internal error: invalid access size (%d-bits)", bits);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
PCIBus->WriteConfigSpace(pciDevice, (pciReg>>2)&0x3C, bits, offset, data);
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::WriteRegister(reg, data):
|
|
*
|
|
* Writes to the MPC10x register space. Accesses one byte at a time so it
|
|
* should be endian-neutral (the caller ends up being responsible for this).
|
|
*/
|
|
void CMPC10x::WriteRegister(unsigned reg, UINT8 data)
|
|
{
|
|
regs[reg&0xFF] = data;
|
|
|
|
if ((reg&0xFF)==0xA8)
|
|
{
|
|
if ((data&0x20))
|
|
ErrorLog("MPC10x little endian mode not yet implemented!");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::Reset(void):
|
|
*
|
|
* Resets the device.
|
|
*/
|
|
void CMPC10x::Reset(void)
|
|
{
|
|
memset(regs, 0, sizeof(regs));
|
|
|
|
// Data is actually stored in little endian format, so we can write directly here
|
|
*(UINT16 *) ®s[0x00] = 0x1057; // vendor ID (Motorola)
|
|
*(UINT16 *) ®s[0x02] = (model==0x106)?0x0002:0x0001; // device ID (MPC105 or MPC106)
|
|
|
|
if (model == 0x106) // MPC106
|
|
{
|
|
*(UINT32 *) ®s[0x04] = 0x00800006; // PCI command and PCI status
|
|
*(UINT32 *) ®s[0x08] = 0x00060000; // class code and revision ID
|
|
*(UINT32 *) ®s[0x0C] = 0x00000800; // cache line size
|
|
*(UINT32 *) ®s[0x70] = 0x00CD0000; // output driver control
|
|
*(UINT32 *) ®s[0xA8] = 0x0010FF00; // processor interface config. 1
|
|
*(UINT32 *) ®s[0xAC] = 0x060C000C; // processor interface config. 2
|
|
*(UINT32 *) ®s[0xB8] = 0x04000000; // TO-DO: CHECK MANUAL
|
|
*(UINT32 *) ®s[0xC0] = 0x00000100; // error enabling 1
|
|
*(UINT32 *) ®s[0xE0] = 0x00420FFF; // emulation support configuration 1
|
|
*(UINT32 *) ®s[0xE8] = 0x00200000; // emulation support configuration 2
|
|
*(UINT32 *) ®s[0xF0] = 0x0000FF02; // memory control config. 1
|
|
*(UINT32 *) ®s[0xF4] = 0x00030000; // memory control config. 2
|
|
*(UINT32 *) ®s[0xFC] = 0x00000010; // memory control config. 4
|
|
}
|
|
else // MPC105
|
|
{
|
|
*(UINT32 *) ®s[0x04] = 0x00800006; // PCI command and PCI status
|
|
*(UINT32 *) ®s[0x08] = 0x00060000; // class code and revision ID
|
|
*(UINT32 *) ®s[0xA8] = 0x0010FF00; // processor interface config. 1
|
|
*(UINT32 *) ®s[0xAC] = 0x060C000C; // processor interface config. 2
|
|
*(UINT32 *) ®s[0xB8] = 0x04000000; // TO-DO: CHECK MANUAL
|
|
*(UINT32 *) ®s[0xF0] = 0x0000FF02; // memory control config. 1
|
|
*(UINT32 *) ®s[0xF4] = 0x00030000; // memory control config. 2
|
|
*(UINT32 *) ®s[0xFC] = 0x00000010; // memory control config. 4
|
|
// To-do: any more??
|
|
}
|
|
|
|
pciBus = 0;
|
|
pciDevice = 0;
|
|
pciFunction = 0;
|
|
pciReg = 0;
|
|
|
|
DebugLog("MPC%X reset\n", model);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Configuration, Initialization, and Shutdown
|
|
******************************************************************************/
|
|
|
|
/*
|
|
* CMPC10x:AttachPCIBus(BusObjectPtr):
|
|
*
|
|
* Attaches a PCI bus object which will handle all PCI register requests.
|
|
*/
|
|
void CMPC10x::AttachPCIBus(CPCIBus *BusObjectPtr)
|
|
{
|
|
PCIBus = BusObjectPtr;
|
|
DebugLog("MPC10x connected to a PCI bus\n");
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::SetModel(modelNum):
|
|
*
|
|
* Sets the device behavior to either MPC105 or MPC106.
|
|
*/
|
|
void CMPC10x::SetModel(int modelNum)
|
|
{
|
|
model = modelNum;
|
|
|
|
if ((modelNum!=0x105) && (modelNum!=0x106))
|
|
{
|
|
ErrorLog("%s:%d: Invalid MPC10x model number (%X).", __FILE__, __LINE__, modelNum);
|
|
model = 0x105;
|
|
}
|
|
|
|
DebugLog("MPC10x set to MPC%X\n", model);
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::GetModel():
|
|
*
|
|
* Returns the model number.
|
|
*/
|
|
int CMPC10x::GetModel() const
|
|
{
|
|
return model;
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::Init():
|
|
*
|
|
* This must be called first and only once during the lifetime of the class.
|
|
*/
|
|
void CMPC10x::Init(void)
|
|
{
|
|
// this function really only exists for consistency with other device classes
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::CMPC10x(void):
|
|
*
|
|
* Constructor.
|
|
*/
|
|
CMPC10x::CMPC10x(void)
|
|
{
|
|
PCIBus = NULL;
|
|
model = 0x105; // default to MPC105
|
|
pciBus = 0;
|
|
pciDevice = 0;
|
|
pciFunction = 0;
|
|
pciReg = 0;
|
|
DebugLog("Built MPC10x\n");
|
|
}
|
|
|
|
/*
|
|
* CMPC10x::~CMPC10x(void):
|
|
*
|
|
* Destructor.
|
|
*/
|
|
CMPC10x::~CMPC10x(void)
|
|
{
|
|
PCIBus = NULL;
|
|
DebugLog("Destroyed MPC10x\n");
|
|
}
|