505 lines
15 KiB
C
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
|
|
}
|
|
}
|