// vgaCommon.c -- Shared VGA register programming // // Implements read/write access to the five standard VGA register // groups. These are used by all chip-specific drivers for basic // mode setup before enabling acceleration. // // Important timing note: on real hardware, some registers require // specific sequencing (e.g. attribute controller must be reset via // a read of Input Status 1 before writing the index). These // functions handle the sequencing internally. #include "vgaCommon.h" #include "pci.h" #include #include #include #include #include #include #include // VESA mode scoring weights (same as DVX) #define MODE_SCORE_16BPP 100 #define MODE_SCORE_15BPP 90 #define MODE_SCORE_32BPP 85 #define MODE_SCORE_8BPP 70 #define MODE_SCORE_PREF_BPP 20 #define MODE_SCORE_EXACT_RES 10 // ============================================================ // Prototypes // ============================================================ bool dpmiMapFramebuffer(uint32_t physAddr, uint32_t size, DpmiMappingT *mapping); void dpmiUnmapFramebuffer(DpmiMappingT *mapping); uint32_t pciSizeBar(uint8_t bus, uint8_t dev, uint8_t func, uint8_t barReg); uint8_t vgaAttrRead(uint8_t index); void vgaAttrReset(void); void vgaAttrWrite(uint8_t index, uint8_t val); void vgaBlankScreen(bool blank); uint8_t vgaCrtcRead(uint8_t index); void vgaCrtcLock(void); void vgaCrtcUnlock(void); void vgaCrtcWrite(uint8_t index, uint8_t val); void vgaDacReadColor(uint8_t index, uint8_t *r, uint8_t *g, uint8_t *b); void vgaDacWriteColor(uint8_t index, uint8_t r, uint8_t g, uint8_t b); uint8_t vgaGfxRead(uint8_t index); void vgaGfxWrite(uint8_t index, uint8_t val); uint8_t vgaMiscRead(void); void vgaMiscWrite(uint8_t val); void vgaRestoreTextMode(void); uint8_t vgaSeqRead(uint8_t index); void vgaSeqWrite(uint8_t index, uint8_t val); bool vesaFindAndSetMode(int32_t reqW, int32_t reqH, int32_t reqBpp, VesaModeResultT *result); void vgaWaitVRetrace(void); // ============================================================ // dpmiMapFramebuffer // ============================================================ // // Maps a physical address region into the DJGPP near pointer // address space via DPMI. This is the three-step process that // every driver needs: // 1. Map physical address to linear address // 2. Lock the pages to prevent swapping // 3. Enable near pointers for direct C pointer access // // Returns true on success. On failure, mapping->ptr is NULL. bool dpmiMapFramebuffer(uint32_t physAddr, uint32_t size, DpmiMappingT *mapping) { __dpmi_meminfo info; memset(mapping, 0, sizeof(*mapping)); info.address = physAddr; info.size = size; if (__dpmi_physical_address_mapping(&info) != 0) { fprintf(stderr, "dpmiMap: Failed to map 0x%08lX (%lu bytes)\n", (unsigned long)physAddr, (unsigned long)size); return false; } __dpmi_meminfo lockInfo; lockInfo.address = info.address; lockInfo.size = size; __dpmi_lock_linear_region(&lockInfo); if (__djgpp_nearptr_enable() == 0) { fprintf(stderr, "dpmiMap: Failed to enable near pointers\n"); return false; } mapping->ptr = (uint8_t *)(info.address + __djgpp_conventional_base); mapping->linearAddr = info.address; mapping->size = size; return true; } // ============================================================ // dpmiUnmapFramebuffer // ============================================================ void dpmiUnmapFramebuffer(DpmiMappingT *mapping) { if (mapping->ptr) { __djgpp_nearptr_disable(); mapping->ptr = NULL; } } // ============================================================ // pciSizeBar // ============================================================ // // Determines the size of a PCI BAR by writing all 1s and reading // back the mask. Saves and restores the original BAR value. uint32_t pciSizeBar(uint8_t bus, uint8_t dev, uint8_t func, uint8_t barReg) { uint32_t saved = pciRead32(bus, dev, func, barReg); pciWrite32(bus, dev, func, barReg, 0xFFFFFFFF); uint32_t mask = pciRead32(bus, dev, func, barReg); pciWrite32(bus, dev, func, barReg, saved); // Decode: invert the writable bits, add 1 mask &= 0xFFFFFFF0; // mask off type bits if (mask == 0) { return 0; } return (~mask) + 1; } // ============================================================ // vesaFindAndSetMode // ============================================================ // // Enumerates VESA VBE modes, scores them against the requested // resolution and bpp, sets the best match with LFB enabled, and // returns the mode details. This replaces ~150 lines of identical // code in every driver. bool vesaFindAndSetMode(int32_t reqW, int32_t reqH, int32_t reqBpp, VesaModeResultT *result) { __dpmi_regs r; memset(result, 0, sizeof(*result)); // Get VBE controller info _farpokeb(_dos_ds, __tb + 0, 'V'); _farpokeb(_dos_ds, __tb + 1, 'B'); _farpokeb(_dos_ds, __tb + 2, 'E'); _farpokeb(_dos_ds, __tb + 3, '2'); memset(&r, 0, sizeof(r)); r.x.ax = 0x4F00; r.x.es = __tb >> 4; r.x.di = __tb & 0x0F; __dpmi_int(0x10, &r); if (r.x.ax != 0x004F) { fprintf(stderr, "vesaFindAndSetMode: VBE not available\n"); return false; } // Copy mode list before 4F01h overwrites __tb uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14); uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16); uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff; uint16_t modes[256]; int32_t modeCount = 0; for (int32_t i = 0; i < 256; i++) { uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2); if (mode == 0xFFFF) { break; } modes[modeCount++] = mode; } // Score each mode and find the best uint16_t bestMode = 0; int32_t bestScore = -1; for (int32_t i = 0; i < modeCount; i++) { memset(&r, 0, sizeof(r)); r.x.ax = 0x4F01; r.x.cx = modes[i]; r.x.es = __tb >> 4; r.x.di = __tb & 0x0F; __dpmi_int(0x10, &r); if (r.x.ax != 0x004F) { continue; } uint16_t attr = _farpeekw(_dos_ds, __tb + 0); int32_t w = _farpeekw(_dos_ds, __tb + 18); int32_t h = _farpeekw(_dos_ds, __tb + 20); int32_t bpp = _farpeekb(_dos_ds, __tb + 25); int32_t pitch = _farpeekw(_dos_ds, __tb + 16); uint32_t phys = _farpeekl(_dos_ds, __tb + 40); // Must have LFB and be a graphics mode if (!(attr & 0x0080) || !(attr & 0x0010)) { continue; } // Must meet requested resolution if (w < reqW || h < reqH) { continue; } // Only 8/15/16/32 bpp if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) { continue; } int32_t score = 0; if (bpp == 16) { score = MODE_SCORE_16BPP; } else if (bpp == 15) { score = MODE_SCORE_15BPP; } else if (bpp == 32) { score = MODE_SCORE_32BPP; } else { score = MODE_SCORE_8BPP; } if (bpp == reqBpp) { score += MODE_SCORE_PREF_BPP; } if (w == reqW && h == reqH) { score += MODE_SCORE_EXACT_RES; } if (score > bestScore) { bestScore = score; bestMode = modes[i]; result->width = w; result->height = h; result->bpp = bpp; result->pitch = pitch; result->lfbPhysAddr = phys; } } if (bestScore < 0) { fprintf(stderr, "vesaFindAndSetMode: No suitable mode for %ldx%ldx%ld\n", (long)reqW, (long)reqH, (long)reqBpp); return false; } // Set the mode with LFB enabled (bit 14) memset(&r, 0, sizeof(r)); r.x.ax = 0x4F02; r.x.bx = bestMode | 0x4000; // bit 14 = enable LFB __dpmi_int(0x10, &r); if (r.x.ax != 0x004F) { fprintf(stderr, "vesaFindAndSetMode: Failed to set mode 0x%04X\n", bestMode); return false; } return true; } // ============================================================ // vgaAttrRead // ============================================================ // // The attribute controller is unusual: reading Input Status 1 // resets its flip-flop so the next write to 0x3C0 is treated as // an index (not data). We must reset before every access. uint8_t vgaAttrRead(uint8_t index) { inportb(VGA_INPUT_STATUS_1); outportb(VGA_ATTR_INDEX, index); return inportb(VGA_ATTR_DATA_R); } // ============================================================ // vgaAttrReset // ============================================================ // // Resets the attribute controller flip-flop by reading Input // Status 1. After this, the next write to 0x3C0 is an index write. void vgaAttrReset(void) { inportb(VGA_INPUT_STATUS_1); } // ============================================================ // vgaAttrWrite // ============================================================ // // Writes to the attribute controller. The flip-flop mechanism // means we must: (1) read Input Status 1 to reset, (2) write // the index to 0x3C0, (3) write the data to 0x3C0. // Bit 5 of the index byte must be set to keep the palette // address source enabled (otherwise the screen goes black). void vgaAttrWrite(uint8_t index, uint8_t val) { inportb(VGA_INPUT_STATUS_1); outportb(VGA_ATTR_INDEX, index); outportb(VGA_ATTR_DATA_W, val); } // ============================================================ // vgaBlankScreen // ============================================================ // // Toggles the screen on/off by setting bit 5 of the sequencer // clocking mode register. Blanking prevents visible garbage // during mode transitions. void vgaBlankScreen(bool blank) { uint8_t val = vgaSeqRead(VGA_SEQ_CLOCK_MODE); if (blank) { val |= VGA_SEQ_SCREEN_OFF; } else { val &= ~VGA_SEQ_SCREEN_OFF; } vgaSeqWrite(VGA_SEQ_CLOCK_MODE, val); } // ============================================================ // vgaCrtcLock // ============================================================ // // Re-enables CRTC write protection by setting bit 7 of the // vertical sync end register. void vgaCrtcLock(void) { uint8_t val = vgaCrtcRead(VGA_CRTC_V_SYNC_END); vgaCrtcWrite(VGA_CRTC_V_SYNC_END, val | 0x80); } // ============================================================ // vgaCrtcRead // ============================================================ uint8_t vgaCrtcRead(uint8_t index) { outportb(VGA_CRTC_INDEX, index); return inportb(VGA_CRTC_DATA); } // ============================================================ // vgaCrtcUnlock // ============================================================ // // Disables CRTC write protection. Registers 0x00-0x07 of the // CRTC are protected by bit 7 of the vertical sync end register // (0x11). Clearing this bit allows writing to those registers. void vgaCrtcUnlock(void) { uint8_t val = vgaCrtcRead(VGA_CRTC_V_SYNC_END); vgaCrtcWrite(VGA_CRTC_V_SYNC_END, val & 0x7F); } // ============================================================ // vgaCrtcWrite // ============================================================ void vgaCrtcWrite(uint8_t index, uint8_t val) { outportb(VGA_CRTC_INDEX, index); outportb(VGA_CRTC_DATA, val); } // ============================================================ // vgaDacReadColor // ============================================================ // // Read one DAC palette entry. Write the index to 0x3C7, then // read three bytes (R, G, B) from 0x3C9. DAC values are 6-bit // (0-63) on standard VGA, 8-bit on some SVGA cards. void vgaDacReadColor(uint8_t index, uint8_t *r, uint8_t *g, uint8_t *b) { outportb(VGA_DAC_READ_ADDR, index); *r = inportb(VGA_DAC_DATA); *g = inportb(VGA_DAC_DATA); *b = inportb(VGA_DAC_DATA); } // ============================================================ // vgaDacWriteColor // ============================================================ // // Write one DAC palette entry. Write the starting index to 0x3C8, // then write three bytes (R, G, B) to 0x3C9. void vgaDacWriteColor(uint8_t index, uint8_t r, uint8_t g, uint8_t b) { outportb(VGA_DAC_WRITE_ADDR, index); outportb(VGA_DAC_DATA, r); outportb(VGA_DAC_DATA, g); outportb(VGA_DAC_DATA, b); } // ============================================================ // vgaGfxRead // ============================================================ uint8_t vgaGfxRead(uint8_t index) { outportb(VGA_GFX_INDEX, index); return inportb(VGA_GFX_DATA); } // ============================================================ // vgaGfxWrite // ============================================================ void vgaGfxWrite(uint8_t index, uint8_t val) { outportb(VGA_GFX_INDEX, index); outportb(VGA_GFX_DATA, val); } // ============================================================ // vgaMiscRead // ============================================================ uint8_t vgaMiscRead(void) { return inportb(VGA_MISC_OUT_R); } // ============================================================ // vgaMiscWrite // ============================================================ void vgaMiscWrite(uint8_t val) { outportb(VGA_MISC_OUT_W, val); } // ============================================================ // vgaRestoreTextMode // ============================================================ // // Restores VGA text mode 3 (80x25, 16 color). Uses INT 10h // because manually reprogramming all VGA registers for text mode // is error-prone and varies by chipset. The BIOS handles it // correctly for all VGA-compatible cards. void vgaRestoreTextMode(void) { __dpmi_regs r; memset(&r, 0, sizeof(r)); r.x.ax = 0x0003; __dpmi_int(0x10, &r); } // ============================================================ // vgaSeqRead // ============================================================ uint8_t vgaSeqRead(uint8_t index) { outportb(VGA_SEQ_INDEX, index); return inportb(VGA_SEQ_DATA); } // ============================================================ // vgaSeqWrite // ============================================================ void vgaSeqWrite(uint8_t index, uint8_t val) { outportb(VGA_SEQ_INDEX, index); outportb(VGA_SEQ_DATA, val); } // ============================================================ // vgaWaitVRetrace // ============================================================ // // Waits for the start of the next vertical retrace by spinning // on bit 3 of Input Status 1 (port 0x3DA). First waits for bit // to clear (if we're currently in retrace), then waits for it // to set (start of next retrace). void vgaWaitVRetrace(void) { // Wait for any current retrace to end while (inportb(VGA_INPUT_STATUS_1) & 0x08) { // spin } // Wait for next retrace to start while (!(inportb(VGA_INPUT_STATUS_1) & 0x08)) { // spin } }