// accelVid.c -- Accelerated video driver manager // // Manages registration, detection, and lifecycle of hardware-specific // video drivers. Drivers register themselves at startup, then the // manager probes each in order to find matching hardware. // // After a chip driver's init() succeeds, the manager fills in // software fallback implementations for any drawing operations // the driver left as NULL. This means callers never need to // check function pointers -- every operation is always callable. // The fallbacks draw directly to the LFB using simple loops. #include "accelVid.h" #include #include #include // Maximum number of registered drivers. This is more than enough // for all chip families we'll ever support. #define MAX_DRIVERS 32 // ============================================================ // Prototypes -- public API // ============================================================ AccelDriverT *accelDetect(void); uint32_t accelGetCaps(const AccelDriverT *drv); const char *accelGetName(const AccelDriverT *drv); bool accelInit(AccelDriverT *drv, const AccelModeRequestT *req); void accelRegisterDriver(AccelDriverT *drv); void accelShutdown(AccelDriverT *drv); // ============================================================ // Prototypes -- software fallbacks // ============================================================ static void swBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h); static void swColorExpand(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h, uint32_t fg, uint32_t bg); static void swHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h); static void swLineDraw(AccelDriverT *drv, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color); static void swRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color); static void swRectFillPat(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *pattern, uint32_t fg, uint32_t bg); static void swSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h); static void swWaitIdle(AccelDriverT *drv); static void swInstallFallbacks(AccelDriverT *drv); // ============================================================ // Inline helpers for software fallbacks // ============================================================ // Write a pixel at (x, y) in the framebuffer. No bounds checking // -- the caller must clip before calling. static inline void swPutPixel(AccelDriverT *drv, int32_t x, int32_t y, uint32_t color) { uint8_t *fb = drv->mode.framebuffer; int32_t bpp = (drv->mode.bpp + 7) / 8; uint8_t *dst = fb + y * drv->mode.pitch + x * bpp; switch (bpp) { case 1: *dst = (uint8_t)color; break; case 2: *(uint16_t *)dst = (uint16_t)color; break; case 4: *(uint32_t *)dst = color; break; } } // ============================================================ // Module state // ============================================================ static AccelDriverT *sDrivers[MAX_DRIVERS]; static int32_t sDriverCount = 0; // Software clip rectangle (used by fallbacks when no hardware clip) static int32_t sClipX = 0; static int32_t sClipY = 0; static int32_t sClipW = 0; static int32_t sClipH = 0; // ============================================================ // accelDetect // ============================================================ // // Iterates all registered drivers and calls detect() on each. // Returns the first driver that claims the hardware, or NULL // if no supported hardware is found. // // Detection order matters: drivers registered first are tried // first. This allows callers to prioritize specific drivers // (e.g. prefer S3 over generic VESA). AccelDriverT *accelDetect(void) { if (!pciDetect()) { fprintf(stderr, "accelVid: PCI bus not detected\n"); return NULL; } for (int32_t i = 0; i < sDriverCount; i++) { if (sDrivers[i]->detect(sDrivers[i])) { printf("accelVid: Detected %s (PCI %02X:%02X.%X, " "vendor=%04X device=%04X)\n", sDrivers[i]->name, sDrivers[i]->pciDev.bus, sDrivers[i]->pciDev.dev, sDrivers[i]->pciDev.func, sDrivers[i]->pciDev.vendorId, sDrivers[i]->pciDev.deviceId); return sDrivers[i]; } } fprintf(stderr, "accelVid: No supported video hardware found\n"); return NULL; } // ============================================================ // accelGetCaps // ============================================================ uint32_t accelGetCaps(const AccelDriverT *drv) { if (!drv) { return 0; } return drv->caps; } // ============================================================ // accelGetName // ============================================================ const char *accelGetName(const AccelDriverT *drv) { if (!drv) { return "none"; } return drv->name; } // ============================================================ // accelInit // ============================================================ bool accelInit(AccelDriverT *drv, const AccelModeRequestT *req) { if (!drv || !drv->init) { return false; } memset(&drv->mode, 0, sizeof(drv->mode)); if (!drv->init(drv, req)) { fprintf(stderr, "accelVid: Failed to initialize %s\n", drv->name); return false; } printf("accelVid: Initialized %s at %ldx%ldx%ld (pitch=%ld, vram=%luKB)\n", drv->name, (long)drv->mode.width, (long)drv->mode.height, (long)drv->mode.bpp, (long)drv->mode.pitch, (unsigned long)(drv->mode.vramSize / 1024)); // Report capabilities printf("accelVid: Capabilities:"); if (drv->caps & ACAP_RECT_FILL) { printf(" RectFill"); } if (drv->caps & ACAP_RECT_FILL_PAT) { printf(" PatFill"); } if (drv->caps & ACAP_BITBLT) { printf(" BitBlt"); } if (drv->caps & ACAP_COLOR_EXPAND) { printf(" ColorExpand"); } if (drv->caps & ACAP_LINE_DRAW) { printf(" LineDraw"); } if (drv->caps & ACAP_HW_CURSOR) { printf(" HwCursor"); } if (drv->caps & ACAP_HOST_BLIT) { printf(" HostBlit"); } if (drv->caps & ACAP_CLIP) { printf(" Clip"); } if (drv->caps & ACAP_TRANSPARENCY) { printf(" Transparency"); } printf("\n"); // Install software fallbacks for any operations the driver // didn't implement in hardware swInstallFallbacks(drv); return true; } // ============================================================ // accelRegisterDriver // ============================================================ void accelRegisterDriver(AccelDriverT *drv) { if (sDriverCount >= MAX_DRIVERS) { fprintf(stderr, "accelVid: Too many drivers registered (max %d)\n", MAX_DRIVERS); return; } sDrivers[sDriverCount++] = drv; } // ============================================================ // accelShutdown // ============================================================ void accelShutdown(AccelDriverT *drv) { if (!drv) { return; } if (drv->waitIdle) { drv->waitIdle(drv); } if (drv->showCursor) { drv->showCursor(drv, false); } if (drv->shutdown) { drv->shutdown(drv); } memset(&drv->mode, 0, sizeof(drv->mode)); } // ============================================================ // Software fallback implementations // ============================================================ // // These draw directly to the LFB. They're correct but slow // (uncached PCI writes). The point isn't performance -- it's // ensuring every operation is always callable so the caller // never needs to check for NULL function pointers. // ============================================================ // swBitBlt // ============================================================ // // Screen-to-screen blit via the LFB. Handles overlapping regions // by choosing copy direction. static void swBitBlt(AccelDriverT *drv, int32_t srcX, int32_t srcY, int32_t dstX, int32_t dstY, int32_t w, int32_t h) { if (w <= 0 || h <= 0) { return; } uint8_t *fb = drv->mode.framebuffer; int32_t pitch = drv->mode.pitch; int32_t bpp = (drv->mode.bpp + 7) / 8; int32_t rowBytes = w * bpp; if (dstY < srcY || (dstY == srcY && dstX <= srcX)) { // Copy forward (top to bottom, left to right) for (int32_t row = 0; row < h; row++) { uint8_t *src = fb + (srcY + row) * pitch + srcX * bpp; uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp; memmove(dst, src, rowBytes); } } else { // Copy backward (bottom to top) for (int32_t row = h - 1; row >= 0; row--) { uint8_t *src = fb + (srcY + row) * pitch + srcX * bpp; uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp; memmove(dst, src, rowBytes); } } } // ============================================================ // swColorExpand // ============================================================ // // Monochrome-to-color expansion via the LFB. Each 1-bit in srcBuf // becomes the fg color, each 0-bit becomes the bg color. static void swColorExpand(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h, uint32_t fg, uint32_t bg) { if (w <= 0 || h <= 0) { return; } uint8_t *fb = drv->mode.framebuffer; int32_t pitch = drv->mode.pitch; int32_t bpp = (drv->mode.bpp + 7) / 8; for (int32_t row = 0; row < h; row++) { const uint8_t *mono = srcBuf + row * srcPitch; uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp; for (int32_t col = 0; col < w; col++) { int32_t byteIdx = col / 8; int32_t bitIdx = 7 - (col % 8); uint32_t color = (mono[byteIdx] >> bitIdx) & 1 ? fg : bg; switch (bpp) { case 1: dst[col] = (uint8_t)color; break; case 2: ((uint16_t *)dst)[col] = (uint16_t)color; break; case 4: ((uint32_t *)dst)[col] = color; break; } } } } // ============================================================ // swHostBlit // ============================================================ // // CPU-to-screen blit via the LFB. Just a memcpy per scanline. static void swHostBlit(AccelDriverT *drv, const uint8_t *srcBuf, int32_t srcPitch, int32_t dstX, int32_t dstY, int32_t w, int32_t h) { if (w <= 0 || h <= 0) { return; } uint8_t *fb = drv->mode.framebuffer; int32_t pitch = drv->mode.pitch; int32_t bpp = (drv->mode.bpp + 7) / 8; int32_t rowBytes = w * bpp; for (int32_t row = 0; row < h; row++) { const uint8_t *src = srcBuf + row * srcPitch; uint8_t *dst = fb + (dstY + row) * pitch + dstX * bpp; memcpy(dst, src, rowBytes); } } // ============================================================ // swLineDraw // ============================================================ // // Bresenham line draw via the LFB. static void swLineDraw(AccelDriverT *drv, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) { int32_t dx = abs(x2 - x1); int32_t dy = abs(y2 - y1); int32_t sx = (x1 < x2) ? 1 : -1; int32_t sy = (y1 < y2) ? 1 : -1; int32_t err = dx - dy; int32_t x = x1; int32_t y = y1; for (;;) { if (x >= sClipX && x < sClipX + sClipW && y >= sClipY && y < sClipY + sClipH) { swPutPixel(drv, x, y, color); } if (x == x2 && y == y2) { break; } int32_t e2 = 2 * err; if (e2 > -dy) { err -= dy; x += sx; } if (e2 < dx) { err += dx; y += sy; } } } // ============================================================ // swRectFill // ============================================================ // // Solid rectangle fill via the LFB. static void swRectFill(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { if (w <= 0 || h <= 0) { return; } uint8_t *fb = drv->mode.framebuffer; int32_t pitch = drv->mode.pitch; int32_t bpp = (drv->mode.bpp + 7) / 8; for (int32_t row = 0; row < h; row++) { uint8_t *dst = fb + (y + row) * pitch + x * bpp; switch (bpp) { case 1: memset(dst, (uint8_t)color, w); break; case 2: { uint16_t *dst16 = (uint16_t *)dst; for (int32_t col = 0; col < w; col++) { dst16[col] = (uint16_t)color; } break; } case 4: { uint32_t *dst32 = (uint32_t *)dst; for (int32_t col = 0; col < w; col++) { dst32[col] = color; } break; } } } } // ============================================================ // swRectFillPat // ============================================================ // // 8x8 monochrome pattern fill via the LFB. The pattern is 8 bytes, // one bit per pixel, MSB-first, row 0 first. Each 1-bit gets the // fg color, each 0-bit gets the bg color. The pattern tiles across // the destination rectangle with alignment to screen coordinates // (so patterns line up across adjacent fills). static void swRectFillPat(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *pattern, uint32_t fg, uint32_t bg) { if (w <= 0 || h <= 0) { return; } uint8_t *fb = drv->mode.framebuffer; int32_t pitch = drv->mode.pitch; int32_t bpp = (drv->mode.bpp + 7) / 8; for (int32_t row = 0; row < h; row++) { uint8_t patRow = pattern[(y + row) & 7]; uint8_t *dst = fb + (y + row) * pitch + x * bpp; for (int32_t col = 0; col < w; col++) { int32_t patBit = 7 - ((x + col) & 7); uint32_t color = (patRow >> patBit) & 1 ? fg : bg; switch (bpp) { case 1: dst[col] = (uint8_t)color; break; case 2: ((uint16_t *)dst)[col] = (uint16_t)color; break; case 4: ((uint32_t *)dst)[col] = color; break; } } } } // ============================================================ // swSetClip // ============================================================ // // Software clip rectangle for fallback line drawing. static void swSetClip(AccelDriverT *drv, int32_t x, int32_t y, int32_t w, int32_t h) { (void)drv; sClipX = x; sClipY = y; sClipW = w; sClipH = h; } // ============================================================ // swWaitIdle // ============================================================ // // No-op -- software operations complete synchronously. static void swWaitIdle(AccelDriverT *drv) { (void)drv; } // ============================================================ // swInstallFallbacks // ============================================================ // // Fills in software implementations for any NULL function // pointers in the driver struct. Called by accelInit() after // the chip driver's init() succeeds. This guarantees that // every drawing operation is always callable. static void swInstallFallbacks(AccelDriverT *drv) { int32_t count = 0; if (!drv->waitIdle) { drv->waitIdle = swWaitIdle; } if (!drv->setClip) { drv->setClip = swSetClip; count++; } if (!drv->rectFill) { drv->rectFill = swRectFill; count++; } if (!drv->bitBlt) { drv->bitBlt = swBitBlt; count++; } if (!drv->hostBlit) { drv->hostBlit = swHostBlit; count++; } if (!drv->colorExpand) { drv->colorExpand = swColorExpand; count++; } if (!drv->rectFillPat) { drv->rectFillPat = swRectFillPat; count++; } if (!drv->lineDraw) { drv->lineDraw = swLineDraw; count++; } // Initialize the software clip rect to full screen sClipX = 0; sClipY = 0; sClipW = drv->mode.width; sClipH = drv->mode.height; if (count > 0) { printf("accelVid: %ld operation(s) using software fallback\n", (long)count); } }