Mercurial > vesa
comparison vbe.c @ 0:cbded07e50d8 default tip
*: initial commit
here's a simple VBE thing. It's intended to be able to work
pretty much anywhere, but I don't really have anything to
test VBE 1.0 with (and i'm not entirely sure if this code
works in the first place)
it's balls simple, no BS, and ONLY works with DJGPP.
| author | Paper <paper@tflc.us> |
|---|---|
| date | Sat, 02 Aug 2025 12:55:21 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:cbded07e50d8 |
|---|---|
| 1 /** | |
| 2 * VBE.C -- interface into VESA BIOS extensions from DJGPP-compiled | |
| 3 * MS-DOS programs. | |
| 4 * | |
| 5 * Copyright (c) 2025 Paper | |
| 6 * | |
| 7 * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 8 * of this software and associated documentation files (the "Software"), to deal | |
| 9 * in the Software without restriction, including without limitation the rights | |
| 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 11 * copies of the Software, and to permit persons to whom the Software is | |
| 12 * furnished to do so, subject to the following conditions: | |
| 13 * | |
| 14 * The above copyright notice and this permission notice shall be included in all | |
| 15 * copies or substantial portions of the Software. | |
| 16 * | |
| 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 23 * SOFTWARE. | |
| 24 **/ | |
| 25 | |
| 26 #include <stdio.h> | |
| 27 #include <stdint.h> | |
| 28 #include <dpmi.h> | |
| 29 #include <go32.h> | |
| 30 #include <sys/farptr.h> | |
| 31 #include <stdlib.h> | |
| 32 #include <string.h> | |
| 33 #include <inttypes.h> | |
| 34 #include <dos.h> | |
| 35 | |
| 36 #include "vbe.h" | |
| 37 | |
| 38 /* pack all structures */ | |
| 39 #pragma pack(push, 1) | |
| 40 | |
| 41 struct VbeInfoBlock { | |
| 42 char VbeSignature[4]; | |
| 43 uint16_t VbeVersion; | |
| 44 uint16_t OemStringPtr[2]; | |
| 45 uint8_t Capabilities[4]; | |
| 46 uint16_t VideoModePtr[2]; | |
| 47 uint16_t TotalMemory; /* in units of 64kB blocks */ | |
| 48 uint8_t Reserved[492]; | |
| 49 }; | |
| 50 | |
| 51 struct VbeModeInfoBlock { | |
| 52 uint16_t Attributes; /* deprecated */ | |
| 53 uint8_t WindowA; /* deprecated */ | |
| 54 uint8_t WindowB; /* deprecated */ | |
| 55 uint16_t Granularity; /* deprecated */ | |
| 56 uint16_t WindowSize; | |
| 57 uint16_t SegmentA; | |
| 58 uint16_t SegmentB; | |
| 59 uint32_t WinFuncPtr; /* deprecated */ | |
| 60 uint16_t Pitch; /* bytes per horizontal line */ | |
| 61 | |
| 62 /* --- OPTIONAL UNTIL VBE 1.2 */ | |
| 63 uint16_t Width; /* in pixels */ | |
| 64 uint16_t Height; /* in pixels */ | |
| 65 uint8_t WChar; /* unused */ | |
| 66 uint8_t YChar; /* unused */ | |
| 67 uint8_t Planes; | |
| 68 uint8_t BitsPerPixel; | |
| 69 uint8_t Banks; /* deprecated */ | |
| 70 uint8_t MemoryModel; | |
| 71 uint8_t BankSize; /* deprecated, almost always 64kB but maybe 16kB */ | |
| 72 uint8_t ImagePages; | |
| 73 uint8_t Reserved0[1]; | |
| 74 | |
| 75 /* Direct color fields */ | |
| 76 uint8_t RedMask; | |
| 77 uint8_t RedPosition; | |
| 78 uint8_t GreenMask; | |
| 79 uint8_t GreenPosition; | |
| 80 uint8_t BlueMask; | |
| 81 uint8_t BluePosition; | |
| 82 uint8_t ReservedMask; | |
| 83 uint8_t ReservedPosition; | |
| 84 uint8_t DirectColorAttributes; | |
| 85 | |
| 86 /* --- OPTIONAL UNTIL VBE 2.0 */ | |
| 87 uint32_t Framebuffer; /* physical address of the linear framebuffer */ | |
| 88 | |
| 89 uint8_t Reserved1[6]; | |
| 90 | |
| 91 /* --- OPTIONAL UNTIL VBE 3.0 */ | |
| 92 uint16_t LinBytesPerScanLine; /* bytes per scan line for linear modes */ | |
| 93 uint8_t BnkNumberOfImagePages; /* number of images for banked modes */ | |
| 94 uint8_t LinNumberOfImagePages; /* number of images for linear modes */ | |
| 95 uint8_t LinRedMaskSize; /* size of direct color red mask (linear modes) */ | |
| 96 uint8_t LinRedFieldPosition; /* bit position of lsb of red mask (linear modes) */ | |
| 97 uint8_t LinGreenMaskSize; /* size of direct color green mask (linear modes) */ | |
| 98 uint8_t LinGreenFieldPosition; /* bit position of lsb of green mask (linear modes) */ | |
| 99 uint8_t LinBlueMaskSize; /* size of direct color blue mask (linear modes) */ | |
| 100 uint8_t LinBlueFieldPosition; /* bit position of lsb of blue mask (linear modes) */ | |
| 101 uint8_t LinRsvdMaskSize; /* size of direct color reserved mask (linear modes) */ | |
| 102 uint8_t LinRsvdFieldPosition; /* bit position of lsb of reserved mask (linear modes) */ | |
| 103 uint32_t MaxPixelClock; /* maximum pixel clock (in Hz) for graphics mode */ | |
| 104 | |
| 105 uint8_t Reserved2[189]; | |
| 106 }; | |
| 107 | |
| 108 #pragma pack(pop) | |
| 109 | |
| 110 #define VMI_ATTRIBUTE_HARDWARE 0x01 | |
| 111 #define VMI_ATTRIBUTE_RESERVED 0x02 /* only relevant for VBE < 1.2 */ | |
| 112 #define VMI_ATTRIBUTE_TTY 0x04 | |
| 113 #define VMI_ATTRIBUTE_COLOR 0x08 | |
| 114 #define VMI_ATTRIBUTE_GRAPHICS 0x10 | |
| 115 #define VMI_ATTRIBUTE_VGACOMPAT 0x20 | |
| 116 #define VMI_ATTRIBUTE_VGAWINMEM 0x40 | |
| 117 #define VMI_ATTRIBUTE_LINEARBUFFER 0x80 /* linear framebuffer */ | |
| 118 #define VMI_ATTRIBUTE_DOUBLESCAN 0x100 /* ? */ | |
| 119 #define VMI_ATTRIBUTE_INTERLACE 0x200 | |
| 120 #define VMI_ATTRIBUTE_TRIPLEBUF 0x400 | |
| 121 #define VMI_ATTRIBUTE_STEREOSCOPIC 0x800 /* huh? 3D?? */ | |
| 122 | |
| 123 /* check if a selection of attributes is enabled */ | |
| 124 #define VMI_ATTRIBUTE(x, a) (((x) & (a)) == (a)) | |
| 125 | |
| 126 #define MIN(a, b) ((a)<(b)?(a):(b)) | |
| 127 | |
| 128 #define VBE_FARPTR(x) (((x)[1] * 16) + (x)[0]) | |
| 129 | |
| 130 /* ------------------------------------------------------------------------ */ | |
| 131 /* this code is kind of a mess, sorry... */ | |
| 132 | |
| 133 /* loosely based off the information on the osdev wiki, | |
| 134 * thanks a lot! | |
| 135 * | |
| 136 * this function is a thin wrapper around real-mode | |
| 137 * VESA routines. `id` is the identifier that goes into | |
| 138 * the AX register, and `ptr` and `s` are the pointer and | |
| 139 * size the resulting structure is supposed to be. | |
| 140 * `cx`, is obviously the "cx" register. this only needs | |
| 141 * to be provided if the underlying function requires it. */ | |
| 142 static int vesa_get_block(uint16_t id, uint16_t cx, void *ptr, size_t s) | |
| 143 { | |
| 144 __dpmi_regs r; | |
| 145 long dosbuf; | |
| 146 size_t c; | |
| 147 | |
| 148 /* use the conventional memory transfer buffer */ | |
| 149 dosbuf = (__tb & 0xFFFFF); | |
| 150 | |
| 151 /* zero-initialize */ | |
| 152 for (c = 0; c < s; c++) | |
| 153 _farpokeb(_dos_ds, dosbuf + c, 0); | |
| 154 | |
| 155 /* this isn't necessary for functions other than | |
| 156 * 0x4F00, but I suppose it won't hurt. */ | |
| 157 dosmemput("VBE2", 4, dosbuf); | |
| 158 | |
| 159 r.x.ax = id; | |
| 160 r.x.cx = cx; | |
| 161 r.x.di = dosbuf & 0xF; | |
| 162 r.x.es = (dosbuf >> 4) & 0xFFFF; | |
| 163 __dpmi_int(0x10, &r); | |
| 164 | |
| 165 /* high byte is the result code, | |
| 166 * low byte must always be 0x4F */ | |
| 167 if (r.x.ax != 0x004F) | |
| 168 return -1; | |
| 169 | |
| 170 /* copy the resulting data into our structure */ | |
| 171 dosmemget(dosbuf, s, ptr); | |
| 172 | |
| 173 return 0; | |
| 174 } | |
| 175 | |
| 176 static int vesa_get_info(struct VbeInfoBlock *block) | |
| 177 { | |
| 178 if (vesa_get_block(0x4F00, 0, block, sizeof(*block)) != 0) | |
| 179 return -1; | |
| 180 | |
| 181 if (memcmp(block->VbeSignature, "VESA", 4) != 0) | |
| 182 return -1; | |
| 183 | |
| 184 return 0; | |
| 185 } | |
| 186 | |
| 187 /* This could probably be a public API. I just don't care enough. :) */ | |
| 188 | |
| 189 typedef void (*vesa_video_mode_cb_spec)(uint16_t mode, | |
| 190 struct VbeModeInfoBlock *pvmi, void *userdata); | |
| 191 | |
| 192 static int vesa_enum_video_modes(const struct VbeInfoBlock *pvbe, | |
| 193 vesa_video_mode_cb_spec cb, void *userdata) | |
| 194 { | |
| 195 struct VbeModeInfoBlock vmi; | |
| 196 uint16_t mode; | |
| 197 uint32_t ptr; | |
| 198 | |
| 199 for (ptr = VBE_FARPTR(pvbe->VideoModePtr); /* nothing */; ptr += 2) { | |
| 200 mode = _farpeekw(_dos_ds, ptr); | |
| 201 if (mode == 0xFFFF) | |
| 202 break; /* end marker */ | |
| 203 | |
| 204 if (vesa_get_block(0x4F01, mode, &vmi, sizeof(vmi)) != 0) | |
| 205 continue; /* ...? */ | |
| 206 | |
| 207 cb(mode, &vmi, userdata); | |
| 208 } | |
| 209 | |
| 210 return 0; | |
| 211 } | |
| 212 | |
| 213 struct vesa_best_mode_info { | |
| 214 uint16_t width; | |
| 215 uint16_t height; | |
| 216 | |
| 217 /* video mode score; higher is better */ | |
| 218 int16_t score; | |
| 219 /* video mode */ | |
| 220 uint16_t mode; | |
| 221 struct VbeModeInfoBlock vmi; | |
| 222 }; | |
| 223 | |
| 224 static void vesa_best_mode_cb(uint16_t mode, struct VbeModeInfoBlock *pvmi, | |
| 225 void *userdata) | |
| 226 { | |
| 227 struct vesa_best_mode_info *info = userdata; | |
| 228 int16_t score = 0; | |
| 229 | |
| 230 /* need a graphics mode. linear framebuffer is optional ;) | |
| 231 * we also need the "extended" bits that were optional in VBE <1.2. | |
| 232 * newer versions require that this bit be 1, so... */ | |
| 233 if (!VMI_ATTRIBUTE(pvmi->Attributes, VMI_ATTRIBUTE_RESERVED|VMI_ATTRIBUTE_COLOR|VMI_ATTRIBUTE_GRAPHICS|VMI_ATTRIBUTE_HARDWARE)) | |
| 234 return; | |
| 235 | |
| 236 /* put significant emphasis on resolution, then | |
| 237 * prefer higher bpp if available. */ | |
| 238 if (!(pvmi->Width % info->width)) | |
| 239 score += 16 / (pvmi->Width / info->width); | |
| 240 | |
| 241 if (!(pvmi->Height % info->height)) | |
| 242 score += 16 / (pvmi->Width / info->width); | |
| 243 | |
| 244 score += (pvmi->BitsPerPixel / 8); | |
| 245 | |
| 246 if (score > info->score) { | |
| 247 info->score = score; | |
| 248 info->mode = mode; | |
| 249 memcpy(&info->vmi, pvmi, sizeof(info->vmi)); | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 static int vesa_find_best_mode(struct VbeInfoBlock *vbe, uint16_t width, | |
| 254 uint16_t height, uint16_t *pmode, struct VbeModeInfoBlock *pvmi) | |
| 255 { | |
| 256 struct vesa_best_mode_info bmi = {0}; | |
| 257 | |
| 258 /* start at non-zero, so we always get at least one valid video mode | |
| 259 * (that is, if the video card actually gives us any) */ | |
| 260 bmi.width = width; | |
| 261 bmi.height = height; | |
| 262 bmi.score = -1; | |
| 263 | |
| 264 vesa_enum_video_modes(vbe, vesa_best_mode_cb, &bmi); | |
| 265 | |
| 266 if (bmi.score == -1) | |
| 267 return -1; | |
| 268 | |
| 269 *pmode = bmi.mode; | |
| 270 memcpy(pvmi, &bmi.vmi, sizeof(*pvmi)); | |
| 271 | |
| 272 return 0; | |
| 273 } | |
| 274 | |
| 275 static int vesa_set_mode(uint16_t mode) | |
| 276 { | |
| 277 __dpmi_regs r; | |
| 278 | |
| 279 r.x.ax = 0x4F02; | |
| 280 r.x.bx = mode; | |
| 281 | |
| 282 __dpmi_int(0x10, &r); | |
| 283 | |
| 284 if (r.x.ax != 0x004F) | |
| 285 return -1; | |
| 286 | |
| 287 return 0; | |
| 288 } | |
| 289 | |
| 290 static int vesa_set_bank(int bank_number) | |
| 291 { | |
| 292 __dpmi_regs r; | |
| 293 | |
| 294 r.x.ax = 0x4F05; | |
| 295 r.x.bx = 0; | |
| 296 r.x.dx = bank_number; | |
| 297 | |
| 298 __dpmi_int(0x10, &r); | |
| 299 | |
| 300 if (r.x.ax != 0x004F) | |
| 301 return -1; | |
| 302 | |
| 303 return 0; | |
| 304 } | |
| 305 | |
| 306 /* ------------------------------------------------------------------------ */ | |
| 307 /* plain VGA stuff for resetting the video mode */ | |
| 308 | |
| 309 static void vga_set_mode(uint8_t mode) | |
| 310 { | |
| 311 __dpmi_regs r; | |
| 312 | |
| 313 r.h.ah = 0x00; | |
| 314 r.h.al = mode; | |
| 315 | |
| 316 __dpmi_int(0x10, &r); | |
| 317 } | |
| 318 | |
| 319 /* ------------------------------------------------------------------------ */ | |
| 320 /* public functions and structures */ | |
| 321 | |
| 322 static int vesa_init_fb(struct vbe *vbe, uint16_t mode, uint32_t fb_address) | |
| 323 { | |
| 324 /* since we're running from protected mode, we have to allocate | |
| 325 * a logical address that maps to the physical framebuffer. */ | |
| 326 __dpmi_meminfo mi; | |
| 327 | |
| 328 return -1; | |
| 329 | |
| 330 if (vesa_set_mode(mode | 0x4000) != 0) | |
| 331 return -1; | |
| 332 | |
| 333 mi.address = fb_address; | |
| 334 mi.size = vbe->size; | |
| 335 | |
| 336 if (__dpmi_physical_address_mapping(&mi) != 0) | |
| 337 return -1; | |
| 338 | |
| 339 vbe->blit.fb.selector = __dpmi_allocate_ldt_descriptors(1); | |
| 340 if (vbe->blit.fb.selector == -1) | |
| 341 return -1; | |
| 342 | |
| 343 if (__dpmi_set_segment_base_address(vbe->blit.fb.selector, mi.address) != 0) | |
| 344 return -1; | |
| 345 | |
| 346 if (__dpmi_set_segment_limit(vbe->blit.fb.selector, mi.size - 1) != 0) | |
| 347 return -1; | |
| 348 | |
| 349 vbe->blit.fb.address = mi.address; | |
| 350 | |
| 351 return 0; | |
| 352 } | |
| 353 | |
| 354 int vbe_init(struct vbe *vbe, uint16_t width, uint16_t height) | |
| 355 { | |
| 356 struct VbeInfoBlock vib; | |
| 357 struct VbeModeInfoBlock vmi; | |
| 358 uint16_t mode; | |
| 359 | |
| 360 memset(vbe, 0, sizeof(*vbe)); | |
| 361 | |
| 362 if (vesa_get_info(&vib) != 0) | |
| 363 return -1; | |
| 364 | |
| 365 if (vesa_find_best_mode(&vib, width, height, &mode, &vmi) != 0) | |
| 366 return -1; | |
| 367 | |
| 368 /* I'm fairly sure this can be 15, for 5R-5G-5B. Whatever. :) */ | |
| 369 if (vmi.BitsPerPixel % 8) | |
| 370 return -1; | |
| 371 | |
| 372 vbe->width = vmi.Width; | |
| 373 vbe->height = vmi.Height; | |
| 374 vbe->pitch = vmi.Pitch; | |
| 375 vbe->bpp = (vmi.BitsPerPixel / 8); | |
| 376 | |
| 377 vbe->size = (vbe->pitch * vbe->height * vbe->bpp); | |
| 378 | |
| 379 if (VMI_ATTRIBUTE(vmi.Attributes, VMI_ATTRIBUTE_LINEARBUFFER) | |
| 380 && !vesa_init_fb(vbe, mode, vmi.Framebuffer)) { | |
| 381 vbe->blit_type = VBE_BLIT_FRAMEBUFFER; | |
| 382 } else if (!vesa_set_mode(mode)) { | |
| 383 vbe->blit_type = VBE_BLIT_BANKS; | |
| 384 | |
| 385 vbe->blit.banks.size = vmi.WindowSize * 1024; | |
| 386 vbe->blit.banks.granularity = vmi.Granularity * 1024; | |
| 387 } else { | |
| 388 return -1; | |
| 389 } | |
| 390 | |
| 391 return 0; | |
| 392 } | |
| 393 | |
| 394 static inline __attribute__((__always_inline__)) void farcpy(int selector, | |
| 395 uint32_t offset, const void *ptr, uint32_t size) | |
| 396 { | |
| 397 if (!size) | |
| 398 return; /* LOL */ | |
| 399 | |
| 400 /* do the bulk of the copying with longs */ | |
| 401 while (size >= 4) { | |
| 402 _farpokel(selector, offset, *(const uint32_t *)ptr); | |
| 403 ptr = (const char *)ptr + 4; | |
| 404 offset += 4; | |
| 405 size -= 4; | |
| 406 } | |
| 407 | |
| 408 /* now, copy any remaining bytes. */ | |
| 409 while (size > 0) { | |
| 410 _farpokeb(selector, offset, *(const uint8_t *)ptr); | |
| 411 ptr = (const char *)ptr + 1; | |
| 412 offset++; | |
| 413 size--; | |
| 414 } | |
| 415 } | |
| 416 | |
| 417 void vbe_blit(struct vbe *vbe, const void *ptr) | |
| 418 { | |
| 419 switch (vbe->blit_type) { | |
| 420 case VBE_BLIT_FRAMEBUFFER: { | |
| 421 farcpy(vbe->blit.fb.selector, 0, ptr, vbe->size); | |
| 422 break; | |
| 423 } | |
| 424 case VBE_BLIT_BANKS: { | |
| 425 const uint32_t bank_size = vbe->blit.banks.size; | |
| 426 const uint32_t bank_granularity = vbe->blit.banks.granularity; | |
| 427 int bank_number = 0; | |
| 428 int todo = vbe->size; | |
| 429 | |
| 430 while (todo > 0) { | |
| 431 int copy_size; | |
| 432 | |
| 433 /* select the appropriate bank */ | |
| 434 vesa_set_bank(bank_number); | |
| 435 | |
| 436 /* how much can we copy in one go? */ | |
| 437 copy_size = MIN(todo, bank_size); | |
| 438 | |
| 439 /* copy a bank of data to the screen */ | |
| 440 dosmemput(ptr, copy_size, 0xA0000); | |
| 441 | |
| 442 /* move on to the next bank of data */ | |
| 443 todo -= copy_size; | |
| 444 ptr = (const char *)ptr + copy_size; | |
| 445 bank_number += (bank_size / bank_granularity); | |
| 446 } | |
| 447 break; | |
| 448 } | |
| 449 default: | |
| 450 /* nope ? */ | |
| 451 break; | |
| 452 } | |
| 453 } | |
| 454 | |
| 455 void vbe_quit(struct vbe *vbe) | |
| 456 { | |
| 457 /* reset VGA mode to DOS text mode */ | |
| 458 vga_set_mode(0x03); | |
| 459 } |
