/* 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 . */ /** High-level VIA-CUDA combo device emulation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include 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 (new NVram("pram.bin", 256)); // establish ADB bus connection this->adb_bus_obj = dynamic_cast(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; 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() { 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( 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) { uint8_t value; /* reading from some VIA registers triggers special actions */ switch (reg & 0xF) { case 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: value = (this->_via_ier | 0x80); // bit 7 always reads as "1" break; case VIA_IFR: value = this->_via_ifr; break; case VIA_T1CL: this->_via_ifr &= ~VIA_IF_T1; update_irq(); value = this->calc_counter_val(this->t1_counter, this->t1_start_time) & 0xFFU; break; case VIA_T1CH: 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(); value = this->calc_counter_val(this->t2_counter, this->t2_start_time) & 0xFFU; break; case VIA_T2CH: 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 value; } 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(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(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) { // 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; } 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() { uint8_t poll_command = this->autopoll_enabled ? this->adb_bus_obj->poll() : 0; 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)); } 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)); } } } 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); 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 // 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); 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) { // 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 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: 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); } } 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(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]); } 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 Cuda_Subdevices = { "AdbBus", "AdbMouse", "AdbKeyboard" }; static const DeviceDescription ViaCuda_Descriptor = { ViaCuda::create, Cuda_Subdevices, {} }; REGISTER_DEVICE(ViaCuda, ViaCuda_Descriptor);