dingusppc/devices/common/viacuda.cpp
Mihai Parparita 57e6e90002 Add support for the CUDA_RESTART_SYSTEM command
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.
2024-03-07 23:32:23 -08:00

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);