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

505 lines
15 KiB
C

// 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 <dpmi.h>
#include <go32.h>
#include <pc.h>
#include <stdio.h>
#include <string.h>
#include <sys/farptr.h>
#include <sys/nearptr.h>
// 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
}
}