mirror of
https://github.com/dingusdev/dingusppc.git
synced 2024-12-28 21:30:27 +00:00
11d61359c1
Allow the dbdma program to initiate reading/writing by adding in and out callbacks. Support the DBDMA flush command by adding a flush callback. If the transfer completes, then clear the flush flag, otherwise leave it unchanged. Clear the flush flag after it is copied to the xferStatus of the DBDMA command.
569 lines
19 KiB
C++
569 lines
19 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/>.
|
|
*/
|
|
|
|
/** @file Descriptor-based direct memory access emulation. */
|
|
|
|
#include <core/timermanager.h>
|
|
#include <cpu/ppc/ppcmmu.h>
|
|
#include <devices/common/dbdma.h>
|
|
#include <devices/common/dmacore.h>
|
|
#include <devices/common/hwinterrupt.h>
|
|
#include <devices/common/mmiodevice.h>
|
|
#include <endianswap.h>
|
|
#include <memaccess.h>
|
|
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
#include <loguru.hpp>
|
|
|
|
void DMAChannel::set_callbacks(DbdmaCallback start_cb, DbdmaCallback stop_cb) {
|
|
this->start_cb = start_cb;
|
|
this->stop_cb = stop_cb;
|
|
}
|
|
|
|
void DMAChannel::set_data_callbacks(DbdmaCallback in_cb, DbdmaCallback out_cb, DbdmaCallback flush_cb) {
|
|
this->in_cb = in_cb;
|
|
this->out_cb = out_cb;
|
|
this->flush_cb = flush_cb;
|
|
}
|
|
|
|
/* Load DMACmd from physical memory. */
|
|
DMACmd* DMAChannel::fetch_cmd(uint32_t cmd_addr, DMACmd* p_cmd, bool *is_writable) {
|
|
MapDmaResult res = mmu_map_dma_mem(cmd_addr, 16, false);
|
|
if (is_writable) *is_writable = res.is_writable;
|
|
DMACmd* cmd_host = (DMACmd*)res.host_va;
|
|
p_cmd->req_count = READ_WORD_LE_A(&cmd_host->req_count);
|
|
p_cmd->cmd_bits = cmd_host->cmd_bits;
|
|
p_cmd->cmd_key = cmd_host->cmd_key;
|
|
p_cmd->address = READ_DWORD_LE_A(&cmd_host->address);
|
|
p_cmd->cmd_arg = READ_DWORD_LE_A(&cmd_host->cmd_arg);
|
|
p_cmd->res_count = READ_WORD_LE_A(&cmd_host->res_count);
|
|
p_cmd->xfer_stat = READ_WORD_LE_A(&cmd_host->xfer_stat);
|
|
return cmd_host;
|
|
}
|
|
|
|
uint8_t DMAChannel::interpret_cmd() {
|
|
DMACmd cmd_struct;
|
|
MapDmaResult res;
|
|
|
|
if (this->cmd_in_progress) {
|
|
// return current command if there is data to transfer
|
|
if (this->queue_len)
|
|
return this->cur_cmd;
|
|
|
|
this->finish_cmd();
|
|
}
|
|
|
|
bool cmd_is_writable;
|
|
DMACmd *cmd_host = fetch_cmd(this->cmd_ptr, &cmd_struct, &cmd_is_writable);
|
|
|
|
this->ch_stat &= ~CH_STAT_WAKE; // clear wake bit (DMA spec, 5.5.3.4)
|
|
|
|
this->cur_cmd = cmd_struct.cmd_key >> 4;
|
|
|
|
switch (this->cur_cmd) {
|
|
case DBDMA_Cmd::OUTPUT_MORE:
|
|
case DBDMA_Cmd::OUTPUT_LAST:
|
|
case DBDMA_Cmd::INPUT_MORE:
|
|
case DBDMA_Cmd::INPUT_LAST:
|
|
if (cmd_struct.cmd_key & 7) {
|
|
LOG_F(ERROR, "%s: Key > 0 not implemented", this->get_name().c_str());
|
|
break;
|
|
}
|
|
this->queue_len = cmd_struct.req_count;
|
|
if (this->queue_len) {
|
|
res = mmu_map_dma_mem(cmd_struct.address, cmd_struct.req_count, false);
|
|
this->queue_data = res.host_va;
|
|
this->res_count = 0;
|
|
this->cmd_in_progress = true;
|
|
switch (this->cur_cmd) {
|
|
case DBDMA_Cmd::OUTPUT_MORE:
|
|
case DBDMA_Cmd::OUTPUT_LAST:
|
|
if (this->out_cb)
|
|
this->out_cb();
|
|
break;
|
|
case DBDMA_Cmd::INPUT_MORE:
|
|
case DBDMA_Cmd::INPUT_LAST:
|
|
if (this->in_cb)
|
|
this->in_cb();
|
|
break;
|
|
}
|
|
} else
|
|
this->finish_cmd();
|
|
break;
|
|
case DBDMA_Cmd::STORE_QUAD:
|
|
if ((cmd_struct.cmd_key & 7) != 6)
|
|
LOG_F(ERROR, "%s: Invalid key %d in STORE_QUAD", this->get_name().c_str(),
|
|
cmd_struct.cmd_key & 7);
|
|
this->xfer_quad(&cmd_struct, nullptr);
|
|
break;
|
|
case DBDMA_Cmd::LOAD_QUAD:
|
|
if ((cmd_struct.cmd_key & 7) != 6) {
|
|
LOG_F(ERROR, "%s: Invalid key %d in LOAD_QUAD", this->get_name().c_str(),
|
|
cmd_struct.cmd_key & 7);
|
|
}
|
|
if (!cmd_is_writable)
|
|
LOG_F(ERROR, "%s: DMACmd is not writeable!", this->get_name().c_str());
|
|
this->xfer_quad(&cmd_struct, cmd_host);
|
|
break;
|
|
case DBDMA_Cmd::NOP:
|
|
this->finish_cmd();
|
|
break;
|
|
case DBDMA_Cmd::STOP:
|
|
this->ch_stat &= ~CH_STAT_ACTIVE;
|
|
this->cmd_in_progress = false;
|
|
break;
|
|
default:
|
|
LOG_F(ERROR, "%s: Unsupported DMA command 0x%X", this->get_name().c_str(),
|
|
this->cur_cmd);
|
|
this->ch_stat |= CH_STAT_DEAD;
|
|
this->ch_stat &= ~CH_STAT_ACTIVE;
|
|
}
|
|
|
|
return this->cur_cmd;
|
|
}
|
|
|
|
void DMAChannel::finish_cmd() {
|
|
bool branch_taken = false;
|
|
|
|
// obtain real pointer to the descriptor of the command to be finished
|
|
MapDmaResult res = mmu_map_dma_mem(this->cmd_ptr, 16, false);
|
|
uint8_t *cmd_desc = res.host_va;
|
|
|
|
// get command code
|
|
this->cur_cmd = cmd_desc[3] >> 4;
|
|
|
|
// all commands except STOP update cmd.xferStatus and
|
|
// perform actions under control of "i" interrupt, "b" branch, and "w" wait bits
|
|
if (this->cur_cmd < DBDMA_Cmd::STOP) {
|
|
// react to cmd.w (wait) bits
|
|
if (cmd_desc[2] & 3) {
|
|
bool cond = true;
|
|
if ((cmd_desc[2] & 3) != 3) {
|
|
uint16_t wt_mask = this->wait_select >> 16;
|
|
cond = (this->ch_stat & wt_mask) == (this->wait_select & wt_mask);
|
|
if ((cmd_desc[2] & 3) == 2) {
|
|
cond = !cond; // wait if cond = false
|
|
}
|
|
}
|
|
|
|
if (cond)
|
|
return;
|
|
}
|
|
|
|
if (res.is_writable)
|
|
WRITE_WORD_LE_A(&cmd_desc[14], this->ch_stat | CH_STAT_ACTIVE);
|
|
this->ch_stat &= ~CH_STAT_FLUSH;
|
|
|
|
// react to cmd.b (branch) bits
|
|
if (cmd_desc[2] & 0xC) {
|
|
bool cond = true;
|
|
if ((cmd_desc[2] & 0xC) != 0xC) {
|
|
uint16_t br_mask = this->branch_select >> 16;
|
|
cond = (this->ch_stat & br_mask) == (this->branch_select & br_mask);
|
|
if ((cmd_desc[2] & 0xC) == 0x8) {
|
|
cond = !cond; // branch if cond = false
|
|
}
|
|
}
|
|
if (cond) {
|
|
this->cmd_ptr = READ_DWORD_LE_A(&cmd_desc[8]);
|
|
branch_taken = true;
|
|
}
|
|
}
|
|
|
|
this->update_irq();
|
|
}
|
|
|
|
// all INPUT and OUTPUT commands including LOAD_QUAD and STORE_QUAD update cmd.resCount
|
|
if (this->cur_cmd < DBDMA_Cmd::NOP && res.is_writable) {
|
|
WRITE_WORD_LE_A(&cmd_desc[12], this->res_count);
|
|
this->queue_len = 0;
|
|
this->res_count = 0;
|
|
}
|
|
|
|
if (!branch_taken)
|
|
this->cmd_ptr += 16;
|
|
|
|
this->cmd_in_progress = false;
|
|
}
|
|
|
|
void DMAChannel::xfer_quad(const DMACmd *cmd_desc, DMACmd *cmd_host) {
|
|
MapDmaResult res;
|
|
uint32_t addr;
|
|
|
|
// parse and fix reqCount
|
|
uint32_t xfer_size = cmd_desc->req_count & 7;
|
|
if (xfer_size & 4) {
|
|
xfer_size = 4;
|
|
} else if (xfer_size & 2) {
|
|
xfer_size = 2;
|
|
} else {
|
|
xfer_size = 1;
|
|
}
|
|
this->res_count = cmd_desc->req_count; // this is the value that gets written to cmd.resCount
|
|
|
|
addr = cmd_desc->address;
|
|
if (addr & (xfer_size - 1)) {
|
|
LOG_F(ERROR, "%s: QUAD address 0x%08x is not aligned!", this->get_name().c_str(), addr);
|
|
addr &= ~(xfer_size - 1);
|
|
}
|
|
|
|
res = mmu_map_dma_mem(addr, xfer_size, true);
|
|
|
|
// prepare data pointers and perform data transfer
|
|
if (!cmd_host) {
|
|
if (res.type & RT_MMIO) {
|
|
res.dev_obj->write(res.dev_base, addr - res.dev_base, cmd_desc->cmd_arg, xfer_size);
|
|
} else if (res.is_writable) {
|
|
switch (xfer_size) {
|
|
case 1: *res.host_va = cmd_desc->cmd_arg; break;
|
|
case 2: WRITE_WORD_LE_A(res.host_va, cmd_desc->cmd_arg); break;
|
|
case 4: WRITE_DWORD_LE_A(res.host_va, cmd_desc->cmd_arg); break;
|
|
}
|
|
} else {
|
|
LOG_F(ERROR, "SOS: DMA access is not to RAM %08X!\n", addr);
|
|
}
|
|
} else {
|
|
uint32_t value;
|
|
if (res.type & RT_MMIO) {
|
|
value = res.dev_obj->read(res.dev_base, addr - res.dev_base, xfer_size);
|
|
} else {
|
|
switch (xfer_size) {
|
|
case 1: value = *res.host_va; break;
|
|
case 2: value = READ_WORD_LE_A(res.host_va); break;
|
|
case 4: value = READ_DWORD_LE_A(res.host_va); break;
|
|
default: value = 0; break;
|
|
}
|
|
}
|
|
WRITE_DWORD_LE_A(&cmd_host->cmd_arg, value);
|
|
}
|
|
|
|
if (cmd_desc->cmd_bits & 0xC)
|
|
ABORT_F("%s: cmd_bits.b should be zero for LOAD/STORE_QUAD!",
|
|
this->get_name().c_str());
|
|
|
|
this->finish_cmd();
|
|
}
|
|
|
|
void DMAChannel::update_irq() {
|
|
// obtain real pointer to the descriptor of the completed command
|
|
MapDmaResult res = mmu_map_dma_mem(this->cmd_ptr, 16, false);
|
|
uint8_t *cmd_desc = res.host_va;
|
|
|
|
// STOP doesn't generate interrupts
|
|
if (this->cur_cmd < DBDMA_Cmd::STOP) {
|
|
// react to cmd.i (interrupt) bits
|
|
if (cmd_desc[2] & 0x30) {
|
|
bool cond = true;
|
|
if ((cmd_desc[2] & 0x30) != 0x30) {
|
|
uint16_t int_mask = this->int_select >> 16;
|
|
cond = (this->ch_stat & int_mask) == (this->int_select & int_mask);
|
|
if ((cmd_desc[2] & 0x30) == 0x20) {
|
|
cond = !cond; // generate interrupt if cond = false
|
|
}
|
|
}
|
|
if (cond) {
|
|
if (int_ctrl) {
|
|
TimerManager::get_instance()->add_immediate_timer([this] {
|
|
this->int_ctrl->ack_dma_int(this->irq_id, 1);
|
|
});
|
|
} else
|
|
LOG_F(ERROR, "%s Interrupt ignored", this->get_name().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t DMAChannel::reg_read(uint32_t offset, int size) {
|
|
if (size != 4) {
|
|
ABORT_F("%s: non-DWORD read from a DMA channel not supported",
|
|
this->get_name().c_str());
|
|
}
|
|
|
|
switch (offset) {
|
|
case DMAReg::CH_CTRL:
|
|
return 0; // ChannelControl reads as 0 (DBDMA spec 5.5.1, table 74)
|
|
case DMAReg::CH_STAT:
|
|
return BYTESWAP_32(this->ch_stat);
|
|
case DMAReg::CMD_PTR_LO:
|
|
return BYTESWAP_32(this->cmd_ptr);
|
|
default:
|
|
LOG_F(WARNING, "%s: Unsupported DMA channel register read at 0x%X",
|
|
this->get_name().c_str(), offset);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void DMAChannel::reg_write(uint32_t offset, uint32_t value, int size) {
|
|
uint16_t mask, old_stat, new_stat;
|
|
|
|
if (size != 4) {
|
|
ABORT_F("%s: non-DWORD writes to a DMA channel not supported",
|
|
this->get_name().c_str());
|
|
}
|
|
|
|
value = BYTESWAP_32(value);
|
|
old_stat = this->ch_stat;
|
|
|
|
switch (offset) {
|
|
case DMAReg::CH_CTRL:
|
|
mask = value >> 16;
|
|
new_stat = (value & mask & 0xF0FFU) | (old_stat & ~mask);
|
|
LOG_F(9, "%s: New ChannelStatus value = 0x%X", this->get_name().c_str(), new_stat);
|
|
|
|
// update ch_stat.s0...s7 if requested (needed for interrupt generation)
|
|
if ((new_stat & 0xFF) != (old_stat & 0xFF)) {
|
|
this->ch_stat |= new_stat & 0xFF;
|
|
}
|
|
|
|
// flush bit can be set at the same time the run bit is cleared.
|
|
// That means we need to update memory before channel operation
|
|
// is aborted to prevent data loss.
|
|
if (new_stat & CH_STAT_FLUSH) {
|
|
if (this->cur_cmd <= DBDMA_Cmd::INPUT_LAST && this->flush_cb && (new_stat & CH_STAT_ACTIVE) && !(this->ch_stat & CH_STAT_DEAD)) {
|
|
this->flush_cb();
|
|
} else {
|
|
// NOTE: because this implementation doesn't currently support
|
|
// partial memory updates no special action is taken here
|
|
new_stat &= ~CH_STAT_FLUSH;
|
|
}
|
|
this->ch_stat = new_stat;
|
|
}
|
|
|
|
if ((new_stat & CH_STAT_RUN) != (old_stat & CH_STAT_RUN)) {
|
|
if (new_stat & CH_STAT_RUN) {
|
|
new_stat |= CH_STAT_ACTIVE;
|
|
this->ch_stat = new_stat;
|
|
this->start();
|
|
} else {
|
|
this->abort();
|
|
this->update_irq();
|
|
new_stat &= ~CH_STAT_ACTIVE;
|
|
new_stat &= ~CH_STAT_DEAD;
|
|
this->cmd_in_progress = false;
|
|
this->ch_stat = new_stat;
|
|
}
|
|
} else if ((new_stat & CH_STAT_WAKE) != (old_stat & CH_STAT_WAKE)) {
|
|
new_stat |= CH_STAT_ACTIVE;
|
|
this->ch_stat = new_stat;
|
|
this->resume();
|
|
} else if ((new_stat & CH_STAT_PAUSE) != (old_stat & CH_STAT_PAUSE)) {
|
|
if (new_stat & CH_STAT_PAUSE) {
|
|
new_stat &= ~CH_STAT_ACTIVE;
|
|
this->ch_stat = new_stat;
|
|
this->pause();
|
|
}
|
|
}
|
|
break;
|
|
case DMAReg::CH_STAT:
|
|
break; // ingore writes to ChannelStatus
|
|
case DMAReg::CMD_PTR_HI:
|
|
if (value != 0) {
|
|
LOG_F(WARNING, "%s: Unsupported DMA channel register write @%02x.%c = %0*x", this->get_name().c_str(), offset, SIZE_ARG(size), size * 2, value);
|
|
}
|
|
break;
|
|
case DMAReg::CMD_PTR_LO:
|
|
if (!(this->ch_stat & CH_STAT_RUN) && !(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
this->cmd_ptr = value;
|
|
LOG_F(9, "%s: CommandPtrLo set to 0x%X", this->get_name().c_str(),
|
|
this->cmd_ptr);
|
|
}
|
|
break;
|
|
case DMAReg::INT_SELECT:
|
|
this->int_select = value & 0xFF00FFUL;
|
|
break;
|
|
case DMAReg::BRANCH_SELECT:
|
|
this->branch_select = value & 0xFF00FFUL;
|
|
break;
|
|
case DMAReg::WAIT_SELECT:
|
|
this->wait_select = value & 0xFF00FFUL;
|
|
break;
|
|
default:
|
|
LOG_F(WARNING, "%s: Unsupported DMA channel register write at 0x%X",
|
|
this->get_name().c_str(), offset);
|
|
}
|
|
}
|
|
|
|
DmaPullResult DMAChannel::pull_data(uint32_t req_len, uint32_t *avail_len, uint8_t **p_data)
|
|
{
|
|
*avail_len = 0;
|
|
|
|
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
// dead or idle channel? -> no more data
|
|
LOG_F(WARNING, "%s: Dead/idle channel -> no more data", this->get_name().c_str());
|
|
return DmaPullResult::NoMoreData;
|
|
}
|
|
|
|
// interpret DBDMA program until we get data or become idle
|
|
while ((this->ch_stat & CH_STAT_ACTIVE) && !this->queue_len) {
|
|
this->interpret_cmd();
|
|
}
|
|
|
|
// dequeue data if any
|
|
if (this->queue_len) {
|
|
if (this->queue_len >= req_len) {
|
|
LOG_F(9, "%s: Return req_len = %d data", this->get_name().c_str(), req_len);
|
|
*p_data = this->queue_data;
|
|
*avail_len = req_len;
|
|
this->queue_len -= req_len;
|
|
this->res_count += req_len;
|
|
this->queue_data += req_len;
|
|
} else { // return less data than req_len
|
|
LOG_F(9, "%s: Return queue_len = %d data", this->get_name().c_str(),
|
|
this->queue_len);
|
|
*p_data = this->queue_data;
|
|
*avail_len = this->queue_len;
|
|
this->res_count += this->queue_len;
|
|
this->queue_len = 0;
|
|
}
|
|
return DmaPullResult::MoreData; // tell the caller there is more data
|
|
}
|
|
|
|
return DmaPullResult::NoMoreData; // tell the caller there is no more data
|
|
}
|
|
|
|
int DMAChannel::push_data(const char* src_ptr, int len) {
|
|
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
LOG_F(WARNING, "%s: attempt to push data to dead/idle channel",
|
|
this->get_name().c_str());
|
|
return -1;
|
|
}
|
|
|
|
// interpret DBDMA program until we get buffer to fill in or become idle
|
|
while ((this->ch_stat & CH_STAT_ACTIVE) && !this->queue_len) {
|
|
this->interpret_cmd();
|
|
}
|
|
|
|
if (this->queue_len) {
|
|
len = std::min((int)this->queue_len, len);
|
|
std::memcpy(this->queue_data, src_ptr, len);
|
|
this->queue_data += len;
|
|
this->res_count += len;
|
|
this->queue_len -= len;
|
|
}
|
|
|
|
// proceed with the DBDMA program if the buffer became exhausted
|
|
if (!this->queue_len) {
|
|
this->interpret_cmd();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void DMAChannel::end_pull_data() {
|
|
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
// dead or idle channel? -> no more data
|
|
LOG_F(WARNING, "%s: Ending Dead/idle channel -> no more data", this->get_name().c_str());
|
|
return;
|
|
}
|
|
|
|
if (this->queue_len) {
|
|
this->queue_len = 0;
|
|
} else {
|
|
this->ch_stat &= ~CH_STAT_FLUSH;
|
|
}
|
|
|
|
// proceed with the DBDMA program
|
|
this->interpret_cmd();
|
|
}
|
|
|
|
void DMAChannel::end_push_data() {
|
|
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
LOG_F(WARNING, "%s: Attempt to end push data to dead/idle channel",
|
|
this->get_name().c_str());
|
|
return;
|
|
}
|
|
|
|
if (this->queue_len) {
|
|
this->queue_len = 0;
|
|
} else {
|
|
this->ch_stat &= ~CH_STAT_FLUSH;
|
|
}
|
|
|
|
// proceed with the DBDMA program
|
|
this->interpret_cmd();
|
|
}
|
|
|
|
bool DMAChannel::is_out_active() {
|
|
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool DMAChannel::is_in_active() {
|
|
if (this->ch_stat & CH_STAT_DEAD || !(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void DMAChannel::start() {
|
|
if (this->ch_stat & CH_STAT_PAUSE) {
|
|
LOG_F(WARNING, "%s: Cannot start DMA channel, PAUSE bit is set",
|
|
this->get_name().c_str());
|
|
return;
|
|
}
|
|
|
|
this->queue_len = 0;
|
|
|
|
this->cmd_in_progress = false;
|
|
|
|
if (this->start_cb)
|
|
this->start_cb();
|
|
|
|
// some DBDMA programs contain commands that don't transfer data
|
|
// between a device and memory (LOAD_QUAD, STORE_QUAD, NOP and STOP).
|
|
// We thus interprete the DBDMA program until a data transfer between
|
|
// a device and memory is queued or the channel becomes idle/dead.
|
|
while (!this->cmd_in_progress && !(this->ch_stat & CH_STAT_DEAD) &&
|
|
(this->ch_stat & CH_STAT_ACTIVE)) {
|
|
this->interpret_cmd();
|
|
}
|
|
}
|
|
|
|
void DMAChannel::resume() {
|
|
if (this->ch_stat & CH_STAT_PAUSE) {
|
|
LOG_F(WARNING, "%s: Cannot resume DMA channel, PAUSE bit is set",
|
|
this->get_name().c_str());
|
|
return;
|
|
}
|
|
|
|
LOG_F(INFO, "%s: Resuming DMA channel", this->get_name().c_str());
|
|
}
|
|
|
|
void DMAChannel::abort() {
|
|
LOG_F(9, "%s: Aborting DMA channel", this->get_name().c_str());
|
|
if (this->stop_cb)
|
|
this->stop_cb();
|
|
}
|
|
|
|
void DMAChannel::pause() {
|
|
LOG_F(INFO, "%s: Pausing DMA channel", this->get_name().c_str());
|
|
if (this->stop_cb)
|
|
this->stop_cb();
|
|
}
|