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 }