diff options
Diffstat (limited to 'src/dev/pci/copy_engine.cc')
-rw-r--r-- | src/dev/pci/copy_engine.cc | 749 |
1 files changed, 749 insertions, 0 deletions
diff --git a/src/dev/pci/copy_engine.cc b/src/dev/pci/copy_engine.cc new file mode 100644 index 000000000..fba96a110 --- /dev/null +++ b/src/dev/pci/copy_engine.cc @@ -0,0 +1,749 @@ +/* + * Copyright (c) 2012 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Copyright (c) 2008 The Regents of The University of Michigan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Ali Saidi + */ + +/* @file + * Device model for Intel's I/O AT DMA copy engine. + */ + +#include "dev/pci/copy_engine.hh" + +#include <algorithm> + +#include "base/cp_annotate.hh" +#include "base/trace.hh" +#include "debug/DMACopyEngine.hh" +#include "debug/Drain.hh" +#include "mem/packet.hh" +#include "mem/packet_access.hh" +#include "params/CopyEngine.hh" +#include "sim/stats.hh" +#include "sim/system.hh" + +using namespace CopyEngineReg; + +CopyEngine::CopyEngine(const Params *p) + : PciDevice(p) +{ + // All Reg regs are initialized to 0 by default + regs.chanCount = p->ChanCnt; + regs.xferCap = findMsbSet(p->XferCap); + regs.attnStatus = 0; + + if (regs.chanCount > 64) + fatal("CopyEngine interface doesn't support more than 64 DMA engines\n"); + + for (int x = 0; x < regs.chanCount; x++) { + CopyEngineChannel *ch = new CopyEngineChannel(this, x); + chan.push_back(ch); + } +} + + +CopyEngine::CopyEngineChannel::CopyEngineChannel(CopyEngine *_ce, int cid) + : cePort(_ce, _ce->sys), + ce(_ce), channelId(cid), busy(false), underReset(false), + refreshNext(false), latBeforeBegin(ce->params()->latBeforeBegin), + latAfterCompletion(ce->params()->latAfterCompletion), + completionDataReg(0), nextState(Idle), + fetchCompleteEvent(this), addrCompleteEvent(this), + readCompleteEvent(this), writeCompleteEvent(this), + statusCompleteEvent(this) + +{ + cr.status.dma_transfer_status(3); + cr.descChainAddr = 0; + cr.completionAddr = 0; + + curDmaDesc = new DmaDesc; + memset(curDmaDesc, 0, sizeof(DmaDesc)); + copyBuffer = new uint8_t[ce->params()->XferCap]; +} + +CopyEngine::~CopyEngine() +{ + for (int x = 0; x < chan.size(); x++) { + delete chan[x]; + } +} + +CopyEngine::CopyEngineChannel::~CopyEngineChannel() +{ + delete curDmaDesc; + delete [] copyBuffer; +} + +BaseMasterPort & +CopyEngine::getMasterPort(const std::string &if_name, PortID idx) +{ + if (if_name != "dma") { + // pass it along to our super class + return PciDevice::getMasterPort(if_name, idx); + } else { + if (idx >= static_cast<int>(chan.size())) { + panic("CopyEngine::getMasterPort: unknown index %d\n", idx); + } + + return chan[idx]->getMasterPort(); + } +} + + +BaseMasterPort & +CopyEngine::CopyEngineChannel::getMasterPort() +{ + return cePort; +} + +void +CopyEngine::CopyEngineChannel::recvCommand() +{ + if (cr.command.start_dma()) { + assert(!busy); + cr.status.dma_transfer_status(0); + nextState = DescriptorFetch; + fetchAddress = cr.descChainAddr; + if (ce->drainState() == DrainState::Running) + fetchDescriptor(cr.descChainAddr); + } else if (cr.command.append_dma()) { + if (!busy) { + nextState = AddressFetch; + if (ce->drainState() == DrainState::Running) + fetchNextAddr(lastDescriptorAddr); + } else + refreshNext = true; + } else if (cr.command.reset_dma()) { + if (busy) + underReset = true; + else { + cr.status.dma_transfer_status(3); + nextState = Idle; + } + } else if (cr.command.resume_dma() || cr.command.abort_dma() || + cr.command.suspend_dma()) + panic("Resume, Abort, and Suspend are not supported\n"); + cr.command(0); +} + +Tick +CopyEngine::read(PacketPtr pkt) +{ + int bar; + Addr daddr; + + if (!getBAR(pkt->getAddr(), bar, daddr)) + panic("Invalid PCI memory access to unmapped memory.\n"); + + // Only Memory register BAR is allowed + assert(bar == 0); + + int size = pkt->getSize(); + if (size != sizeof(uint64_t) && size != sizeof(uint32_t) && + size != sizeof(uint16_t) && size != sizeof(uint8_t)) { + panic("Unknown size for MMIO access: %d\n", pkt->getSize()); + } + + DPRINTF(DMACopyEngine, "Read device register %#X size: %d\n", daddr, size); + + /// + /// Handle read of register here + /// + + if (daddr < 0x80) { + switch (daddr) { + case GEN_CHANCOUNT: + assert(size == sizeof(regs.chanCount)); + pkt->set<uint8_t>(regs.chanCount); + break; + case GEN_XFERCAP: + assert(size == sizeof(regs.xferCap)); + pkt->set<uint8_t>(regs.xferCap); + break; + case GEN_INTRCTRL: + assert(size == sizeof(uint8_t)); + pkt->set<uint8_t>(regs.intrctrl()); + regs.intrctrl.master_int_enable(0); + break; + case GEN_ATTNSTATUS: + assert(size == sizeof(regs.attnStatus)); + pkt->set<uint32_t>(regs.attnStatus); + regs.attnStatus = 0; + break; + default: + panic("Read request to unknown register number: %#x\n", daddr); + } + pkt->makeAtomicResponse(); + return pioDelay; + } + + + // Find which channel we're accessing + int chanid = 0; + daddr -= 0x80; + while (daddr >= 0x80) { + chanid++; + daddr -= 0x80; + } + + if (chanid >= regs.chanCount) + panic("Access to channel %d (device only configured for %d channels)", + chanid, regs.chanCount); + + /// + /// Channel registers are handled here + /// + chan[chanid]->channelRead(pkt, daddr, size); + + pkt->makeAtomicResponse(); + return pioDelay; +} + +void +CopyEngine::CopyEngineChannel::channelRead(Packet *pkt, Addr daddr, int size) +{ + switch (daddr) { + case CHAN_CONTROL: + assert(size == sizeof(uint16_t)); + pkt->set<uint16_t>(cr.ctrl()); + cr.ctrl.in_use(1); + break; + case CHAN_STATUS: + assert(size == sizeof(uint64_t)); + pkt->set<uint64_t>(cr.status() | ~busy); + break; + case CHAN_CHAINADDR: + assert(size == sizeof(uint64_t) || size == sizeof(uint32_t)); + if (size == sizeof(uint64_t)) + pkt->set<uint64_t>(cr.descChainAddr); + else + pkt->set<uint32_t>(bits(cr.descChainAddr,0,31)); + break; + case CHAN_CHAINADDR_HIGH: + assert(size == sizeof(uint32_t)); + pkt->set<uint32_t>(bits(cr.descChainAddr,32,63)); + break; + case CHAN_COMMAND: + assert(size == sizeof(uint8_t)); + pkt->set<uint32_t>(cr.command()); + break; + case CHAN_CMPLNADDR: + assert(size == sizeof(uint64_t) || size == sizeof(uint32_t)); + if (size == sizeof(uint64_t)) + pkt->set<uint64_t>(cr.completionAddr); + else + pkt->set<uint32_t>(bits(cr.completionAddr,0,31)); + break; + case CHAN_CMPLNADDR_HIGH: + assert(size == sizeof(uint32_t)); + pkt->set<uint32_t>(bits(cr.completionAddr,32,63)); + break; + case CHAN_ERROR: + assert(size == sizeof(uint32_t)); + pkt->set<uint32_t>(cr.error()); + break; + default: + panic("Read request to unknown channel register number: (%d)%#x\n", + channelId, daddr); + } +} + + +Tick +CopyEngine::write(PacketPtr pkt) +{ + int bar; + Addr daddr; + + + if (!getBAR(pkt->getAddr(), bar, daddr)) + panic("Invalid PCI memory access to unmapped memory.\n"); + + // Only Memory register BAR is allowed + assert(bar == 0); + + int size = pkt->getSize(); + + /// + /// Handle write of register here + /// + + if (size == sizeof(uint64_t)) { + uint64_t val M5_VAR_USED = pkt->get<uint64_t>(); + DPRINTF(DMACopyEngine, "Wrote device register %#X value %#X\n", daddr, val); + } else if (size == sizeof(uint32_t)) { + uint32_t val M5_VAR_USED = pkt->get<uint32_t>(); + DPRINTF(DMACopyEngine, "Wrote device register %#X value %#X\n", daddr, val); + } else if (size == sizeof(uint16_t)) { + uint16_t val M5_VAR_USED = pkt->get<uint16_t>(); + DPRINTF(DMACopyEngine, "Wrote device register %#X value %#X\n", daddr, val); + } else if (size == sizeof(uint8_t)) { + uint8_t val M5_VAR_USED = pkt->get<uint8_t>(); + DPRINTF(DMACopyEngine, "Wrote device register %#X value %#X\n", daddr, val); + } else { + panic("Unknown size for MMIO access: %d\n", size); + } + + if (daddr < 0x80) { + switch (daddr) { + case GEN_CHANCOUNT: + case GEN_XFERCAP: + case GEN_ATTNSTATUS: + DPRINTF(DMACopyEngine, "Warning, ignorning write to register %x\n", + daddr); + break; + case GEN_INTRCTRL: + regs.intrctrl.master_int_enable(bits(pkt->get<uint8_t>(),0,1)); + break; + default: + panic("Read request to unknown register number: %#x\n", daddr); + } + pkt->makeAtomicResponse(); + return pioDelay; + } + + // Find which channel we're accessing + int chanid = 0; + daddr -= 0x80; + while (daddr >= 0x80) { + chanid++; + daddr -= 0x80; + } + + if (chanid >= regs.chanCount) + panic("Access to channel %d (device only configured for %d channels)", + chanid, regs.chanCount); + + /// + /// Channel registers are handled here + /// + chan[chanid]->channelWrite(pkt, daddr, size); + + pkt->makeAtomicResponse(); + return pioDelay; +} + +void +CopyEngine::CopyEngineChannel::channelWrite(Packet *pkt, Addr daddr, int size) +{ + switch (daddr) { + case CHAN_CONTROL: + assert(size == sizeof(uint16_t)); + int old_int_disable; + old_int_disable = cr.ctrl.interrupt_disable(); + cr.ctrl(pkt->get<uint16_t>()); + if (cr.ctrl.interrupt_disable()) + cr.ctrl.interrupt_disable(0); + else + cr.ctrl.interrupt_disable(old_int_disable); + break; + case CHAN_STATUS: + assert(size == sizeof(uint64_t)); + DPRINTF(DMACopyEngine, "Warning, ignorning write to register %x\n", + daddr); + break; + case CHAN_CHAINADDR: + assert(size == sizeof(uint64_t) || size == sizeof(uint32_t)); + if (size == sizeof(uint64_t)) + cr.descChainAddr = pkt->get<uint64_t>(); + else + cr.descChainAddr = (uint64_t)pkt->get<uint32_t>() | + (cr.descChainAddr & ~mask(32)); + DPRINTF(DMACopyEngine, "Chain Address %x\n", cr.descChainAddr); + break; + case CHAN_CHAINADDR_HIGH: + assert(size == sizeof(uint32_t)); + cr.descChainAddr = ((uint64_t)pkt->get<uint32_t>() <<32) | + (cr.descChainAddr & mask(32)); + DPRINTF(DMACopyEngine, "Chain Address %x\n", cr.descChainAddr); + break; + case CHAN_COMMAND: + assert(size == sizeof(uint8_t)); + cr.command(pkt->get<uint8_t>()); + recvCommand(); + break; + case CHAN_CMPLNADDR: + assert(size == sizeof(uint64_t) || size == sizeof(uint32_t)); + if (size == sizeof(uint64_t)) + cr.completionAddr = pkt->get<uint64_t>(); + else + cr.completionAddr = pkt->get<uint32_t>() | + (cr.completionAddr & ~mask(32)); + break; + case CHAN_CMPLNADDR_HIGH: + assert(size == sizeof(uint32_t)); + cr.completionAddr = ((uint64_t)pkt->get<uint32_t>() <<32) | + (cr.completionAddr & mask(32)); + break; + case CHAN_ERROR: + assert(size == sizeof(uint32_t)); + cr.error(~pkt->get<uint32_t>() & cr.error()); + break; + default: + panic("Read request to unknown channel register number: (%d)%#x\n", + channelId, daddr); + } +} + +void +CopyEngine::regStats() +{ + using namespace Stats; + bytesCopied + .init(regs.chanCount) + .name(name() + ".bytes_copied") + .desc("Number of bytes copied by each engine") + .flags(total) + ; + copiesProcessed + .init(regs.chanCount) + .name(name() + ".copies_processed") + .desc("Number of copies processed by each engine") + .flags(total) + ; +} + +void +CopyEngine::CopyEngineChannel::fetchDescriptor(Addr address) +{ + anDq(); + anBegin("FetchDescriptor"); + DPRINTF(DMACopyEngine, "Reading descriptor from at memory location %#x(%#x)\n", + address, ce->pciToDma(address)); + assert(address); + busy = true; + + DPRINTF(DMACopyEngine, "dmaAction: %#x, %d bytes, to addr %#x\n", + ce->pciToDma(address), sizeof(DmaDesc), curDmaDesc); + + cePort.dmaAction(MemCmd::ReadReq, ce->pciToDma(address), + sizeof(DmaDesc), &fetchCompleteEvent, + (uint8_t*)curDmaDesc, latBeforeBegin); + lastDescriptorAddr = address; +} + +void +CopyEngine::CopyEngineChannel::fetchDescComplete() +{ + DPRINTF(DMACopyEngine, "Read of descriptor complete\n"); + + if ((curDmaDesc->command & DESC_CTRL_NULL)) { + DPRINTF(DMACopyEngine, "Got NULL descriptor, skipping\n"); + assert(!(curDmaDesc->command & DESC_CTRL_CP_STS)); + if (curDmaDesc->command & DESC_CTRL_CP_STS) { + panic("Shouldn't be able to get here\n"); + nextState = CompletionWrite; + if (inDrain()) return; + writeCompletionStatus(); + } else { + anBegin("Idle"); + anWait(); + busy = false; + nextState = Idle; + inDrain(); + } + return; + } + + if (curDmaDesc->command & ~DESC_CTRL_CP_STS) + panic("Descriptor has flag other that completion status set\n"); + + nextState = DMARead; + if (inDrain()) return; + readCopyBytes(); +} + +void +CopyEngine::CopyEngineChannel::readCopyBytes() +{ + anBegin("ReadCopyBytes"); + DPRINTF(DMACopyEngine, "Reading %d bytes from buffer to memory location %#x(%#x)\n", + curDmaDesc->len, curDmaDesc->dest, + ce->pciToDma(curDmaDesc->src)); + cePort.dmaAction(MemCmd::ReadReq, ce->pciToDma(curDmaDesc->src), + curDmaDesc->len, &readCompleteEvent, copyBuffer, 0); +} + +void +CopyEngine::CopyEngineChannel::readCopyBytesComplete() +{ + DPRINTF(DMACopyEngine, "Read of bytes to copy complete\n"); + + nextState = DMAWrite; + if (inDrain()) return; + writeCopyBytes(); +} + +void +CopyEngine::CopyEngineChannel::writeCopyBytes() +{ + anBegin("WriteCopyBytes"); + DPRINTF(DMACopyEngine, "Writing %d bytes from buffer to memory location %#x(%#x)\n", + curDmaDesc->len, curDmaDesc->dest, + ce->pciToDma(curDmaDesc->dest)); + + cePort.dmaAction(MemCmd::WriteReq, ce->pciToDma(curDmaDesc->dest), + curDmaDesc->len, &writeCompleteEvent, copyBuffer, 0); + + ce->bytesCopied[channelId] += curDmaDesc->len; + ce->copiesProcessed[channelId]++; +} + +void +CopyEngine::CopyEngineChannel::writeCopyBytesComplete() +{ + DPRINTF(DMACopyEngine, "Write of bytes to copy complete user1: %#x\n", + curDmaDesc->user1); + + cr.status.compl_desc_addr(lastDescriptorAddr >> 6); + completionDataReg = cr.status() | 1; + + anQ("DMAUsedDescQ", channelId, 1); + anQ("AppRecvQ", curDmaDesc->user1, curDmaDesc->len); + if (curDmaDesc->command & DESC_CTRL_CP_STS) { + nextState = CompletionWrite; + if (inDrain()) return; + writeCompletionStatus(); + return; + } + + continueProcessing(); +} + +void +CopyEngine::CopyEngineChannel::continueProcessing() +{ + busy = false; + + if (underReset) { + anBegin("Reset"); + anWait(); + underReset = false; + refreshNext = false; + busy = false; + nextState = Idle; + return; + } + + if (curDmaDesc->next) { + nextState = DescriptorFetch; + fetchAddress = curDmaDesc->next; + if (inDrain()) return; + fetchDescriptor(curDmaDesc->next); + } else if (refreshNext) { + nextState = AddressFetch; + refreshNext = false; + if (inDrain()) return; + fetchNextAddr(lastDescriptorAddr); + } else { + inDrain(); + nextState = Idle; + anWait(); + anBegin("Idle"); + } +} + +void +CopyEngine::CopyEngineChannel::writeCompletionStatus() +{ + anBegin("WriteCompletionStatus"); + DPRINTF(DMACopyEngine, "Writing completion status %#x to address %#x(%#x)\n", + completionDataReg, cr.completionAddr, + ce->pciToDma(cr.completionAddr)); + + cePort.dmaAction(MemCmd::WriteReq, + ce->pciToDma(cr.completionAddr), + sizeof(completionDataReg), &statusCompleteEvent, + (uint8_t*)&completionDataReg, latAfterCompletion); +} + +void +CopyEngine::CopyEngineChannel::writeStatusComplete() +{ + DPRINTF(DMACopyEngine, "Writing completion status complete\n"); + continueProcessing(); +} + +void +CopyEngine::CopyEngineChannel::fetchNextAddr(Addr address) +{ + anBegin("FetchNextAddr"); + DPRINTF(DMACopyEngine, "Fetching next address...\n"); + busy = true; + cePort.dmaAction(MemCmd::ReadReq, + ce->pciToDma(address + offsetof(DmaDesc, next)), + sizeof(Addr), &addrCompleteEvent, + (uint8_t*)curDmaDesc + offsetof(DmaDesc, next), 0); +} + +void +CopyEngine::CopyEngineChannel::fetchAddrComplete() +{ + DPRINTF(DMACopyEngine, "Fetching next address complete: %#x\n", + curDmaDesc->next); + if (!curDmaDesc->next) { + DPRINTF(DMACopyEngine, "Got NULL descriptor, nothing more to do\n"); + busy = false; + nextState = Idle; + anWait(); + anBegin("Idle"); + inDrain(); + return; + } + nextState = DescriptorFetch; + fetchAddress = curDmaDesc->next; + if (inDrain()) return; + fetchDescriptor(curDmaDesc->next); +} + +bool +CopyEngine::CopyEngineChannel::inDrain() +{ + if (drainState() == DrainState::Draining) { + DPRINTF(Drain, "CopyEngine done draining, processing drain event\n"); + signalDrainDone(); + } + + return ce->drainState() != DrainState::Running; +} + +DrainState +CopyEngine::CopyEngineChannel::drain() +{ + if (nextState == Idle || ce->drainState() != DrainState::Running) { + return DrainState::Drained; + } else { + DPRINTF(Drain, "CopyEngineChannel not drained\n"); + return DrainState::Draining; + } +} + +void +CopyEngine::serialize(CheckpointOut &cp) const +{ + PciDevice::serialize(cp); + regs.serialize(cp); + for (int x =0; x < chan.size(); x++) + chan[x]->serializeSection(cp, csprintf("channel%d", x)); +} + +void +CopyEngine::unserialize(CheckpointIn &cp) +{ + PciDevice::unserialize(cp); + regs.unserialize(cp); + for (int x = 0; x < chan.size(); x++) + chan[x]->unserializeSection(cp, csprintf("channel%d", x)); +} + +void +CopyEngine::CopyEngineChannel::serialize(CheckpointOut &cp) const +{ + SERIALIZE_SCALAR(channelId); + SERIALIZE_SCALAR(busy); + SERIALIZE_SCALAR(underReset); + SERIALIZE_SCALAR(refreshNext); + SERIALIZE_SCALAR(lastDescriptorAddr); + SERIALIZE_SCALAR(completionDataReg); + SERIALIZE_SCALAR(fetchAddress); + int nextState = this->nextState; + SERIALIZE_SCALAR(nextState); + arrayParamOut(cp, "curDmaDesc", (uint8_t*)curDmaDesc, sizeof(DmaDesc)); + SERIALIZE_ARRAY(copyBuffer, ce->params()->XferCap); + cr.serialize(cp); + +} +void +CopyEngine::CopyEngineChannel::unserialize(CheckpointIn &cp) +{ + UNSERIALIZE_SCALAR(channelId); + UNSERIALIZE_SCALAR(busy); + UNSERIALIZE_SCALAR(underReset); + UNSERIALIZE_SCALAR(refreshNext); + UNSERIALIZE_SCALAR(lastDescriptorAddr); + UNSERIALIZE_SCALAR(completionDataReg); + UNSERIALIZE_SCALAR(fetchAddress); + int nextState; + UNSERIALIZE_SCALAR(nextState); + this->nextState = (ChannelState)nextState; + arrayParamIn(cp, "curDmaDesc", (uint8_t*)curDmaDesc, sizeof(DmaDesc)); + UNSERIALIZE_ARRAY(copyBuffer, ce->params()->XferCap); + cr.unserialize(cp); + +} + +void +CopyEngine::CopyEngineChannel::restartStateMachine() +{ + switch(nextState) { + case AddressFetch: + fetchNextAddr(lastDescriptorAddr); + break; + case DescriptorFetch: + fetchDescriptor(fetchAddress); + break; + case DMARead: + readCopyBytes(); + break; + case DMAWrite: + writeCopyBytes(); + break; + case CompletionWrite: + writeCompletionStatus(); + break; + case Idle: + break; + default: + panic("Unknown state for CopyEngineChannel\n"); + } +} + +void +CopyEngine::CopyEngineChannel::drainResume() +{ + DPRINTF(DMACopyEngine, "Restarting state machine at state %d\n", nextState); + restartStateMachine(); +} + +CopyEngine * +CopyEngineParams::create() +{ + return new CopyEngine(this); +} |