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