Compare commits

...

10 Commits

Author SHA1 Message Date
dingusdev a5416f521a Added a note for the 603 2024-04-21 11:11:56 -07:00
joevt a8023bb41b ppcmmu: Remove last_dma_area.
DMA may happen on CPU and audio threads but update of last_dma_area is not thread safe.
2024-04-21 09:13:42 -07:00
dingusdev fa01699786 Slight formatting fix 2024-04-21 08:59:18 -07:00
dingusdev 5f1150cf4e Initial expansion of documentation 2024-04-21 08:44:03 -07:00
joevt 5022ac89b4 ppcexec: Improve realtime. 2024-04-21 07:16:47 -07:00
joevt 382246905b videoctrl: Init palette to black. 2024-04-21 06:06:13 -07:00
joevt a5241d27c1 viacuda: Cleanup. 2024-04-21 06:03:32 -07:00
joevt 4479ecb1bb viacuda: One exit path for read. 2024-04-21 06:03:17 -07:00
joevt b454ab45fe viacuda: Spelling. 2024-04-21 06:02:52 -07:00
joevt d06d80619e viacuda: Add 1 Sec Timer, fix Get/Set Real Time.
GET_REAL_TIME was using the wrong offset and wasn't setting out_count.

May need to add time zone offset which could be different between host and guest.
get-time and set-time can be tested in Open Firmware.
SET_REAL_TIME is used by the Date/Time Control Panel when you change the date.
It is unknown what method Mac OS uses to get the time at boot. Mac OS 8.6 does not use GET_REAL_TIME during boot, so the time is left as 12:00 AM Jan 1, 1904.
2024-04-21 06:01:40 -07:00
15 changed files with 225 additions and 49 deletions

View File

@ -25,8 +25,10 @@
- AppleFritter
- Archive.org
- Bitsavers
- Blitter.net
- Emaculation
- GitHub
- PenguinPPC
- The makers of Loguru, SDL2, Capstone, and CLI11
- The developers of other PowerPC Mac emulators, past and present
- All those preserving the software of 68k and PowerPC Macs

View File

