/********************************************************** * Copyright 2008-2009 VMware, Inc. All rights reserved. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * **********************************************************/ /* * svga.c -- * * This is a simple example driver for the VMware SVGA device. * It handles initialization, register accesses, low-level * command FIFO writes, and host/guest synchronization. */ #include "svga.h" #include "pci.h" #include "console_vga.h" #include "io.h" #include "intr.h" #include "svga_reg.h" SVGADevice gSVGA; static void SVGAFIFOFull(void); static void SVGAInterruptHandler(int vector); /* *----------------------------------------------------------------------------- * * SVGA_Init -- * * Initialize the global SVGA device. This locates it on the PCI bus, * negotiates device version, and maps the command FIFO and framebuffer * memory. * * Intr_Init() must have already been called. If the SVGA device * supports interrupts, this will initalize them. * * Does not switch video modes. * * Results: * void. * * Side effects: * Steals various IOspace and memory regions. * In this example code they're constant addresses, but in reality * you'll need to negotiate these with the operating system. * *----------------------------------------------------------------------------- */ void SVGA_Init(void) { if (!PCI_FindDevice(PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_SVGA2, &gSVGA.pciAddr)) { Console_Panic("No VMware SVGA device found."); } /* * Use the default base address for each memory region. * We must map at least ioBase before using ReadReg/WriteReg. */ PCI_SetMemEnable(&gSVGA.pciAddr, TRUE); gSVGA.ioBase = PCI_GetBARAddr(&gSVGA.pciAddr, 0); gSVGA.fbMem = (void*) PCI_GetBARAddr(&gSVGA.pciAddr, 1); gSVGA.fifoMem = (void*) PCI_GetBARAddr(&gSVGA.pciAddr, 2); /* * Version negotiation: * * 1. Write to SVGA_REG_ID the maximum ID supported by this driver. * 2. Read from SVGA_REG_ID * a. If we read back the same value, this ID is supported. We're done. * b. If not, decrement the ID and repeat. */ gSVGA.deviceVersionId = SVGA_ID_2; do { SVGA_WriteReg(SVGA_REG_ID, gSVGA.deviceVersionId); if (SVGA_ReadReg(SVGA_REG_ID) == gSVGA.deviceVersionId) { break; } else { gSVGA.deviceVersionId--; } } while (gSVGA.deviceVersionId >= SVGA_ID_0); if (gSVGA.deviceVersionId < SVGA_ID_0) { Console_Panic("Error negotiating SVGA device version."); } /* * We must determine the FIFO and FB size after version * negotiation, since the default version (SVGA_ID_0) * does not support the FIFO buffer at all. */ gSVGA.fbSize = SVGA_ReadReg(SVGA_REG_FB_SIZE); gSVGA.fifoSize = SVGA_ReadReg(SVGA_REG_MEM_SIZE); /* * Sanity-check the FIFO and framebuffer sizes. * These are arbitrary values. */ if (gSVGA.fbSize < 0x100000) { SVGA_Panic("FB size very small, probably incorrect."); } if (gSVGA.fifoSize < 0x20000) { SVGA_Panic("FIFO size very small, probably incorrect."); } /* * If the device is new enough to support capability flags, get the * capabilities register. */ if (gSVGA.deviceVersionId >= SVGA_ID_1) { gSVGA.capabilities = SVGA_ReadReg(SVGA_REG_CAPABILITIES); } /* * Optional interrupt initialization. * * This uses the default IRQ that was assigned to our * device by the BIOS. */ if (gSVGA.capabilities & SVGA_CAP_IRQMASK) { uint8 irq = PCI_ConfigRead8(&gSVGA.pciAddr, offsetof(PCIConfigSpace, intrLine)); /* Start out with all SVGA IRQs masked */ SVGA_WriteReg(SVGA_REG_IRQMASK, 0); /* Clear all pending IRQs stored by the device */ IO_Out32(gSVGA.ioBase + SVGA_IRQSTATUS_PORT, 0xFF); /* Clear all pending IRQs stored by us */ SVGA_ClearIRQ(); /* Enable the IRQ */ Intr_SetHandler(IRQ_VECTOR(irq), SVGAInterruptHandler); Intr_SetMask(irq, TRUE); } } /* *----------------------------------------------------------------------------- * * SVGA_SetMode -- * * This switches to SVGA video mode, and enables the command FIFO. * * Results: * void. * * Side effects: * Switches modes. Disables legacy video modes (VGA, VBE). * Clears the command FIFO, and asks the host to start processing it. * *----------------------------------------------------------------------------- */ void SVGA_SetMode(uint32 width, // IN uint32 height, // IN uint32 bpp) // IN { gSVGA.width = width; gSVGA.height = height; gSVGA.bpp = bpp; SVGA_WriteReg(SVGA_REG_WIDTH, width); SVGA_WriteReg(SVGA_REG_HEIGHT, height); SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, bpp); SVGA_WriteReg(SVGA_REG_ENABLE, TRUE); gSVGA.pitch = SVGA_ReadReg(SVGA_REG_BYTES_PER_LINE); /* * Initialize the command FIFO. The beginning of FIFO memory is * used for an additional set of registers, the "FIFO registers". * These are higher-performance memory mapped registers which * happen to live in the same space as the FIFO. The driver is * responsible for allocating space for these registers, according * to the maximum number of registers supported by this driver * release. */ gSVGA.fifoMem[SVGA_FIFO_MIN] = SVGA_FIFO_NUM_REGS * sizeof(uint32); gSVGA.fifoMem[SVGA_FIFO_MAX] = gSVGA.fifoSize; gSVGA.fifoMem[SVGA_FIFO_NEXT_CMD] = gSVGA.fifoMem[SVGA_FIFO_MIN]; gSVGA.fifoMem[SVGA_FIFO_STOP] = gSVGA.fifoMem[SVGA_FIFO_MIN]; /* * Prep work for 3D version negotiation. See SVGA3D_Init for * details, but we have to give the host our 3D protocol version * before enabling the FIFO. */ if (SVGA_HasFIFOCap(SVGA_CAP_EXTENDED_FIFO) && SVGA_IsFIFORegValid(SVGA_FIFO_GUEST_3D_HWVERSION)) { gSVGA.fifoMem[SVGA_FIFO_GUEST_3D_HWVERSION] = SVGA3D_HWVERSION_CURRENT; } /* * Enable the FIFO. */ SVGA_WriteReg(SVGA_REG_CONFIG_DONE, TRUE); /* * Now that the FIFO is initialized, we can do an IRQ sanity check. * This makes sure that the VM's chipset and our own IRQ code * works. Better to find out now if something's wrong, than to * deadlock later. * * This inserts a FIFO fence, does a legacy sync to drain the FIFO, * then ensures that we received all applicable interrupts. */ if (gSVGA.capabilities & SVGA_CAP_IRQMASK) { SVGA_WriteReg(SVGA_REG_IRQMASK, SVGA_IRQFLAG_ANY_FENCE); SVGA_ClearIRQ(); SVGA_InsertFence(); SVGA_WriteReg(SVGA_REG_SYNC, 1); while (SVGA_ReadReg(SVGA_REG_BUSY) != FALSE); SVGA_WriteReg(SVGA_REG_IRQMASK, 0); /* Check whether the interrupt occurred without blocking. */ if ((gSVGA.irq.pending & SVGA_IRQFLAG_ANY_FENCE) == 0) { SVGA_Panic("SVGA IRQ appears to be present but broken."); } SVGA_WaitForIRQ(); } } /* *----------------------------------------------------------------------------- * * SVGA_Disable -- * * Disable the SVGA portion of the video adapter, switching back * to VGA mode. * * Results: * void. * * Side effects: * Switches modes. Disables the command FIFO, without flushing it. * (Any commands still in the FIFO will be lost.) * *----------------------------------------------------------------------------- */ void SVGA_Disable(void) { SVGA_WriteReg(SVGA_REG_ENABLE, FALSE); } /* *----------------------------------------------------------------------------- * * SVGA_Panic -- * * This is a Panic handler which works in SVGA mode. It disables SVGA, * then executes the Console's panic handler. * * Results: * Never returns. * * Side effects: * Disables SVGA mode, initializes VGA text mode. * *----------------------------------------------------------------------------- */ void SVGA_Panic(const char *msg) // IN { SVGA_Disable(); ConsoleVGA_Init(); Console_Panic(msg); } /* *----------------------------------------------------------------------------- * * SVGA_DefaultFaultHandler -- * * This is a default fault handler which works in SVGA mode. It * disables SVGA, then executes the Console's default fault * handler. * * Results: * Never returns. * * Side effects: * Disables SVGA mode, initializes VGA text mode. * *----------------------------------------------------------------------------- */ void SVGA_DefaultFaultHandler(int vector) // IN { SVGA_Disable(); Console_UnhandledFault(vector); } /* *----------------------------------------------------------------------------- * * SVGA_ReadReg -- * * Read an SVGA device register (SVGA_REG_*). * This must be called after the PCI device's IOspace has been mapped. * * Results: * 32-bit register value. * * Side effects: * Depends on the register value. Some special registers * like SVGA_REG_BUSY have significant side-effects. * *----------------------------------------------------------------------------- */ uint32 SVGA_ReadReg(uint32 index) // IN { IO_Out32(gSVGA.ioBase + SVGA_INDEX_PORT, index); return IO_In32(gSVGA.ioBase + SVGA_VALUE_PORT); } /* *----------------------------------------------------------------------------- * * SVGA_WriteReg -- * * Write an SVGA device register (SVGA_REG_*). * This must be called after the PCI device's IOspace has been mapped. * * Results: * void. * * Side effects: * Depends on the register value. * *----------------------------------------------------------------------------- */ void SVGA_WriteReg(uint32 index, // IN uint32 value) // IN { IO_Out32(gSVGA.ioBase + SVGA_INDEX_PORT, index); IO_Out32(gSVGA.ioBase + SVGA_VALUE_PORT, value); } /* *----------------------------------------------------------------------------- * * SVGA_IsFIFORegValid -- * * Check whether space has been allocated for a particular FIFO register. * * This isn't particularly useful for our simple examples, since the * same binary is responsible for both allocating and using the FIFO, * but a real driver may need to check this if the module which handles * initialization and mode switches is separate from the module which * actually writes to the FIFO. * * Results: * TRUE if the register has been allocated, FALSE if the register * does not exist. * * Side effects: * None. * *----------------------------------------------------------------------------- */ Bool SVGA_IsFIFORegValid(int reg) { return gSVGA.fifoMem[SVGA_FIFO_MIN] > (reg << 2); } /* *----------------------------------------------------------------------------- * * SVGA_HasFIFOCap -- * * Check whether the SVGA device has a particular FIFO capability bit. * * Results: * TRUE if the capability is present, FALSE if it's absent. * * Side effects: * None. * *----------------------------------------------------------------------------- */ Bool SVGA_HasFIFOCap(int cap) { return (gSVGA.fifoMem[SVGA_FIFO_CAPABILITIES] & cap) != 0; } /* *----------------------------------------------------------------------------- * * SVGA_FIFOReserve -- * * Begin writing a command to the FIFO buffer. There are several * examples floating around which show how to write to the FIFO * buffer, but this is the preferred method: write directly to * FIFO memory in the common case, but if the command would not * be contiguous, use a bounce buffer. * * This method is easy to use, and quite fast. The X.org driver * does not yet use this method, but recent Windows drivers use * it. * * The main principles here are: * * - There are multiple code paths. In the best case, we write * directly to the FIFO. In the next-best case, we use a * static bounce buffer. If you need to support arbitrarily * large commands, you can have a worst case in which you use * a dynamically sized bounce buffer. * * - We must tell the host that we're reserving FIFO * space. This is important because the device doesn't * guarantee it will preserve the contents of FIFO memory * which hasn't been reserved. If we write to a totally * unused portion of the FIFO and the VM is suspended, on * resume that data will no longer exist. * * This function is not re-entrant. If your driver is * multithreaded or may be used from multiple processes * concurrently, you must make sure to serialize all FIFO * commands. * * The caller must pair this command with SVGA_FIFOCommit or * SVGA_FIFOCommitAll. * * Results: * Returns a pointer to the location where the FIFO command can * be written. There will be room for at least 'bytes' bytes of * data. * * Side effects: * Begins a FIFO command, reserves space in the FIFO. * May block (in SVGAFIFOFull) if the FIFO is full. * *----------------------------------------------------------------------------- */ void * SVGA_FIFOReserve(uint32 bytes) // IN { volatile uint32 *fifo = gSVGA.fifoMem; uint32 max = fifo[SVGA_FIFO_MAX]; uint32 min = fifo[SVGA_FIFO_MIN]; uint32 nextCmd = fifo[SVGA_FIFO_NEXT_CMD]; Bool reserveable = SVGA_HasFIFOCap(SVGA_FIFO_CAP_RESERVE); /* * This example implementation uses only a statically allocated * buffer. If you want to support arbitrarily large commands, * dynamically allocate a buffer if and only if it's necessary. */ if (bytes > sizeof gSVGA.fifo.bounceBuffer || bytes > (max - min)) { SVGA_Panic("FIFO command too large"); } if (bytes % sizeof(uint32)) { SVGA_Panic("FIFO command length not 32-bit aligned"); } if (gSVGA.fifo.reservedSize != 0) { SVGA_Panic("FIFOReserve before FIFOCommit"); } gSVGA.fifo.reservedSize = bytes; while (1) { uint32 stop = fifo[SVGA_FIFO_STOP]; Bool reserveInPlace = FALSE; Bool needBounce = FALSE; /* * Find a strategy for dealing with "bytes" of data: * - reserve in place, if there's room and the FIFO supports it * - reserve in bounce buffer, if there's room in FIFO but not * contiguous or FIFO can't safely handle reservations * - otherwise, sync the FIFO and try again. */ if (nextCmd >= stop) { /* There is no valid FIFO data between nextCmd and max */ if (nextCmd + bytes < max || (nextCmd + bytes == max && stop > min)) { /* * Fastest path 1: There is already enough contiguous space * between nextCmd and max (the end of the buffer). * * Note the edge case: If the "<" path succeeds, we can * quickly return without performing any other tests. If * we end up on the "==" path, we're writing exactly up to * the top of the FIFO and we still need to make sure that * there is at least one unused DWORD at the bottom, in * order to be sure we don't fill the FIFO entirely. * * If the "==" test succeeds, but stop <= min (the FIFO * would be completely full if we were to reserve this * much space) we'll end up hitting the FIFOFull path below. */ reserveInPlace = TRUE; } else if ((max - nextCmd) + (stop - min) <= bytes) { /* * We have to split the FIFO command into two pieces, * but there still isn't enough total free space in * the FIFO to store it. * * Note the "<=". We need to keep at least one DWORD * of the FIFO free at all times, or we won't be able * to tell the difference between full and empty. */ SVGAFIFOFull(); } else { /* * Data fits in FIFO but only if we split it. * Need to bounce to guarantee contiguous buffer. */ needBounce = TRUE; } } else { /* There is FIFO data between nextCmd and max */ if (nextCmd + bytes < stop) { /* * Fastest path 2: There is already enough contiguous space * between nextCmd and stop. */ reserveInPlace = TRUE; } else { /* * There isn't enough room between nextCmd and stop. * The FIFO is too full to accept this command. */ SVGAFIFOFull(); } } /* * If we decided we can write directly to the FIFO, make sure * the VMX can safely support this. */ if (reserveInPlace) { if (reserveable || bytes <= sizeof(uint32)) { gSVGA.fifo.usingBounceBuffer = FALSE; if (reserveable) { fifo[SVGA_FIFO_RESERVED] = bytes; } return nextCmd + (uint8*) fifo; } else { /* * Need to bounce because we can't trust the VMX to safely * handle uncommitted data in FIFO. */ needBounce = TRUE; } } /* * If we reach here, either we found a full FIFO, called * SVGAFIFOFull to make more room, and want to try again, or we * decided to use a bounce buffer instead. */ if (needBounce) { gSVGA.fifo.usingBounceBuffer = TRUE; return gSVGA.fifo.bounceBuffer; } } /* while (1) */ } /* *----------------------------------------------------------------------------- * * SVGA_FIFOCommit -- * * Commit a block of FIFO data which was placed in the buffer * returned by SVGA_FIFOReserve. Every Reserve must be paired * with exactly one Commit, but the sizes don't have to match. * The caller is free to commit less space than they * reserved. This can be used if the command size isn't known in * advance, but it is reasonable to make a worst-case estimate. * * The commit size does not have to match the size of a single * FIFO command. This can be used to write a partial command, or * to write multiple commands at once. * * Results: * None. * * Side effects: * None. * *----------------------------------------------------------------------------- */ void SVGA_FIFOCommit(uint32 bytes) // IN { volatile uint32 *fifo = gSVGA.fifoMem; uint32 nextCmd = fifo[SVGA_FIFO_NEXT_CMD]; uint32 max = fifo[SVGA_FIFO_MAX]; uint32 min = fifo[SVGA_FIFO_MIN]; Bool reserveable = SVGA_HasFIFOCap(SVGA_FIFO_CAP_RESERVE); if (gSVGA.fifo.reservedSize == 0) { SVGA_Panic("FIFOCommit before FIFOReserve"); } gSVGA.fifo.reservedSize = 0; if (gSVGA.fifo.usingBounceBuffer) { /* * Slow paths: copy out of a bounce buffer. */ uint8 *buffer = gSVGA.fifo.bounceBuffer; if (reserveable) { /* * Slow path: bulk copy out of a bounce buffer in two chunks. * * Note that the second chunk may be zero-length if the reserved * size was large enough to wrap around but the commit size was * small enough that everything fit contiguously into the FIFO. * * Note also that we didn't need to tell the FIFO about the * reservation in the bounce buffer, but we do need to tell it * about the data we're bouncing from there into the FIFO. */ uint32 chunkSize = MIN(bytes, max - nextCmd); fifo[SVGA_FIFO_RESERVED] = bytes; memcpy(nextCmd + (uint8*) fifo, buffer, chunkSize); memcpy(min + (uint8*) fifo, buffer + chunkSize, bytes - chunkSize); } else { /* * Slowest path: copy one dword at a time, updating NEXT_CMD as * we go, so that we bound how much data the guest has written * and the host doesn't know to checkpoint. */ uint32 *dword = (uint32 *)buffer; while (bytes > 0) { fifo[nextCmd / sizeof *dword] = *dword++; nextCmd += sizeof *dword; if (nextCmd == max) { nextCmd = min; } fifo[SVGA_FIFO_NEXT_CMD] = nextCmd; bytes -= sizeof *dword; } } } /* * Atomically update NEXT_CMD, if we didn't already */ if (!gSVGA.fifo.usingBounceBuffer || reserveable) { nextCmd += bytes; if (nextCmd >= max) { nextCmd -= max - min; } fifo[SVGA_FIFO_NEXT_CMD] = nextCmd; } /* * Clear the reservation in the FIFO. */ if (reserveable) { fifo[SVGA_FIFO_RESERVED] = 0; } } /* *----------------------------------------------------------------------------- * * SVGA_FIFOCommitAll -- * * This is a convenience wrapper for SVGA_FIFOCommit(), which * always commits the last reserved block in its entirety. * * Results: * None. * * Side effects: * SVGA_FIFOCommit. * *----------------------------------------------------------------------------- */ void SVGA_FIFOCommitAll(void) { SVGA_FIFOCommit(gSVGA.fifo.reservedSize); } /* *----------------------------------------------------------------------------- * * SVGA_FIFOReserveCmd -- * * This is a convenience wrapper around SVGA_FIFOReserve, which * prefixes the reserved memory block with a uint32 that * indicates the command type. * * Results: * Always returns a pointer to 'bytes' bytes of reserved space in the FIFO. * * Side effects: * Begins a FIFO command, reserves space in the FIFO. Writes a * 1-word header into the FIFO. May block (in SVGAFIFOFull) if * the FIFO is full. * *----------------------------------------------------------------------------- */ void * SVGA_FIFOReserveCmd(uint32 type, // IN uint32 bytes) // IN { uint32 *cmd = SVGA_FIFOReserve(bytes + sizeof type); cmd[0] = type; return cmd + 1; } /* *----------------------------------------------------------------------------- * * SVGA_FIFOReserveEscape -- * * This is a convenience wrapper around SVGA_FIFOReserve, which * prefixes the reserved memory block with an ESCAPE command header. * * ESCAPE commands are a way of encoding extensible and * variable-length packets within the basic FIFO protocol * itself. ESCAPEs are used for some SVGA device functionality, * like video overlays, for VMware's internal debugging tools, * and for communicating with third party code that can load into * the SVGA device. * * Results: * Always returns a pointer to 'bytes' bytes of reserved space in the FIFO. * * Side effects: * Begins a FIFO command, reserves space in the FIFO. Writes a * 3-word header into the FIFO. May block (in SVGAFIFOFull) if * the FIFO is full. * *----------------------------------------------------------------------------- */ void * SVGA_FIFOReserveEscape(uint32 nsid, // IN uint32 bytes) // IN { uint32 paddedBytes = (bytes + 3) & ~3UL; struct { uint32 cmd; uint32 nsid; uint32 size; } __attribute__ ((__packed__)) *header = SVGA_FIFOReserve(paddedBytes + sizeof *header); header->cmd = SVGA_CMD_ESCAPE; header->nsid = nsid; header->size = bytes; return header + 1; } /* *----------------------------------------------------------------------------- * * SVGAFIFOFull -- * * This function is called repeatedly as long as the FIFO has too * little free space for us to continue. * * The simplest implementation of this function is a no-op. This * will just burn guest CPU until space is available. (That's a * bad idea, since the host probably needs that CPU in order to * make progress on emptying the FIFO.) * * A better implementation would sleep until a FIFO progress * interrupt occurs. Depending on the OS you're writing drivers * for, this may deschedule the calling task or it may simply put * the CPU to sleep. * * Results: * None. * * Side effects: * None. * *----------------------------------------------------------------------------- */ void SVGAFIFOFull(void) { if (SVGA_IsFIFORegValid(SVGA_FIFO_FENCE_GOAL) && (gSVGA.capabilities & SVGA_CAP_IRQMASK)) { /* * On hosts which support interrupts, we can sleep until the * FIFO_PROGRESS interrupt occurs. This is the most efficient * thing to do when the FIFO fills up. * * As with the IRQ-based SVGA_SyncToFence(), this will only work * on Workstation 6.5 virtual machines and later. */ SVGA_WriteReg(SVGA_REG_IRQMASK, SVGA_IRQFLAG_FIFO_PROGRESS); SVGA_ClearIRQ(); SVGA_RingDoorbell(); SVGA_WaitForIRQ(); SVGA_WriteReg(SVGA_REG_IRQMASK, 0); } else { /* * Fallback implementation: Perform one iteration of the * legacy-style sync. This synchronously processes FIFO commands * for an arbitrary amount of time, then returns control back to * the guest CPU. */ SVGA_WriteReg(SVGA_REG_SYNC, 1); SVGA_ReadReg(SVGA_REG_BUSY); } } /* *----------------------------------------------------------------------------- * * SVGA_InsertFence -- * * Write a new fence value to the FIFO. * * Fences are the basis of the SVGA device's synchronization * model. A fence is a marker inserted into the FIFO by the * guest. The host processes all FIFO commands in order. Once the * fence is reached, the host does two things: * * - The fence value is written to the SVGA_FIFO_FENCE register. * - Optionally, an interrupt is raised. * * There are multiple ways to use fences for synchronization. See * SVGA_SyncToFence and SVGA_HasFencePassed. * * Results: * * Returns the value of the fence we inserted. Fence values * increment, wrapping around at 32 bits. Fence value zero is * reserved to mean "no fence". This function never returns zero. * * Certain very old versions of the VMware SVGA device do not * support fences. On these devices, we always return 1. On these * devices, SyncToFence will always do a full Sync and * SyncToFence will always return FALSE, so the actual fence * value we use is unimportant. Code written to use fences will * run inefficiently, but it will still be correct. * * Side effects: * Writes to the FIFO. Increments our nextFence. * *----------------------------------------------------------------------------- */ uint32 SVGA_InsertFence(void) { uint32 fence; struct { uint32 id; uint32 fence; } __attribute__ ((__packed__)) *cmd; if (!SVGA_HasFIFOCap(SVGA_FIFO_CAP_FENCE)) { return 1; } if (gSVGA.fifo.nextFence == 0) { gSVGA.fifo.nextFence = 1; } fence = gSVGA.fifo.nextFence++; cmd = SVGA_FIFOReserve(sizeof *cmd); cmd->id = SVGA_CMD_FENCE; cmd->fence = fence; SVGA_FIFOCommitAll(); return fence; } /* *----------------------------------------------------------------------------- * * SVGA_SyncToFence -- * * Sleep until the SVGA device has processed all FIFO commands * prior to the insertion point of the specified fence. * * This is the most important way to maintain synchronization * between the driver and the SVGA3D device itself. It can be * used to provide flow control between the host and the guest, * or to ensure that DMA operations have completed before reusing * guest memory. * * If the provided fence is zero or it has already passed, * this is a no-op. * * If the SVGA device and virtual machine hardware version are * both new enough (Workstation 6.5 or later), this will use an * efficient interrupt-driven mechanism to sleep until just after * the host processes the fence. * * If not, this will use a less efficient synchronization * mechanism which may require the host to process significantly * more of the FIFO than is necessary. * * Results: * void. * * Side effects: * None. * *----------------------------------------------------------------------------- */ void SVGA_SyncToFence(uint32 fence) // IN { if (!fence) { return; } if (!SVGA_HasFIFOCap(SVGA_FIFO_CAP_FENCE)) { /* * Fall back on the legacy sync if the host does not support * fences. This is the old sync mechanism that has been * supported in the SVGA device pretty much since the dawn of * time: write to the SYNC register, then read from BUSY until * it's nonzero. This will drain the entire FIFO. * * The parameter we write to SVGA_REG_SYNC is an arbitrary * nonzero value which can be used for debugging, but which is * ignored by release builds of VMware products. */ SVGA_WriteReg(SVGA_REG_SYNC, 1); while (SVGA_ReadReg(SVGA_REG_BUSY) != FALSE); return; } if (SVGA_HasFencePassed(fence)) { /* * Nothing to do */ return; } if (SVGA_IsFIFORegValid(SVGA_FIFO_FENCE_GOAL) && (gSVGA.capabilities & SVGA_CAP_IRQMASK)) { /* * On hosts which support interrupts and which support the * FENCE_GOAL interrupt, we can use our preferred * synchronization mechanism. * * This provides low latency notification both from guest to * host and from host to guest, and it doesn't block the guest * while we're waiting. * * This will only work on Workstation 6.5 virtual machines * or later. Older virtual machines did not allocate an IRQ * for the SVGA device, and the IRQMASK capability will be * unset. */ /* * Set the fence goal. This asks the host to send an interrupt * when this specific fence has been reached. */ gSVGA.fifoMem[SVGA_FIFO_FENCE_GOAL] = fence; SVGA_WriteReg(SVGA_REG_IRQMASK, SVGA_IRQFLAG_FENCE_GOAL); SVGA_ClearIRQ(); /* * Must check again, in case we reached the fence between the * first HasFencePassed and when we set up the IRQ. * * As a small performance optimization, we check yet again after * RingDoorbell, since there's a chance that RingDoorbell will * deschedule this VM and process some SVGA FIFO commands in a * way that appears synchronous from the VM's point of view. */ if (!SVGA_HasFencePassed(fence)) { /* * We're about to go to sleep. Make sure the host is awake. */ SVGA_RingDoorbell(); if (!SVGA_HasFencePassed(fence)) { SVGA_WaitForIRQ(); } } SVGA_WriteReg(SVGA_REG_IRQMASK, 0); } else { /* * Sync-to-fence mechanism for older hosts. Wake up the host, * and spin on BUSY until we've reached the fence. This * processes FIFO commands synchronously, blocking the VM's * execution entirely until it's done. */ Bool busy = TRUE; SVGA_WriteReg(SVGA_REG_SYNC, 1); while (!SVGA_HasFencePassed(fence) && busy) { busy = (SVGA_ReadReg(SVGA_REG_BUSY) != 0); } } if (!SVGA_HasFencePassed(fence)) { /* * This shouldn't happen. If it does, there might be a bug in * the SVGA device. */ SVGA_Panic("SyncToFence failed!"); } } /* *----------------------------------------------------------------------------- * * SVGA_HasFencePassed -- * * Test whether the host has processed all FIFO commands prior to * the insertion point of the specified fence. * * This function tolerates fence wrap-around, but it will return * the wrong result if 'fence' is more than 2^31 fences old. It * is recommended that callers don't allow fence values to * persist indefinitely. Once we notice that a fence has been * passed, that fence variable should be set to zero so we don't * test it in the future. * * Results: * TRUE if the fence has been passed, * TRUE if fence==0 (no fence), * FALSE if the fence has not been passed, * FALSE if the SVGA device does not support fences. * * Side effects: * None. * *----------------------------------------------------------------------------- */ Bool SVGA_HasFencePassed(uint32 fence) // IN { if (!fence) { return TRUE; } if (!SVGA_HasFIFOCap(SVGA_FIFO_CAP_FENCE)) { return FALSE; } return ((int32)(gSVGA.fifoMem[SVGA_FIFO_FENCE] - fence)) >= 0; } /* *----------------------------------------------------------------------------- * * SVGA_RingDoorbell -- * * FIFO fences are fundamentally a host-to-guest notification * mechanism. This is the opposite: we can explicitly wake up * the host when we know there is work for it to do. * * Background: The host processes the SVGA command FIFO in a * separate thread which runs asynchronously with the virtual * machine's CPU and other I/O devices. When the SVGA device is * idle, this thread is sleeping. It periodically wakes up to * poll for new commands. This polling must occur for various * reasons, but it's mostly related to the historical way in * which the SVGA device processes 2D updates. * * This polling introduces significant latency between when the * first new command is placed in an empty FIFO, and when the * host begins processing it. Normally this isn't a huge problem * since the host and guest run fairly asynchronously, but in * a synchronization-heavy workload this can be a bottleneck. * * For example, imagine an application with a worst-case * synchronization bottleneck: The guest enqueues a single FIFO * command, then waits for that command to finish using * SyncToFence, then the guest spends a little time doing * CPU-intensive processing before the cycle repeats. The * workload may be latency-bound if the host-to-guest or * guest-to-host notifications ever block. * * One solution would be for the guest to explicitly wake up the * SVGA3D device any time a command is enqueued. This would solve * the latency bottleneck above, but it would be inefficient on * single-CPU host machines. One could easily imagine a situation * in which we wake up the host after enqueueing one FIFO * command, the physical CPU context switches to the SVGA * device's thread, the single command is processed, then we * context switch back to running guest code. * * Our recommended solution is to wake up the host only either: * * - After a "significant" amount of work has been enqueued into * the FIFO. For example, at least one 3D drawing command. * * - After a complete frame has been rendered. * * - Just before the guest sleeps for any reason. * * This function implements the above guest wakeups. It uses the * SVGA_FIFO_BUSY register to quickly assess whether the SVGA * device may be idle. If so, it asynchronously wakes up the host * by writing to SVGA_REG_SYNC. * * Results: * None. * * Side effects: * May wake up the SVGA3D device. * *----------------------------------------------------------------------------- */ void SVGA_RingDoorbell(void) { if (SVGA_IsFIFORegValid(SVGA_FIFO_BUSY) && gSVGA.fifoMem[SVGA_FIFO_BUSY] == FALSE) { /* Remember that we already rang the doorbell. */ gSVGA.fifoMem[SVGA_FIFO_BUSY] = TRUE; /* * Asynchronously wake up the SVGA3D device. The second * parameter is an arbitrary nonzero 'sync reason' which can be * used for debugging, but which isn't part of the SVGA3D * protocol proper and which isn't used by release builds of * VMware products. */ SVGA_WriteReg(SVGA_REG_SYNC, 1); } } /* *----------------------------------------------------------------------------- * * SVGA_ClearIRQ -- * * Clear all pending IRQs. Any IRQs which occurred prior to this * function call will be ignored by the next SVGA_WaitForIRQ() * call. * * Does not affect the current IRQ mask. This function is not * useful unless the SVGA device has IRQ support. * * Results: * Returns a mask of all the interrupt flags that were set prior * to the clear. * * Side effects: * None. * *----------------------------------------------------------------------------- */ uint32 SVGA_ClearIRQ(void) { uint32 flags = 0; Atomic_Exchange(gSVGA.irq.pending, flags); return flags; } /* *----------------------------------------------------------------------------- * * SVGA_WaitForIRQ -- * * Sleep until an SVGA interrupt occurs. When we wake up, return * a mask of all SVGA IRQs which occurred while we were asleep. * * The design of this function will be different for every OS, * but it is imperative that you ensure it will work properly * no matter when the interrupt actually occurs. The IRQ could have * already happened (any time since the last ClearIRQ, it could * happen while this function is running, or it could happen * after we decide to sleep. * * In this example: * * 1. If the IRQ occurred before this function was called, * the bit will already be set in irq.pending. We can return. * * 2. If the IRQ occurs while this function is executing, we * ask our IRQ handler to do a light-weight context switch * back to the beginning of this function, so we can * re-examine irq.pending before sleeping. * * 3. If the IRQ occurs while we're sleeping, we wake up * from the HLT instruction and re-test irq.pending. * * Results: * Returns a mask of all the interrupt flags that were set prior * to the clear. This will always be nonzero. * * Side effects: * Clears the irq.pending flags for exactly the set of IRQs we return. * *----------------------------------------------------------------------------- */ static __attribute__ ((noinline)) uint32 SVGAWaitForIRQInternal(void *arg, ...) { uint32 flags = 0; gSVGA.irq.switchContext = TRUE; Atomic_Exchange(gSVGA.irq.pending, flags); if (!flags) { Intr_Halt(); } gSVGA.irq.switchContext = FALSE; return flags; } uint32 SVGA_WaitForIRQ(void) { uint32 flags; /* * Store this CPU context, and tell the IRQ handler to return * here if an IRQ occurs. We use IntrContext here just like a * setjmp/longjmp that works in ISRs. * * This must be above the irq.pending check, so that we re-test * irq.pending if an IRQ occurs after testing irq.pending but * before we halt. Without this, we could deadlock if an IRQ * comes in before we actually halt but after we've decided to * halt. */ Intr_SaveContext(&gSVGA.irq.newContext); do { /* * XXX: The arguments here are a kludge to prevent the interrupt * stack frame from overlapping the stack frame referenced * by Intr_SaveContext(). */ flags = SVGAWaitForIRQInternal(NULL, NULL, NULL, NULL); } while (flags == 0); return flags; } /* *----------------------------------------------------------------------------- * * SVGAInterruptHandler -- * * This is the ISR for the SVGA device's interrupt. We ask the * SVGA device which interrupt occurred, and clear its flag. * * To report this IRQ to the rest of the driver, we: * * 1. Atomically remember the IRQ in the irq.pending bitmask. * * 2. Optionally switch to a different light-weight thread * or a different place in this thread upon returning. * This is analogous to waking up a sleeping thread in * a driver for a real operating system. See SVGA_WaitForIRQ * for details. * * Results: * void. * * Side effects: * Sets bits in pendingIRQs. Reads and clears the device's IRQ flags. * May switch execution contexts. * *----------------------------------------------------------------------------- */ void SVGAInterruptHandler(int vector) // IN (unused) { volatile IntrContext *context = Intr_GetContext(vector); /* * The SVGA_IRQSTATUS_PORT is a separate I/O port, not a register. * Reading from it gives us the set of IRQ flags which are * set. Writing a '1' to any bit in this register will clear the * corresponding flag. * * Here, we read then clear all flags. */ uint16 port = gSVGA.ioBase + SVGA_IRQSTATUS_PORT; uint32 irqFlags = IO_In32(port); IO_Out32(port, irqFlags); gSVGA.irq.count++; if (!irqFlags) { SVGA_Panic("Spurious SVGA IRQ"); } Atomic_Or(gSVGA.irq.pending, irqFlags); if (gSVGA.irq.switchContext) { gSVGA.irq.oldContext = *context; *context = gSVGA.irq.newContext; gSVGA.irq.switchContext = FALSE; } } /* *----------------------------------------------------------------------------- * * SVGA_Update -- * * Send a 2D update rectangle through the FIFO. * * Results: * None. * * Side effects: * None. * *----------------------------------------------------------------------------- */ void SVGA_Update(uint32 x, // IN uint32 y, // IN uint32 width, // IN uint32 height) // IN { SVGAFifoCmdUpdate *cmd = SVGA_FIFOReserveCmd(SVGA_CMD_UPDATE, sizeof *cmd); cmd->x = x; cmd->y = y; cmd->width = width; cmd->height = height; SVGA_FIFOCommitAll(); } /* *----------------------------------------------------------------------------- * * SVGA_BeginDefineCursor -- * * Begin an SVGA_CMD_DEFINE_CURSOR command. This copies the command header * into the FIFO, and reserves enough space for the cursor image itself. * We return pointers to FIFO memory where the AND/XOR masks can be written. * When finished, the caller must invoke SVGA_FIFOCommitAll(). * * Results: * Returns pointers to memory where the caller can store the AND/XOR masks. * * Side effects: * Reserves space in the FIFO. * *----------------------------------------------------------------------------- */ void SVGA_BeginDefineCursor(const SVGAFifoCmdDefineCursor *cursorInfo, // IN void **andMask, // OUT void **xorMask) // OUT { uint32 andPitch = ((cursorInfo->andMaskDepth * cursorInfo->width + 31) >> 5) << 2; uint32 andSize = andPitch * cursorInfo->height; uint32 xorPitch = ((cursorInfo->xorMaskDepth * cursorInfo->width + 31) >> 5) << 2; uint32 xorSize = xorPitch * cursorInfo->height; SVGAFifoCmdDefineCursor *cmd = SVGA_FIFOReserveCmd(SVGA_CMD_DEFINE_CURSOR, sizeof *cmd + andSize + xorSize); *cmd = *cursorInfo; *andMask = (void*) (cmd + 1); *xorMask = (void*) (andSize + (uint8*) *andMask); } /* *----------------------------------------------------------------------------- * * SVGA_BeginDefineAlphaCursor -- * * Begin an SVGA_CMD_DEFINE_ALPHA_CURSOR command. This copies the * command header into the FIFO, and reserves enough space for * the cursor image itself. We return a pointer to the FIFO * memory where the caller should write a pre-multiplied BGRA * image. When finished, the caller must invoke SVGA_FIFOCommitAll(). * * Results: * Returns pointers to memory where the caller can store the image. * * Side effects: * Reserves space in the FIFO. * *----------------------------------------------------------------------------- */ void SVGA_BeginDefineAlphaCursor(const SVGAFifoCmdDefineAlphaCursor *cursorInfo, // IN void **data) // OUT { uint32 imageSize = cursorInfo->width * cursorInfo->height * sizeof(uint32); SVGAFifoCmdDefineAlphaCursor *cmd = SVGA_FIFOReserveCmd(SVGA_CMD_DEFINE_ALPHA_CURSOR, sizeof *cmd + imageSize); *cmd = *cursorInfo; *data = (void*) (cmd + 1); } /* *----------------------------------------------------------------------------- * * SVGA_MoveCursor -- * * Change the position or visibility of the hardware cursor, using * the Cursor Bypass 3 protocol. * * Results: * None. * * Side effects: * Modifies FIFO registers. * *----------------------------------------------------------------------------- */ void SVGA_MoveCursor(uint32 visible, // IN uint32 x, // IN uint32 y) // IN { if (SVGA_HasFIFOCap(SVGA_FIFO_CAP_CURSOR_BYPASS_3)) { gSVGA.fifoMem[SVGA_FIFO_CURSOR_ON] = visible; gSVGA.fifoMem[SVGA_FIFO_CURSOR_X] = x; gSVGA.fifoMem[SVGA_FIFO_CURSOR_Y] = y; gSVGA.fifoMem[SVGA_FIFO_CURSOR_COUNT]++; } } /* *----------------------------------------------------------------------------- * * SVGA_BeginVideoSetRegs -- * * Begin an SVGA_ESCAPE_VMWARE_VIDEO_SET_REGS command. This can be * used to set an arbitrary number of video registers atomically. * * Results: * Returns a pointer to the command. The caller must fill in (*setRegs)->items. * * Side effects: * Reserves space in the FIFO. * *----------------------------------------------------------------------------- */ void SVGA_BeginVideoSetRegs(uint32 streamId, // IN uint32 numItems, // IN SVGAEscapeVideoSetRegs **setRegs) // OUT { SVGAEscapeVideoSetRegs *cmd; uint32 cmdSize = (sizeof *cmd - sizeof cmd->items + numItems * sizeof cmd->items[0]); cmd = SVGA_FIFOReserveEscape(SVGA_ESCAPE_NSID_VMWARE, cmdSize); cmd->header.cmdType = SVGA_ESCAPE_VMWARE_VIDEO_SET_REGS; cmd->header.streamId = streamId; *setRegs = cmd; } /* *----------------------------------------------------------------------------- * * SVGA_VideoSetReg -- * * Atomically set all registers in a video overlay unit. * * 'maxReg' should be the highest video overlay register that the * caller understands. We will not set any registers with IDs * higher than maxReg. This prevents older applications compiled * with newer headers from sending improper values to new overlay * registers. * * Each video overlay unit (specified by streamId) has a collection * of 32-bit registers which control the operation of that overlay. * Changes to these registers take effect at the next Flush. * * Results: * None. * * Side effects: * Writes a FIFO command. * *----------------------------------------------------------------------------- */ void SVGA_VideoSetAllRegs(uint32 streamId, // IN SVGAOverlayUnit *regs, // IN uint32 maxReg) // IN { uint32 *regArray = (uint32*) regs; const uint32 numRegs = maxReg + 1; SVGAEscapeVideoSetRegs *setRegs; uint32 i; SVGA_BeginVideoSetRegs(streamId, numRegs, &setRegs); for (i = 0; i < numRegs; i++) { setRegs->items[i].registerId = i; setRegs->items[i].value = regArray[i]; } SVGA_FIFOCommitAll(); } /* *----------------------------------------------------------------------------- * * SVGA_VideoSetReg -- * * Set a single video overlay register. * * Each video overlay unit (specified by streamId) has a collection * of 32-bit registers which control the operation of that overlay. * Changes to these registers take effect at the next Flush. * * Results: * None. * * Side effects: * Writes a FIFO command. * *----------------------------------------------------------------------------- */ void SVGA_VideoSetReg(uint32 streamId, // IN uint32 registerId, // IN uint32 value) // IN { SVGAEscapeVideoSetRegs *setRegs; SVGA_BeginVideoSetRegs(streamId, 1, &setRegs); setRegs->items[0].registerId = registerId; setRegs->items[0].value = value; SVGA_FIFOCommitAll(); } /* *----------------------------------------------------------------------------- * * SVGA_VideoFlush -- * * Update a video overlay. On a VideoFlush, all register changes * take effect, and the device will perform a DMA transfer to * copy the next frame of video from guest memory. * * The SVGA device will generally perform a DMA transfer from * overlay memory only during a VideoFlush. The DMA operation * will usually be completed before the host moves to the next * FIFO command. However, this is not guaranteed. In unusual * conditions, the device may re-perform the DMA operation at any * time. This means that, as long as an overlay is enabled, its * corresponding guest memory must be valid. * * Results: * None. * * Side effects: * Writes a FIFO command. May cause an asynchronous DMA transfer. * *----------------------------------------------------------------------------- */ void SVGA_VideoFlush(uint32 streamId) // IN { SVGAEscapeVideoFlush *cmd; cmd = SVGA_FIFOReserveEscape(SVGA_ESCAPE_NSID_VMWARE, sizeof *cmd); cmd->cmdType = SVGA_ESCAPE_VMWARE_VIDEO_FLUSH; cmd->streamId = streamId; SVGA_FIFOCommitAll(); }