/* * Copyright (c) 2010-2013, 2015, 2017 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. * * 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: Chris Emmons * Andreas Sandberg */ #include "dev/arm/hdlcd.hh" #include "base/output.hh" #include "base/trace.hh" #include "base/vnc/vncinput.hh" #include "debug/Checkpoint.hh" #include "debug/HDLcd.hh" #include "dev/arm/amba_device.hh" #include "dev/arm/base_gic.hh" #include "enums/ImageFormat.hh" #include "mem/packet.hh" #include "mem/packet_access.hh" #include "params/HDLcd.hh" #include "sim/system.hh" using std::vector; // initialize hdlcd registers HDLcd::HDLcd(const HDLcdParams *p) : AmbaDmaDevice(p, 0xFFFF), // Parameters vnc(p->vnc), workaroundSwapRB(p->workaround_swap_rb), workaroundDmaLineCount(p->workaround_dma_line_count), addrRanges{RangeSize(pioAddr, pioSize)}, enableCapture(p->enable_capture), pixelBufferSize(p->pixel_buffer_size), virtRefreshRate(p->virt_refresh_rate), // Registers version(VERSION_RESETV), int_rawstat(0), int_mask(0), fb_base(0), fb_line_length(0), fb_line_count(0), fb_line_pitch(0), bus_options(BUS_OPTIONS_RESETV), v_sync(0), v_back_porch(0), v_data(0), v_front_porch(0), h_sync(0), h_back_porch(0), h_data(0), h_front_porch(0), polarities(0), command(0), pixel_format(0), red_select(0), green_select(0), blue_select(0), virtRefreshEvent([this]{ virtRefresh(); }, name()), // Other imgFormat(p->frame_format), pic(NULL), conv(PixelConverter::rgba8888_le), pixelPump(*this, *p->pxl_clk, p->pixel_chunk) { if (vnc) vnc->setFrameBuffer(&pixelPump.fb); imgWriter = createImgWriter(imgFormat, &pixelPump.fb); } HDLcd::~HDLcd() { } void HDLcd::regStats() { AmbaDmaDevice::regStats(); using namespace Stats; stats.underruns .name(name() + ".underruns") .desc("number of buffer underruns") .flags(nozero) ; } void HDLcd::serialize(CheckpointOut &cp) const { DPRINTF(Checkpoint, "Serializing ARM HDLCD\n"); SERIALIZE_SCALAR(int_rawstat); SERIALIZE_SCALAR(int_mask); SERIALIZE_SCALAR(fb_base); SERIALIZE_SCALAR(fb_line_length); SERIALIZE_SCALAR(fb_line_count); SERIALIZE_SCALAR(fb_line_pitch); SERIALIZE_SCALAR(bus_options); SERIALIZE_SCALAR(v_sync); SERIALIZE_SCALAR(v_back_porch); SERIALIZE_SCALAR(v_data); SERIALIZE_SCALAR(v_front_porch); SERIALIZE_SCALAR(h_sync); SERIALIZE_SCALAR(h_back_porch); SERIALIZE_SCALAR(h_data); SERIALIZE_SCALAR(h_front_porch); SERIALIZE_SCALAR(polarities); SERIALIZE_SCALAR(command); SERIALIZE_SCALAR(pixel_format); SERIALIZE_SCALAR(red_select); SERIALIZE_SCALAR(green_select); SERIALIZE_SCALAR(blue_select); SERIALIZE_OBJ(pixelPump); if (enabled()) dmaEngine->serializeSection(cp, "dmaEngine"); } void HDLcd::unserialize(CheckpointIn &cp) { DPRINTF(Checkpoint, "Unserializing ARM HDLCD\n"); UNSERIALIZE_SCALAR(int_rawstat); UNSERIALIZE_SCALAR(int_mask); UNSERIALIZE_SCALAR(fb_base); UNSERIALIZE_SCALAR(fb_line_length); UNSERIALIZE_SCALAR(fb_line_count); UNSERIALIZE_SCALAR(fb_line_pitch); UNSERIALIZE_SCALAR(bus_options); UNSERIALIZE_SCALAR(v_sync); UNSERIALIZE_SCALAR(v_back_porch); UNSERIALIZE_SCALAR(v_data); UNSERIALIZE_SCALAR(v_front_porch); UNSERIALIZE_SCALAR(h_sync); UNSERIALIZE_SCALAR(h_back_porch); UNSERIALIZE_SCALAR(h_data); UNSERIALIZE_SCALAR(h_front_porch); UNSERIALIZE_SCALAR(polarities); UNSERIALIZE_SCALAR(command); UNSERIALIZE_SCALAR(pixel_format); UNSERIALIZE_SCALAR(red_select); UNSERIALIZE_SCALAR(green_select); UNSERIALIZE_SCALAR(blue_select); { // Try to unserialize the pixel pump. It might not exist if // we're unserializing an old checkpoint. ScopedCheckpointSection sec(cp, "pixelPump"); if (cp.sectionExists(Serializable::currentSection())) pixelPump.unserialize(cp); } if (enabled()) { // Create the DMA engine and read its state from the // checkpoint. We don't need to worry about the pixel pump as // it is a proper SimObject. createDmaEngine(); dmaEngine->unserializeSection(cp, "dmaEngine"); conv = pixelConverter(); } } void HDLcd::drainResume() { AmbaDmaDevice::drainResume(); if (enabled()) { if (sys->bypassCaches()) { // We restart the HDLCD if we are in KVM mode. This // ensures that we always use the fast refresh logic if we // resume in KVM mode. cmdDisable(); cmdEnable(); } else if (!pixelPump.active()) { // We restored from an old checkpoint without a pixel // pump, start an new refresh. This typically happens when // restoring from old checkpoints. cmdEnable(); } } // We restored from a checkpoint and need to update the VNC server if (pixelPump.active() && vnc) vnc->setDirty(); } void HDLcd::virtRefresh() { pixelPump.renderFrame(); schedule(virtRefreshEvent, (curTick() + virtRefreshRate)); } // read registers and frame buffer Tick HDLcd::read(PacketPtr pkt) { assert(pkt->getAddr() >= pioAddr && pkt->getAddr() < pioAddr + pioSize); const Addr daddr(pkt->getAddr() - pioAddr); panic_if(pkt->getSize() != 4, "Unhandled read size (address: 0x.4x, size: %u)", daddr, pkt->getSize()); const uint32_t data(readReg(daddr)); DPRINTF(HDLcd, "read register 0x%04x: 0x%x\n", daddr, data); pkt->set(data); pkt->makeAtomicResponse(); return pioDelay; } // write registers and frame buffer Tick HDLcd::write(PacketPtr pkt) { assert(pkt->getAddr() >= pioAddr && pkt->getAddr() < pioAddr + pioSize); const Addr daddr(pkt->getAddr() - pioAddr); panic_if(pkt->getSize() != 4, "Unhandled read size (address: 0x.4x, size: %u)", daddr, pkt->getSize()); const uint32_t data(pkt->get()); DPRINTF(HDLcd, "write register 0x%04x: 0x%x\n", daddr, data); writeReg(daddr, data); pkt->makeAtomicResponse(); return pioDelay; } uint32_t HDLcd::readReg(Addr offset) { switch (offset) { case Version: return version; case Int_RawStat: return int_rawstat; case Int_Clear: panic("HDLCD INT_CLEAR register is Write-Only\n"); case Int_Mask: return int_mask; case Int_Status: return intStatus(); case Fb_Base: return fb_base; case Fb_Line_Length: return fb_line_length; case Fb_Line_Count: return fb_line_count; case Fb_Line_Pitch: return fb_line_pitch; case Bus_Options: return bus_options; case V_Sync: return v_sync; case V_Back_Porch: return v_back_porch; case V_Data: return v_data; case V_Front_Porch: return v_front_porch; case H_Sync: return h_sync; case H_Back_Porch: return h_back_porch; case H_Data: return h_data; case H_Front_Porch: return h_front_porch; case Polarities: return polarities; case Command: return command; case Pixel_Format: return pixel_format; case Red_Select: return red_select; case Green_Select: return green_select; case Blue_Select: return blue_select; default: panic("Tried to read HDLCD register that doesn't exist\n", offset); } } void HDLcd::writeReg(Addr offset, uint32_t value) { switch (offset) { case Version: panic("HDLCD VERSION register is read-Only\n"); case Int_RawStat: intRaise(value); return; case Int_Clear: intClear(value); return; case Int_Mask: intMask(value); return; case Int_Status: panic("HDLCD INT_STATUS register is read-Only\n"); break; case Fb_Base: fb_base = value; return; case Fb_Line_Length: fb_line_length = value; return; case Fb_Line_Count: fb_line_count = value; return; case Fb_Line_Pitch: fb_line_pitch = value; return; case Bus_Options: { const BusOptsReg old_bus_options(bus_options); bus_options = value; if (bus_options.max_outstanding != old_bus_options.max_outstanding) { DPRINTF(HDLcd, "Changing HDLcd outstanding DMA transactions: %d -> %d\n", old_bus_options.max_outstanding, bus_options.max_outstanding); } if (bus_options.burst_len != old_bus_options.burst_len) { DPRINTF(HDLcd, "Changing HDLcd DMA burst flags: 0x%x -> 0x%x\n", old_bus_options.burst_len, bus_options.burst_len); } } return; case V_Sync: v_sync = value; return; case V_Back_Porch: v_back_porch = value; return; case V_Data: v_data = value; return; case V_Front_Porch: v_front_porch = value; return; case H_Sync: h_sync = value; return; case H_Back_Porch: h_back_porch = value; return; case H_Data: h_data = value; return; case H_Front_Porch: h_front_porch = value; return; case Polarities: polarities = value; return; case Command: { const CommandReg new_command(value); if (new_command.enable != command.enable) { DPRINTF(HDLcd, "HDLCD switched %s\n", new_command.enable ? "on" : "off"); if (new_command.enable) { cmdEnable(); } else { cmdDisable(); } } command = new_command; } return; case Pixel_Format: pixel_format = value; return; case Red_Select: red_select = value; return; case Green_Select: green_select = value; return; case Blue_Select: blue_select = value; return; default: panic("Tried to write HDLCD register that doesn't exist\n", offset); return; } } PixelConverter HDLcd::pixelConverter() const { ByteOrder byte_order( pixel_format.big_endian ? BigEndianByteOrder : LittleEndianByteOrder); /* Some Linux kernels have a broken driver that swaps the red and * blue color select registers. */ if (!workaroundSwapRB) { return PixelConverter( pixel_format.bytes_per_pixel + 1, red_select.offset, green_select.offset, blue_select.offset, red_select.size, green_select.size, blue_select.size, byte_order); } else { return PixelConverter( pixel_format.bytes_per_pixel + 1, blue_select.offset, green_select.offset, red_select.offset, blue_select.size, green_select.size, red_select.size, byte_order); } } DisplayTimings HDLcd::displayTimings() const { return DisplayTimings( h_data.val + 1, v_data.val + 1, h_back_porch.val + 1, h_sync.val + 1, h_front_porch.val + 1, v_back_porch.val + 1, v_sync.val + 1, v_front_porch.val + 1); } void HDLcd::createDmaEngine() { if (bus_options.max_outstanding == 0) { warn("Maximum number of outstanding DMA transfers set to 0."); return; } const uint32_t dma_burst_flags(bus_options.burst_len); const uint32_t dma_burst_len( dma_burst_flags ? (1UL << (findMsbSet(dma_burst_flags) - 1)) : MAX_BURST_LEN); // Some drivers seem to set the DMA line count incorrectly. This // could either be a driver bug or a specification bug. Unlike for // timings, the specification does not require 1 to be added to // the DMA engine's line count. const uint32_t dma_lines( fb_line_count + (workaroundDmaLineCount ? 1 : 0)); dmaEngine.reset(new DmaEngine( *this, pixelBufferSize, AXI_PORT_WIDTH * dma_burst_len, bus_options.max_outstanding, fb_line_length, fb_line_pitch, dma_lines)); } void HDLcd::cmdEnable() { createDmaEngine(); conv = pixelConverter(); // Update timing parameter before rendering frames pixelPump.updateTimings(displayTimings()); if (sys->bypassCaches()) { schedule(virtRefreshEvent, clockEdge()); } else { pixelPump.start(); } } void HDLcd::cmdDisable() { pixelPump.stop(); // Disable the virtual refresh event if (virtRefreshEvent.scheduled()) { assert(sys->bypassCaches()); deschedule(virtRefreshEvent); } dmaEngine->abortFrame(); } bool HDLcd::pxlNext(Pixel &p) { uint8_t pixel_data[MAX_PIXEL_SIZE]; assert(conv.length <= sizeof(pixel_data)); if (dmaEngine->tryGet(pixel_data, conv.length)) { p = conv.toPixel(pixel_data); return true; } else { return false; } } void HDLcd::pxlVSyncBegin() { DPRINTF(HDLcd, "Raising VSYNC interrupt.\n"); intRaise(INT_VSYNC); } void HDLcd::pxlVSyncEnd() { DPRINTF(HDLcd, "End of VSYNC, starting DMA engine\n"); dmaEngine->startFrame(fb_base); } void HDLcd::pxlUnderrun() { DPRINTF(HDLcd, "Buffer underrun, stopping DMA fill.\n"); ++stats.underruns; intRaise(INT_UNDERRUN); dmaEngine->abortFrame(); } void HDLcd::pxlFrameDone() { DPRINTF(HDLcd, "Reached end of last visible line.\n"); if (dmaEngine->size()) { warn("HDLCD %u bytes still in FIFO after frame: Ensure that DMA " "and PixelPump configuration is consistent\n", dmaEngine->size()); dmaEngine->dumpSettings(); pixelPump.dumpSettings(); } if (vnc) vnc->setDirty(); if (enableCapture) { if (!pic) { pic = simout.create( csprintf("%s.framebuffer.%s", sys->name(), imgWriter->getImgExtension()), true); } assert(pic); pic->stream()->seekp(0); imgWriter->write(*pic->stream()); } } void HDLcd::setInterrupts(uint32_t ints, uint32_t mask) { const bool old_ints(intStatus()); int_mask = mask; int_rawstat = ints; if (!old_ints && intStatus()) { gic->sendInt(intNum); } else if (old_ints && !intStatus()) { gic->clearInt(intNum); } } HDLcd::DmaEngine::DmaEngine(HDLcd &_parent, size_t size, unsigned request_size, unsigned max_pending, size_t line_size, ssize_t line_pitch, unsigned num_lines) : DmaReadFifo( _parent.dmaPort, size, request_size, max_pending, Request::UNCACHEABLE), parent(_parent), lineSize(line_size), linePitch(line_pitch), numLines(num_lines), nextLineAddr(0) { } void HDLcd::DmaEngine::serialize(CheckpointOut &cp) const { DmaReadFifo::serialize(cp); SERIALIZE_SCALAR(nextLineAddr); SERIALIZE_SCALAR(frameEnd); } void HDLcd::DmaEngine::unserialize(CheckpointIn &cp) { DmaReadFifo::unserialize(cp); UNSERIALIZE_SCALAR(nextLineAddr); UNSERIALIZE_SCALAR(frameEnd); } void HDLcd::DmaEngine::startFrame(Addr fb_base) { nextLineAddr = fb_base; frameEnd = fb_base + numLines * linePitch; startFill(nextLineAddr, lineSize); } void HDLcd::DmaEngine::abortFrame() { nextLineAddr = frameEnd; stopFill(); flush(); } void HDLcd::DmaEngine::dumpSettings() { inform("DMA line size: %u bytes", lineSize); inform("DMA line pitch: %i bytes", linePitch); inform("DMA num lines: %u", numLines); } void HDLcd::DmaEngine::onEndOfBlock() { if (nextLineAddr == frameEnd) // We're done with this frame. Ignore calls to this method // until the next frame has been started. return; nextLineAddr += linePitch; if (nextLineAddr != frameEnd) startFill(nextLineAddr, lineSize); } void HDLcd::DmaEngine::onIdle() { parent.intRaise(INT_DMA_END); } void HDLcd::PixelPump::dumpSettings() { const DisplayTimings &t(timings()); inform("PixelPump width: %u", t.width); inform("PixelPump height: %u", t.height); inform("PixelPump horizontal back porch: %u", t.hBackPorch); inform("PixelPump horizontal fron porch: %u", t.hFrontPorch); inform("PixelPump horizontal fron porch: %u", t.hSync); inform("PixelPump vertical back porch: %u", t.vBackPorch); inform("PixelPump vertical fron porch: %u", t.vFrontPorch); inform("PixelPump vertical fron porch: %u", t.vSync); } HDLcd * HDLcdParams::create() { return new HDLcd(this); }