mirror of
https://github.com/dingusdev/dingusppc.git
synced 2024-12-25 18:29:49 +00:00
57e6e90002
There are cases where when it's necessary (e.g. given uninitialized NVRAM, the Beige G3 with the 10.2 install CD inserted will update the boot device and restart to boot from it). Restart support was done by wrapping the ppc_exec function in a loop and checking for a restart power off reason. We also need to disconnect all event listeners, since they will be recreated when the machine is re-initialized.
763 lines
26 KiB
C++
763 lines
26 KiB
C++
/*
|
|
DingusPPC - The Experimental PowerPC Macintosh emulator
|
|
Copyright (C) 2018-23 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/>.
|
|
*/
|
|
|
|
/** High-level VIA-CUDA combo device emulation.
|
|
*/
|
|
|
|
#include <core/hostevents.h>
|
|
#include <core/timermanager.h>
|
|
#include <devices/common/adb/adbbus.h>
|
|
#include <cpu/ppc/ppcemu.h>
|
|
#include <devices/common/hwinterrupt.h>
|
|
#include <devices/common/viacuda.h>
|
|
#include <devices/deviceregistry.h>
|
|
#include <loguru.hpp>
|
|
#include <machines/machinebase.h>
|
|
#include <memaccess.h>
|
|
|
|
#include <cinttypes>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
|
|
ViaCuda::ViaCuda() {
|
|
this->name = "ViaCuda";
|
|
|
|
supports_types(HWCompType::ADB_HOST | HWCompType::I2C_HOST);
|
|
|
|
// 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;
|
|
|
|
this->_via_ifr = 0; // all flags cleared
|
|
this->_via_ier = 0; // all interrupts disabled
|
|
|
|
// intialize counters/timers
|
|
this->t1_counter = 0xFFFF;
|
|
this->t1_active = false;
|
|
this->t2_counter = 0xFFFF;
|
|
this->t2_active = false;
|
|
|
|
// calculate VIA clock duration in ns
|
|
this->via_clk_dur = 1.0f / VIA_CLOCK_HZ * NS_PER_SEC;
|
|
|
|
// PRAM is part of Cuda
|
|
this->pram_obj = std::unique_ptr<NVram> (new NVram("pram.bin", 256));
|
|
|
|
// establish ADB bus connection
|
|
this->adb_bus_obj = dynamic_cast<AdbBus*>(gMachineObj->get_comp_by_type(HWCompType::ADB_HOST));
|
|
|
|
// autopoll handler will be called during post-processing of the host events
|
|
EventManager::get_instance()->add_post_handler(this, &ViaCuda::autopoll_handler);
|
|
|
|
this->cuda_init();
|
|
|
|
this->int_ctrl = nullptr;
|
|
}
|
|
|
|
ViaCuda::~ViaCuda()
|
|
{
|
|
if (this->sr_timer_on) {
|
|
TimerManager::get_instance()->cancel_timer(this->sr_timer_id);
|
|
this->sr_timer_on = false;
|
|
}
|
|
if (this->t1_active) {
|
|
TimerManager::get_instance()->cancel_timer(this->t1_timer_id);
|
|
this->t1_active = false;
|
|
}
|
|
if (this->t2_active) {
|
|
TimerManager::get_instance()->cancel_timer(this->t2_timer_id);
|
|
this->t2_active = false;
|
|
}
|
|
if (this->treq_timer_id) {
|
|
TimerManager::get_instance()->cancel_timer(this->treq_timer_id);
|
|
this->treq_timer_id = 0;
|
|
}
|
|
}
|
|
|
|
int ViaCuda::device_postinit()
|
|
{
|
|
this->int_ctrl = dynamic_cast<InterruptCtrl*>(
|
|
gMachineObj->get_comp_by_type(HWCompType::INT_CTRL));
|
|
this->irq_id = this->int_ctrl->register_dev_int(IntSrc::VIA_CUDA);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ViaCuda::cuda_init() {
|
|
this->old_tip = 1;
|
|
this->old_byteack = 1;
|
|
this->treq = 1;
|
|
this->in_count = 0;
|
|
this->out_count = 0;
|
|
this->poll_rate = 11;
|
|
}
|
|
|
|
uint8_t ViaCuda::read(int reg) {
|
|
/* reading from some VIA registers triggers special actions */
|
|
switch (reg & 0xF) {
|
|
case VIA_B:
|
|
return (this->via_regs[VIA_B]);
|
|
case VIA_A:
|
|
case VIA_ANH:
|
|
LOG_F(WARNING, "Attempted read from VIA Port A!");
|
|
break;
|
|
case VIA_IER:
|
|
return (this->_via_ier | 0x80); // bit 7 always reads as "1"
|
|
case VIA_IFR:
|
|
return this->_via_ifr;
|
|
case VIA_T1CL:
|
|
this->_via_ifr &= ~VIA_IF_T1;
|
|
update_irq();
|
|
return this->calc_counter_val(this->t1_counter, this->t1_start_time) & 0xFFU;
|
|
case VIA_T1CH:
|
|
return this->calc_counter_val(this->t1_counter, this->t1_start_time) >> 8;
|
|
case VIA_T2CL:
|
|
this->_via_ifr &= ~VIA_IF_T2;
|
|
update_irq();
|
|
return this->calc_counter_val(this->t2_counter, this->t2_start_time) & 0xFFU;
|
|
case VIA_T2CH:
|
|
return this->calc_counter_val(this->t2_counter, this->t2_start_time) >> 8;
|
|
case VIA_SR:
|
|
this->_via_ifr &= ~VIA_IF_SR;
|
|
update_irq();
|
|
break;
|
|
}
|
|
|
|
return (this->via_regs[reg & 0xF]);
|
|
}
|
|
|
|
void ViaCuda::write(int reg, uint8_t value) {
|
|
this->via_regs[reg & 0xF] = value;
|
|
|
|
switch (reg & 0xF) {
|
|
case VIA_B:
|
|
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);
|
|
break;
|
|
case VIA_DIRA:
|
|
LOG_F(9, "VIA_DIRA = 0x%X", value);
|
|
break;
|
|
case VIA_PCR:
|
|
LOG_F(9, "VIA_PCR = 0x%X", value);
|
|
break;
|
|
case VIA_ACR:
|
|
LOG_F(9, "VIA_ACR = 0x%X", value);
|
|
break;
|
|
case VIA_IFR:
|
|
// for each "1" in value clear the corresponding flags
|
|
this->_via_ifr &= ~value;
|
|
update_irq();
|
|
break;
|
|
case VIA_IER:
|
|
if (value & 0x80) {
|
|
this->_via_ier |= value & 0x7F;
|
|
} else {
|
|
this->_via_ier &= ~value;
|
|
}
|
|
update_irq();
|
|
print_enabled_ints();
|
|
break;
|
|
case VIA_T1CH:
|
|
if (this->via_regs[VIA_ACR] & 0xC0) {
|
|
ABORT_F("Unsupported VIA T1 mode, ACR=0x%X", this->via_regs[VIA_ACR]);
|
|
}
|
|
// cancel active T1 timer task
|
|
if (this->t1_active) {
|
|
TimerManager::get_instance()->cancel_timer(this->t1_timer_id);
|
|
this->t1_active = false;
|
|
}
|
|
// clear T1 flag in IFR
|
|
this->_via_ifr &= ~VIA_IF_T1;
|
|
update_irq();
|
|
// load initial value into counter 1
|
|
this->t1_counter = (value << 8) | this->via_regs[VIA_T1CL];
|
|
// TODO: delay for one phase 2 clock
|
|
// sample current vCPU time and remember it
|
|
this->t1_start_time = TimerManager::get_instance()->current_time_ns();
|
|
// set up timout timer for T1
|
|
this->t1_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
static_cast<uint64_t>(this->via_clk_dur * (this->t1_counter + 3) + 0.5f),
|
|
[this]() {
|
|
this->assert_t1_int();
|
|
}
|
|
);
|
|
this->t1_active = true;
|
|
break;
|
|
case VIA_T2CH:
|
|
if (this->via_regs[VIA_ACR] & 0x20) {
|
|
ABORT_F("VIA T2 pulse count mode not supported!");
|
|
}
|
|
// cancel active T2 timer task
|
|
if (this->t2_active) {
|
|
TimerManager::get_instance()->cancel_timer(this->t2_timer_id);
|
|
this->t2_active = false;
|
|
}
|
|
// clear T2 flag in IFR
|
|
this->_via_ifr &= ~VIA_IF_T2;
|
|
update_irq();
|
|
// load initial value into counter 2
|
|
this->t2_counter = (value << 8) | this->via_regs[VIA_T2CL];
|
|
// TODO: delay for one phase 2 clock
|
|
// sample current vCPU time and remember it
|
|
this->t2_start_time = TimerManager::get_instance()->current_time_ns();
|
|
// set up timeout timer for T2
|
|
this->t2_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
static_cast<uint64_t>(this->via_clk_dur * (this->t2_counter + 3) + 0.5f),
|
|
[this]() {
|
|
this->assert_t2_int();
|
|
}
|
|
);
|
|
this->t2_active = true;
|
|
break;
|
|
case VIA_SR:
|
|
this->_via_ifr &= ~VIA_IF_SR;
|
|
update_irq();
|
|
break;
|
|
}
|
|
}
|
|
|
|
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
|
|
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;
|
|
}
|
|
|
|
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_ier & (1 << i))
|
|
LOG_F(INFO, "VIA %s interrupt enabled", via_int_src[i]);
|
|
}
|
|
}
|
|
|
|
void ViaCuda::update_irq()
|
|
{
|
|
uint8_t active_ints = this->_via_ifr & this->_via_ier & 0x7F;
|
|
if (active_ints != this->old_ifr) {
|
|
uint8_t new_irq = !!active_ints;
|
|
this->_via_ifr = (active_ints) | (new_irq << 7);
|
|
this->old_ifr = active_ints;
|
|
LOG_F(6, "VIA: assert IRQ, IFR=0x%02X", this->_via_ifr);
|
|
this->int_ctrl->ack_int(this->irq_id, new_irq);
|
|
}
|
|
}
|
|
|
|
void ViaCuda::assert_sr_int() {
|
|
this->sr_timer_on = false;
|
|
this->_via_ifr |= VIA_IF_SR;
|
|
update_irq();
|
|
}
|
|
|
|
void ViaCuda::assert_t1_int() {
|
|
this->_via_ifr |= VIA_IF_T1;
|
|
this->t1_active = false;
|
|
update_irq();
|
|
}
|
|
|
|
void ViaCuda::assert_t2_int() {
|
|
this->_via_ifr |= VIA_IF_T2;
|
|
this->t2_active = false;
|
|
update_irq();
|
|
}
|
|
|
|
#ifdef DEBUG_CPU_INT
|
|
void ViaCuda::assert_int(uint8_t flags) {
|
|
this->_via_ifr |= (flags & 0x7F);
|
|
update_irq();
|
|
}
|
|
#endif
|
|
|
|
void ViaCuda::assert_ctrl_line(ViaLine line)
|
|
{
|
|
switch (line) {
|
|
case ViaLine::CA1:
|
|
this->_via_ifr |= VIA_IF_CA1;
|
|
break;
|
|
case ViaLine::CA2:
|
|
this->_via_ifr |= VIA_IF_CA2;
|
|
break;
|
|
case ViaLine::CB1:
|
|
this->_via_ifr |= VIA_IF_CB1;
|
|
break;
|
|
case ViaLine::CB2:
|
|
this->_via_ifr |= VIA_IF_CB1;
|
|
break;
|
|
default:
|
|
ABORT_F("Assertion of unknown VIA line requested!");
|
|
}
|
|
update_irq();
|
|
}
|
|
|
|
void ViaCuda::schedule_sr_int(uint64_t timeout_ns) {
|
|
if (this->sr_timer_on) {
|
|
TimerManager::get_instance()->cancel_timer(this->sr_timer_id);
|
|
this->sr_timer_on = false;
|
|
}
|
|
this->sr_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
timeout_ns,
|
|
[this]() { this->assert_sr_int(); });
|
|
this->sr_timer_on = true;
|
|
}
|
|
|
|
void ViaCuda::write(uint8_t new_state) {
|
|
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;
|
|
|
|
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->treq_timer_id = TimerManager::get_instance()->add_oneshot_timer(
|
|
USECS_TO_NSECS(13), // delay TREQ assertion for New World
|
|
[this]() {
|
|
this->via_regs[VIA_B] &= ~CUDA_TREQ; // assert TREQ
|
|
this->treq = 0;
|
|
this->treq_timer_id = 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;
|
|
}
|
|
|
|
// send dummy byte as idle acknowledge or attention
|
|
schedule_sr_int(USECS_TO_NSECS(61));
|
|
} else {
|
|
if (this->via_regs[VIA_ACR] & 0x10) { // data transfer: Host --> Cuda
|
|
if (this->in_count < sizeof(this->in_buf)) {
|
|
this->in_buf[this->in_count++] = this->via_regs[VIA_SR];
|
|
// tell the system we've read the byte after 71 usecs
|
|
schedule_sr_int(USECS_TO_NSECS(71));
|
|
} else {
|
|
LOG_F(WARNING, "Cuda input buffer too small. Truncating data!");
|
|
}
|
|
} else { // data transfer: Cuda --> Host
|
|
(this->*out_handler)();
|
|
// tell the system we've written next byte after 88 usecs
|
|
schedule_sr_int(USECS_TO_NSECS(88));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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) {
|
|
this->via_regs[VIA_SR] = this->out_buf[this->out_pos++];
|
|
if (!this->is_open_ended && this->out_pos >= this->out_count) {
|
|
// tell the host this will be the last byte
|
|
this->via_regs[VIA_B] |= CUDA_TREQ; // negate TREQ
|
|
this->treq = 1;
|
|
}
|
|
} else if (this->is_open_ended) {
|
|
this->out_handler = this->next_out_handler;
|
|
this->next_out_handler = &ViaCuda::null_out_handler;
|
|
(this->*out_handler)();
|
|
} else {
|
|
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");
|
|
this->process_adb_command();
|
|
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 adb_stat, output_size;
|
|
|
|
adb_stat = this->adb_bus_obj->process_command(&this->in_buf[1],
|
|
this->in_count - 1);
|
|
response_header(CUDA_PKT_ADB, adb_stat);
|
|
output_size = this->adb_bus_obj->get_output_count();
|
|
if (output_size) {
|
|
std::memcpy(&this->out_buf[3], this->adb_bus_obj->get_output_buf(), output_size);
|
|
this->out_count += output_size;
|
|
}
|
|
}
|
|
|
|
void ViaCuda::autopoll_handler() {
|
|
if (!this->autopoll_enabled)
|
|
return;
|
|
|
|
uint8_t poll_command = this->adb_bus_obj->poll();
|
|
|
|
if (poll_command) {
|
|
if (!this->old_tip || !this->treq) {
|
|
LOG_F(WARNING, "Cuda transaction probably in progress");
|
|
}
|
|
|
|
// prepare autopoll packet
|
|
response_header(CUDA_PKT_ADB, ADB_STAT_OK | ADB_STAT_AUTOPOLL);
|
|
this->out_buf[2] = poll_command; // put the proper ADB command
|
|
uint8_t output_size = this->adb_bus_obj->get_output_count();
|
|
if (output_size) {
|
|
std::memcpy(&this->out_buf[3], this->adb_bus_obj->get_output_buf(), output_size);
|
|
this->out_count += output_size;
|
|
}
|
|
|
|
// assert TREQ
|
|
this->via_regs[VIA_B] &= ~CUDA_TREQ;
|
|
this->treq = 0;
|
|
|
|
// draw guest system's attention
|
|
schedule_sr_int(USECS_TO_NSECS(30));
|
|
}
|
|
}
|
|
|
|
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);
|
|
this->autopoll_enabled = true;
|
|
} else {
|
|
LOG_F(INFO, "Cuda: autopoll stopped");
|
|
this->autopoll_enabled = false;
|
|
}
|
|
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_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);
|
|
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_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]);
|
|
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_FILE_SERVER_FLAG:
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
if (this->in_buf[2]) {
|
|
LOG_F(INFO, "Cuda: File server flag on");
|
|
this->file_server = true;
|
|
} else {
|
|
LOG_F(INFO, "Cuda: File server flag off");
|
|
this->file_server = false;
|
|
}
|
|
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_SET_DEVICE_LIST:
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
this->device_mask = ((uint16_t)in_buf[2]) >> 8;
|
|
this->device_mask += ((uint16_t)in_buf[3]);
|
|
break;
|
|
case CUDA_GET_DEVICE_LIST:
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
this->out_buf[2] = (uint8_t)((this->device_mask >> 8) & 0xFF);
|
|
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]);
|
|
response_header(CUDA_PKT_PSEUDO, 0);
|
|
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;
|
|
case CUDA_RESTART_SYSTEM:
|
|
LOG_F(INFO, "Cuda: system restart");
|
|
power_on = false;
|
|
power_off_reason = po_restart;
|
|
break;
|
|
case CUDA_WARM_START:
|
|
case CUDA_POWER_DOWN:
|
|
case CUDA_MONO_STABLE_RESET:
|
|
/* really kludge temp code */
|
|
LOG_F(INFO, "Cuda: Restart/Shutdown signal sent with command 0x%x!", cmd);
|
|
//exit(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;
|
|
}
|
|
}
|
|
|
|
static const vector<string> Cuda_Subdevices = {
|
|
"AdbBus", "AdbMouse", "AdbKeyboard"
|
|
};
|
|
|
|
static const DeviceDescription ViaCuda_Descriptor = {
|
|
ViaCuda::create, Cuda_Subdevices, {}
|
|
};
|
|
|
|
REGISTER_DEVICE(ViaCuda, ViaCuda_Descriptor);
|