VGA: Breakup biosfn_scroll.
[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     if (GET_GLOBAL(vmode_g->memmodel) & TEXT) {
288         memset16_far(GET_GLOBAL(vmode_g->sstart), 0, 0x0720, 32*1024);
289         return;
290     }
291     if (GET_GLOBAL(vmode_g->svgamode) < 0x0d) {
292         memset16_far(GET_GLOBAL(vmode_g->sstart), 0, 0x0000, 32*1024);
293         return;
294     }
295     outb(0x02, VGAREG_SEQU_ADDRESS);
296     u8 mmask = inb(VGAREG_SEQU_DATA);
297     outb(0x0f, VGAREG_SEQU_DATA);   // all planes
298     memset16_far(GET_GLOBAL(vmode_g->sstart), 0, 0x0000, 64*1024);
299     outb(mmask, VGAREG_SEQU_DATA);
300 }
301
302
303 /****************************************************************
304  * Read/write characters to screen
305  ****************************************************************/
306
307 static void
308 write_gfx_char_pl4(struct vgamode_s *vmode_g
309                    , struct cursorpos cp, struct carattr ca)
310 {
311     u16 nbcols = GET_BDA(video_cols);
312     if (cp.x >= nbcols)
313         return;
314
315     struct VideoParam_s *vparam_g = GET_GLOBAL(vmode_g->vparam);
316     u8 cheight = GET_GLOBAL(vparam_g->cheight);
317     u8 *fdata_g;
318     switch (cheight) {
319     case 14:
320         fdata_g = vgafont14;
321         break;
322     case 16:
323         fdata_g = vgafont16;
324         break;
325     default:
326         fdata_g = vgafont8;
327     }
328     u16 addr = cp.x + cp.y * cheight * nbcols;
329     u16 src = ca.car * cheight;
330     outw(0x0f02, VGAREG_SEQU_ADDRESS);
331     outw(0x0205, VGAREG_GRDC_ADDRESS);
332     if (ca.attr & 0x80)
333         outw(0x1803, VGAREG_GRDC_ADDRESS);
334     else
335         outw(0x0003, VGAREG_GRDC_ADDRESS);
336     u8 i;
337     for (i = 0; i < cheight; i++) {
338         u8 *dest_far = (void*)(addr + i * nbcols);
339         u8 j;
340         for (j = 0; j < 8; j++) {
341             u8 mask = 0x80 >> j;
342             outw((mask << 8) | 0x08, VGAREG_GRDC_ADDRESS);
343             GET_FARVAR(SEG_GRAPH, *dest_far);
344             if (GET_GLOBAL(fdata_g[src + i]) & mask)
345                 SET_FARVAR(SEG_GRAPH, *dest_far, ca.attr & 0x0f);
346             else
347                 SET_FARVAR(SEG_GRAPH, *dest_far, 0x00);
348         }
349     }
350     outw(0xff08, VGAREG_GRDC_ADDRESS);
351     outw(0x0005, VGAREG_GRDC_ADDRESS);
352     outw(0x0003, VGAREG_GRDC_ADDRESS);
353 }
354
355 static void
356 write_gfx_char_cga(struct vgamode_s *vmode_g
357                    , struct cursorpos cp, struct carattr ca)
358 {
359     u16 nbcols = GET_BDA(video_cols);
360     if (cp.x >= nbcols)
361         return;
362
363     u8 *fdata_g = vgafont8;
364     u8 bpp = GET_GLOBAL(vmode_g->pixbits);
365     u16 addr = (cp.x * bpp) + cp.y * 320;
366     u16 src = ca.car * 8;
367     u8 i;
368     for (i = 0; i < 8; i++) {
369         u8 *dest_far = (void*)(addr + (i >> 1) * 80);
370         if (i & 1)
371             dest_far += 0x2000;
372         u8 mask = 0x80;
373         if (bpp == 1) {
374             u8 data = 0;
375             if (ca.attr & 0x80)
376                 data = GET_FARVAR(SEG_CTEXT, *dest_far);
377             u8 j;
378             for (j = 0; j < 8; j++) {
379                 if (GET_GLOBAL(fdata_g[src + i]) & mask) {
380                     if (ca.attr & 0x80)
381                         data ^= (ca.attr & 0x01) << (7 - j);
382                     else
383                         data |= (ca.attr & 0x01) << (7 - j);
384                 }
385                 mask >>= 1;
386             }
387             SET_FARVAR(SEG_CTEXT, *dest_far, data);
388         } else {
389             while (mask > 0) {
390                 u8 data = 0;
391                 if (ca.attr & 0x80)
392                     data = GET_FARVAR(SEG_CTEXT, *dest_far);
393                 u8 j;
394                 for (j = 0; j < 4; j++) {
395                     if (GET_GLOBAL(fdata_g[src + i]) & mask) {
396                         if (ca.attr & 0x80)
397                             data ^= (ca.attr & 0x03) << ((3 - j) * 2);
398                         else
399                             data |= (ca.attr & 0x03) << ((3 - j) * 2);
400                     }
401                     mask >>= 1;
402                 }
403                 SET_FARVAR(SEG_CTEXT, *dest_far, data);
404                 dest_far += 1;
405             }
406         }
407     }
408 }
409
410 static void
411 write_gfx_char_lin(struct vgamode_s *vmode_g
412                    , struct cursorpos cp, struct carattr ca)
413 {
414     // Get the dimensions
415     u16 nbcols = GET_BDA(video_cols);
416     if (cp.x >= nbcols)
417         return;
418
419     u8 *fdata_g = vgafont8;
420     u16 addr = cp.x * 8 + cp.y * nbcols * 64;
421     u16 src = ca.car * 8;
422     u8 i;
423     for (i = 0; i < 8; i++) {
424         u8 *dest_far = (void*)(addr + i * nbcols * 8);
425         u8 mask = 0x80;
426         u8 j;
427         for (j = 0; j < 8; j++) {
428             u8 data = 0x00;
429             if (GET_GLOBAL(fdata_g[src + i]) & mask)
430                 data = ca.attr;
431             SET_FARVAR(SEG_GRAPH, dest_far[j], data);
432             mask >>= 1;
433         }
434     }
435 }
436
437 static void
438 write_text_char(struct vgamode_s *vmode_g
439                 , struct cursorpos cp, struct carattr ca)
440 {
441     // Get the dimensions
442     u16 nbrows = GET_BDA(video_rows) + 1;
443     u16 nbcols = GET_BDA(video_cols);
444
445     // Compute the address
446     void *address_far = (void*)(SCREEN_MEM_START(nbcols, nbrows, cp.page)
447                                 + (cp.x + cp.y * nbcols) * 2);
448
449     if (ca.use_attr) {
450         u16 dummy = (ca.attr << 8) | ca.car;
451         SET_FARVAR(GET_GLOBAL(vmode_g->sstart), *(u16*)address_far, dummy);
452     } else {
453         SET_FARVAR(GET_GLOBAL(vmode_g->sstart), *(u8*)address_far, ca.car);
454     }
455 }
456
457 void
458 vgafb_write_char(struct cursorpos cp, struct carattr ca)
459 {
460     // Get the mode
461     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
462     if (!vmode_g)
463         return;
464
465     // FIXME gfx mode not complete
466     switch (GET_GLOBAL(vmode_g->memmodel)) {
467     case CTEXT:
468     case MTEXT:
469         write_text_char(vmode_g, cp, ca);
470         break;
471     case PLANAR4:
472     case PLANAR1:
473         write_gfx_char_pl4(vmode_g, cp, ca);
474         break;
475     case CGA:
476         write_gfx_char_cga(vmode_g, cp, ca);
477         break;
478     case LINEAR8:
479         write_gfx_char_lin(vmode_g, cp, ca);
480         break;
481     }
482 }
483
484 struct carattr
485 vgafb_read_char(struct cursorpos cp)
486 {
487     // Get the mode
488     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
489     if (!vmode_g)
490         goto fail;
491
492     if (!(GET_GLOBAL(vmode_g->memmodel) & TEXT)) {
493         // FIXME gfx mode
494         dprintf(1, "Read char in graphics mode\n");
495         goto fail;
496     }
497
498     // Get the dimensions
499     u16 nbrows = GET_BDA(video_rows) + 1;
500     u16 nbcols = GET_BDA(video_cols);
501
502     // Compute the address
503     u16 *address_far = (void*)(SCREEN_MEM_START(nbcols, nbrows, cp.page)
504                                + (cp.x + cp.y * nbcols) * 2);
505     u16 v = GET_FARVAR(GET_GLOBAL(vmode_g->sstart), *address_far);
506     struct carattr ca = {v, v>>8, 0};
507     return ca;
508
509 fail: ;
510     struct carattr ca2 = {0, 0, 0};
511     return ca2;
512 }
513
514
515 /****************************************************************
516  * Read/write pixels
517  ****************************************************************/
518
519 void
520 biosfn_write_pixel(u8 BH, u8 AL, u16 CX, u16 DX)
521 {
522     // Get the mode
523     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
524     if (!vmode_g)
525         return;
526     if (GET_GLOBAL(vmode_g->memmodel) & TEXT)
527         return;
528
529     u8 *addr_far, mask, attr, data;
530     switch (GET_GLOBAL(vmode_g->memmodel)) {
531     case PLANAR4:
532     case PLANAR1:
533         addr_far = (void*)(CX / 8 + DX * GET_BDA(video_cols));
534         mask = 0x80 >> (CX & 0x07);
535         outw((mask << 8) | 0x08, VGAREG_GRDC_ADDRESS);
536         outw(0x0205, VGAREG_GRDC_ADDRESS);
537         data = GET_FARVAR(SEG_GRAPH, *addr_far);
538         if (AL & 0x80)
539             outw(0x1803, VGAREG_GRDC_ADDRESS);
540         SET_FARVAR(SEG_GRAPH, *addr_far, AL);
541         outw(0xff08, VGAREG_GRDC_ADDRESS);
542         outw(0x0005, VGAREG_GRDC_ADDRESS);
543         outw(0x0003, VGAREG_GRDC_ADDRESS);
544         break;
545     case CGA:
546         if (GET_GLOBAL(vmode_g->pixbits) == 2)
547             addr_far = (void*)((CX >> 2) + (DX >> 1) * 80);
548         else
549             addr_far = (void*)((CX >> 3) + (DX >> 1) * 80);
550         if (DX & 1)
551             addr_far += 0x2000;
552         data = GET_FARVAR(SEG_CTEXT, *addr_far);
553         if (GET_GLOBAL(vmode_g->pixbits) == 2) {
554             attr = (AL & 0x03) << ((3 - (CX & 0x03)) * 2);
555             mask = 0x03 << ((3 - (CX & 0x03)) * 2);
556         } else {
557             attr = (AL & 0x01) << (7 - (CX & 0x07));
558             mask = 0x01 << (7 - (CX & 0x07));
559         }
560         if (AL & 0x80) {
561             data ^= attr;
562         } else {
563             data &= ~mask;
564             data |= attr;
565         }
566         SET_FARVAR(SEG_CTEXT, *addr_far, data);
567         break;
568     case LINEAR8:
569         addr_far = (void*)(CX + DX * (GET_BDA(video_cols) * 8));
570         SET_FARVAR(SEG_GRAPH, *addr_far, AL);
571         break;
572     }
573 }
574
575 void
576 biosfn_read_pixel(u8 BH, u16 CX, u16 DX, u16 *AX)
577 {
578     // Get the mode
579     struct vgamode_s *vmode_g = find_vga_entry(GET_BDA(video_mode));
580     if (!vmode_g)
581         return;
582     if (GET_GLOBAL(vmode_g->memmodel) & TEXT)
583         return;
584
585     u8 *addr_far, mask, attr=0, data, i;
586     switch (GET_GLOBAL(vmode_g->memmodel)) {
587     case PLANAR4:
588     case PLANAR1:
589         addr_far = (void*)(CX / 8 + DX * GET_BDA(video_cols));
590         mask = 0x80 >> (CX & 0x07);
591         attr = 0x00;
592         for (i = 0; i < 4; i++) {
593             outw((i << 8) | 0x04, VGAREG_GRDC_ADDRESS);
594             data = GET_FARVAR(SEG_GRAPH, *addr_far) & mask;
595             if (data > 0)
596                 attr |= (0x01 << i);
597         }
598         break;
599     case CGA:
600         addr_far = (void*)((CX >> 2) + (DX >> 1) * 80);
601         if (DX & 1)
602             addr_far += 0x2000;
603         data = GET_FARVAR(SEG_CTEXT, *addr_far);
604         if (GET_GLOBAL(vmode_g->pixbits) == 2)
605             attr = (data >> ((3 - (CX & 0x03)) * 2)) & 0x03;
606         else
607             attr = (data >> (7 - (CX & 0x07))) & 0x01;
608         break;
609     case LINEAR8:
610         addr_far = (void*)(CX + DX * (GET_BDA(video_cols) * 8));
611         attr = GET_FARVAR(SEG_GRAPH, *addr_far);
612         break;
613     }
614     *AX = (*AX & 0xff00) | attr;
615 }
616
617
618 /****************************************************************
619  * Font loading
620  ****************************************************************/
621
622 void
623 vgafb_load_font(u16 seg, void *src_far, u16 count
624                 , u16 start, u8 destflags, u8 fontsize)
625 {
626     get_font_access();
627     u16 blockaddr = ((destflags & 0x03) << 14) + ((destflags & 0x04) << 11);
628     void *dest_far = (void*)(blockaddr + start*32);
629     u16 i;
630     for (i = 0; i < count; i++)
631         memcpy_far(SEG_GRAPH, dest_far + i*32
632                    , seg, src_far + i*fontsize, fontsize);
633     release_font_access();
634 }