// sis.c -- SiS 6326/300/305/315/330 accelerated video driver // // Supports the SiS 6326, 300, 305, 315, and 330 integrated graphics // chipsets. These share a similar 2D engine interface based on a // queue-based command submission model: // - Hardware rectangle fill // - Screen-to-screen BitBLT // - CPU-to-screen blit (host blit via data port) // - Hardware clip rectangle // - 64x64 hardware cursor // // Register access: // BAR0 maps the linear framebuffer. // BAR1 maps 128KB of MMIO registers. The 2D engine registers // live at offsets 0x8200-0x8244 within this block. Host data // is written to the MMIO data port at offset 0x8300. // // The 2D engine uses a command register at 0x822C to specify the // operation type and ROP, then a fire register at 0x8230 to trigger // execution. Engine status is polled at 0x8244. #include "accelVid.h" #include "vgaCommon.h" #include "pci.h" #include #include #include #include // ============================================================ // SiS vendor/device IDs // ============================================================ #define SIS_VENDOR_ID 0x1039 #define SIS_6326 0x6326 #define SIS_300 0x0300 #define SIS_305 0x0305 #define SIS_315 0x0315 #define SIS_330 0x0330 static const uint16_t sSisDeviceIds[] = { SIS_VENDOR_ID, SIS_6326, SIS_VENDOR_ID, SIS_300, SIS_VENDOR_ID, SIS_305, SIS_VENDOR_ID, SIS_315, SIS_VENDOR_ID, SIS_330, 0, 0 }; // ============================================================ // 2D engine register offsets (from MMIO base) // ============================================================ #define SIS_SRC_ADDR 0x8200 // source address (for blit) #define SIS_SRC_PITCH 0x8204 // source pitch #define SIS_SRC_YX 0x8208 // src Y<<16 | X #define SIS_DST_YX 0x820C // dst Y<<16 | X #define SIS_RECT_WH 0x8210 // width<<16 | height #define SIS_FG_COLOR 0x8214 // foreground color #define SIS_BG_COLOR 0x8218 // background color #define SIS_MONO_PAT0 0x821C // mono pattern 0 #define SIS_MONO_PAT1 0x8220 // mono pattern 1 #define SIS_CLIP_LT 0x8224 // clip left<<16 | top #define SIS_CLIP_RB 0x8228 // clip right<<16 | bottom #define SIS_CMD 0x822C // command register #define SIS_FIRE 0x8230 // fire trigger #define SIS_LINE_PARAMS 0x8234 // line parameters #define SIS_DST_ADDR 0x8238 // destination address #define SIS_SRC_DST_PITCH 0x823C // src/dst pitch combined #define SIS_AGP_BASE 0x8240 // AGP base (unused) // ============================================================ // Engine status register // ============================================================ #define SIS_ENGINE_STATUS 0x8244 // bit 0 = queues empty, bit 1 = idle #define SIS_STATUS_QUEUE_EMPTY 0x01 #define SIS_STATUS_ENGINE_IDLE 0x02 #define SIS_STATUS_ALL_IDLE (SIS_STATUS_QUEUE_EMPTY | SIS_STATUS_ENGINE_IDLE) // ============================================================ // Host data port // ============================================================ #define SIS_HOST_DATA 0x8300 // write pixel data here as dwords // ============================================================ // Command register encoding // ============================================================ // Bits 7:0 = ROP #define SIS_ROP_COPY 0xCC #define SIS_ROP_PAT_COPY 0xF0 // Bit 8 = X direction #define SIS_CMD_XDIR_RIGHT (1 << 8) // Bit 9 = Y direction #define SIS_CMD_YDIR_DOWN (1 << 9) // Bits 13:10 = command type #define SIS_CMD_BITBLT 0x0000 #define SIS_CMD_COLOREXP 0x0400 #define SIS_CMD_LINEDRAW 0x0800 #define SIS_CMD_TRAPEZOID 0x0C00 // Bit 14 = pattern enable #define SIS_CMD_PAT_ENABLE (1 << 14) // Bit 16 = clipping enable #define SIS_CMD_CLIP_ENABLE (1 << 16) // Bit 24 = source is mono #define SIS_CMD_SRC_MONO (1 << 24) // ============================================================ // Hardware cursor registers // ============================================================ #define SIS_CURSOR_ENABLE 0x8500 // bit 0 = enable #define SIS_CURSOR_X 0x8504 // cursor X position #define SIS_CURSOR_Y 0x8508 // cursor Y position #define SIS_CURSOR_ADDR 0x850C // cursor VRAM byte offset // ============================================================ // Misc constants // ============================================================ #define SIS_MMIO_SIZE 131072 // BAR1: 128KB MMIO #define SIS_MAX_IDLE_WAIT 1000000 #define SIS_HW_CURSOR_SIZE 64 // ============================================================ // Private driver state // ============================================================ typedef struct { uint32_t lfbPhysAddr; uint32_t mmioPhysAddr; uint32_t vramSize; int32_t bytesPerPixel; int32_t screenPitch; volatile uint32_t *mmio; DpmiMappingT mmioMapping; DpmiMappingT lfbMapping; } SisPrivateT; // ============================================================ // Prototypes // ============================================================ static void sisBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h); static bool sisDetect(AccelDriverT *drv); static void sisHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h); static bool sisInit(AccelDriverT *drv, const AccelModeRequestT *req); static void sisMoveCursor(AccelDriverT *drv, int32_t x, int32_t y); static void sisRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color); static void sisSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h); static void sisSetCursor(AccelDriverT *drv, const HwCursorImageT *image); static void sisShowCursor(AccelDriverT *drv, bool visible); static void sisShutdown(AccelDriverT *drv); static void sisWaitIdle(AccelDriverT *drv); static inline void sisWrite(SisPrivateT *priv, uint32_t reg, uint32_t val) { priv->mmio[reg / 4] = val; } static inline uint32_t sisRead(SisPrivateT *priv, uint32_t reg) { return priv->mmio[reg / 4]; } // ============================================================ // Driver instance // ============================================================ static SisPrivateT sSisPrivate; static AccelDriverT sSisDriver = { .name = "SiS 6326", .chipFamily = "sis", .caps = 0, .privData = &sSisPrivate, .detect = sisDetect, .init = sisInit, .shutdown = sisShutdown, .waitIdle = sisWaitIdle, .setClip = sisSetClip, .rectFill = sisRectFill, .rectFillPat = NULL, .bitBlt = sisBitBlt, .hostBlit = sisHostBlit, .colorExpand = NULL, .lineDraw = NULL, .setCursor = sisSetCursor, .moveCursor = sisMoveCursor, .showCursor = sisShowCursor, }; // ============================================================ // sisRegisterDriver // ============================================================ void sisRegisterDriver(void) { accelRegisterDriver(&sSisDriver); } // ============================================================ // sisBitBlt // ============================================================ // // Screen-to-screen BitBLT. Handles overlapping regions by choosing // the correct X/Y direction based on source and destination positions. static void sisBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) { SisPrivateT *priv = (SisPrivateT *)drv->privData; if (w <= 0 || h <= 0) { return; } sisWaitIdle(drv); // Determine blit direction for overlapping regions uint32_t cmd = SIS_CMD_BITBLT | SIS_ROP_COPY | SIS_CMD_CLIP_ENABLE; int32_t sx = srcX; int32_t sy = srcY; int32_t dx = dstX; int32_t dy = dstY; if (dstX <= srcX) { cmd |= SIS_CMD_XDIR_RIGHT; } else { sx += w - 1; dx += w - 1; } if (dstY <= srcY) { cmd |= SIS_CMD_YDIR_DOWN; } else { sy += h - 1; dy += h - 1; } uint32_t pitch = ((uint32_t)priv->screenPitch << 16) | (uint32_t)priv->screenPitch; sisWrite(priv, SIS_SRC_DST_PITCH, pitch); sisWrite(priv, SIS_SRC_YX, ((uint32_t)sy << 16) | (uint32_t)sx); sisWrite(priv, SIS_DST_YX, ((uint32_t)dy << 16) | (uint32_t)dx); sisWrite(priv, SIS_RECT_WH, ((uint32_t)w << 16) | (uint32_t)h); sisWrite(priv, SIS_CMD, cmd); sisWrite(priv, SIS_FIRE, 0); } // ============================================================ // sisDetect // ============================================================ static bool sisDetect(AccelDriverT *drv) { int32_t matchIdx; if (!pciFindDeviceList(sSisDeviceIds, &drv->pciDev, &matchIdx)) { return false; } switch (drv->pciDev.deviceId) { case SIS_6326: drv->name = "SiS 6326"; break; case SIS_300: drv->name = "SiS 300"; break; case SIS_305: drv->name = "SiS 305"; break; case SIS_315: drv->name = "SiS 315"; break; case SIS_330: drv->name = "SiS 330"; break; default: drv->name = "SiS 6326/3xx"; break; } return true; } // ============================================================ // sisHostBlit // ============================================================ // // CPU-to-screen blit. Issues a BitBLT command, then feeds pixel data // as dwords through the MMIO host data port at offset 0x8300. static void sisHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) { SisPrivateT *priv = (SisPrivateT *)drv->privData; if (w <= 0 || h <= 0) { return; } int32_t bytesPerRow = w * priv->bytesPerPixel; int32_t dwordsPerRow = (bytesPerRow + 3) / 4; sisWaitIdle(drv); sisWrite(priv, SIS_SRC_DST_PITCH, (uint32_t)priv->screenPitch); sisWrite(priv, SIS_DST_YX, ((uint32_t)dstY << 16) | (uint32_t)dstX); sisWrite(priv, SIS_RECT_WH, ((uint32_t)w << 16) | (uint32_t)h); sisWrite(priv, SIS_FG_COLOR, 0); sisWrite(priv, SIS_CMD, SIS_CMD_BITBLT | SIS_ROP_COPY | SIS_CMD_CLIP_ENABLE | SIS_CMD_XDIR_RIGHT | SIS_CMD_YDIR_DOWN | SIS_CMD_SRC_MONO); sisWrite(priv, SIS_FIRE, 0); // Feed pixel data row by row through the host data port for (int32_t row = 0; row < h; row++) { const uint8_t *rowPtr = srcBuf + row * srcPitch; for (int32_t dw = 0; dw < dwordsPerRow; dw++) { uint32_t val = 0; int32_t offset = dw * 4; for (int32_t b = 0; b < 4; b++) { if (offset + b < bytesPerRow) { val |= (uint32_t)rowPtr[offset + b] << (b * 8); } } sisWrite(priv, SIS_HOST_DATA, val); } } } // ============================================================ // sisInit // ============================================================ static bool sisInit(AccelDriverT *drv, const AccelModeRequestT *req) { SisPrivateT *priv = (SisPrivateT *)drv->privData; // Read BARs uint32_t bar0 = pciRead32(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR0); uint32_t bar1 = pciRead32(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR1); priv->lfbPhysAddr = bar0 & 0xFFFFFFF0; priv->mmioPhysAddr = bar1 & 0xFFFFFFF0; // Size the framebuffer BAR priv->vramSize = pciSizeBar(drv->pciDev.bus, drv->pciDev.dev, drv->pciDev.func, PCI_BAR0); // Map MMIO control registers (128KB) if (!dpmiMapFramebuffer(priv->mmioPhysAddr, SIS_MMIO_SIZE, &priv->mmioMapping)) { return false; } priv->mmio = (volatile uint32_t *)priv->mmioMapping.ptr; // Find and set VESA mode VesaModeResultT vesa; if (!vesaFindAndSetMode(req->width, req->height, req->bpp, &vesa)) { dpmiUnmapFramebuffer(&priv->mmioMapping); return false; } // Map framebuffer if (!dpmiMapFramebuffer(priv->lfbPhysAddr, priv->vramSize, &priv->lfbMapping)) { vgaRestoreTextMode(); dpmiUnmapFramebuffer(&priv->mmioMapping); return false; } priv->bytesPerPixel = (vesa.bpp + 7) / 8; priv->screenPitch = vesa.pitch; drv->mode.width = vesa.width; drv->mode.height = vesa.height; drv->mode.bpp = vesa.bpp; drv->mode.pitch = vesa.pitch; drv->mode.framebuffer = priv->lfbMapping.ptr; drv->mode.vramSize = priv->vramSize; drv->mode.offscreenBase = vesa.pitch * vesa.height; // Wait for engine idle before configuring sisWaitIdle(drv); drv->caps = ACAP_RECT_FILL | ACAP_BITBLT | ACAP_HOST_BLIT | ACAP_HW_CURSOR | ACAP_CLIP; // Full screen clip sisSetClip(drv, 0, 0, vesa.width, vesa.height); return true; } // ============================================================ // sisMoveCursor // ============================================================ static void sisMoveCursor(AccelDriverT *drv, int32_t x, int32_t y) { SisPrivateT *priv = (SisPrivateT *)drv->privData; if (x < 0) { x = 0; } if (y < 0) { y = 0; } sisWrite(priv, SIS_CURSOR_X, (uint32_t)x); sisWrite(priv, SIS_CURSOR_Y, (uint32_t)y); } // ============================================================ // sisRectFill // ============================================================ // // Solid rectangle fill. Sets the foreground color, loads the // destination coordinates and dimensions, then fires a BitBLT // command with PAT_COPY ROP and pattern enable to fill with a // solid color. static void sisRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { SisPrivateT *priv = (SisPrivateT *)drv->privData; if (w <= 0 || h <= 0) { return; } sisWaitIdle(drv); sisWrite(priv, SIS_SRC_DST_PITCH, (uint32_t)priv->screenPitch); sisWrite(priv, SIS_FG_COLOR, color); sisWrite(priv, SIS_MONO_PAT0, 0xFFFFFFFF); sisWrite(priv, SIS_MONO_PAT1, 0xFFFFFFFF); sisWrite(priv, SIS_DST_YX, ((uint32_t)y << 16) | (uint32_t)x); sisWrite(priv, SIS_RECT_WH, ((uint32_t)w << 16) | (uint32_t)h); sisWrite(priv, SIS_CMD, SIS_CMD_BITBLT | SIS_ROP_PAT_COPY | SIS_CMD_PAT_ENABLE | SIS_CMD_CLIP_ENABLE | SIS_CMD_XDIR_RIGHT | SIS_CMD_YDIR_DOWN); sisWrite(priv, SIS_FIRE, 0); } // ============================================================ // sisSetClip // ============================================================ static void sisSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h) { SisPrivateT *priv = (SisPrivateT *)drv->privData; sisWrite(priv, SIS_CLIP_LT, ((uint32_t)x << 16) | (uint32_t)y); sisWrite(priv, SIS_CLIP_RB, ((uint32_t)(x + w - 1) << 16) | (uint32_t)(y + h - 1)); } // ============================================================ // sisSetCursor // ============================================================ // // Upload a 64x64 hardware cursor image to VRAM. The SiS cursor // format is 2bpp: AND mask and XOR mask interleaved per row, // 16 bytes per row (8 AND + 8 XOR). Total size is 1024 bytes. static void sisSetCursor(AccelDriverT *drv, const HwCursorImageT *image) { SisPrivateT *priv = (SisPrivateT *)drv->privData; if (!image) { sisShowCursor(drv, false); return; } sisWaitIdle(drv); // Store cursor image at end of VRAM (1KB aligned) uint32_t cursorOffset = priv->vramSize - 1024; cursorOffset &= ~0x3FF; uint8_t *cursorMem = drv->mode.framebuffer + cursorOffset; // Write AND mask then XOR mask, interleaved per row for (int32_t row = 0; row < SIS_HW_CURSOR_SIZE; row++) { for (int32_t byteIdx = 0; byteIdx < 8; byteIdx++) { int32_t srcIdx = row * 8 + byteIdx; uint8_t andByte; uint8_t xorByte; if (row < image->height && byteIdx < (image->width + 7) / 8) { andByte = image->andMask[srcIdx]; xorByte = image->xorMask[srcIdx]; } else { andByte = 0xFF; // transparent xorByte = 0x00; } cursorMem[row * 16 + byteIdx] = andByte; cursorMem[row * 16 + byteIdx + 8] = xorByte; } } // Set cursor address register sisWrite(priv, SIS_CURSOR_ADDR, cursorOffset); } // ============================================================ // sisShowCursor // ============================================================ static void sisShowCursor(AccelDriverT *drv, bool visible) { SisPrivateT *priv = (SisPrivateT *)drv->privData; sisWrite(priv, SIS_CURSOR_ENABLE, visible ? 1 : 0); } // ============================================================ // sisShutdown // ============================================================ static void sisShutdown(AccelDriverT *drv) { SisPrivateT *priv = (SisPrivateT *)drv->privData; sisShowCursor(drv, false); vgaRestoreTextMode(); dpmiUnmapFramebuffer(&priv->lfbMapping); dpmiUnmapFramebuffer(&priv->mmioMapping); priv->mmio = NULL; } // ============================================================ // sisWaitIdle // ============================================================ // // Wait until the 2D engine is completely idle. Both bit 0 (queues // empty) and bit 1 (engine idle) of the status register at 0x8244 // must be set. static void sisWaitIdle(AccelDriverT *drv) { SisPrivateT *priv = (SisPrivateT *)drv->privData; for (int32_t i = 0; i < SIS_MAX_IDLE_WAIT; i++) { uint32_t stat = sisRead(priv, SIS_ENGINE_STATUS); if ((stat & SIS_STATUS_ALL_IDLE) == SIS_STATUS_ALL_IDLE) { return; } } }