DOS_Video/sis.c
2026-04-13 19:40:45 -05:00

561 lines
18 KiB
C

// 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 <pc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// 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;
}
}
}