1 | /* $NetBSD: vga.c,v 1.115 2015/03/01 07:05:59 mlelstv Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 1995, 1996 Carnegie-Mellon University. |
5 | * All rights reserved. |
6 | * |
7 | * Author: Chris G. Demetriou |
8 | * |
9 | * Permission to use, copy, modify and distribute this software and |
10 | * its documentation is hereby granted, provided that both the copyright |
11 | * notice and this permission notice appear in all copies of the |
12 | * software, derivative works or modified versions, and any portions |
13 | * thereof, and that both notices appear in supporting documentation. |
14 | * |
15 | * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" |
16 | * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND |
17 | * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. |
18 | * |
19 | * Carnegie Mellon requests users of this software to return to |
20 | * |
21 | * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU |
22 | * School of Computer Science |
23 | * Carnegie Mellon University |
24 | * Pittsburgh PA 15213-3890 |
25 | * |
26 | * any improvements or extensions that they make and grant Carnegie the |
27 | * rights to redistribute these changes. |
28 | */ |
29 | |
30 | #include <sys/cdefs.h> |
31 | __KERNEL_RCSID(0, "$NetBSD: vga.c,v 1.115 2015/03/01 07:05:59 mlelstv Exp $" ); |
32 | |
33 | #include "opt_vga.h" |
34 | /* for WSCONS_SUPPORT_PCVTFONTS */ |
35 | #include "opt_wsdisplay_compat.h" |
36 | /* for WSDISPLAY_CUSTOM_BORDER */ |
37 | #include "opt_wsdisplay_border.h" |
38 | /* for WSDISPLAY_CUSTOM_OUTPUT */ |
39 | #include "opt_wsmsgattrs.h" |
40 | |
41 | #include <sys/param.h> |
42 | #include <sys/systm.h> |
43 | #include <sys/callout.h> |
44 | #include <sys/kernel.h> |
45 | #include <sys/device.h> |
46 | #include <sys/malloc.h> |
47 | #include <sys/queue.h> |
48 | #include <sys/bus.h> |
49 | |
50 | #include <dev/ic/mc6845reg.h> |
51 | #include <dev/ic/pcdisplayvar.h> |
52 | #include <dev/ic/vgareg.h> |
53 | #include <dev/ic/vgavar.h> |
54 | |
55 | #include <dev/wscons/wsdisplayvar.h> |
56 | #include <dev/wscons/wsconsio.h> |
57 | #include <dev/wscons/unicode.h> |
58 | #include <dev/wsfont/wsfont.h> |
59 | |
60 | #include <dev/ic/pcdisplay.h> |
61 | |
62 | int vga_no_builtinfont = 0; |
63 | |
64 | static struct wsdisplay_font _vga_builtinfont = { |
65 | "builtin" , /* typeface name */ |
66 | 0, /* firstchar */ |
67 | 256, /* numbers */ |
68 | WSDISPLAY_FONTENC_IBM, /* encoding */ |
69 | 8, /* width */ |
70 | 16, /* height */ |
71 | 1, /* stride */ |
72 | WSDISPLAY_FONTORDER_L2R, /* bit order */ |
73 | 0, /* byte order */ |
74 | NULL /* data */ |
75 | }; |
76 | |
77 | struct egavga_font { |
78 | struct wsdisplay_font *wsfont; |
79 | int cookie; /* wsfont handle, -1 invalid */ |
80 | int slot; /* in adapter RAM */ |
81 | int usecount; |
82 | TAILQ_ENTRY(egavga_font) next; /* LRU queue */ |
83 | }; |
84 | |
85 | static struct egavga_font vga_builtinfont = { |
86 | .wsfont = &_vga_builtinfont, |
87 | .cookie = -1, |
88 | .slot = 0, |
89 | }; |
90 | |
91 | #ifdef VGA_CONSOLE_SCREENTYPE |
92 | static struct egavga_font vga_consolefont; |
93 | #endif |
94 | |
95 | struct vgascreen { |
96 | struct pcdisplayscreen pcs; |
97 | |
98 | LIST_ENTRY(vgascreen) next; |
99 | |
100 | struct vga_config *cfg; |
101 | |
102 | /* videostate */ |
103 | struct egavga_font *fontset1, *fontset2; |
104 | /* font data */ |
105 | |
106 | int mindispoffset, maxdispoffset; |
107 | int vga_rollover; |
108 | int visibleoffset; |
109 | }; |
110 | |
111 | static int vgaconsole, vga_console_type, vga_console_attached; |
112 | static struct vgascreen vga_console_screen; |
113 | static struct vga_config vga_console_vc; |
114 | |
115 | static struct egavga_font *egavga_getfont(struct vga_config *, struct vgascreen *, |
116 | const char *, int); |
117 | static void egavga_unreffont(struct vga_config *, struct egavga_font *); |
118 | |
119 | static int vga_selectfont(struct vga_config *, struct vgascreen *, const char *, |
120 | const char *); |
121 | static void vga_init_screen(struct vga_config *, struct vgascreen *, |
122 | const struct wsscreen_descr *, int, long *); |
123 | static void vga_init(struct vga_config *, bus_space_tag_t, bus_space_tag_t); |
124 | static void vga_setfont(struct vga_config *, struct vgascreen *); |
125 | |
126 | static int vga_mapchar(void *, int, unsigned int *); |
127 | static void vga_putchar(void *, int, int, u_int, long); |
128 | static int vga_allocattr(void *, int, int, int, long *); |
129 | static void vga_copyrows(void *, int, int, int); |
130 | #ifdef WSDISPLAY_SCROLLSUPPORT |
131 | static void vga_scroll (void *, void *, int); |
132 | #endif |
133 | |
134 | const struct wsdisplay_emulops vga_emulops = { |
135 | pcdisplay_cursor, |
136 | vga_mapchar, |
137 | vga_putchar, |
138 | pcdisplay_copycols, |
139 | pcdisplay_erasecols, |
140 | vga_copyrows, |
141 | pcdisplay_eraserows, |
142 | vga_allocattr, |
143 | #ifdef WSDISPLAY_CUSTOM_OUTPUT |
144 | pcdisplay_replaceattr, |
145 | #else |
146 | NULL, |
147 | #endif |
148 | }; |
149 | |
150 | /* |
151 | * translate WS(=ANSI) color codes to standard pc ones |
152 | */ |
153 | static const unsigned char fgansitopc[] = { |
154 | #ifdef __alpha__ |
155 | /* |
156 | * XXX DEC HAS SWITCHED THE CODES FOR BLUE AND RED!!! |
157 | * XXX We should probably not bother with this |
158 | * XXX (reinitialize the palette registers). |
159 | */ |
160 | FG_BLACK, FG_BLUE, FG_GREEN, FG_CYAN, FG_RED, |
161 | FG_MAGENTA, FG_BROWN, FG_LIGHTGREY |
162 | #else |
163 | FG_BLACK, FG_RED, FG_GREEN, FG_BROWN, FG_BLUE, |
164 | FG_MAGENTA, FG_CYAN, FG_LIGHTGREY |
165 | #endif |
166 | }, bgansitopc[] = { |
167 | #ifdef __alpha__ |
168 | BG_BLACK, BG_BLUE, BG_GREEN, BG_CYAN, BG_RED, |
169 | BG_MAGENTA, BG_BROWN, BG_LIGHTGREY |
170 | #else |
171 | BG_BLACK, BG_RED, BG_GREEN, BG_BROWN, BG_BLUE, |
172 | BG_MAGENTA, BG_CYAN, BG_LIGHTGREY |
173 | #endif |
174 | }; |
175 | |
176 | const struct wsscreen_descr vga_25lscreen = { |
177 | "80x25" , 80, 25, |
178 | &vga_emulops, |
179 | 8, 16, |
180 | WSSCREEN_WSCOLORS | WSSCREEN_HILIT | WSSCREEN_BLINK, |
181 | NULL, |
182 | }, vga_25lscreen_mono = { |
183 | "80x25" , 80, 25, |
184 | &vga_emulops, |
185 | 8, 16, |
186 | WSSCREEN_HILIT | WSSCREEN_UNDERLINE | WSSCREEN_BLINK | WSSCREEN_REVERSE, |
187 | NULL, |
188 | }, vga_25lscreen_bf = { |
189 | "80x25bf" , 80, 25, |
190 | &vga_emulops, |
191 | 8, 16, |
192 | WSSCREEN_WSCOLORS | WSSCREEN_BLINK, |
193 | NULL, |
194 | }, vga_40lscreen = { |
195 | "80x40" , 80, 40, |
196 | &vga_emulops, |
197 | 8, 10, |
198 | WSSCREEN_WSCOLORS | WSSCREEN_HILIT | WSSCREEN_BLINK, |
199 | NULL, |
200 | }, vga_40lscreen_mono = { |
201 | "80x40" , 80, 40, |
202 | &vga_emulops, |
203 | 8, 10, |
204 | WSSCREEN_HILIT | WSSCREEN_UNDERLINE | WSSCREEN_BLINK | WSSCREEN_REVERSE, |
205 | NULL, |
206 | }, vga_40lscreen_bf = { |
207 | "80x40bf" , 80, 40, |
208 | &vga_emulops, |
209 | 8, 10, |
210 | WSSCREEN_WSCOLORS | WSSCREEN_BLINK, |
211 | NULL, |
212 | }, vga_50lscreen = { |
213 | "80x50" , 80, 50, |
214 | &vga_emulops, |
215 | 8, 8, |
216 | WSSCREEN_WSCOLORS | WSSCREEN_HILIT | WSSCREEN_BLINK, |
217 | NULL, |
218 | }, vga_50lscreen_mono = { |
219 | "80x50" , 80, 50, |
220 | &vga_emulops, |
221 | 8, 8, |
222 | WSSCREEN_HILIT | WSSCREEN_UNDERLINE | WSSCREEN_BLINK | WSSCREEN_REVERSE, |
223 | NULL, |
224 | }, vga_50lscreen_bf = { |
225 | "80x50bf" , 80, 50, |
226 | &vga_emulops, |
227 | 8, 8, |
228 | WSSCREEN_WSCOLORS | WSSCREEN_BLINK, |
229 | NULL, |
230 | }, vga_24lscreen = { |
231 | "80x24" , 80, 24, |
232 | &vga_emulops, |
233 | 8, 16, |
234 | WSSCREEN_WSCOLORS | WSSCREEN_HILIT | WSSCREEN_BLINK, |
235 | NULL, |
236 | }, vga_24lscreen_mono = { |
237 | "80x24" , 80, 24, |
238 | &vga_emulops, |
239 | 8, 16, |
240 | WSSCREEN_HILIT | WSSCREEN_UNDERLINE | WSSCREEN_BLINK | WSSCREEN_REVERSE, |
241 | NULL, |
242 | }, vga_24lscreen_bf = { |
243 | "80x24bf" , 80, 24, |
244 | &vga_emulops, |
245 | 8, 16, |
246 | WSSCREEN_WSCOLORS | WSSCREEN_BLINK, |
247 | NULL, |
248 | }; |
249 | |
250 | #define VGA_SCREEN_CANTWOFONTS(type) (!((type)->capabilities & WSSCREEN_HILIT)) |
251 | |
252 | const struct wsscreen_descr *_vga_scrlist[] = { |
253 | &vga_25lscreen, |
254 | &vga_25lscreen_bf, |
255 | &vga_40lscreen, |
256 | &vga_40lscreen_bf, |
257 | &vga_50lscreen, |
258 | &vga_50lscreen_bf, |
259 | &vga_24lscreen, |
260 | &vga_24lscreen_bf, |
261 | /* XXX other formats, graphics screen? */ |
262 | }, *_vga_scrlist_mono[] = { |
263 | &vga_25lscreen_mono, |
264 | &vga_40lscreen_mono, |
265 | &vga_50lscreen_mono, |
266 | &vga_24lscreen_mono, |
267 | /* XXX other formats, graphics screen? */ |
268 | }; |
269 | |
270 | const struct wsscreen_list vga_screenlist = { |
271 | sizeof(_vga_scrlist) / sizeof(struct wsscreen_descr *), |
272 | _vga_scrlist |
273 | }, vga_screenlist_mono = { |
274 | sizeof(_vga_scrlist_mono) / sizeof(struct wsscreen_descr *), |
275 | _vga_scrlist_mono |
276 | }; |
277 | |
278 | static int vga_ioctl(void *, void *, u_long, void *, int, struct lwp *); |
279 | static paddr_t vga_mmap(void *, void *, off_t, int); |
280 | static int vga_alloc_screen(void *, const struct wsscreen_descr *, |
281 | void **, int *, int *, long *); |
282 | static void vga_free_screen(void *, void *); |
283 | static int vga_show_screen(void *, void *, int, |
284 | void (*)(void *, int, int), void *); |
285 | static int vga_load_font(void *, void *, struct wsdisplay_font *); |
286 | #ifdef WSDISPLAY_CUSTOM_BORDER |
287 | static int vga_getborder(struct vga_config *, u_int *); |
288 | static int vga_setborder(struct vga_config *, u_int); |
289 | #endif /* WSDISPLAY_CUSTOM_BORDER */ |
290 | |
291 | static void vga_doswitch(struct vga_config *); |
292 | static void vga_save_palette(struct vga_config *); |
293 | static void vga_restore_palette(struct vga_config *); |
294 | |
295 | |
296 | const struct wsdisplay_accessops vga_accessops = { |
297 | vga_ioctl, |
298 | vga_mmap, |
299 | vga_alloc_screen, |
300 | vga_free_screen, |
301 | vga_show_screen, |
302 | vga_load_font, |
303 | NULL, |
304 | #ifdef WSDISPLAY_SCROLLSUPPORT |
305 | vga_scroll, |
306 | #else |
307 | NULL, |
308 | #endif |
309 | }; |
310 | |
311 | /* |
312 | * We want at least ASCII 32..127 be present in the |
313 | * first font slot. |
314 | */ |
315 | #define vga_valid_primary_font(f) \ |
316 | (f->wsfont->encoding == WSDISPLAY_FONTENC_IBM || \ |
317 | f->wsfont->encoding == WSDISPLAY_FONTENC_ISO || \ |
318 | f->wsfont->encoding == WSDISPLAY_FONTENC_ISO2 || \ |
319 | f->wsfont->encoding == WSDISPLAY_FONTENC_ISO7 || \ |
320 | f->wsfont->encoding == WSDISPLAY_FONTENC_KOI8_R) |
321 | |
322 | static struct egavga_font * |
323 | egavga_getfont(struct vga_config *vc, struct vgascreen *scr, const char *name, |
324 | int primary) |
325 | { |
326 | struct egavga_font *f; |
327 | int cookie; |
328 | struct wsdisplay_font *wf; |
329 | |
330 | TAILQ_FOREACH(f, &vc->vc_fontlist, next) { |
331 | if (wsfont_matches(f->wsfont, name, |
332 | 8, scr->pcs.type->fontheight, 0, WSFONT_FIND_BITMAP) && |
333 | (!primary || vga_valid_primary_font(f))) { |
334 | #ifdef VGAFONTDEBUG |
335 | if (scr != &vga_console_screen || vga_console_attached) |
336 | printf("vga_getfont: %s already present\n" , |
337 | name ? name : "<default>" ); |
338 | #endif |
339 | goto found; |
340 | } |
341 | } |
342 | |
343 | cookie = wsfont_find(name, 8, scr->pcs.type->fontheight, 0, |
344 | WSDISPLAY_FONTORDER_L2R, 0, WSFONT_FIND_BITMAP); |
345 | /* XXX obey "primary" */ |
346 | if (cookie == -1) { |
347 | #ifdef VGAFONTDEBUG |
348 | if (scr != &vga_console_screen || vga_console_attached) |
349 | printf("vga_getfont: %s not found\n" , |
350 | name ? name : "<default>" ); |
351 | #endif |
352 | return (0); |
353 | } |
354 | |
355 | if (wsfont_lock(cookie, &wf)) |
356 | return (0); |
357 | |
358 | #ifdef VGA_CONSOLE_SCREENTYPE |
359 | if (scr == &vga_console_screen) |
360 | f = &vga_consolefont; |
361 | else |
362 | #endif |
363 | f = malloc(sizeof(struct egavga_font), M_DEVBUF, M_NOWAIT); |
364 | if (!f) { |
365 | wsfont_unlock(cookie); |
366 | return (0); |
367 | } |
368 | f->wsfont = wf; |
369 | f->cookie = cookie; |
370 | f->slot = -1; /* not yet loaded */ |
371 | f->usecount = 0; /* incremented below */ |
372 | TAILQ_INSERT_TAIL(&vc->vc_fontlist, f, next); |
373 | |
374 | found: |
375 | f->usecount++; |
376 | #ifdef VGAFONTDEBUG |
377 | if (scr != &vga_console_screen || vga_console_attached) |
378 | printf("vga_getfont: usecount=%d\n" , f->usecount); |
379 | #endif |
380 | return (f); |
381 | } |
382 | |
383 | static void |
384 | egavga_unreffont(struct vga_config *vc, struct egavga_font *f) |
385 | { |
386 | |
387 | f->usecount--; |
388 | #ifdef VGAFONTDEBUG |
389 | printf("vga_unreffont: usecount=%d\n" , f->usecount); |
390 | #endif |
391 | if (f->usecount == 0 && f->cookie != -1) { |
392 | TAILQ_REMOVE(&vc->vc_fontlist, f, next); |
393 | if (f->slot != -1) { |
394 | KASSERT(vc->vc_fonts[f->slot] == f); |
395 | vc->vc_fonts[f->slot] = 0; |
396 | } |
397 | wsfont_unlock(f->cookie); |
398 | #ifdef VGA_CONSOLE_SCREENTYPE |
399 | if (f != &vga_consolefont) |
400 | #endif |
401 | free(f, M_DEVBUF); |
402 | } |
403 | } |
404 | |
405 | static int |
406 | vga_selectfont(struct vga_config *vc, struct vgascreen *scr, const char *name1, |
407 | const char *name2) |
408 | { |
409 | const struct wsscreen_descr *type = scr->pcs.type; |
410 | struct egavga_font *f1, *f2; |
411 | |
412 | f1 = egavga_getfont(vc, scr, name1, 1); |
413 | if (!f1) |
414 | return (ENXIO); |
415 | |
416 | if (VGA_SCREEN_CANTWOFONTS(type) && name2) { |
417 | f2 = egavga_getfont(vc, scr, name2, 0); |
418 | if (!f2) { |
419 | egavga_unreffont(vc, f1); |
420 | return (ENXIO); |
421 | } |
422 | } else |
423 | f2 = 0; |
424 | |
425 | #ifdef VGAFONTDEBUG |
426 | if (scr != &vga_console_screen || vga_console_attached) { |
427 | printf("vga (%s): font1=%s (slot %d)" , type->name, |
428 | f1->wsfont->name, f1->slot); |
429 | if (f2) |
430 | printf(", font2=%s (slot %d)" , |
431 | f2->wsfont->name, f2->slot); |
432 | printf("\n" ); |
433 | } |
434 | #endif |
435 | if (scr->fontset1) |
436 | egavga_unreffont(vc, scr->fontset1); |
437 | scr->fontset1 = f1; |
438 | if (scr->fontset2) |
439 | egavga_unreffont(vc, scr->fontset2); |
440 | scr->fontset2 = f2; |
441 | return (0); |
442 | } |
443 | |
444 | static void |
445 | vga_init_screen(struct vga_config *vc, struct vgascreen *scr, |
446 | const struct wsscreen_descr *type, int existing, long *attrp) |
447 | { |
448 | int cpos; |
449 | int res __diagused; |
450 | |
451 | scr->cfg = vc; |
452 | scr->pcs.hdl = (struct pcdisplay_handle *)&vc->hdl; |
453 | scr->pcs.type = type; |
454 | scr->pcs.active = existing; |
455 | scr->mindispoffset = 0; |
456 | if (vc->vc_quirks & VGA_QUIRK_NOFASTSCROLL) |
457 | scr->maxdispoffset = 0; |
458 | else |
459 | scr->maxdispoffset = 0x8000 - type->nrows * type->ncols * 2; |
460 | |
461 | if (existing) { |
462 | vc->active = scr; |
463 | |
464 | cpos = vga_6845_read(&vc->hdl, cursorh) << 8; |
465 | cpos |= vga_6845_read(&vc->hdl, cursorl); |
466 | |
467 | /* make sure we have a valid cursor position */ |
468 | if (cpos < 0 || cpos >= type->nrows * type->ncols) |
469 | cpos = 0; |
470 | |
471 | scr->pcs.dispoffset = vga_6845_read(&vc->hdl, startadrh) << 9; |
472 | scr->pcs.dispoffset |= vga_6845_read(&vc->hdl, startadrl) << 1; |
473 | |
474 | /* make sure we have a valid memory offset */ |
475 | if (scr->pcs.dispoffset < scr->mindispoffset || |
476 | scr->pcs.dispoffset > scr->maxdispoffset) |
477 | scr->pcs.dispoffset = scr->mindispoffset; |
478 | |
479 | if (type != vc->currenttype) { |
480 | vga_setscreentype(&vc->hdl, type); |
481 | vc->currenttype = type; |
482 | } |
483 | } else { |
484 | cpos = 0; |
485 | scr->pcs.dispoffset = scr->mindispoffset; |
486 | } |
487 | |
488 | scr->pcs.visibleoffset = scr->pcs.dispoffset; |
489 | scr->vga_rollover = 0; |
490 | |
491 | scr->pcs.cursorrow = cpos / type->ncols; |
492 | scr->pcs.cursorcol = cpos % type->ncols; |
493 | pcdisplay_cursor_init(&scr->pcs, existing); |
494 | |
495 | #ifdef __alpha__ |
496 | if (!vc->hdl.vh_mono) |
497 | /* |
498 | * DEC firmware uses a blue background. |
499 | * XXX These should be specified as kernel options for |
500 | * XXX alpha only, not hardcoded here (which is wrong |
501 | * XXX anyway because the emulation layer will assume |
502 | * XXX the default attribute is white on black). |
503 | */ |
504 | res = vga_allocattr(scr, WSCOL_WHITE, WSCOL_BLUE, |
505 | WSATTR_WSCOLORS, attrp); |
506 | else |
507 | #endif |
508 | res = vga_allocattr(scr, 0, 0, 0, attrp); |
509 | #ifdef DIAGNOSTIC |
510 | if (res) |
511 | panic("vga_init_screen: attribute botch" ); |
512 | #endif |
513 | |
514 | scr->pcs.mem = NULL; |
515 | |
516 | scr->fontset1 = scr->fontset2 = 0; |
517 | if (vga_selectfont(vc, scr, 0, 0)) { |
518 | if (scr == &vga_console_screen) |
519 | panic("vga_init_screen: no font" ); |
520 | else |
521 | printf("vga_init_screen: no font\n" ); |
522 | } |
523 | if (existing) |
524 | vga_setfont(vc, scr); |
525 | |
526 | vc->nscreens++; |
527 | LIST_INSERT_HEAD(&vc->screens, scr, next); |
528 | } |
529 | |
530 | static void |
531 | vga_init(struct vga_config *vc, bus_space_tag_t iot, bus_space_tag_t memt) |
532 | { |
533 | struct vga_handle *vh = &vc->hdl; |
534 | uint8_t mor; |
535 | int i; |
536 | |
537 | vh->vh_iot = iot; |
538 | vh->vh_memt = memt; |
539 | |
540 | if (bus_space_map(vh->vh_iot, 0x3c0, 0x10, 0, &vh->vh_ioh_vga)) |
541 | panic("vga_init: couldn't map vga io" ); |
542 | |
543 | /* read "misc output register" */ |
544 | mor = vga_raw_read(vh, VGA_MISC_DATAR); |
545 | vh->vh_mono = !(mor & 1); |
546 | |
547 | if (bus_space_map(vh->vh_iot, (vh->vh_mono ? 0x3b0 : 0x3d0), 0x10, 0, |
548 | &vh->vh_ioh_6845)) |
549 | panic("vga_init: couldn't map 6845 io" ); |
550 | |
551 | if (bus_space_map(vh->vh_memt, 0xa0000, 0x20000, 0, &vh->vh_allmemh)) |
552 | panic("vga_init: couldn't map memory" ); |
553 | |
554 | if (bus_space_subregion(vh->vh_memt, vh->vh_allmemh, |
555 | (vh->vh_mono ? 0x10000 : 0x18000), 0x8000, &vh->vh_memh)) |
556 | panic("vga_init: mem subrange failed" ); |
557 | |
558 | vc->nscreens = 0; |
559 | LIST_INIT(&vc->screens); |
560 | vc->active = NULL; |
561 | vc->currenttype = vh->vh_mono ? &vga_25lscreen_mono : &vga_25lscreen; |
562 | callout_init(&vc->vc_switch_callout, 0); |
563 | |
564 | wsfont_init(); |
565 | if (vga_no_builtinfont) { |
566 | struct wsdisplay_font *wf; |
567 | int cookie; |
568 | |
569 | cookie = wsfont_find(NULL, 8, 16, 0, |
570 | WSDISPLAY_FONTORDER_L2R, 0, WSFONT_FIND_BITMAP); |
571 | if (cookie == -1 || wsfont_lock(cookie, &wf)) |
572 | panic("vga_init: can't load console font" ); |
573 | vga_loadchars(&vc->hdl, 0, wf->firstchar, wf->numchars, |
574 | wf->fontheight, wf->data); |
575 | vga_builtinfont.wsfont = wf; |
576 | vga_builtinfont.cookie = cookie; |
577 | vga_builtinfont.slot = 0; |
578 | } |
579 | vc->vc_fonts[0] = &vga_builtinfont; |
580 | for (i = 1; i < 8; i++) |
581 | vc->vc_fonts[i] = 0; |
582 | TAILQ_INIT(&vc->vc_fontlist); |
583 | TAILQ_INSERT_HEAD(&vc->vc_fontlist, &vga_builtinfont, next); |
584 | |
585 | vc->currentfontset1 = vc->currentfontset2 = 0; |
586 | |
587 | if (!vh->vh_mono && (u_int)WSDISPLAY_BORDER_COLOR < sizeof(fgansitopc)) |
588 | _vga_attr_write(vh, VGA_ATC_OVERSCAN, |
589 | fgansitopc[WSDISPLAY_BORDER_COLOR]); |
590 | vga_save_palette(vc); |
591 | } |
592 | |
593 | void |
594 | vga_common_attach(struct vga_softc *sc, bus_space_tag_t iot, |
595 | bus_space_tag_t memt, int type, int quirks, |
596 | const struct vga_funcs *vf) |
597 | { |
598 | int console; |
599 | struct vga_config *vc; |
600 | struct wsemuldisplaydev_attach_args aa; |
601 | |
602 | console = vga_is_console(iot, type); |
603 | |
604 | if (console) { |
605 | vc = &vga_console_vc; |
606 | vga_console_attached = 1; |
607 | } else { |
608 | vc = malloc(sizeof(struct vga_config), M_DEVBUF, M_WAITOK); |
609 | vga_init(vc, iot, memt); |
610 | } |
611 | |
612 | if (quirks & VGA_QUIRK_ONEFONT) { |
613 | vc->vc_nfontslots = 1; |
614 | #ifndef VGA_CONSOLE_ATI_BROKEN_FONTSEL |
615 | /* |
616 | * XXX maybe invalidate font in slot > 0, but this can |
617 | * only be happen with VGA_CONSOLE_SCREENTYPE, and then |
618 | * we require VGA_CONSOLE_ATI_BROKEN_FONTSEL anyway. |
619 | */ |
620 | #endif |
621 | } else { |
622 | vc->vc_nfontslots = 8; |
623 | #ifndef VGA_CONSOLE_ATI_BROKEN_FONTSEL |
624 | /* |
625 | * XXX maybe validate builtin font shifted to slot 1 if |
626 | * slot 0 got overwritten because of VGA_CONSOLE_SCREENTYPE, |
627 | * but it will be reloaded anyway if needed. |
628 | */ |
629 | #endif |
630 | } |
631 | |
632 | /* |
633 | * Save the builtin font to memory. In case it got overwritten |
634 | * in console initialization, use the copy in slot 1. |
635 | */ |
636 | #ifdef VGA_CONSOLE_ATI_BROKEN_FONTSEL |
637 | #define BUILTINFONTLOC (vga_builtinfont.slot == -1 ? 1 : 0) |
638 | #else |
639 | KASSERT(vga_builtinfont.slot == 0); |
640 | #define BUILTINFONTLOC (0) |
641 | #endif |
642 | if (!vga_no_builtinfont) { |
643 | char *data = |
644 | malloc(256 * vga_builtinfont.wsfont->fontheight, |
645 | M_DEVBUF, M_WAITOK); |
646 | vga_readoutchars(&vc->hdl, BUILTINFONTLOC, 0, 256, |
647 | vga_builtinfont.wsfont->fontheight, data); |
648 | vga_builtinfont.wsfont->data = data; |
649 | } |
650 | |
651 | vc->vc_type = type; |
652 | vc->vc_funcs = vf; |
653 | vc->vc_quirks = quirks; |
654 | |
655 | sc->sc_vc = vc; |
656 | vc->softc = sc; |
657 | |
658 | aa.console = console; |
659 | aa.scrdata = (vc->hdl.vh_mono ? &vga_screenlist_mono : &vga_screenlist); |
660 | aa.accessops = &vga_accessops; |
661 | aa.accesscookie = vc; |
662 | |
663 | config_found_ia(sc->sc_dev, "wsemuldisplaydev" , &aa, wsemuldisplaydevprint); |
664 | } |
665 | |
666 | int |
667 | vga_cnattach(bus_space_tag_t iot, bus_space_tag_t memt, int type, int check) |
668 | { |
669 | long defattr; |
670 | const struct wsscreen_descr *scr; |
671 | |
672 | if (check && !vga_common_probe(iot, memt)) |
673 | return (ENXIO); |
674 | |
675 | /* set up bus-independent VGA configuration */ |
676 | vga_init(&vga_console_vc, iot, memt); |
677 | #ifdef VGA_CONSOLE_SCREENTYPE |
678 | scr = wsdisplay_screentype_pick(vga_console_vc.hdl.vh_mono ? |
679 | &vga_screenlist_mono : &vga_screenlist, VGA_CONSOLE_SCREENTYPE); |
680 | if (!scr) |
681 | panic("vga_cnattach: invalid screen type" ); |
682 | #else |
683 | scr = vga_console_vc.currenttype; |
684 | #endif |
685 | #ifdef VGA_CONSOLE_ATI_BROKEN_FONTSEL |
686 | /* |
687 | * On some (most/all?) ATI cards, only font slot 0 is usable. |
688 | * vga_init_screen() might need font slot 0 for a non-default |
689 | * console font, so save the builtin VGA font to another font slot. |
690 | * The attach() code will take care later. |
691 | */ |
692 | vga_console_vc.vc_quirks |= VGA_QUIRK_ONEFONT; /* redundant */ |
693 | vga_copyfont01(&vga_console_vc.hdl); |
694 | vga_console_vc.vc_nfontslots = 1; |
695 | #else |
696 | vga_console_vc.vc_nfontslots = 8; |
697 | #endif |
698 | #ifdef notdef |
699 | /* until we know better, assume "fast scrolling" does not work */ |
700 | vga_console_vc.vc_quirks |= VGA_QUIRK_NOFASTSCROLL; |
701 | #endif |
702 | |
703 | vga_init_screen(&vga_console_vc, &vga_console_screen, scr, 1, &defattr); |
704 | |
705 | wsdisplay_cnattach(scr, &vga_console_screen, |
706 | vga_console_screen.pcs.cursorcol, |
707 | vga_console_screen.pcs.cursorrow, defattr); |
708 | |
709 | vgaconsole = 1; |
710 | vga_console_type = type; |
711 | return (0); |
712 | } |
713 | |
714 | int |
715 | vga_cndetach(void) |
716 | { |
717 | struct vga_config *vc; |
718 | struct vga_handle *vh; |
719 | |
720 | vc = &vga_console_vc; |
721 | vh = &vc->hdl; |
722 | |
723 | if (vgaconsole) { |
724 | wsdisplay_cndetach(); |
725 | |
726 | bus_space_unmap(vh->vh_iot, vh->vh_ioh_vga, 0x10); |
727 | bus_space_unmap(vh->vh_iot, vh->vh_ioh_6845, 0x10); |
728 | bus_space_unmap(vh->vh_memt, vh->vh_allmemh, 0x20000); |
729 | |
730 | vga_console_attached = 0; |
731 | vgaconsole = 0; |
732 | |
733 | return 1; |
734 | } |
735 | |
736 | return 0; |
737 | } |
738 | |
739 | int |
740 | vga_is_console(bus_space_tag_t iot, int type) |
741 | { |
742 | if (vgaconsole && |
743 | !vga_console_attached && |
744 | bus_space_is_equal(iot, vga_console_vc.hdl.vh_iot) && |
745 | (vga_console_type == -1 || (type == vga_console_type))) |
746 | return (1); |
747 | return (0); |
748 | } |
749 | |
750 | static int |
751 | vga_get_video(struct vga_config *vc) |
752 | { |
753 | |
754 | return (vga_ts_read(&vc->hdl, mode) & VGA_TS_MODE_BLANK) == 0; |
755 | } |
756 | |
757 | static void |
758 | vga_set_video(struct vga_config *vc, int state) |
759 | { |
760 | int val; |
761 | |
762 | vga_ts_write(&vc->hdl, syncreset, 0x01); |
763 | if (state) { /* unblank screen */ |
764 | val = vga_ts_read(&vc->hdl, mode); |
765 | vga_ts_write(&vc->hdl, mode, val & ~VGA_TS_MODE_BLANK); |
766 | #ifndef VGA_NO_VBLANK |
767 | val = vga_6845_read(&vc->hdl, mode); |
768 | vga_6845_write(&vc->hdl, mode, val | 0x80); |
769 | #endif |
770 | } else { /* blank screen */ |
771 | val = vga_ts_read(&vc->hdl, mode); |
772 | vga_ts_write(&vc->hdl, mode, val | VGA_TS_MODE_BLANK); |
773 | #ifndef VGA_NO_VBLANK |
774 | val = vga_6845_read(&vc->hdl, mode); |
775 | vga_6845_write(&vc->hdl, mode, val & ~0x80); |
776 | #endif |
777 | } |
778 | vga_ts_write(&vc->hdl, syncreset, 0x03); |
779 | } |
780 | |
781 | static int |
782 | vga_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, struct lwp *l) |
783 | { |
784 | struct vga_config *vc = v; |
785 | struct vgascreen *scr = vs; |
786 | const struct vga_funcs *vf = vc->vc_funcs; |
787 | |
788 | switch (cmd) { |
789 | case WSDISPLAYIO_SMODE: |
790 | if (*(u_int *)data == WSDISPLAYIO_MODE_EMUL) |
791 | vga_restore_palette(vc); |
792 | return 0; |
793 | |
794 | case WSDISPLAYIO_GTYPE: |
795 | *(int *)data = vc->vc_type; |
796 | return 0; |
797 | |
798 | case WSDISPLAYIO_GINFO: |
799 | /* XXX should get detailed hardware information here */ |
800 | return EPASSTHROUGH; |
801 | |
802 | case WSDISPLAYIO_GVIDEO: |
803 | *(int *)data = (vga_get_video(vc) ? |
804 | WSDISPLAYIO_VIDEO_ON : WSDISPLAYIO_VIDEO_OFF); |
805 | return 0; |
806 | |
807 | case WSDISPLAYIO_SVIDEO: |
808 | vga_set_video(vc, *(int *)data == WSDISPLAYIO_VIDEO_ON); |
809 | return 0; |
810 | |
811 | case WSDISPLAYIO_GETWSCHAR: |
812 | KASSERT(scr != NULL); |
813 | return pcdisplay_getwschar(&scr->pcs, |
814 | (struct wsdisplay_char *)data); |
815 | |
816 | case WSDISPLAYIO_PUTWSCHAR: |
817 | KASSERT(scr != NULL); |
818 | return pcdisplay_putwschar(&scr->pcs, |
819 | (struct wsdisplay_char *)data); |
820 | |
821 | #ifdef WSDISPLAY_CUSTOM_BORDER |
822 | case WSDISPLAYIO_GBORDER: |
823 | return (vga_getborder(vc, (u_int *)data)); |
824 | |
825 | case WSDISPLAYIO_SBORDER: |
826 | return (vga_setborder(vc, *(u_int *)data)); |
827 | #endif |
828 | |
829 | case WSDISPLAYIO_GETCMAP: |
830 | case WSDISPLAYIO_PUTCMAP: |
831 | case WSDISPLAYIO_GCURPOS: |
832 | case WSDISPLAYIO_SCURPOS: |
833 | case WSDISPLAYIO_GCURMAX: |
834 | case WSDISPLAYIO_GCURSOR: |
835 | case WSDISPLAYIO_SCURSOR: |
836 | /* NONE of these operations are by the generic VGA driver. */ |
837 | return EPASSTHROUGH; |
838 | } |
839 | |
840 | if (vc->vc_funcs == NULL) |
841 | return (EPASSTHROUGH); |
842 | |
843 | if (vf->vf_ioctl == NULL) |
844 | return (EPASSTHROUGH); |
845 | |
846 | return ((*vf->vf_ioctl)(v, cmd, data, flag, l)); |
847 | } |
848 | |
849 | static paddr_t |
850 | vga_mmap(void *v, void *vs, off_t offset, int prot) |
851 | { |
852 | struct vga_config *vc = v; |
853 | const struct vga_funcs *vf = vc->vc_funcs; |
854 | |
855 | if (vc->vc_funcs == NULL) |
856 | return (-1); |
857 | |
858 | if (vf->vf_mmap == NULL) |
859 | return (-1); |
860 | |
861 | return ((*vf->vf_mmap)(v, offset, prot)); |
862 | } |
863 | |
864 | static int |
865 | vga_alloc_screen(void *v, const struct wsscreen_descr *type, void **cookiep, |
866 | int *curxp, int *curyp, long *defattrp) |
867 | { |
868 | struct vga_config *vc = v; |
869 | struct vgascreen *scr; |
870 | |
871 | if (vc->nscreens == 1) { |
872 | struct vgascreen *scr1 = vc->screens.lh_first; |
873 | /* |
874 | * When allocating the second screen, get backing store |
875 | * for the first one too. |
876 | * XXX We could be more clever and use video RAM. |
877 | */ |
878 | scr1->pcs.mem = |
879 | malloc(scr1->pcs.type->ncols * scr1->pcs.type->nrows * 2, |
880 | M_DEVBUF, M_WAITOK); |
881 | } |
882 | |
883 | scr = malloc(sizeof(struct vgascreen), M_DEVBUF, M_WAITOK); |
884 | vga_init_screen(vc, scr, type, vc->nscreens == 0, defattrp); |
885 | |
886 | if (vc->nscreens > 1) { |
887 | scr->pcs.mem = malloc(type->ncols * type->nrows * 2, |
888 | M_DEVBUF, M_WAITOK); |
889 | pcdisplay_eraserows(&scr->pcs, 0, type->nrows, *defattrp); |
890 | } |
891 | |
892 | *cookiep = scr; |
893 | *curxp = scr->pcs.cursorcol; |
894 | *curyp = scr->pcs.cursorrow; |
895 | |
896 | return (0); |
897 | } |
898 | |
899 | static void |
900 | vga_free_screen(void *v, void *cookie) |
901 | { |
902 | struct vgascreen *vs = cookie; |
903 | struct vga_config *vc = vs->cfg; |
904 | |
905 | LIST_REMOVE(vs, next); |
906 | vc->nscreens--; |
907 | if (vs->fontset1) |
908 | egavga_unreffont(vc, vs->fontset1); |
909 | if (vs->fontset2) |
910 | egavga_unreffont(vc, vs->fontset2); |
911 | |
912 | if (vs != &vga_console_screen) |
913 | free(vs, M_DEVBUF); |
914 | else |
915 | panic("vga_free_screen: console" ); |
916 | |
917 | if (vc->active == vs) |
918 | vc->active = 0; |
919 | } |
920 | |
921 | static void vga_usefont(struct vga_config *, struct egavga_font *); |
922 | |
923 | static void |
924 | vga_usefont(struct vga_config *vc, struct egavga_font *f) |
925 | { |
926 | int slot; |
927 | struct egavga_font *of; |
928 | |
929 | if (f->slot != -1) |
930 | goto toend; |
931 | |
932 | for (slot = 0; slot < vc->vc_nfontslots; slot++) { |
933 | if (!vc->vc_fonts[slot]) |
934 | goto loadit; |
935 | } |
936 | |
937 | /* have to kick out another one */ |
938 | TAILQ_FOREACH(of, &vc->vc_fontlist, next) { |
939 | if (of->slot != -1) { |
940 | KASSERT(vc->vc_fonts[of->slot] == of); |
941 | slot = of->slot; |
942 | of->slot = -1; |
943 | goto loadit; |
944 | } |
945 | } |
946 | panic("vga_usefont" ); |
947 | |
948 | loadit: |
949 | vga_loadchars(&vc->hdl, slot, f->wsfont->firstchar, |
950 | f->wsfont->numchars, f->wsfont->fontheight, f->wsfont->data); |
951 | f->slot = slot; |
952 | vc->vc_fonts[slot] = f; |
953 | |
954 | toend: |
955 | TAILQ_REMOVE(&vc->vc_fontlist, f, next); |
956 | TAILQ_INSERT_TAIL(&vc->vc_fontlist, f, next); |
957 | } |
958 | |
959 | static void |
960 | vga_setfont(struct vga_config *vc, struct vgascreen *scr) |
961 | { |
962 | int fontslot1, fontslot2; |
963 | |
964 | if (scr->fontset1) |
965 | vga_usefont(vc, scr->fontset1); |
966 | if (scr->fontset2) |
967 | vga_usefont(vc, scr->fontset2); |
968 | |
969 | fontslot1 = (scr->fontset1 ? scr->fontset1->slot : 0); |
970 | fontslot2 = (scr->fontset2 ? scr->fontset2->slot : fontslot1); |
971 | if (vc->currentfontset1 != fontslot1 || |
972 | vc->currentfontset2 != fontslot2) { |
973 | vga_setfontset(&vc->hdl, fontslot1, fontslot2); |
974 | vc->currentfontset1 = fontslot1; |
975 | vc->currentfontset2 = fontslot2; |
976 | } |
977 | } |
978 | |
979 | static int |
980 | vga_show_screen(void *v, void *cookie, int waitok, |
981 | void (*cb)(void *, int, int), void *cbarg) |
982 | { |
983 | struct vgascreen *scr = cookie, *oldscr; |
984 | struct vga_config *vc = scr->cfg; |
985 | |
986 | oldscr = vc->active; /* can be NULL! */ |
987 | if (scr == oldscr) { |
988 | return (0); |
989 | } |
990 | |
991 | vc->wantedscreen = cookie; |
992 | vc->switchcb = cb; |
993 | vc->switchcbarg = cbarg; |
994 | if (cb) { |
995 | callout_reset(&vc->vc_switch_callout, 0, |
996 | (void(*)(void *))vga_doswitch, vc); |
997 | return (EAGAIN); |
998 | } |
999 | |
1000 | vga_doswitch(vc); |
1001 | return (0); |
1002 | } |
1003 | |
1004 | static void |
1005 | vga_doswitch(struct vga_config *vc) |
1006 | { |
1007 | struct vgascreen *scr, *oldscr; |
1008 | struct vga_handle *vh = &vc->hdl; |
1009 | const struct wsscreen_descr *type; |
1010 | |
1011 | scr = vc->wantedscreen; |
1012 | if (!scr) { |
1013 | printf("vga_doswitch: disappeared\n" ); |
1014 | (*vc->switchcb)(vc->switchcbarg, EIO, 0); |
1015 | return; |
1016 | } |
1017 | type = scr->pcs.type; |
1018 | oldscr = vc->active; /* can be NULL! */ |
1019 | #ifdef DIAGNOSTIC |
1020 | if (oldscr) { |
1021 | if (!oldscr->pcs.active) |
1022 | panic("vga_show_screen: not active" ); |
1023 | if (oldscr->pcs.type != vc->currenttype) |
1024 | panic("vga_show_screen: bad type" ); |
1025 | } |
1026 | #endif |
1027 | if (scr == oldscr) { |
1028 | return; |
1029 | } |
1030 | #ifdef DIAGNOSTIC |
1031 | if (scr->pcs.active) |
1032 | panic("vga_show_screen: active" ); |
1033 | #endif |
1034 | |
1035 | if (oldscr) { |
1036 | const struct wsscreen_descr *oldtype = oldscr->pcs.type; |
1037 | |
1038 | oldscr->pcs.active = 0; |
1039 | bus_space_read_region_2(vh->vh_memt, vh->vh_memh, |
1040 | oldscr->pcs.dispoffset, oldscr->pcs.mem, |
1041 | oldtype->ncols * oldtype->nrows); |
1042 | } |
1043 | |
1044 | if (vc->currenttype != type) { |
1045 | vga_setscreentype(vh, type); |
1046 | vc->currenttype = type; |
1047 | } |
1048 | |
1049 | vga_setfont(vc, scr); |
1050 | vga_restore_palette(vc); |
1051 | |
1052 | scr->pcs.visibleoffset = scr->pcs.dispoffset = scr->mindispoffset; |
1053 | if (!oldscr || (scr->pcs.dispoffset != oldscr->pcs.dispoffset)) { |
1054 | vga_6845_write(vh, startadrh, scr->pcs.dispoffset >> 9); |
1055 | vga_6845_write(vh, startadrl, scr->pcs.dispoffset >> 1); |
1056 | } |
1057 | |
1058 | bus_space_write_region_2(vh->vh_memt, vh->vh_memh, |
1059 | scr->pcs.dispoffset, scr->pcs.mem, type->ncols * type->nrows); |
1060 | scr->pcs.active = 1; |
1061 | |
1062 | vc->active = scr; |
1063 | |
1064 | pcdisplay_cursor(&scr->pcs, scr->pcs.cursoron, |
1065 | scr->pcs.cursorrow, scr->pcs.cursorcol); |
1066 | |
1067 | vc->wantedscreen = 0; |
1068 | if (vc->switchcb) |
1069 | (*vc->switchcb)(vc->switchcbarg, 0, 0); |
1070 | } |
1071 | |
1072 | static int |
1073 | vga_load_font(void *v, void *cookie, struct wsdisplay_font *data) |
1074 | { |
1075 | struct vga_config *vc = v; |
1076 | struct vgascreen *scr = cookie; |
1077 | char *name2; |
1078 | int res; |
1079 | |
1080 | if (scr) { |
1081 | name2 = NULL; |
1082 | if (data->name) { |
1083 | name2 = strchr(data->name, ','); |
1084 | if (name2) |
1085 | *name2++ = '\0'; |
1086 | } |
1087 | res = vga_selectfont(vc, scr, data->name, name2); |
1088 | if (!res && scr->pcs.active) |
1089 | vga_setfont(vc, scr); |
1090 | return (res); |
1091 | } |
1092 | |
1093 | return (0); |
1094 | } |
1095 | |
1096 | static int |
1097 | vga_allocattr(void *id, int fg, int bg, int flags, long *attrp) |
1098 | { |
1099 | struct vgascreen *scr = id; |
1100 | struct vga_config *vc = scr->cfg; |
1101 | |
1102 | if (__predict_false((unsigned int)fg >= sizeof(fgansitopc) || |
1103 | (unsigned int)bg >= sizeof(bgansitopc))) |
1104 | return (EINVAL); |
1105 | |
1106 | if (vc->hdl.vh_mono) { |
1107 | if (flags & WSATTR_WSCOLORS) |
1108 | return (EINVAL); |
1109 | if (flags & WSATTR_REVERSE) |
1110 | *attrp = 0x70; |
1111 | else |
1112 | *attrp = 0x07; |
1113 | if (flags & WSATTR_UNDERLINE) |
1114 | *attrp |= FG_UNDERLINE; |
1115 | if (flags & WSATTR_HILIT) |
1116 | *attrp |= FG_INTENSE; |
1117 | } else { |
1118 | if (flags & (WSATTR_UNDERLINE | WSATTR_REVERSE)) |
1119 | return (EINVAL); |
1120 | if (flags & WSATTR_WSCOLORS) |
1121 | *attrp = fgansitopc[fg] | bgansitopc[bg]; |
1122 | else |
1123 | *attrp = 7; |
1124 | if (flags & WSATTR_HILIT) |
1125 | *attrp += 8; |
1126 | } |
1127 | if (flags & WSATTR_BLINK) |
1128 | *attrp |= FG_BLINK; |
1129 | return (0); |
1130 | } |
1131 | |
1132 | static void |
1133 | vga_copyrows(void *id, int srcrow, int dstrow, int nrows) |
1134 | { |
1135 | struct vgascreen *scr = id; |
1136 | bus_space_tag_t memt = scr->pcs.hdl->ph_memt; |
1137 | bus_space_handle_t memh = scr->pcs.hdl->ph_memh; |
1138 | int ncols = scr->pcs.type->ncols; |
1139 | bus_size_t srcoff, dstoff; |
1140 | |
1141 | srcoff = srcrow * ncols + 0; |
1142 | dstoff = dstrow * ncols + 0; |
1143 | |
1144 | if (scr->pcs.active) { |
1145 | if (dstrow == 0 && (srcrow + nrows == scr->pcs.type->nrows)) { |
1146 | #ifdef PCDISPLAY_SOFTCURSOR |
1147 | int cursoron = scr->pcs.cursoron; |
1148 | |
1149 | if (cursoron) |
1150 | pcdisplay_cursor(&scr->pcs, 0, |
1151 | scr->pcs.cursorrow, scr->pcs.cursorcol); |
1152 | #endif |
1153 | /* scroll up whole screen */ |
1154 | if ((scr->pcs.dispoffset + srcrow * ncols * 2) |
1155 | <= scr->maxdispoffset) { |
1156 | scr->pcs.dispoffset += srcrow * ncols * 2; |
1157 | } else { |
1158 | bus_space_copy_region_2(memt, memh, |
1159 | scr->pcs.dispoffset + srcoff * 2, |
1160 | memh, scr->mindispoffset, nrows * ncols); |
1161 | scr->pcs.dispoffset = scr->mindispoffset; |
1162 | } |
1163 | vga_6845_write(&scr->cfg->hdl, startadrh, |
1164 | scr->pcs.dispoffset >> 9); |
1165 | vga_6845_write(&scr->cfg->hdl, startadrl, |
1166 | scr->pcs.dispoffset >> 1); |
1167 | #ifdef PCDISPLAY_SOFTCURSOR |
1168 | if (cursoron) |
1169 | pcdisplay_cursor(&scr->pcs, 1, |
1170 | scr->pcs.cursorrow, scr->pcs.cursorcol); |
1171 | #endif |
1172 | } else { |
1173 | bus_space_copy_region_2(memt, memh, |
1174 | scr->pcs.dispoffset + srcoff * 2, |
1175 | memh, scr->pcs.dispoffset + dstoff * 2, |
1176 | nrows * ncols); |
1177 | } |
1178 | } else |
1179 | memcpy(&scr->pcs.mem[dstoff], &scr->pcs.mem[srcoff], |
1180 | nrows * ncols * 2); |
1181 | } |
1182 | |
1183 | #ifdef WSCONS_SUPPORT_PCVTFONTS |
1184 | |
1185 | #define NOTYET 0xffff |
1186 | static const uint16_t pcvt_unichars[0xa0] = { |
1187 | /* 0 */ _e006U, /* N/L control */ |
1188 | NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, |
1189 | NOTYET, |
1190 | 0x2409, /* SYMBOL FOR HORIZONTAL TABULATION */ |
1191 | 0x240a, /* SYMBOL FOR LINE FEED */ |
1192 | 0x240b, /* SYMBOL FOR VERTICAL TABULATION */ |
1193 | 0x240c, /* SYMBOL FOR FORM FEED */ |
1194 | 0x240d, /* SYMBOL FOR CARRIAGE RETURN */ |
1195 | NOTYET, NOTYET, |
1196 | /* 1 */ NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, |
1197 | NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, |
1198 | /* 2 */ NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, |
1199 | NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, |
1200 | /* 3 */ NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, |
1201 | NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, NOTYET, |
1202 | /* 4 */ 0x03c1, /* GREEK SMALL LETTER RHO */ |
1203 | 0x03c8, /* GREEK SMALL LETTER PSI */ |
1204 | 0x2202, /* PARTIAL DIFFERENTIAL */ |
1205 | 0x03bb, /* GREEK SMALL LETTER LAMDA */ |
1206 | 0x03b9, /* GREEK SMALL LETTER IOTA */ |
1207 | 0x03b7, /* GREEK SMALL LETTER ETA */ |
1208 | 0x03b5, /* GREEK SMALL LETTER EPSILON */ |
1209 | 0x03c7, /* GREEK SMALL LETTER CHI */ |
1210 | 0x2228, /* LOGICAL OR */ |
1211 | 0x2227, /* LOGICAL AND */ |
1212 | 0x222a, /* UNION */ |
1213 | 0x2283, /* SUPERSET OF */ |
1214 | 0x2282, /* SUBSET OF */ |
1215 | 0x03a5, /* GREEK CAPITAL LETTER UPSILON */ |
1216 | 0x039e, /* GREEK CAPITAL LETTER XI */ |
1217 | 0x03a8, /* GREEK CAPITAL LETTER PSI */ |
1218 | /* 5 */ 0x03a0, /* GREEK CAPITAL LETTER PI */ |
1219 | 0x21d2, /* RIGHTWARDS DOUBLE ARROW */ |
1220 | 0x21d4, /* LEFT RIGHT DOUBLE ARROW */ |
1221 | 0x039b, /* GREEK CAPITAL LETTER LAMDA */ |
1222 | 0x0398, /* GREEK CAPITAL LETTER THETA */ |
1223 | 0x2243, /* ASYMPTOTICALLY EQUAL TO */ |
1224 | 0x2207, /* NABLA */ |
1225 | 0x2206, /* INCREMENT */ |
1226 | 0x221d, /* PROPORTIONAL TO */ |
1227 | 0x2234, /* THEREFORE */ |
1228 | 0x222b, /* INTEGRAL */ |
1229 | 0x2215, /* DIVISION SLASH */ |
1230 | 0x2216, /* SET MINUS */ |
1231 | _e00eU, /* angle? */ |
1232 | _e00dU, /* inverted angle? */ |
1233 | _e00bU, /* braceleftmid */ |
1234 | /* 6 */ _e00cU, /* bracerightmid */ |
1235 | _e007U, /* bracelefttp */ |
1236 | _e008U, /* braceleftbt */ |
1237 | _e009U, /* bracerighttp */ |
1238 | _e00aU, /* bracerightbt */ |
1239 | 0x221a, /* SQUARE ROOT */ |
1240 | 0x03c9, /* GREEK SMALL LETTER OMEGA */ |
1241 | 0x00a5, /* YEN SIGN */ |
1242 | 0x03be, /* GREEK SMALL LETTER XI */ |
1243 | 0x00fd, /* LATIN SMALL LETTER Y WITH ACUTE */ |
1244 | 0x00fe, /* LATIN SMALL LETTER THORN */ |
1245 | 0x00f0, /* LATIN SMALL LETTER ETH */ |
1246 | 0x00de, /* LATIN CAPITAL LETTER THORN */ |
1247 | 0x00dd, /* LATIN CAPITAL LETTER Y WITH ACUTE */ |
1248 | 0x00d7, /* MULTIPLICATION SIGN */ |
1249 | 0x00d0, /* LATIN CAPITAL LETTER ETH */ |
1250 | /* 7 */ 0x00be, /* VULGAR FRACTION THREE QUARTERS */ |
1251 | 0x00b8, /* CEDILLA */ |
1252 | 0x00b4, /* ACUTE ACCENT */ |
1253 | 0x00af, /* MACRON */ |
1254 | 0x00ae, /* REGISTERED SIGN */ |
1255 | 0x00ad, /* SOFT HYPHEN */ |
1256 | 0x00ac, /* NOT SIGN */ |
1257 | 0x00a8, /* DIAERESIS */ |
1258 | 0x2260, /* NOT EQUAL TO */ |
1259 | 0x23bd, /* scan 9 */ |
1260 | 0x23bc, /* scan 7 */ |
1261 | 0x2500, /* scan 5 */ |
1262 | 0x23bb, /* scan 3 */ |
1263 | 0x23ba, /* scan 1 */ |
1264 | 0x03c5, /* GREEK SMALL LETTER UPSILON */ |
1265 | 0x00f8, /* LATIN SMALL LETTER O WITH STROKE */ |
1266 | /* 8 */ 0x0153, /* LATIN SMALL LIGATURE OE */ |
1267 | 0x00f5, /* LATIN SMALL LETTER O WITH TILDE !!!doc bug */ |
1268 | 0x00e3, /* LATIN SMALL LETTER A WITH TILDE */ |
1269 | 0x0178, /* LATIN CAPITAL LETTER Y WITH DIAERESIS */ |
1270 | 0x00db, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */ |
1271 | 0x00da, /* LATIN CAPITAL LETTER U WITH ACUTE */ |
1272 | 0x00d9, /* LATIN CAPITAL LETTER U WITH GRAVE */ |
1273 | 0x00d8, /* LATIN CAPITAL LETTER O WITH STROKE */ |
1274 | 0x0152, /* LATIN CAPITAL LIGATURE OE */ |
1275 | 0x00d5, /* LATIN CAPITAL LETTER O WITH TILDE */ |
1276 | 0x00d4, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */ |
1277 | 0x00d3, /* LATIN CAPITAL LETTER O WITH ACUTE */ |
1278 | 0x00d2, /* LATIN CAPITAL LETTER O WITH GRAVE */ |
1279 | 0x00cf, /* LATIN CAPITAL LETTER I WITH DIAERESIS */ |
1280 | 0x00ce, /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */ |
1281 | 0x00cd, /* LATIN CAPITAL LETTER I WITH ACUTE */ |
1282 | /* 9 */ 0x00cc, /* LATIN CAPITAL LETTER I WITH GRAVE */ |
1283 | 0x00cb, /* LATIN CAPITAL LETTER E WITH DIAERESIS */ |
1284 | 0x00ca, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */ |
1285 | 0x00c8, /* LATIN CAPITAL LETTER E WITH GRAVE */ |
1286 | 0x00c3, /* LATIN CAPITAL LETTER A WITH TILDE */ |
1287 | 0x00c2, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */ |
1288 | 0x00c1, /* LATIN CAPITAL LETTER A WITH ACUTE */ |
1289 | 0x00c0, /* LATIN CAPITAL LETTER A WITH GRAVE */ |
1290 | 0x00b9, /* SUPERSCRIPT ONE */ |
1291 | 0x00b7, /* MIDDLE DOT */ |
1292 | 0x03b6, /* GREEK SMALL LETTER ZETA */ |
1293 | 0x00b3, /* SUPERSCRIPT THREE */ |
1294 | 0x00a9, /* COPYRIGHT SIGN */ |
1295 | 0x00a4, /* CURRENCY SIGN */ |
1296 | 0x03ba, /* GREEK SMALL LETTER KAPPA */ |
1297 | _e000U /* mirrored question mark? */ |
1298 | }; |
1299 | |
1300 | static int vga_pcvt_mapchar(int, u_int *); |
1301 | |
1302 | static int |
1303 | vga_pcvt_mapchar(int uni, u_int *index) |
1304 | { |
1305 | int i; |
1306 | |
1307 | for (i = 0; i < 0xa0; i++) /* 0xa0..0xff are reserved */ |
1308 | if (uni == pcvt_unichars[i]) { |
1309 | *index = i; |
1310 | return (5); |
1311 | } |
1312 | *index = 0x99; /* middle dot */ |
1313 | return (0); |
1314 | } |
1315 | |
1316 | #endif /* WSCONS_SUPPORT_PCVTFONTS */ |
1317 | |
1318 | #ifdef WSCONS_SUPPORT_ISO7FONTS |
1319 | |
1320 | static int |
1321 | vga_iso7_mapchar(int uni, u_int *index) |
1322 | { |
1323 | |
1324 | /* |
1325 | * U+0384 (GREEK TONOS) to |
1326 | * U+03ce (GREEK SMALL LETTER OMEGA WITH TONOS) |
1327 | * map directly to the iso-9 font |
1328 | */ |
1329 | if (uni >= 0x0384 && uni <= 0x03ce) { |
1330 | /* U+0384 is at offset 0xb4 in the font */ |
1331 | *index = uni - 0x0384 + 0xb4; |
1332 | return (5); |
1333 | } |
1334 | |
1335 | /* XXX more chars in the iso-9 font */ |
1336 | |
1337 | *index = 0xa4; /* shaded rectangle */ |
1338 | return (0); |
1339 | } |
1340 | |
1341 | #endif /* WSCONS_SUPPORT_ISO7FONTS */ |
1342 | |
1343 | static const uint16_t iso2_unichars[0x60] = { |
1344 | 0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7, |
1345 | 0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B, |
1346 | 0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7, |
1347 | 0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C, |
1348 | 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, |
1349 | 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, |
1350 | 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, |
1351 | 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, |
1352 | 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, |
1353 | 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, |
1354 | 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, |
1355 | 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9 |
1356 | }; |
1357 | |
1358 | static const uint16_t koi8_unichars[0x40] = { |
1359 | 0x044E, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, |
1360 | 0x0445, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, |
1361 | 0x043F, 0x044F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, |
1362 | 0x044C, 0x044B, 0x0437, 0x0448, 0x044D, 0x0449, 0x0447, 0x044A, |
1363 | 0x042E, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, |
1364 | 0x0425, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, |
1365 | 0x041F, 0x042F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, |
1366 | 0x042C, 0x042B, 0x0417, 0x0428, 0x042D, 0x0429, 0x0427, 0x042A |
1367 | }; |
1368 | |
1369 | static int _vga_mapchar(void *, const struct egavga_font *, int, u_int *); |
1370 | |
1371 | static int |
1372 | _vga_mapchar(void *id, const struct egavga_font *font, int uni, u_int *index) |
1373 | { |
1374 | |
1375 | switch (font->wsfont->encoding) { |
1376 | case WSDISPLAY_FONTENC_ISO: |
1377 | if (uni < 256) { |
1378 | *index = uni; |
1379 | return (5); |
1380 | } else { |
1381 | *index = ' '; |
1382 | return (0); |
1383 | } |
1384 | case WSDISPLAY_FONTENC_ISO2: |
1385 | if (uni < 0xa0) { |
1386 | *index = uni; |
1387 | return (5); |
1388 | } else { |
1389 | int i; |
1390 | for (i = 0; i < 0x60; i++) { |
1391 | if (uni == iso2_unichars[i]) { |
1392 | *index = i + 0xa0; |
1393 | return (5); |
1394 | } |
1395 | } |
1396 | *index = 0xa4; /* currency sign */ |
1397 | return (0); |
1398 | } |
1399 | case WSDISPLAY_FONTENC_KOI8_R: |
1400 | if (uni < 0x80) { |
1401 | *index = uni; |
1402 | return (5); |
1403 | } else { |
1404 | int i; |
1405 | for (i = 0; i < 0x40; i++) { |
1406 | if (uni == koi8_unichars[i]) { |
1407 | *index = i + 0xc0; |
1408 | return (5); |
1409 | } |
1410 | } |
1411 | *index = 0x94; /* box */ |
1412 | return (0); |
1413 | } |
1414 | case WSDISPLAY_FONTENC_IBM: |
1415 | return (pcdisplay_mapchar(id, uni, index)); |
1416 | #ifdef WSCONS_SUPPORT_PCVTFONTS |
1417 | case WSDISPLAY_FONTENC_PCVT: |
1418 | return (vga_pcvt_mapchar(uni, index)); |
1419 | #endif |
1420 | #ifdef WSCONS_SUPPORT_ISO7FONTS |
1421 | case WSDISPLAY_FONTENC_ISO7: |
1422 | return (vga_iso7_mapchar(uni, index)); |
1423 | #endif |
1424 | default: |
1425 | #ifdef VGAFONTDEBUG |
1426 | printf("_vga_mapchar: encoding=%d\n" , font->wsfont->encoding); |
1427 | #endif |
1428 | *index = ' '; |
1429 | return (0); |
1430 | } |
1431 | } |
1432 | |
1433 | static int |
1434 | vga_mapchar(void *id, int uni, u_int *index) |
1435 | { |
1436 | struct vgascreen *scr = id; |
1437 | u_int idx1, idx2; |
1438 | int res1, res2; |
1439 | |
1440 | res1 = 0; |
1441 | idx1 = ' '; /* space */ |
1442 | if (scr->fontset1) |
1443 | res1 = _vga_mapchar(id, scr->fontset1, uni, &idx1); |
1444 | res2 = -1; |
1445 | if (scr->fontset2) { |
1446 | KASSERT(VGA_SCREEN_CANTWOFONTS(scr->pcs.type)); |
1447 | res2 = _vga_mapchar(id, scr->fontset2, uni, &idx2); |
1448 | } |
1449 | if (res2 > res1) { |
1450 | *index = idx2 | 0x0800; /* attribute bit 3 */ |
1451 | return (res2); |
1452 | } |
1453 | *index = idx1; |
1454 | return (res1); |
1455 | } |
1456 | |
1457 | #ifdef WSDISPLAY_SCROLLSUPPORT |
1458 | static void |
1459 | vga_scroll(void *v, void *cookie, int lines) |
1460 | { |
1461 | struct vga_config *vc = v; |
1462 | struct vgascreen *scr = cookie; |
1463 | struct vga_handle *vh = &vc->hdl; |
1464 | |
1465 | if (lines == 0) { |
1466 | if (scr->pcs.visibleoffset == scr->pcs.dispoffset) |
1467 | return; |
1468 | |
1469 | scr->pcs.visibleoffset = scr->pcs.dispoffset; |
1470 | } |
1471 | else { |
1472 | int vga_scr_end; |
1473 | int margin = scr->pcs.type->ncols * 2; |
1474 | int ul, we, p, st; |
1475 | |
1476 | vga_scr_end = (scr->pcs.dispoffset + scr->pcs.type->ncols * |
1477 | scr->pcs.type->nrows * 2); |
1478 | if (scr->vga_rollover > vga_scr_end + margin) { |
1479 | ul = vga_scr_end; |
1480 | we = scr->vga_rollover + scr->pcs.type->ncols * 2; |
1481 | } else { |
1482 | ul = 0; |
1483 | we = 0x8000; |
1484 | } |
1485 | p = (scr->pcs.visibleoffset - ul + we) % we + lines * |
1486 | (scr->pcs.type->ncols * 2); |
1487 | st = (scr->pcs.dispoffset - ul + we) % we; |
1488 | if (p < margin) |
1489 | p = 0; |
1490 | if (p > st - margin) |
1491 | p = st; |
1492 | scr->pcs.visibleoffset = (p + ul) % we; |
1493 | } |
1494 | |
1495 | vga_6845_write(vh, startadrh, scr->pcs.visibleoffset >> 9); |
1496 | vga_6845_write(vh, startadrl, scr->pcs.visibleoffset >> 1); |
1497 | } |
1498 | #endif |
1499 | |
1500 | static void |
1501 | vga_putchar(void *c, int row, int col, u_int uc, long attr) |
1502 | { |
1503 | |
1504 | pcdisplay_putchar(c, row, col, uc, attr); |
1505 | } |
1506 | |
1507 | #ifdef WSDISPLAY_CUSTOM_BORDER |
1508 | static int |
1509 | vga_getborder(struct vga_config *vc, u_int *valuep) |
1510 | { |
1511 | struct vga_handle *vh = &vc->hdl; |
1512 | u_int idx; |
1513 | uint8_t value; |
1514 | |
1515 | if (vh->vh_mono) |
1516 | return ENODEV; |
1517 | |
1518 | value = _vga_attr_read(vh, VGA_ATC_OVERSCAN); |
1519 | for (idx = 0; idx < sizeof(fgansitopc); idx++) { |
1520 | if (fgansitopc[idx] == value) { |
1521 | *valuep = idx; |
1522 | return (0); |
1523 | } |
1524 | } |
1525 | return (EIO); |
1526 | } |
1527 | |
1528 | static int |
1529 | vga_setborder(struct vga_config *vc, u_int value) |
1530 | { |
1531 | struct vga_handle *vh = &vc->hdl; |
1532 | |
1533 | if (vh->vh_mono) |
1534 | return ENODEV; |
1535 | if (value >= sizeof(fgansitopc)) |
1536 | return EINVAL; |
1537 | |
1538 | _vga_attr_write(vh, VGA_ATC_OVERSCAN, fgansitopc[value]); |
1539 | return (0); |
1540 | } |
1541 | #endif /* WSDISPLAY_CUSTOM_BORDER */ |
1542 | |
1543 | void |
1544 | vga_resume(struct vga_softc *sc) |
1545 | { |
1546 | #ifdef VGA_RESET_ON_RESUME |
1547 | vga_initregs(&sc->sc_vc->hdl); |
1548 | #endif |
1549 | #ifdef PCDISPLAY_SOFTCURSOR |
1550 | /* Disable the hardware cursor */ |
1551 | vga_6845_write(&sc->sc_vc->hdl, curstart, 0x20); |
1552 | vga_6845_write(&sc->sc_vc->hdl, curend, 0x00); |
1553 | #endif |
1554 | } |
1555 | |
1556 | static void |
1557 | vga_save_palette(struct vga_config *vc) |
1558 | { |
1559 | struct vga_handle *vh = &vc->hdl; |
1560 | size_t i; |
1561 | uint8_t *palette = vc->palette; |
1562 | |
1563 | if (vh->vh_mono) |
1564 | return; |
1565 | |
1566 | vga_raw_write(vh, VGA_DAC_PELMASK, 0xff); |
1567 | vga_raw_write(vh, VGA_DAC_ADDRR, 0x00); |
1568 | for (i = 0; i < sizeof(vc->palette); i++) |
1569 | *palette++ = vga_raw_read(vh, VGA_DAC_PALETTE); |
1570 | |
1571 | vga_reset_state(vh); /* reset flip/flop */ |
1572 | } |
1573 | |
1574 | static void |
1575 | vga_restore_palette(struct vga_config *vc) |
1576 | { |
1577 | struct vga_handle *vh = &vc->hdl; |
1578 | size_t i; |
1579 | uint8_t *palette = vc->palette; |
1580 | |
1581 | if (vh->vh_mono) |
1582 | return; |
1583 | |
1584 | vga_raw_write(vh, VGA_DAC_PELMASK, 0xff); |
1585 | vga_raw_write(vh, VGA_DAC_ADDRW, 0x00); |
1586 | for (i = 0; i < sizeof(vc->palette); i++) |
1587 | vga_raw_write(vh, VGA_DAC_PALETTE, *palette++); |
1588 | |
1589 | vga_reset_state(vh); /* reset flip/flop */ |
1590 | vga_enable(vh); |
1591 | } |
1592 | |