VGA: Simplify scrolling implementation.
[seabios.git] / vgasrc / vgafb.c
1 // Code for manipulating VGA framebuffers.
2 //
3 // Copyright (C) 2009  Kevin O'Connor <kevin@koconnor.net>
4 // Copyright (C) 2001-2008 the LGPL VGABios developers Team
5 //
6 // This file may be distributed under the terms of the GNU LGPLv3 license.
7
8 #include "biosvar.h" // GET_BDA
9 #include "util.h" // memset_far
10 #include "vgatables.h" // find_vga_entry
11
12 // TODO
13 //  * extract hw code from framebuffer code
14 //  * use clear_screen() in scroll code
15 //  * normalize params (don't use AX/BX/CX/etc.)
16
17 // XXX
18 inline void
19 memcpy16_far(u16 d_seg, void *d_far, u16 s_seg, const void *s_far, size_t len)
20 {
21     memcpy_far(d_seg, d_far, s_seg, s_far, len);
22 }
23
24
25 /****************************************************************
26  * Screen scrolling
27  ****************************************************************/
28
29 static void
30 vgamem_copy_pl4(u8 xstart, u8 ysrc, u8 ydest, u8 cols, u8 nbcols,
31                 u8 cheight)
32 {
33     u16 src = ysrc * cheight * nbcols + xstart;
34     u16 dest = ydest * cheight * nbcols + xstart;
35     outw(0x0105, VGAREG_GRDC_ADDRESS);
36     u8 i;
37     for (i = 0; i < cheight; i++)
38         memcpy_far(SEG_GRAPH, (void*)(dest + i * nbcols)
39                    , SEG_GRAPH, (void*)(src + i * nbcols), cols);
40     outw(0x0005, VGAREG_GRDC_ADDRESS);
41 }
42
43 static void
44 vgamem_fill_pl4(u8 xstart, u8 ystart, u8 cols, u8 nbcols, u8 cheight,
45                 u8 attr)
46 {
47     u16 dest = ystart * cheight * nbcols + xstart;
48     outw(0x0205, VGAREG_GRDC_ADDRESS);
49     u8 i;
50     for (i = 0; i < cheight; i++)
51         memset_far(SEG_GRAPH, (void*)(dest + i * nbcols), attr, cols);
52     outw(0x0005, VGAREG_GRDC_ADDRESS);
53 }
54
55 static void
56 scroll_pl4(struct vgamode_s *vmode_g, int nblines, int attr
57            , struct cursorpos ul, struct cursorpos lr)
58 {
59     if (attr < 0)
60         attr = 0;
61     u8 dir = SCROLL_UP;
62     if (nblines < 0) {
63         nblines = -nblines;
64         dir = SCROLL_DOWN;
65     }
66     // Get the dimensions
67     u16 nbcols = GET_BDA(video_cols);
68     u8 cols = lr.x - ul.x + 1;
69
70     struct VideoParam_s *vparam_g = GET_GLOBAL(vmode_g->vparam);
71     u8 cheight = GET_GLOBAL(vparam_g->cheight);
72     if (dir == SCROLL_UP) {
73         u16 i;
74         for (i = ul.y; i <= lr.y; i++)
75             if (i + nblines > lr.y)
76                 vgamem_fill_pl4(ul.x, i, cols, nbcols, cheight,
77                                 attr);
78             else
79                 vgamem_copy_pl4(ul.x, i + nblines, i, cols,
80                                 nbcols, cheight);
81         return;
82     }
83     u16 i;
84     for (i = lr.y; i >= ul.y; i--)
85         if (i < ul.y + nblines)
86             vgamem_fill_pl4(ul.x, i, cols, nbcols, cheight,
87                             attr);
88         else
89             vgamem_copy_pl4(ul.x, i, i - nblines, cols,
90                             nbcols, cheight);
91 }
92
93 static void
94 vgamem_copy_cga(u8 xstart, u8 ysrc, u8 ydest, u8 cols, u8 nbcols,
95                 u8 cheight)
96 {
97     u16 src = ((ysrc * cheight * nbcols) >> 1) + xstart;
98     u16 dest = ((ydest * cheight * nbcols) >> 1) + xstart;
99     u8 i;
100     for (i = 0; i < cheight; i++)
101         if (i & 1)
102             memcpy_far(SEG_CTEXT, (void*)(0x2000 + dest + (i >> 1) * nbcols)
103                        , SEG_CTEXT, (void*)(0x2000 + src + (i >> 1) * nbcols)
104                        , cols);
105         else
106             memcpy_far(SEG_CTEXT, (void*)(dest + (i >> 1) * nbcols)
107                        , SEG_CTEXT, (void*)(src + (i >> 1) * nbcols), cols);
108 }
109
110 static void
111 vgamem_fill_cga(u8 xstart, u8 ystart, u8 cols, u8 nbcols, u8 cheight,
112                 u8 attr)
113 {
114     u16 dest = ((ystart * cheight * nbcols) >> 1) + xstart;
115     u8 i;
116     for (i = 0; i < cheight; i++)
117         if (i & 1)
118             memset_far(SEG_CTEXT, (void*)(0x2000 + dest + (i >> 1) * nbcols)
119                        , attr, cols);
120         else
121             memset_far(SEG_CTEXT, (void*)(dest + (i >> 1) * nbcols), attr, cols);
122 }
123
124 static void
125 scroll_cga(struct vgamode_s *vmode_g, int nblines, int attr
126             , struct cursorpos ul, struct cursorpos lr)
127 {
128     if (attr < 0)
129         attr = 0;
130     u8 dir = SCROLL_UP;
131     if (nblines < 0) {
132         nblines = -nblines;
133         dir = SCROLL_DOWN;
134     }
135     // Get the dimensions
136     u16 nbcols = GET_BDA(video_cols);
137     u8 cols = lr.x - ul.x + 1;
138
139     struct VideoParam_s *vparam_g = GET_GLOBAL(vmode_g->vparam);
140     u8 cheight = GET_GLOBAL(vparam_g->cheight);
141     u8 bpp = GET_GLOBAL(vmode_g->pixbits);
142     if (bpp == 2) {
143         ul.x <<= 1;
144         cols <<= 1;
145         nbcols <<= 1;
146     }
147     // if Scroll up
148     if (dir == SCROLL_UP) {
149         u16 i;
150         for (i = ul.y; i <= lr.y; i++)
151             if (i + nblines > lr.y)
152                 vgamem_fill_cga(ul.x, i, cols, nbcols, cheight,
153                                 attr);
154             else
155                 vgamem_copy_cga(ul.x, i + nblines, i, cols,
156                                 nbcols, cheight);
157         return;
158     }
159     u16 i;
160     for (i = lr.y; i >= ul.y; i--)
161         if (i < ul.y + nblines)
162             vgamem_fill_cga(ul.x, i, cols, nbcols, cheight,
163                             attr);
164         else
165             vgamem_copy_cga(ul.x, i, i - nblines, cols,
166                             nbcols, cheight);
167 }
168
169 static void
170 scroll_text(struct vgamode_s *vmode_g, int nblines, int attr
171             , struct cursorpos ul, struct cursorpos lr)
172 {
173     if (attr < 0)
174         attr = 0x07;
175     u8 dir = SCROLL_UP;
176     if (nblines < 0) {
177         nblines = -nblines;
178         dir = SCROLL_DOWN;
179     }
180     // Get the dimensions
181     u16 nbrows = GET_BDA(video_rows) + 1;
182     u16 nbcols = GET_BDA(video_cols);
183     u8 cols = lr.x - ul.x + 1;
184
185     // Compute the address
186     void *address_far = (void*)(SCREEN_MEM_START(nbcols, nbrows, ul.page));
187     dprintf(3, "Scroll, address %p (%d %d %02x)\n"
188             , address_far, nbrows, nbcols, ul.page);
189
190     if (dir == SCROLL_UP) {
191         u16 i;
192         for (i = ul.y; i <= lr.y; i++)
193             if (i + nblines > lr.y)
194                 memset16_far(GET_GLOBAL(vmode_g->sstart)
195                              , address_far + (i * nbcols + ul.x) * 2
196                              , (u16)attr * 0x100 + ' ', cols * 2);
197             else
198                 memcpy16_far(GET_GLOBAL(vmode_g->sstart)
199                              , address_far + (i * nbcols + ul.x) * 2
200                              , GET_GLOBAL(vmode_g->sstart)
201                              , (void*)(((i + nblines) * nbcols + ul.x) * 2)
202                              , cols * 2);
203         return;
204     }
205     u16 i;
206     for (i = lr.y; i >= ul.y; i--)
207         if (i < ul.y + nblines)
208             memset16_far(GET_GLOBAL(vmode_g->sstart)
209                          , address_far + (i * nbcols + ul.x) * 2
210                          , (u16)attr * 0x100 + ' ', cols * 2);
211         else
212             memcpy16_far(GET_GLOBAL(vmode_g->sstart)
213                          , address_far + (i * nbcols + ul.x) * 2
214                          , GET_GLOBAL(vmode_g->sstart)
215                          , (void*)(((i - nblines) * nbcols + ul.x) * 2)
216                          , cols * 2);
217 }
218
219 void
220 vgafb_scroll(int nblines, int attr, struct cursorpos ul, struct cursorpos lr)
221 {
222     // Get the mode
223     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
224     if (!vmode_g)
225         return;
226
227     // FIXME gfx mode not complete
228     switch (GET_GLOBAL(vmode_g->memmodel)) {
229     case CTEXT:
230     case MTEXT:
231         scroll_text(vmode_g, nblines, attr, ul, lr);
232         break;
233     case PLANAR4:
234     case PLANAR1:
235         scroll_pl4(vmode_g, nblines, attr, ul, lr);
236         break;
237     case CGA:
238         scroll_cga(vmode_g, nblines, attr, ul, lr);
239         break;
240     default:
241         dprintf(1, "Scroll in graphics mode\n");
242     }
243 }
244
245 void
246 clear_screen(struct vgamode_s *vmode_g)
247 {
248     switch (GET_GLOBAL(vmode_g->memmodel)) {
249     case CTEXT:
250     case MTEXT:
251         memset16_far(GET_GLOBAL(vmode_g->sstart), 0, 0x0720, 32*1024);
252         break;
253     case CGA:
254         memset16_far(GET_GLOBAL(vmode_g->sstart), 0, 0x0000, 32*1024);
255         break;
256     default: {
257         outb(0x02, VGAREG_SEQU_ADDRESS);
258         u8 mmask = inb(VGAREG_SEQU_DATA);
259         outb(0x0f, VGAREG_SEQU_DATA);   // all planes
260         memset16_far(GET_GLOBAL(vmode_g->sstart), 0, 0x0000, 64*1024);
261         outb(mmask, VGAREG_SEQU_DATA);
262     }
263     }
264 }
265
266
267 /****************************************************************
268  * Read/write characters to screen
269  ****************************************************************/
270
271 static void
272 write_gfx_char_pl4(struct vgamode_s *vmode_g
273                    , struct cursorpos cp, struct carattr ca)
274 {
275     u16 nbcols = GET_BDA(video_cols);
276     if (cp.x >= nbcols)
277         return;
278
279     struct VideoParam_s *vparam_g = GET_GLOBAL(vmode_g->vparam);
280     u8 cheight = GET_GLOBAL(vparam_g->cheight);
281     u8 *fdata_g;
282     switch (cheight) {
283     case 14:
284         fdata_g = vgafont14;
285         break;
286     case 16:
287         fdata_g = vgafont16;
288         break;
289     default:
290         fdata_g = vgafont8;
291     }
292     u16 addr = cp.x + cp.y * cheight * nbcols;
293     u16 src = ca.car * cheight;
294     outw(0x0f02, VGAREG_SEQU_ADDRESS);
295     outw(0x0205, VGAREG_GRDC_ADDRESS);
296     if (ca.attr & 0x80)
297         outw(0x1803, VGAREG_GRDC_ADDRESS);
298     else
299         outw(0x0003, VGAREG_GRDC_ADDRESS);
300     u8 i;
301     for (i = 0; i < cheight; i++) {
302         u8 *dest_far = (void*)(addr + i * nbcols);
303         u8 j;
304         for (j = 0; j < 8; j++) {
305             u8 mask = 0x80 >> j;
306             outw((mask << 8) | 0x08, VGAREG_GRDC_ADDRESS);
307             GET_FARVAR(SEG_GRAPH, *dest_far);
308             if (GET_GLOBAL(fdata_g[src + i]) & mask)
309                 SET_FARVAR(SEG_GRAPH, *dest_far, ca.attr & 0x0f);
310             else
311                 SET_FARVAR(SEG_GRAPH, *dest_far, 0x00);
312         }
313     }
314     outw(0xff08, VGAREG_GRDC_ADDRESS);
315     outw(0x0005, VGAREG_GRDC_ADDRESS);
316     outw(0x0003, VGAREG_GRDC_ADDRESS);
317 }
318
319 static void
320 write_gfx_char_cga(struct vgamode_s *vmode_g
321                    , struct cursorpos cp, struct carattr ca)
322 {
323     u16 nbcols = GET_BDA(video_cols);
324     if (cp.x >= nbcols)
325         return;
326
327     u8 *fdata_g = vgafont8;
328     u8 bpp = GET_GLOBAL(vmode_g->pixbits);
329     u16 addr = (cp.x * bpp) + cp.y * 320;
330     u16 src = ca.car * 8;
331     u8 i;
332     for (i = 0; i < 8; i++) {
333         u8 *dest_far = (void*)(addr + (i >> 1) * 80);
334         if (i & 1)
335             dest_far += 0x2000;
336         u8 mask = 0x80;
337         if (bpp == 1) {
338             u8 data = 0;
339             if (ca.attr & 0x80)
340                 data = GET_FARVAR(SEG_CTEXT, *dest_far);
341             u8 j;
342             for (j = 0; j < 8; j++) {
343                 if (GET_GLOBAL(fdata_g[src + i]) & mask) {
344                     if (ca.attr & 0x80)
345                         data ^= (ca.attr & 0x01) << (7 - j);
346                     else
347                         data |= (ca.attr & 0x01) << (7 - j);
348                 }
349                 mask >>= 1;
350             }
351             SET_FARVAR(SEG_CTEXT, *dest_far, data);
352         } else {
353             while (mask > 0) {
354                 u8 data = 0;
355                 if (ca.attr & 0x80)
356                     data = GET_FARVAR(SEG_CTEXT, *dest_far);
357                 u8 j;
358                 for (j = 0; j < 4; j++) {
359                     if (GET_GLOBAL(fdata_g[src + i]) & mask) {
360                         if (ca.attr & 0x80)
361                             data ^= (ca.attr & 0x03) << ((3 - j) * 2);
362                         else
363                             data |= (ca.attr & 0x03) << ((3 - j) * 2);
364                     }
365                     mask >>= 1;
366                 }
367                 SET_FARVAR(SEG_CTEXT, *dest_far, data);
368                 dest_far += 1;
369             }
370         }
371     }
372 }
373
374 static void
375 write_gfx_char_lin(struct vgamode_s *vmode_g
376                    , struct cursorpos cp, struct carattr ca)
377 {
378     // Get the dimensions
379     u16 nbcols = GET_BDA(video_cols);
380     if (cp.x >= nbcols)
381         return;
382
383     u8 *fdata_g = vgafont8;
384     u16 addr = cp.x * 8 + cp.y * nbcols * 64;
385     u16 src = ca.car * 8;
386     u8 i;
387     for (i = 0; i < 8; i++) {
388         u8 *dest_far = (void*)(addr + i * nbcols * 8);
389         u8 mask = 0x80;
390         u8 j;
391         for (j = 0; j < 8; j++) {
392             u8 data = 0x00;
393             if (GET_GLOBAL(fdata_g[src + i]) & mask)
394                 data = ca.attr;
395             SET_FARVAR(SEG_GRAPH, dest_far[j], data);
396             mask >>= 1;
397         }
398     }
399 }
400
401 static void
402 write_text_char(struct vgamode_s *vmode_g
403                 , struct cursorpos cp, struct carattr ca)
404 {
405     // Get the dimensions
406     u16 nbrows = GET_BDA(video_rows) + 1;
407     u16 nbcols = GET_BDA(video_cols);
408
409     // Compute the address
410     void *address_far = (void*)(SCREEN_MEM_START(nbcols, nbrows, cp.page)
411                                 + (cp.x + cp.y * nbcols) * 2);
412
413     if (ca.use_attr) {
414         u16 dummy = (ca.attr << 8) | ca.car;
415         SET_FARVAR(GET_GLOBAL(vmode_g->sstart), *(u16*)address_far, dummy);
416     } else {
417         SET_FARVAR(GET_GLOBAL(vmode_g->sstart), *(u8*)address_far, ca.car);
418     }
419 }
420
421 void
422 vgafb_write_char(struct cursorpos cp, struct carattr ca)
423 {
424     // Get the mode
425     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
426     if (!vmode_g)
427         return;
428
429     // FIXME gfx mode not complete
430     switch (GET_GLOBAL(vmode_g->memmodel)) {
431     case CTEXT:
432     case MTEXT:
433         write_text_char(vmode_g, cp, ca);
434         break;
435     case PLANAR4:
436     case PLANAR1:
437         write_gfx_char_pl4(vmode_g, cp, ca);
438         break;
439     case CGA:
440         write_gfx_char_cga(vmode_g, cp, ca);
441         break;
442     case LINEAR8:
443         write_gfx_char_lin(vmode_g, cp, ca);
444         break;
445     }
446 }
447
448 struct carattr
449 vgafb_read_char(struct cursorpos cp)
450 {
451     // Get the mode
452     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
453     if (!vmode_g)
454         goto fail;
455
456     if (!(GET_GLOBAL(vmode_g->memmodel) & TEXT)) {
457         // FIXME gfx mode
458         dprintf(1, "Read char in graphics mode\n");
459         goto fail;
460     }
461
462     // Get the dimensions
463     u16 nbrows = GET_BDA(video_rows) + 1;
464     u16 nbcols = GET_BDA(video_cols);
465
466     // Compute the address
467     u16 *address_far = (void*)(SCREEN_MEM_START(nbcols, nbrows, cp.page)
468                                + (cp.x + cp.y * nbcols) * 2);
469     u16 v = GET_FARVAR(GET_GLOBAL(vmode_g->sstart), *address_far);
470     struct carattr ca = {v, v>>8, 0};
471     return ca;
472
473 fail: ;
474     struct carattr ca2 = {0, 0, 0};
475     return ca2;
476 }
477
478
479 /****************************************************************
480  * Read/write pixels
481  ****************************************************************/
482
483 void
484 biosfn_write_pixel(u8 BH, u8 AL, u16 CX, u16 DX)
485 {
486     // Get the mode
487     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
488     if (!vmode_g)
489         return;
490     if (GET_GLOBAL(vmode_g->memmodel) & TEXT)
491         return;
492
493     u8 *addr_far, mask, attr, data;
494     switch (GET_GLOBAL(vmode_g->memmodel)) {
495     case PLANAR4:
496     case PLANAR1:
497         addr_far = (void*)(CX / 8 + DX * GET_BDA(video_cols));
498         mask = 0x80 >> (CX & 0x07);
499         outw((mask << 8) | 0x08, VGAREG_GRDC_ADDRESS);
500         outw(0x0205, VGAREG_GRDC_ADDRESS);
501         data = GET_FARVAR(SEG_GRAPH, *addr_far);
502         if (AL & 0x80)
503             outw(0x1803, VGAREG_GRDC_ADDRESS);
504         SET_FARVAR(SEG_GRAPH, *addr_far, AL);
505         outw(0xff08, VGAREG_GRDC_ADDRESS);
506         outw(0x0005, VGAREG_GRDC_ADDRESS);
507         outw(0x0003, VGAREG_GRDC_ADDRESS);
508         break;
509     case CGA:
510         if (GET_GLOBAL(vmode_g->pixbits) == 2)
511             addr_far = (void*)((CX >> 2) + (DX >> 1) * 80);
512         else
513             addr_far = (void*)((CX >> 3) + (DX >> 1) * 80);
514         if (DX & 1)
515             addr_far += 0x2000;
516         data = GET_FARVAR(SEG_CTEXT, *addr_far);
517         if (GET_GLOBAL(vmode_g->pixbits) == 2) {
518             attr = (AL & 0x03) << ((3 - (CX & 0x03)) * 2);
519             mask = 0x03 << ((3 - (CX & 0x03)) * 2);
520         } else {
521             attr = (AL & 0x01) << (7 - (CX & 0x07));
522             mask = 0x01 << (7 - (CX & 0x07));
523         }
524         if (AL & 0x80) {
525             data ^= attr;
526         } else {
527             data &= ~mask;
528             data |= attr;
529         }
530         SET_FARVAR(SEG_CTEXT, *addr_far, data);
531         break;
532     case LINEAR8:
533         addr_far = (void*)(CX + DX * (GET_BDA(video_cols) * 8));
534         SET_FARVAR(SEG_GRAPH, *addr_far, AL);
535         break;
536     }
537 }
538
539 void
540 biosfn_read_pixel(u8 BH, u16 CX, u16 DX, u16 *AX)
541 {
542     // Get the mode
543     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
544     if (!vmode_g)
545         return;
546     if (GET_GLOBAL(vmode_g->memmodel) & TEXT)
547         return;
548
549     u8 *addr_far, mask, attr=0, data, i;
550     switch (GET_GLOBAL(vmode_g->memmodel)) {
551     case PLANAR4:
552     case PLANAR1:
553         addr_far = (void*)(CX / 8 + DX * GET_BDA(video_cols));
554         mask = 0x80 >> (CX & 0x07);
555         attr = 0x00;
556         for (i = 0; i < 4; i++) {
557             outw((i << 8) | 0x04, VGAREG_GRDC_ADDRESS);
558             data = GET_FARVAR(SEG_GRAPH, *addr_far) & mask;
559             if (data > 0)
560                 attr |= (0x01 << i);
561         }
562         break;
563     case CGA:
564         addr_far = (void*)((CX >> 2) + (DX >> 1) * 80);
565         if (DX & 1)
566             addr_far += 0x2000;
567         data = GET_FARVAR(SEG_CTEXT, *addr_far);
568         if (GET_GLOBAL(vmode_g->pixbits) == 2)
569             attr = (data >> ((3 - (CX & 0x03)) * 2)) & 0x03;
570         else
571             attr = (data >> (7 - (CX & 0x07))) & 0x01;
572         break;
573     case LINEAR8:
574         addr_far = (void*)(CX + DX * (GET_BDA(video_cols) * 8));
575         attr = GET_FARVAR(SEG_GRAPH, *addr_far);
576         break;
577     }
578     *AX = (*AX & 0xff00) | attr;
579 }
580
581
582 /****************************************************************
583  * Font loading
584  ****************************************************************/
585
586 void
587 vgafb_load_font(u16 seg, void *src_far, u16 count
588                 , u16 start, u8 destflags, u8 fontsize)
589 {
590     get_font_access();
591     u16 blockaddr = ((destflags & 0x03) << 14) + ((destflags & 0x04) << 11);
592     void *dest_far = (void*)(blockaddr + start*32);
593     u16 i;
594     for (i = 0; i < count; i++)
595         memcpy_far(SEG_GRAPH, dest_far + i*32
596                    , seg, src_far + i*fontsize, fontsize);
597     release_font_access();
598 }