939a679609d63612de50a32e7ac54d203ad928c9
[retrobench] / src / dos / gfx.c
1 #include <stdlib.h>
2 #include <string.h>
3 #include <dos.h>
4 #include "cdpmi.h"
5 #include "gfx.h"
6 #include "vbe.h"
7 #include "vga.h"
8 #include "util.h"
9
10
11 #define SAME_BPP(a, b)  \
12         ((a) == (b) || ((a) == 16 && (b) == 15) || ((a) == 15 && (b) == 16) || \
13          ((a) == 32 && (b) == 24) || ((a) == 24 && (b) == 32))
14
15 void (*blit_frame)(void*, int);
16
17 int resizefb(int x, int y, int bpp, int pitch);
18
19 static void blit_frame_lfb(void *pixels, int vsync);
20 static void blit_frame_banked(void *pixels, int vsync);
21 static uint32_t calc_mask(int sz, int pos);
22
23 static void enable_wrcomb(uint32_t addr, int len);
24 static void print_mtrr(void);
25
26 static struct video_mode *vmodes;
27 static int num_vmodes;
28
29 static int vbe_init_ver;
30 static struct vbe_info vbe;
31
32 /* current mode */
33 static struct video_mode *curmode;
34 static void *vpgaddr[2];
35 static int frontidx, backidx;
36 static int pgcount, pgsize, fbsize;
37
38
39 int init_video(void)
40 {
41         int i, num, max_modes;
42         struct video_mode *vmptr;
43
44         if(vbe_info(&vbe) == -1) {
45                 fprintf(stderr, "failed to retrieve VBE information\n");
46                 return -1;
47         }
48         vbe_print_info(stdout, &vbe);
49
50         num_vmodes = 0;
51         max_modes = 64;
52         if(!(vmodes = malloc(max_modes * sizeof *vmodes))) {
53                 fprintf(stderr, "failed to allocate video modes list\n");
54                 return -1;
55         }
56
57         num = vbe_num_modes(&vbe);
58         for(i=0; i<num; i++) {
59                 struct vbe_mode_info minf;
60
61                 if(vbe_mode_info(vbe.modes[i], &minf) == -1) {
62                         continue;
63                 }
64
65                 if(num_vmodes >= max_modes) {
66                         int newmax = max_modes ? (max_modes << 1) : 16;
67                         if(!(vmptr = realloc(vmodes, newmax * sizeof *vmodes))) {
68                                 fprintf(stderr, "failed to grow video mode list (%d)\n", newmax);
69                                 free(vmodes);
70                                 return -1;
71                         }
72                         vmodes = vmptr;
73                         max_modes = newmax;
74                 }
75
76                 vmptr = vmodes + num_vmodes++;
77                 memset(vmptr, 0, sizeof *vmptr);
78                 vmptr->mode = vbe.modes[i];
79                 vmptr->xsz = minf.xres;
80                 vmptr->ysz = minf.yres;
81                 vmptr->bpp = minf.bpp;
82                 vmptr->pitch = minf.scanline_bytes;
83                 if(minf.mem_model == VBE_TYPE_DIRECT) {
84                         vmptr->rbits = minf.rsize;
85                         vmptr->gbits = minf.gsize;
86                         vmptr->bbits = minf.bsize;
87                         vmptr->rshift = minf.rpos;
88                         vmptr->gshift = minf.gpos;
89                         vmptr->bshift = minf.bpos;
90                         vmptr->rmask = calc_mask(minf.rsize, minf.rpos);
91                         vmptr->gmask = calc_mask(minf.gsize, minf.gpos);
92                         vmptr->bmask = calc_mask(minf.bsize, minf.bpos);
93                         /*vmptr->bpp = vmptr->rbits + vmptr->gbits + vmptr->bbits;*/
94                 }
95                 if(minf.attr & VBE_ATTR_LFB) {
96                         vmptr->fb_addr = minf.fb_addr;
97                 }
98                 vmptr->max_pages = minf.num_img_pages;
99                 vmptr->win_gran = minf.win_gran;
100
101                 printf("%04x: ", vbe.modes[i]);
102                 vbe_print_mode_info(stdout, &minf);
103         }
104         fflush(stdout);
105
106         vbe_init_ver = VBE_VER_MAJOR(vbe.ver);
107         return 0;
108 }
109
110 void cleanup_video(void)
111 {
112         free(vmodes);
113         vmodes = 0;
114 }
115
116 struct video_mode *video_modes(void)
117 {
118         return vmodes;
119 }
120
121 int num_video_modes(void)
122 {
123         return num_vmodes;
124 }
125
126 struct video_mode *get_video_mode(int idx)
127 {
128         if(idx == VMODE_CURRENT) {
129                 return curmode;
130         }
131         return vmodes + idx;
132 }
133
134 int match_video_mode(int xsz, int ysz, int bpp)
135 {
136         int i, best = -1;
137         struct video_mode *vm;
138
139         for(i=0; i<num_vmodes; i++) {
140                 vm = vmodes + i;
141                 if(vm->xsz != xsz || vm->ysz != ysz) continue;
142                 if(SAME_BPP(vm->bpp, bpp)) {
143                         best = i;
144                 }
145                 if(vm->bpp == bpp) break;
146         }
147
148         if(best == -1) {
149                 fprintf(stderr, "failed to find video mode %dx%d %d bpp)\n", xsz, ysz, bpp);
150                 return -1;
151         }
152         return best;
153 }
154
155 int find_video_mode(int mode)
156 {
157         int i;
158         struct video_mode *vm;
159
160         vm = vmodes;
161         for(i=0; i<num_vmodes; i++) {
162                 if(vm->mode == mode) return i;
163         }
164         return -1;
165 }
166
167 void *set_video_mode(int idx, int nbuf)
168 {
169         unsigned int mode;
170         struct video_mode *vm = vmodes + idx;
171         struct cpuid_info cpu;
172
173         if(curmode == vm) return vpgaddr[0];
174
175         printf("setting video mode %x (%dx%d %d bpp)\n", (unsigned int)vm->mode,
176                         vm->xsz, vm->ysz, vm->bpp);
177         fflush(stdout);
178
179         mode = vm->mode | VBE_MODE_LFB;
180         if(vbe_setmode(mode) == -1) {
181                 mode = vm->mode;
182                 if(vbe_setmode(mode) == -1) {
183                         fprintf(stderr, "failed to set video mode %x\n", (unsigned int)vm->mode);
184                         return 0;
185                 }
186                 printf("Warning: failed to get a linear framebuffer. falling back to banked mode\n");
187         }
188
189         /* unmap previous video memory mapping, if there was one (switching modes) */
190         if(vpgaddr[0] && vpgaddr[0] != (void*)0xa0000) {
191                 dpmi_munmap(vpgaddr[0]);
192                 vpgaddr[0] = vpgaddr[1] = 0;
193         }
194
195         curmode = vm;
196         if(nbuf < 1) nbuf = 1;
197         if(nbuf > 2) nbuf = 2;
198         pgcount = nbuf > vm->max_pages + 1 ? vm->max_pages + 1 : nbuf;
199         pgsize = vm->ysz * vm->pitch;
200         fbsize = pgcount * pgsize;
201
202         printf("pgcount: %d, pgsize: %d, fbsize: %d\n", pgcount, pgsize, fbsize);
203         printf("phys addr: %p\n", (void*)vm->fb_addr);
204         fflush(stdout);
205
206         if(vm->fb_addr) {
207                 vpgaddr[0] = (void*)dpmi_mmap(vm->fb_addr, fbsize);
208                 if(!vpgaddr[0]) {
209                         fprintf(stderr, "failed to map framebuffer (phys: %lx, size: %d)\n",
210                                         (unsigned long)vm->fb_addr, fbsize);
211                         set_text_mode();
212                         return 0;
213                 }
214                 memset(vpgaddr[0], 0xaa, pgsize);
215
216                 if(pgcount > 1) {
217                         vpgaddr[1] = (char*)vpgaddr[0] + pgsize;
218                         backidx = 1;
219                         page_flip(FLIP_NOW);    /* start with the second page visible */
220                 } else {
221                         frontidx = backidx = 0;
222                         vpgaddr[1] = 0;
223                 }
224
225                 blit_frame = blit_frame_lfb;
226
227                 if(read_cpuid(&cpu) != -1 && cpu.feat & CPUID_FEAT_MTRR) {
228                         print_mtrr();
229                         enable_wrcomb(vm->fb_addr, fbsize);
230                 }
231
232         } else {
233                 vpgaddr[0] = (void*)0xa0000;
234                 vpgaddr[1] = 0;
235
236                 blit_frame = blit_frame_banked;
237
238                 /* calculate window granularity shift */
239                 vm->win_gran_shift = 0;
240                 vm->win_64k_step = 1;
241                 if(vm->win_gran > 0 && vm->win_gran < 64) {
242                         int gran = vm->win_gran;
243                         while(gran < 64) {
244                                 vm->win_gran_shift++;
245                                 gran <<= 1;
246                         }
247                         vm->win_64k_step = 1 << vm->win_gran_shift;
248                 }
249
250                 printf("granularity: %dk (step: %d)\n", vm->win_gran, vm->win_64k_step);
251         }
252
253         /* allocate main memory framebuffer */
254         if(resizefb(vm->xsz, vm->ysz, vm->bpp, vm->pitch) == -1) {
255                 fprintf(stderr, "failed to allocate %dx%d (%d bpp) framebuffer\n", vm->xsz,
256                                 vm->ysz, vm->bpp);
257                 set_text_mode();
258                 return 0;
259         }
260
261         fflush(stdout);
262         return vpgaddr[0];
263 }
264
265 int set_text_mode(void)
266 {
267         /* unmap previous video memory mapping, if there was one (switching modes) */
268         if(vpgaddr[0] && vpgaddr[0] != (void*)0xa0000) {
269                 dpmi_munmap(vpgaddr[0]);
270                 vpgaddr[0] = vpgaddr[1] = 0;
271         }
272
273         vga_setmode(3);
274         curmode = 0;
275         return 0;
276 }
277
278 void *page_flip(int vsync)
279 {
280         if(!vpgaddr[1]) {
281                 /* page flipping not supported */
282                 return vpgaddr[0];
283         }
284
285         vbe_swap(backidx ? pgsize : 0, vsync ? VBE_SWAP_VBLANK : VBE_SWAP_NOW);
286         frontidx = backidx;
287         backidx = (backidx + 1) & 1;
288
289         return vpgaddr[backidx];
290 }
291
292
293 static void blit_frame_lfb(void *pixels, int vsync)
294 {
295         if(vsync) wait_vsync();
296         memcpy64(vpgaddr[frontidx], pixels, pgsize >> 3);
297 }
298
299 static void blit_frame_banked(void *pixels, int vsync)
300 {
301         int sz, offs, pending;
302         unsigned char *pptr = pixels;
303
304         if(vsync) wait_vsync();
305
306         /* assume initial window offset at 0 */
307         offs = 0;
308         pending = pgsize;
309         while(pending > 0) {
310                 sz = pending > 65536 ? 65536 : pending;
311                 //memcpy64((void*)0xa0000, pptr, sz >> 3);
312                 memcpy((void*)0xa0000, pptr, sz);
313                 pptr += sz;
314                 pending -= sz;
315                 offs += curmode->win_64k_step;
316                 vbe_setwin(0, offs);
317         }
318         vbe_setwin(0, 0);
319 }
320
321 static uint32_t calc_mask(int sz, int pos)
322 {
323         uint32_t mask = 0;
324         while(sz-- > 0) {
325                 mask = (mask << 1) | 1;
326         }
327         return mask << pos;
328 }
329
330 #define get_msr(msr, low, high) \
331         asm volatile( \
332                 "\r\trdmsr" \
333                 : "=a"(low), "=d"(high) \
334                 : "c"(msr))
335
336 #define set_msr(msr, low, high) \
337         asm volatile( \
338                 "\r\twrmsr" \
339                 :: "c"(msr), "a"(low), "d"(high))
340
341 #define MSR_MTRRCAP                     0xfe
342 #define MSR_MTRRDEFTYPE         0x2ff
343 #define MSR_MTRRBASE(x)         (0x200 | ((x) << 1))
344 #define MSR_MTRRMASK(x)         (0x201 | ((x) << 1))
345 #define MTRRDEF_EN                      0x800
346 #define MTRRCAP_HAVE_WC         0x400
347 #define MTRRMASK_VALID          0x800
348
349 #define MTRR_WC                         1
350
351 static int get_page_memtype(uint32_t addr, int num_ranges)
352 {
353         int i;
354         uint32_t rlow, rhigh;
355         uint32_t base, mask;
356
357         for(i=0; i<num_ranges; i++) {
358                 get_msr(MSR_MTRRMASK(i), rlow, rhigh);
359                 if(!(rlow & MTRRMASK_VALID)) {
360                         continue;
361                 }
362
363                 get_msr(MSR_MTRRBASE(i), rlow, rhigh);
364                 base = rlow & 0xfffff000;
365                 mask = rlow & 0xfffff000;
366
367                 if((addr & mask) == (base & mask)) {
368                         return rlow & 0xff;
369                 }
370         }
371
372         get_msr(MSR_MTRRDEFTYPE, rlow, rhigh);
373         return rlow & 0xff;
374 }
375
376 static int check_wrcomb_enabled(uint32_t addr, int len, int num_ranges)
377 {
378         while(len > 0) {
379                 if(get_page_memtype(addr, num_ranges) != MTRR_WC) {
380                         return 0;
381                 }
382                 addr += 4096;
383                 len -= 4096;
384         }
385         return 1;
386 }
387
388 static int alloc_mtrr(int num_ranges)
389 {
390         int i;
391         uint32_t rlow, rhigh;
392
393         for(i=0; i<num_ranges; i++) {
394                 get_msr(MSR_MTRRMASK(i), rlow, rhigh);
395                 if(!(rlow & MTRRMASK_VALID)) {
396                         return i;
397                 }
398         }
399         return -1;
400 }
401
402 static void enable_wrcomb(uint32_t addr, int len)
403 {
404         int num_ranges, mtrr;
405         uint32_t rlow, rhigh;
406         uint32_t def, mask;
407
408         if(len <= 0 || (addr | (uint32_t)len) & 0xfff) {
409                 fprintf(stderr, "failed to enable write combining, unaligned range: %p/%x\n",
410                                 (void*)addr, (unsigned int)len);
411                 return;
412         }
413
414         get_msr(MSR_MTRRCAP, rlow, rhigh);
415         num_ranges = rlow & 0xff;
416
417         printf("enable_wrcomb: addr=%p len=%x\n", (void*)addr, (unsigned int)len);
418
419         if(!(rlow & MTRRCAP_HAVE_WC)) {
420                 fprintf(stderr, "failed to enable write combining, processor doesn't support it\n");
421                 return;
422         }
423
424         if(check_wrcomb_enabled(addr, len, num_ranges)) {
425                 return;
426         }
427
428         if((mtrr = alloc_mtrr(num_ranges)) == -1) {
429                 fprintf(stderr, "failed to enable write combining, no free MTRRs\n");
430                 return;
431         }
432
433         mask = len - 1;
434         mask |= mask >> 1;
435         mask |= mask >> 2;
436         mask |= mask >> 4;
437         mask |= mask >> 8;
438         mask |= mask >> 16;
439         mask = ~mask & 0xfffff000;
440
441         printf("  ... mask: %08x\n", (unsigned int)mask);
442
443         disable();
444         get_msr(MSR_MTRRDEFTYPE, def, rhigh);
445         set_msr(MSR_MTRRDEFTYPE, def & ~MTRRDEF_EN, rhigh);
446
447         set_msr(MSR_MTRRBASE(mtrr), addr | MTRR_WC, 0);
448         set_msr(MSR_MTRRMASK(mtrr), mask | MTRRMASK_VALID, 0);
449
450         set_msr(MSR_MTRRDEFTYPE, def | MTRRDEF_EN, 0);
451         enable();
452 }
453
454 static const char *mtrr_names[] = { "N/A", "W C", "N/A", "N/A", "W T", "W P", "W B" };
455
456 static const char *mtrr_type_name(int type)
457 {
458         if(type < 0 || type >= sizeof mtrr_names / sizeof *mtrr_names) {
459                 return mtrr_names[0];
460         }
461         return mtrr_names[type];
462 }
463
464 static void print_mtrr(void)
465 {
466         int i, num_ranges;
467         uint32_t rlow, rhigh, base, mask;
468
469         get_msr(MSR_MTRRCAP, rlow, rhigh);
470         num_ranges = rlow & 0xff;
471
472         for(i=0; i<num_ranges; i++) {
473                 get_msr(MSR_MTRRBASE(i), base, rhigh);
474                 get_msr(MSR_MTRRMASK(i), mask, rhigh);
475
476                 if(mask & MTRRMASK_VALID) {
477                         printf("mtrr%d: base %p, mask %08x type %s\n", i, (void*)(base & 0xfffff000),
478                                         (unsigned int)(mask & 0xfffff000), mtrr_type_name(base & 0xff));
479                 } else {
480                         printf("mtrr%d unused (%08x/%08x)\n", i, (unsigned int)base,
481                                         (unsigned int)mask);
482                 }
483         }
484         fflush(stdout);
485 }