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