@ -33,6 +33,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <stdio.h>
#include <string>
#ifdef __APPLE__
#include <mach/mach_time.h>
#undef EXC_SYSCALL
static struct mach_timebase_info timebase_info;
static uint64_t
ConvertHostTimeToNanos2(uint64_t host_time)
{
if (timebase_info.numer == timebase_info.denom)
return host_time;
long double answer = host_time;
answer *= timebase_info.numer;
answer /= timebase_info.denom;
return (uint64_t)answer;
}
#endif
using namespace std;
using namespace dppc_interpreter;
@ -65,7 +81,7 @@ bool dec_exception_pending = false;
uint32_t glob_bb_start_la;
/* variables related to virtual time */
bool g_realtime;
const bool g_realtime = false;
uint64_t g_nanoseconds_base;
uint64_t g_icycles_base;
uint64_t g_icycles;
@ -277,9 +293,13 @@ void ppc_main_opcode()
OpcodeGrabber[(ppc_cur_instruction >> 26) & 0x3F]();
}
static long long now_ns() {
long long now_ns() {
#ifdef __APPLE__
return ConvertHostTimeToNanos2(mach_absolute_time());
#else
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
#endif
}
uint64_t get_virt_time_ns()
@ -841,7 +861,9 @@ void ppc_cpu_init(MemCtrlBase* mem_ctrl, uint32_t cpu_version, bool include_601,
TimerManager::get_instance()->set_notify_changes_cb(&force_cycle_counter_reload);
// initialize time base facility
g_realtime = false;
#ifdef __APPLE__
mach_timebase_info(&timebase_info);
#endif
g_nanoseconds_base = now_ns();
g_icycles_base = 0;
g_icycles = 0;

View File

@ -81,7 +81,6 @@ AddressMapEntry last_read_area;
AddressMapEntry last_write_area;
AddressMapEntry last_exec_area;
AddressMapEntry last_ptab_area;
AddressMapEntry last_dma_area;
/** 601-style block address translation. */
static BATResult mpc601_block_address_translation(uint32_t la)
@ -310,22 +309,16 @@ MapDmaResult mmu_map_dma_mem(uint32_t addr, uint32_t size, bool allow_mmio) {
bool is_writable;
AddressMapEntry *cur_dma_rgn;
if (addr >= last_dma_area.start && (addr + size) <= last_dma_area.end) {
cur_dma_rgn = &last_dma_area;
} else {
cur_dma_rgn = mem_ctrl_instance->find_range(addr);
if (!cur_dma_rgn || (addr + size) > cur_dma_rgn->end)
ABORT_F("SOS: DMA access to unmapped physical memory %08X!", addr);
last_dma_area = *cur_dma_rgn;
}
cur_dma_rgn = mem_ctrl_instance->find_range(addr);
if (!cur_dma_rgn || (addr + size) > cur_dma_rgn->end)
ABORT_F("SOS: DMA access to unmapped physical memory %08X!", addr);
if ((cur_dma_rgn->type & RT_MMIO) && !allow_mmio)
ABORT_F("SOS: DMA access to a MMIO region is not allowed");
if (cur_dma_rgn->type & (RT_ROM | RT_RAM)) {
host_va = cur_dma_rgn->mem_ptr + (addr - cur_dma_rgn->start);
is_writable = last_dma_area.type & RT_RAM;
is_writable = cur_dma_rgn->type & RT_RAM;
} else { // RT_MMIO
devobj = cur_dma_rgn->devobj;
dev_base = cur_dma_rgn->start;
@ -1532,7 +1525,6 @@ void ppc_mmu_init()
last_write_area = {0xFFFFFFFF, 0xFFFFFFFF, 0, 0, nullptr, nullptr};
last_exec_area = {0xFFFFFFFF, 0xFFFFFFFF, 0, 0, nullptr, nullptr};
last_ptab_area = {0xFFFFFFFF, 0xFFFFFFFF, 0, 0, nullptr, nullptr};
last_dma_area = {0xFFFFFFFF, 0xFFFFFFFF, 0, 0, nullptr, nullptr};
mmu_exception_handler = ppc_exception_handler;

View File

@ -87,6 +87,17 @@ ViaCuda::ViaCuda() {
this->cuda_init();
this->int_ctrl = nullptr;
std::tm tm = {
.tm_sec = 0,
.tm_min = 0,
.tm_hour = 0,
.tm_mday = 1,
.tm_mon = 1 - 1,
.tm_year = 1904 - 1900,
.tm_isdst = -1 // Use DST value from local time zone
};
mac_epoch = std::chrono::system_clock::from_time_t(std::mktime(&tm));
}
ViaCuda::~ViaCuda()
@ -128,37 +139,49 @@ void ViaCuda::cuda_init() {
}
uint8_t ViaCuda::read(int reg) {
uint8_t value;
/* reading from some VIA registers triggers special actions */
switch (reg & 0xF) {
case VIA_B:
return (this->via_regs[VIA_B]);
value = (this->via_regs[VIA_B]);
break;
case VIA_A:
case VIA_ANH:
value = this->via_regs[reg & 0xF];
LOG_F(WARNING, "Attempted read from VIA Port A!");
break;
case VIA_IER:
return (this->_via_ier | 0x80); // bit 7 always reads as "1"
value = (this->_via_ier | 0x80); // bit 7 always reads as "1"
break;
case VIA_IFR:
return this->_via_ifr;
value = this->_via_ifr;
break;
case VIA_T1CL:
this->_via_ifr &= ~VIA_IF_T1;
update_irq();
return this->calc_counter_val(this->t1_counter, this->t1_start_time) & 0xFFU;
value = this->calc_counter_val(this->t1_counter, this->t1_start_time) & 0xFFU;
break;
case VIA_T1CH:
return this->calc_counter_val(this->t1_counter, this->t1_start_time) >> 8;
value = this->calc_counter_val(this->t1_counter, this->t1_start_time) >> 8;
break;
case VIA_T2CL:
this->_via_ifr &= ~VIA_IF_T2;
update_irq();
return this->calc_counter_val(this->t2_counter, this->t2_start_time) & 0xFFU;
value = this->calc_counter_val(this->t2_counter, this->t2_start_time) & 0xFFU;
break;
case VIA_T2CH:
return this->calc_counter_val(this->t2_counter, this->t2_start_time) >> 8;
value = this->calc_counter_val(this->t2_counter, this->t2_start_time) >> 8;
break;
case VIA_SR:
value = this->via_regs[reg & 0xF];
this->_via_ifr &= ~VIA_IF_SR;
update_irq();
break;
default:
value = this->via_regs[reg & 0xF];
}
return (this->via_regs[reg & 0xF]);
return value;
}
void ViaCuda::write(int reg, uint8_t value) {
@ -166,7 +189,7 @@ void ViaCuda::write(int reg, uint8_t value) {
switch (reg & 0xF) {
case VIA_B:
write(value);
this->write(value);
break;
case VIA_A:
case VIA_ANH:
@ -259,7 +282,7 @@ void ViaCuda::write(int reg, uint8_t value) {
uint16_t ViaCuda::calc_counter_val(const uint16_t last_val, const uint64_t& last_time)
{
// calcualte current counter value based on elapsed time and timer frequency
// calculate current counter value based on elapsed time and timer frequency
uint64_t cur_time = TimerManager::get_instance()->current_time_ns();
uint32_t diff = (cur_time - last_time) / this->via_clk_dur;
return last_val - diff;
@ -495,10 +518,7 @@ void ViaCuda::process_adb_command() {
}
void ViaCuda::autopoll_handler() {
if (!this->autopoll_enabled)
return;
uint8_t poll_command = this->adb_bus_obj->poll();
uint8_t poll_command = this->autopoll_enabled ? this->adb_bus_obj->poll() : 0;
if (poll_command) {
if (!this->old_tip || !this->treq) {
@ -520,6 +540,36 @@ void ViaCuda::autopoll_handler() {
// draw guest system's attention
schedule_sr_int(USECS_TO_NSECS(30));
} else if (this->one_sec_mode == 3) {
uint32_t this_time = calc_real_time();
if (this_time != this->last_time) {
if ((this->last_time & 15) == 0) {
/*
FIXME: This doesn't do anything in Mac OS 8.6.
A real Mac probably doesn't do this. So why do I?
supermario checks for packets that are not PKT_TICK
to set the time but I don't know which Mac OS versions
have that code and if that code is triggered by this.
*/
response_header(9, 0);
this->out_buf[3] = CUDA_GET_REAL_TIME;
uint32_t real_time = this_time + time_offset;
WRITE_DWORD_BE_U(&this->out_buf[3], real_time);
this->out_count = 7;
} else {
response_header(CUDA_PKT_TICK, 0);
this->out_count = 1;
}
this->last_time = this_time;
// assert TREQ
this->via_regs[VIA_B] &= ~CUDA_TREQ;
this->treq = 0;
// draw guest system's attention
schedule_sr_int(USECS_TO_NSECS(30));
}
}
}
@ -556,13 +606,14 @@ void ViaCuda::pseudo_command(int cmd, int data_count) {
}
this->is_open_ended = true;
break;
case CUDA_GET_REAL_TIME:
case CUDA_GET_REAL_TIME: {
response_header(CUDA_PKT_PSEUDO, 0);
this->out_buf[2] = (uint8_t)((this->real_time >> 24) & 0xFF);
this->out_buf[3] = (uint8_t)((this->real_time >> 16) & 0xFF);
this->out_buf[4] = (uint8_t)((this->real_time >> 8) & 0xFF);
this->out_buf[5] = (uint8_t)((this->real_time) & 0xFF);
uint32_t this_time = this->calc_real_time();
uint32_t real_time = this_time + time_offset;
WRITE_DWORD_BE_U(&this->out_buf[3], real_time);
this->out_count = 7;
break;
}
case CUDA_WRITE_MCU_MEM:
addr = READ_WORD_BE_A(&this->in_buf[2]);
// if addr is inside PRAM, update PRAM with data from in_buf
@ -587,13 +638,13 @@ void ViaCuda::pseudo_command(int cmd, int data_count) {
error_response(CUDA_ERR_BAD_PAR);
}
break;
case CUDA_SET_REAL_TIME:
case CUDA_SET_REAL_TIME: {
response_header(CUDA_PKT_PSEUDO, 0);
this->real_time = ((uint32_t)in_buf[2]) >> 24;
this->real_time += ((uint32_t)in_buf[3]) >> 16;
this->real_time += ((uint32_t)in_buf[4]) >> 8;
this->real_time += ((uint32_t)in_buf[5]);
uint32_t real_time = this->calc_real_time();
uint32_t new_time = READ_DWORD_BE_U(&in_buf[2]);
this->time_offset = new_time - real_time;
break;
}
case CUDA_WRITE_PRAM:
addr = READ_WORD_BE_A(&this->in_buf[2]);
if (addr <= 0xFF) {
@ -637,7 +688,8 @@ void ViaCuda::pseudo_command(int cmd, int data_count) {
this->out_buf[3] = (uint8_t)((this->device_mask) & 0xFF);
break;
case CUDA_ONE_SECOND_MODE:
LOG_F(INFO, "Cuda: One Second Interrupt - Byte Sent: %d", this->in_buf[2]);
LOG_F(INFO, "Cuda: One Second Interrupt Mode: %d", this->in_buf[2]);
this->one_sec_mode = this->in_buf[2];
response_header(CUDA_PKT_PSEUDO, 0);
break;
case CUDA_READ_WRITE_I2C:
@ -673,6 +725,13 @@ void ViaCuda::pseudo_command(int cmd, int data_count) {
}
}
uint32_t ViaCuda::calc_real_time() {
auto end = std::chrono::system_clock::now();
auto elapsed_systemclock = end - this->mac_epoch;
auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed_systemclock);
return uint32_t(elapsed_seconds.count());
}
/* sends data from the current I2C to host ad infinitum */
void ViaCuda::i2c_handler() {
this->receive_byte(this->curr_i2c_addr, &this->via_regs[VIA_SR]);

View File

@ -48,6 +48,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <devices/common/nvram.h>
#include <memory>
#include <chrono>
class AdbBus;
class InterruptCtrl;
@ -213,7 +214,10 @@ private:
int32_t out_count;
int32_t out_pos;
uint8_t poll_rate;
int32_t real_time = 0;
uint32_t last_time = 0;
uint32_t time_offset = 0;
std::chrono::time_point<std::chrono::system_clock> mac_epoch;
uint8_t one_sec_mode = 0;
bool file_server;
uint16_t device_mask = 0;
@ -247,6 +251,7 @@ private:
void process_packet();
void process_adb_command();
void pseudo_command(int cmd, int data_count);
uint32_t calc_real_time();
void null_out_handler(void);
void pram_out_handler(void);

View File

@ -88,7 +88,7 @@ protected:
float refresh_rate;
bool draw_fb = true;
uint32_t palette[256]; // internal DAC palette in RGBA format
uint32_t palette[256] = {0}; // internal DAC palette in RGBA format
// Framebuffer parameters
uint8_t* fb_ptr = nullptr;

14
zdocs/developers/amic.md Normal file
View File

@ -0,0 +1,14 @@
The AMIC is the I/O controller used in the Power Mac 6100.
It also:
* Controls the video timing signals
## Subdevices
| Subdevice | Range |
|:--------------:|:--------------------:|
| VIA Cuda | 0x0 - 0x1FFF |
| SCC | 0x4000 - 0x5FFF |
| SCSI | 0x10000 - 0x11FFF |
| DMA | 0x31000 - 0x32FFF |

View File

@ -1,4 +1,4 @@
The ATI Rage is a video card that comes bundled with early Power Mac G3s and New World Macs (like the first revisions of the iMac G3).
The ATI Rage is a video card that comes bundled with early Power Mac G3s and New World Macs (like the first revisions of the iMac G3). Its predecessor was the ATI Mach 64 GX, used in earlier Old World Macs.
# Memory Map

View File

@ -1,5 +1,7 @@
AWACS is an audio controller present on several Old World Macs and can usually be located at IOBase (ex.: 0xF3000000 for Power Mac G3 Beige) + 0x14000.
New World Macs have a Screamer chip, which is backwards compatible with the AWACS chip, but with some additional capabilities.
# Register Maps
## NuBus Macs

View File

@ -0,0 +1,18 @@
The KeyLargo ASIC is an intergrated I/O controller designed for use in New World Power
Macintosh G3 and iMac computers.
It would later be succeeded by the K2 ASIC
## PCI configuration space registers
| Register name | Default value |
|:-------------:|:--------------:|
| VendorID | 0x106B (Apple) |
| DeviceID | 0x0019 |
| RevisionID | 0x01 |
| Class code | 0xFF0000 |
## Additions
* USB support
* MPIC support

View File

@ -2,6 +2,10 @@
The Old World ROM is always 4 megabytes (MB). The first three MB are reserved for the 68k code, while the last MB is for the PowerPC boot-up code.
New World ROMs are 1 MB stubs containing OpenFirmware and some basic drivers, but has an additional ROM stored on the Mac's hard disk to provide Toolbox functionality. The ROMs stored on the Mac's hard disk also had updates distributed.
Within Apple, the project to overhaul Mac OS ROM code from separate portable, low-end, and high-end branches into a single codebase was called SuperMario.
# Serial
For serial, it replicates the functionality of a Zilog ESCC. There are two different ports - one located at (MacIOBase) + 0x13000 for the printer, and the other at (MacIOBase) + 0x13020 for the modem.
@ -63,6 +67,8 @@ Some New World Macs do have a SWIM 3 driver present, but this normally goes unus
Mac OS relies on 8 KB of NVRAM at minimum to run properly. It's usually found at IOBase (ex.: 0xF3000000 for Power Mac G3 Beige) + 0x60000.
On a physical machine, one has to hold the Command/Apple, Option, P and R keys together. However, using DingusPPC, one can simply delete the nvram.bin file instead.
# PMU
| Command Name | Number | Functionality |
@ -77,6 +83,16 @@ Mac OS relies on 8 KB of NVRAM at minimum to run properly. It's usually found at
| PMUPmgrPWRoff | 0x7E |
| PMUResetCPU | 0xD0 |
# USB
# FireWire
Present in several New World Macs is a FireWire controller. Mac OS Classic normally only supports FireWire 400.
# Miscellaneous
The Power Mac G3 Beige has an additional register at 0xFF000004, which is dubbed varyingly as the "cpu-id" (by Open Firmware), the ""systemReg" (display driver) or "MachineID" (platform driver).
* In order for the mouse to move, it generally needs to use the Vertical Blanking Interrupt (VBL) present on the video controller. However, the Pippin instead uses a virtual timer task to accomplish, as there is a bug that prevents the VBL from working in the Taos graphics controller.
* The Power Mac G3 Beige has an additional register at 0xFF000004, which is dubbed varyingly as the "cpu-id" (by Open Firmware), the ""systemReg" (display driver) or "MachineID" (platform driver).

View File

@ -43,6 +43,20 @@ as found in various Power Macintosh models.
*TBD*
## Packages
| Package Name | Purpose | Versions |
|:--------------:|:---------------------------------------------:|:--------:|
| obp-tftp | OpenBoot PROM with tftp | 1.0.5+ |
| aix-boot | | 1.0.5+ |
| xcoff-loader | | 1.0.5+ |
| mac-files | Handle hard drives formatted with HFS | 1.0.5+ |
| mac-parts | Find a partition with a Mac OS installed | 1.0.5+ |
| fat-files | Handle hard drives formatted with FAT(16?) | 1.0.5+ |
| iso-9660-files | Handle CD ROM images formatted with ISO 9660 | 1.0.5+ |
| telnet | | 3.0+ |
## Open Firmware image
### Old World Macs

View File

@ -36,8 +36,28 @@ Up to 128 instruction entries and 128 data entries can be stored at a time.
| Time Base Facility (TBR) | 2 | Calculate, Store, and Load 32-bit fixed-point numbers |
| Condition Register | 1 | Stores conditions based on the results of fixed-point operations |
| Floating Point Condition Register | 1 | Stores conditions based on the results of floating-point operations |
| Vector Status and Control Register (VSCR) | 1 | Stores conditions based on the results of vector operations |
| Machine State Register | 1 | |
# Special Registers
| Register Name | Register Number | Purpose |
| :-------------------------------- | :------------------- | :---------------------------------------------------- |
| Multiply Quotient Register (MQ) | 0 | (601 only) |
| Integer Exception (XER) | 1 | |
| RTC Upper Register (RTCU) | 4 | (601 only) |
| RTC Lower Register (RTCL) | 5 | (601 only) |
| Link Register (LR) | 8 | |
| Counter Quotient Register (CTR) | 9 | |
| Vector Save/Restore | 256 | (G4+) |
| Time Base Lower (TBL) | 268 | (603+) |
| Time Base Upper (TBU) | 269 | (603+) |
| External Access (EAR) | 282 | |
| Processor Version (PVR) | 287 | |
| Hardware Implementation 0 (HID0) | 1008 | |
| Hardware Implementation 1 (HID1) | 1009 | |
# HID 0
| Model | Bits Enabled |
@ -56,4 +76,8 @@ Up to 128 instruction entries and 128 data entries can be stored at a time.
* Apple's memcpy routine uses double floating-point registers rather than general purpose registers to load and store 2 32-bit values at once. As the PowerPC usually operates on at least a 64-bit bus and floating-point processing comes with the processors by default, this saves some instructions and results in slightly faster speeds.
* As the PowerPC does not have an instruction to load an immediate 32-bit value, it's common to see a lis/ori coding pattern.
* As the PowerPC does not have an instruction to load an immediate 32-bit value, it's common to see a lis/ori coding pattern.
* The 603 relies on the instructions tlbld and tlbli to assist in TLB reloading.
* To accomodate for early programs compiled on PowerPC 601 Macs, the classic Mac OS has to emulate the POWER instructions that were removed from later processors.

View File

@ -1,5 +1,7 @@
Using a combination of a 6522 along with some integrated circuits, the VIA Cuda is a microcontroller that heavily controls system operations. It's largely similar to Egret (used in many 68k Macs), but removes some commands.
In many New World Macs, the Cuda itself is emulated within various VLSI chips.
The usual offset for a VIA Cuda is IOBase (ex.: 0xF3000000 for Power Mac G3 Beige) + 0x16000. The registers are spaced out by 0x200 bytes on the Heathrow.
# Usage

View File

@ -22,7 +22,7 @@ DingusPPC uses two windows when booted up; a command line window and a monitor w
## Commands
DingusPPC is operated using the command line interface. As such, we will list the commands as required.
DingusPPC is operated using the command line interface. As such, we will list the commands as required. These commands are separated by spaces.
```
-r, --realtime
@ -77,6 +77,12 @@ fdd_img
Set the floppy disk image
```
fdd_wr_prot=1
```
Set the floppy to read-only
```
hdd_img
```
@ -102,8 +108,8 @@ emmo
Access the factory tests
```
serial_backend stdio
serial_backend socket
serial_backend=stdio
serial_backend=socket
```
Change where the output of OpenFirmware is directed to, either to the command line (with stdio) or a Unix socket (unavailable in Windows builds). OpenFirmware 1.x outputs here by default.
@ -133,7 +139,7 @@ Currently, ISO images are supported. However, support is not yet implemented for
### Hard Disks
Because Sheepshaver, Basilisk II, and Mini vMac operate on raw disks, it is required to a program such as BlueSCSI to make their hard disk images work in an emulator like DingusPPC. This is because the Mac OS normally requires certain values in the hard disks that these emulators don't normally
Because Sheepshaver, Basilisk II, and Mini vMac operate on raw disks, it is required to a program such as BlueSCSI to make their hard disk images work in an emulator like DingusPPC. This is because the Mac OS normally requires certain values in the hard disks that these emulators don't normally insert into the images. You may also need a third-party utility to create an HFS or HFS+ disk image.
### OS Support