mirror of
https://github.com/dingusdev/dingusppc.git
synced 2025-01-11 20:29:46 +00:00
498 lines
17 KiB
C++
498 lines
17 KiB
C++
/*
|
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
|
Copyright (C) 2018-21 divingkatae and maximum
|
|
(theweirdo) spatium
|
|
|
|
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
|
|
|
|
This program 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.
|
|
|
|
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** VIA-CUDA combo device emulation.
|
|
|
|
Author: Max Poliakovski 2019
|
|
*/
|
|
|
|
#include <devices/common/adb/adb.h>
|
|
#include <devices/common/viacuda.h>
|
|
#include <loguru.hpp>
|
|
#include <memaccess.h>
|
|
|
|
#include <cinttypes>
|
|
#include <memory>
|
|
|
|
using namespace std;
|
|
|
|
ViaCuda::ViaCuda() {
|
|
this->name = "ViaCuda";
|
|
|
|
// VIA reset clears all internal registers to logic 0
|
|
// except timers/counters and the shift register
|
|
// as stated in the 6522 datasheet
|
|
this->via_regs[VIA_A] = 0;
|
|
this->via_regs[VIA_B] = 0;
|
|
this->via_regs[VIA_DIRB] = 0;
|
|
this->via_regs[VIA_DIRA] = 0;
|
|
this->via_regs[VIA_IER] = 0;
|
|
this->via_regs[VIA_ACR] = 0;
|
|
this->via_regs[VIA_PCR] = 0;
|
|
this->via_regs[VIA_IFR] = 0;
|
|
|
|
// load maximum value into the timer registers for safety
|
|
// (not prescribed in the 6522 datasheet)
|
|
this->via_regs[VIA_T1LL] = 0xFF;
|
|
this->via_regs[VIA_T1LH] = 0xFF;
|
|
this->via_regs[VIA_T2CL] = 0xFF;
|
|
this->via_regs[VIA_T2CH] = 0xFF;
|
|
|
|
// PRAM is part of Cuda
|
|
this->pram_obj = std::unique_ptr<NVram> (new NVram("pram.bin", 256));
|
|
|
|
this->adb_obj = new ADB_Bus();
|
|
|
|
this->init();
|
|
}
|
|
|
|
void ViaCuda::init() {
|
|
this->old_tip = 0;
|
|
this->old_byteack = 0;
|
|
this->treq = 1;
|
|
this->in_count = 0;
|
|
this->out_count = 0;
|
|
this->poll_rate = 11;
|
|
}
|
|
|
|
uint8_t ViaCuda::read(int reg) {
|
|
uint8_t res;
|
|
|
|
LOG_F(9, "Read VIA reg %d", reg);
|
|
|
|
res = this->via_regs[reg & 0xF];
|
|
|
|
/* reading from some VIA registers triggers special actions */
|
|
switch (reg & 0xF) {
|
|
case VIA_B:
|
|
res = this->via_regs[VIA_B];
|
|
break;
|
|
case VIA_A:
|
|
case VIA_ANH:
|
|
LOG_F(WARNING, "Attempted read from VIA Port A!");
|
|
break;
|
|
case VIA_IER:
|
|
res |= 0x80; /* bit 7 always reads as "1" */
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void ViaCuda::write(int reg, uint8_t value) {
|
|
switch (reg & 0xF) {
|
|
case VIA_B:
|
|
this->via_regs[VIA_B] = value;
|
|
write(value);
|
|
break;
|
|
case VIA_A:
|
|
case VIA_ANH:
|
|
LOG_F(WARNING, "Attempted write to VIA Port A!");
|
|
break;
|
|
case VIA_DIRB:
|
|
LOG_F(9, "VIA_DIRB = 0x%X", value);
|
|
this->via_regs[VIA_DIRB] = value;
|
|
break;
|
|
case VIA_DIRA:
|
|
LOG_F(9, "VIA_DIRA = 0x%X", value);
|
|
this->via_regs[VIA_DIRA] = value;
|
|
break;
|
|
case VIA_PCR:
|
|
LOG_F(9, "VIA_PCR = 0x%X", value);
|
|
this->via_regs[VIA_PCR] = value;
|
|
break;
|
|
case VIA_ACR:
|
|
LOG_F(9, "VIA_ACR = 0x%X", value);
|
|
this->via_regs[VIA_ACR] = value;
|
|
break;
|
|
case VIA_IER:
|
|
this->via_regs[VIA_IER] = (value & 0x80) ? value & 0x7F : this->via_regs[VIA_IER] & ~value;
|
|
LOG_F(9, "VIA_IER updated to 0x%X", this->via_regs[VIA_IER]);
|
|
print_enabled_ints();
|
|
break;
|
|
default:
|
|
this->via_regs[reg & 0xF] = value;
|
|
}
|
|
}
|
|
|
|
void ViaCuda::print_enabled_ints() {
|
|
const char* via_int_src[] = {"CA2", "CA1", "SR", "CB2", "CB1", "T2", "T1"};
|
|
|
|
for (int i = 0; i < 7; i++) {
|
|
if (this->via_regs[VIA_IER] & (1 << i))
|
|
LOG_F(INFO, "VIA %s interrupt enabled", via_int_src[i]);
|
|
}
|
|
}
|
|
|
|
inline bool ViaCuda::ready() {
|
|
return ((this->via_regs[VIA_DIRB] & 0x38) == 0x30);
|
|
}
|
|
|
|
inline void ViaCuda::assert_sr_int() {
|
|
this->via_regs[VIA_IFR] |= 0x84;
|
|
}
|
|
|
|
void ViaCuda::write(uint8_t new_state) {
|
|
if (!ready()) {
|
|
LOG_F(WARNING, "Cuda not ready!");
|
|
return;
|
|
}
|
|
|
|
int new_tip = !!(new_state & CUDA_TIP);
|
|
int new_byteack = !!(new_state & CUDA_BYTEACK);
|
|
|
|
/* return if there is no state change */
|
|
if (new_tip == this->old_tip && new_byteack == this->old_byteack)
|
|
return;
|
|
|
|
LOG_F(9, "Cuda state changed!");
|
|
|
|
this->old_tip = new_tip;
|
|
this->old_byteack = new_byteack;
|
|
|
|
if (new_tip) {
|
|
if (new_byteack) {
|
|
this->via_regs[VIA_B] |= CUDA_TREQ; /* negate TREQ */
|
|
this->treq = 1;
|
|
|
|
if (this->in_count) {
|
|
process_packet();
|
|
|
|
/* start response transaction */
|
|
this->via_regs[VIA_B] &= ~CUDA_TREQ; /* assert TREQ */
|
|
this->treq = 0;
|
|
}
|
|
|
|
this->in_count = 0;
|
|
} else {
|
|
LOG_F(9, "Cuda: enter sync state");
|
|
this->via_regs[VIA_B] &= ~CUDA_TREQ; /* assert TREQ */
|
|
this->treq = 0;
|
|
this->in_count = 0;
|
|
this->out_count = 0;
|
|
}
|
|
|
|
assert_sr_int(); /* send dummy byte as idle acknowledge or attention */
|
|
} else {
|
|
if (this->via_regs[VIA_ACR] & 0x10) { /* data transfer: Host --> Cuda */
|
|
if (this->in_count < 16) {
|
|
this->in_buf[this->in_count++] = this->via_regs[VIA_SR];
|
|
assert_sr_int(); /* tell the system we've read the data */
|
|
} else {
|
|
LOG_F(WARNING, "Cuda input buffer too small. Truncating data!");
|
|
}
|
|
} else { /* data transfer: Cuda --> Host */
|
|
(this->*out_handler)();
|
|
assert_sr_int(); /* tell the system we've written the data */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* sends zeros to host ad infinitum */
|
|
void ViaCuda::null_out_handler() {
|
|
this->via_regs[VIA_SR] = 0;
|
|
}
|
|
|
|
/* sends PRAM content to host ad infinitum */
|
|
void ViaCuda::pram_out_handler()
|
|
{
|
|
this->via_regs[VIA_SR] = this->pram_obj->read_byte(this->cur_pram_addr++);
|
|
}
|
|
|
|
/* sends data from out_buf until exhausted, then switches to next_out_handler */
|
|
void ViaCuda::out_buf_handler() {
|
|
if (this->out_pos < this->out_count) {
|
|
LOG_F(9, "OutBufHandler: sending next byte 0x%X", this->out_buf[this->out_pos]);
|
|
this->via_regs[VIA_SR] = this->out_buf[this->out_pos++];
|
|
} else if (this->is_open_ended) {
|
|
LOG_F(9, "OutBufHandler: switching to next handler");
|
|
this->out_handler = this->next_out_handler;
|
|
this->next_out_handler = &ViaCuda::null_out_handler;
|
|
(this->*out_handler)();
|
|
} else {
|
|
LOG_F(9, "Sending last byte");
|
|
this->out_count = 0;
|
|
this->via_regs[VIA_B] |= CUDA_TREQ; /* negate TREQ */
|
|
this->treq = 1;
|
|
}
|
|
}
|
|
|
|
void ViaCuda::response_header(uint32_t pkt_type, uint32_t pkt_flag) {
|
|
this->out_buf[0] = pkt_type;
|
|
this->out_buf[1] = pkt_flag;
|
|
this->out_buf[2] = this->in_buf[1]; /* copy original cmd */
|
|
this->out_count = 3;
|
|
this->out_pos = 0;
|
|
this->out_handler = &ViaCuda::out_buf_handler;
|
|
this->next_out_handler = &ViaCuda::null_out_handler;
|
|
this->is_open_ended = false;
|
|
}
|
|
|
|
void ViaCuda::error_response(uint32_t error) {
|
|
this->out_buf[0] = CUDA_PKT_ERROR;
|
|
this->out_buf[1] = error;
|
|
this->out_buf[2] = this->in_buf[0];
|
|
this->out_buf[3] = this->in_buf[1]; /* copy original cmd */
|
|
this->out_count = 4;
|
|
this->out_pos = 0;
|
|
this->out_handler = &ViaCuda::out_buf_handler;
|
|
this->next_out_handler = &ViaCuda::null_out_handler;
|
|
this->is_open_ended = false;
|
|
}
|
|
|
|
void ViaCuda::process_packet() {
|
|
if (this->in_count < 2) {
|
|
LOG_F(ERROR, "Cuda: invalid packet (too few data)!");
|
|
error_response(CUDA_ERR_BAD_SIZE);
|
|
return;
|
|
}
|
|
|
|
switch (this->in_buf[0]) {
|
|
case CUDA_PKT_ADB:
|
|
LOG_F(9, "Cuda: ADB packet received \n");
|
|
process_adb_command(this->in_buf[1], this->in_count - 2);
|
|
break;
|
|
case CUDA_PKT_PSEUDO:
|
|
LOG_F(9, "Cuda: pseudo command packet received");
|
|
LOG_F(9, "Command: 0x%X", this->in_buf[1]);
|
|
LOG_F(9, "Data count: %d", this->in_count);
|
|
for (int i = 0; i < this->in_count; i++) {
|
|
LOG_F(9, "0x%X ,", this->in_buf[i]);
|
|
}
|
|
pseudo_command(this->in_buf[1], this->in_count - 2);
|
|
break;
|
|
default:
|
|
LOG_F(ERROR, "Cuda: unsupported packet type = %d", this->in_buf[0]);
|
|
error_response(CUDA_ERR_BAD_PKT);
|
|
}
|
|
}
|
|
|
|
void ViaCuda::process_adb_command(uint8_t cmd_byte, int data_count) {
|
|
int adb_dev = cmd_byte >> 4; // 2 for keyboard, 3 for mouse
|
|
int cmd = cmd_byte & 0xF;
|
|
|
|
if (!cmd) {
|
|
LOG_F(9, "Cuda: ADB SendReset command requested");
|
|
response_header(CUDA_PKT_ADB, 0);
|
|
} else if (cmd == 1) {
|
|
LOG_F(9, "Cuda: ADB Flush command requested");
|
|
response_header(CUDA_PKT_ADB, 0);
|
|
} else if ((cmd & 0xC) == 8) {
|
|
LOG_F(9, "Cuda: ADB Listen command requested");
|
|
int adb_reg = cmd_byte & 0x3;
|
|
if (adb_obj->listen(adb_dev, adb_reg)) {
|
|
response_header(CUDA_PKT_ADB, 0);
|
|
for (int data_ptr = 0; data_ptr < adb_obj->get_output_len(); data_ptr++) {
|
|
this->in_buf[(2 + data_ptr)] = adb_obj->get_output_byte(data_ptr);
|
|
}
|
|
} else {
|
|
response_header(CUDA_PKT_ADB, 2);
|
|
}
|
|
} else if ((cmd & 0xC) == 0xC) {
|
|
LOG_F(9, "Cuda: ADB Talk command requested");
|
|
response_header(CUDA_PKT_ADB, 0);
|
|
int adb_reg = cmd_byte & 0x3;
|
|
if (adb_obj->talk(adb_dev, adb_reg, this->in_buf[2])) {
|
|
response_header(CUDA_PKT_ADB, 0);
|
|
} else {
|
|
response_header(CUDA_PKT_ADB, 2);
|
|
}
|
|
} else {
|
|
LOG_F(ERROR, "Cuda: unsupported ADB command 0x%X", cmd);
|
|
error_response(CUDA_ERR_BAD_CMD);
|
|
}
|
|
}
|
|
|
|
void ViaCuda::pseudo_command(int cmd, int data_count) {
|
|
uint16_t addr;
|
|
int i;
|
|
|
|
switch (cmd) {
|
|
case CUDA_START_STOP_AUTOPOLL:
|
|
if (this->in_buf[2]) {
|
|
LOG_F(INFO, "Cuda: autopoll started, rate: %d ms", this->poll_rate);
|
|
} else {
|
|
LOG_F(INFO, "Cuda: autopoll stopped");
|
|
}
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
break;
|
|
case CUDA_READ_MCU_MEM:
|
|
addr = READ_WORD_BE_A(&this->in_buf[2]);
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
// if starting address is within PRAM region
|
|
// prepare to transfer PRAM content, othwesise we will send zeroes
|
|
if (addr >= CUDA_PRAM_START && addr <= CUDA_PRAM_END) {
|
|
this->cur_pram_addr = addr - CUDA_PRAM_START;
|
|
this->next_out_handler = &ViaCuda::pram_out_handler;
|
|
} else if (addr >= CUDA_ROM_START) {
|
|
// HACK: Cuda ROM dump requsted so let's partially fake it
|
|
this->out_buf[3] = 0; // empty copyright string
|
|
WRITE_WORD_BE_A(&this->out_buf[4], 0x0019U);
|
|
WRITE_WORD_BE_A(&this->out_buf[6], CUDA_FW_VERSION_MAJOR);
|
|
WRITE_WORD_BE_A(&this->out_buf[8], CUDA_FW_VERSION_MINOR);
|
|
this->out_count += 7;
|
|
}
|
|
this->is_open_ended = true;
|
|
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
|
|
// otherwise, ignore data in in_buf
|
|
if (addr >= CUDA_PRAM_START && addr <= CUDA_PRAM_END) {
|
|
for (i = 0; i < this->in_count - 4; i++) {
|
|
this->pram_obj->write_byte((addr - CUDA_PRAM_START + i) & 0xFF,
|
|
this->in_buf[4+i]);
|
|
}
|
|
}
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
break;
|
|
case CUDA_READ_PRAM:
|
|
addr = READ_WORD_BE_A(&this->in_buf[2]);
|
|
if (addr <= 0xFF) {
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
// this command is open-ended so set up the corresponding context
|
|
this->cur_pram_addr = addr;
|
|
this->next_out_handler = &ViaCuda::pram_out_handler;
|
|
this->is_open_ended = true;
|
|
} else {
|
|
error_response(CUDA_ERR_BAD_PAR);
|
|
}
|
|
break;
|
|
case CUDA_WRITE_PRAM:
|
|
addr = READ_WORD_BE_A(&this->in_buf[2]);
|
|
if (addr <= 0xFF) {
|
|
// transfer data from in_buf to PRAM
|
|
for (i = 0; i < this->in_count - 4; i++) {
|
|
this->pram_obj->write_byte((addr + i) & 0xFF, this->in_buf[4+i]);
|
|
}
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
} else {
|
|
error_response(CUDA_ERR_BAD_PAR);
|
|
}
|
|
break;
|
|
case CUDA_SET_AUTOPOLL_RATE:
|
|
this->poll_rate = this->in_buf[2];
|
|
LOG_F(INFO, "Cuda: autopoll rate set to %d ms", this->poll_rate);
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
break;
|
|
case CUDA_GET_AUTOPOLL_RATE:
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
this->out_buf[3] = this->poll_rate;
|
|
this->out_count++;
|
|
break;
|
|
case CUDA_READ_WRITE_I2C:
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
i2c_simple_transaction(this->in_buf[2], &this->in_buf[3], this->in_count - 3);
|
|
break;
|
|
case CUDA_COMB_FMT_I2C:
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
if (this->in_count >= 5) {
|
|
i2c_comb_transaction(
|
|
this->in_buf[2], this->in_buf[3], this->in_buf[4], &this->in_buf[5], this->in_count - 5);
|
|
}
|
|
break;
|
|
case CUDA_OUT_PB0: /* undocumented call! */
|
|
LOG_F(INFO, "Cuda: send %d to PB0", (int)(this->in_buf[2]));
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
break;
|
|
default:
|
|
LOG_F(ERROR, "Cuda: unsupported pseudo command 0x%X", cmd);
|
|
error_response(CUDA_ERR_BAD_CMD);
|
|
}
|
|
}
|
|
|
|
/* 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]);
|
|
}
|
|
|
|
void ViaCuda::i2c_simple_transaction(uint8_t dev_addr, const uint8_t* in_buf, int in_bytes) {
|
|
int op_type = dev_addr & 1; /* 0 - write to device, 1 - read from device */
|
|
|
|
dev_addr >>= 1; /* strip RD/WR bit */
|
|
|
|
if (!this->start_transaction(dev_addr)) {
|
|
LOG_F(WARNING, "Unsupported I2C device 0x%X", dev_addr);
|
|
error_response(CUDA_ERR_I2C);
|
|
return;
|
|
}
|
|
|
|
/* send data to the target I2C device until there is no more data to send
|
|
or the target device doesn't acknowledge that indicates an error */
|
|
for (int i = 0; i < in_bytes; i++) {
|
|
if (!this->send_byte(dev_addr, in_buf[i])) {
|
|
LOG_F(WARNING, "NO_ACK during sending, device 0x%X", dev_addr);
|
|
error_response(CUDA_ERR_I2C);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (op_type) { /* read request initiate an open ended transaction */
|
|
this->curr_i2c_addr = dev_addr;
|
|
this->out_handler = &ViaCuda::out_buf_handler;
|
|
this->next_out_handler = &ViaCuda::i2c_handler;
|
|
this->is_open_ended = true;
|
|
}
|
|
}
|
|
|
|
void ViaCuda::i2c_comb_transaction(
|
|
uint8_t dev_addr, uint8_t sub_addr, uint8_t dev_addr1, const uint8_t* in_buf, int in_bytes) {
|
|
int op_type = dev_addr1 & 1; /* 0 - write to device, 1 - read from device */
|
|
|
|
if ((dev_addr & 0xFE) != (dev_addr1 & 0xFE)) {
|
|
LOG_F(ERROR, "Combined I2C: dev_addr mismatch!");
|
|
error_response(CUDA_ERR_I2C);
|
|
return;
|
|
}
|
|
|
|
dev_addr >>= 1; /* strip RD/WR bit */
|
|
|
|
if (!this->start_transaction(dev_addr)) {
|
|
LOG_F(WARNING, "Unsupported I2C device 0x%X", dev_addr);
|
|
error_response(CUDA_ERR_I2C);
|
|
return;
|
|
}
|
|
|
|
if (!this->send_subaddress(dev_addr, sub_addr)) {
|
|
LOG_F(WARNING, "NO_ACK while sending subaddress, device 0x%X", dev_addr);
|
|
error_response(CUDA_ERR_I2C);
|
|
return;
|
|
}
|
|
|
|
/* send data to the target I2C device until there is no more data to send
|
|
or the target device doesn't acknowledge that indicates an error */
|
|
for (int i = 0; i < in_bytes; i++) {
|
|
if (!this->send_byte(dev_addr, in_buf[i])) {
|
|
LOG_F(WARNING, "NO_ACK during sending, device 0x%X", dev_addr);
|
|
error_response(CUDA_ERR_I2C);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!op_type) { /* return dummy response for writes */
|
|
LOG_F(WARNING, "Combined I2C - write request!");
|
|
} else {
|
|
this->curr_i2c_addr = dev_addr;
|
|
this->out_handler = &ViaCuda::out_buf_handler;
|
|
this->next_out_handler = &ViaCuda::i2c_handler;
|
|
this->is_open_ended = true;
|
|
}
|
|
}
|