2 * This file is part of the Palacios Virtual Machine Monitor developed
3 * by the V3VEE Project with funding from the United States National
4 * Science Foundation and the Department of Energy.
6 * The V3VEE Project is a joint project between Northwestern University
7 * and the University of New Mexico. You can find out more at
10 * Copyright (c) 2009, Robert Deloatch <rtdeloatch@gmail.com>
11 * Copyright (c) 2009, Steven Jaconette <stevenjaconette2007@u.northwestern.edu>
12 * Copyright (c) 2009, The V3VEE Project <http://www.v3vee.org>
13 * All rights reserved.
15 * Author: Robdert Deloatch <rtdeloatch@gmail.com>
16 * Steven Jaconette <stevenjaconette2007@u.northwestern.edu>
18 * Initial VGA support added by Erik van der Kouwe <vdkouwe@cs.vu.nl>
20 * This is free software. You are permitted to use,
21 * redistribute, and modify it as specified in the file "V3VEE_LICENSE".
24 #include <palacios/vmm.h>
25 #include <palacios/vmm_dev_mgr.h>
26 #include <palacios/vmm_emulator.h>
27 #include <palacios/vm_guest_mem.h>
28 #include <palacios/vmm_io.h>
29 #include <palacios/vmm_sprintf.h>
31 #include <devices/console.h>
34 #if V3_CONFIG_DEBUG_CGA >= 2
35 #define PrintVerbose PrintDebug
37 #define PrintVerbose(fmt, args...)
39 #if V3_CONFIG_DEBUG_CGA == 0
41 #define PrintDebug(fmt, args...)
45 #define START_ADDR 0xA0000
46 #define END_ADDR 0xC0000
48 #define FRAMEBUF_SIZE (END_ADDR - START_ADDR)
50 #define BYTES_PER_COL 2
52 struct misc_outp_reg {
61 struct seq_data_reg_clocking_mode {
63 uint8_t reserved1 : 1;
68 uint8_t reserved2 : 2;
71 struct crtc_data_reg_overflow {
82 struct crtc_data_reg_max_scan_line {
89 struct graphc_data_reg_graphics_mode {
91 uint8_t reserved1 : 1;
96 uint8_t reserved2 : 1;
99 struct graphc_data_reg_misc {
103 uint8_t reserved : 4;
106 struct attrc_data_reg_attr_mode_control {
111 uint8_t reserved : 1;
117 #define SEQ_REG_COUNT 5
118 #define SEQ_REGIDX_RESET 0
119 #define SEQ_REGIDX_CLOCKING_MODE 1
120 #define SEQ_REGIDX_MAP_MASK 2
121 #define SEQ_REGIDX_CHARACTER_MAP_SELECT 3
122 #define SEQ_REGIDX_MEMORY_MODE 4
124 #define CRTC_REG_COUNT 25
125 #define CRTC_REGIDX_HORI_TOTAL 0
126 #define CRTC_REGIDX_HORI_DISPLAY_ENABLE 1
127 #define CRTC_REGIDX_START_HORI_BLANKING 2
128 #define CRTC_REGIDX_END_HORI_BLANKING 3
129 #define CRTC_REGIDX_START_HORI_RETRACE 4
130 #define CRTC_REGIDX_END_HORI_RETRACE 5
131 #define CRTC_REGIDX_VERT_TOTAL 6
132 #define CRTC_REGIDX_OVERFLOW 7
133 #define CRTC_REGIDX_PRESET_ROW_SCAN 8
134 #define CRTC_REGIDX_MAX_SCAN_LINE 9
135 #define CRTC_REGIDX_CURSOR_START 10
136 #define CRTC_REGIDX_CURSOR_END 11
137 #define CRTC_REGIDX_START_ADDR_HIGH 12
138 #define CRTC_REGIDX_START_ADDR_LOW 13
139 #define CRTC_REGIDX_CURSOR_LOC_HIGH 14
140 #define CRTC_REGIDX_CURSOR_LOC_LOW 15
141 #define CRTC_REGIDX_VERT_RETRACE_START 16
142 #define CRTC_REGIDX_VERT_RETRACE_END 17
143 #define CRTC_REGIDX_VERT_DISPLAY_ENABLE_END 18
144 #define CRTC_REGIDX_OFFSET 19
145 #define CRTC_REGIDX_UNDERLINE_LOCATION 20
146 #define CRTC_REGIDX_START_VERT_BLANKING 21
147 #define CRTC_REGIDX_END_VERT_BLANKING 22
148 #define CRTC_REGIDX_CRT_MODE_CONTROL 23
149 #define CRTC_REGIDX_LINE_COMPATE 24
151 #define GRAPHC_REG_COUNT 9
152 #define GRAPHC_REGIDX_SET_RESET 0
153 #define GRAPHC_REGIDX_ENABLE_SET_RESET 1
154 #define GRAPHC_REGIDX_COLOR_COMPARE 2
155 #define GRAPHC_REGIDX_DATA_ROTATE 3
156 #define GRAPHC_REGIDX_READ_MAP_SELECT 4
157 #define GRAPHC_REGIDX_GRAPHICS_MODE 5
158 #define GRAPHC_REGIDX_MISC 6
159 #define GRAPHC_REGIDX_COLOR_DONT_CARE 7
160 #define GRAPHC_REGIDX_BIT_MASK 8
162 #define ATTRC_REG_COUNT 21
163 #define ATTRC_REGIDX_PALETTE_0 0
164 #define ATTRC_REGIDX_ATTR_MODE_CONTROL 16
165 #define ATTRC_REGIDX_OVERSCAN_COLOR 17
166 #define ATTRC_REGIDX_COLOR_PLANE_ENABLE 18
167 #define ATTRC_REGIDX_HORI_PEL_PANNING 19
168 #define ATTRC_REGIDX_COLOR_SELECT 20
170 #define DAC_ENTRY_COUNT 256
171 #define DAC_COLOR_COUNT 3
172 #define DAC_REG_COUNT (DAC_ENTRY_COUNT * DAC_COLOR_COUNT)
174 struct video_internal {
179 struct misc_outp_reg misc_outp_reg; // io port 3CC (R) / 3C2 (W)
180 uint8_t seq_index_reg; // io port 3C4
181 uint8_t seq_data_regs[SEQ_REG_COUNT]; // io port 3C5
182 uint8_t crtc_index_reg; // io port 3D4
183 uint8_t crtc_data_regs[CRTC_REG_COUNT]; // io port 3D5
184 uint8_t graphc_index_reg; // io port 3CE
185 uint8_t graphc_data_regs[GRAPHC_REG_COUNT]; // io port 3CF
186 uint8_t attrc_index_flipflop;
187 uint8_t attrc_index_reg; // io port 3C0
188 uint8_t attrc_data_regs[ATTRC_REG_COUNT]; // io port 3C1 (R) / 3C0 (W)
189 uint8_t dac_indexr_reg; // io port 3C8
190 uint8_t dac_indexr_color;
191 uint8_t dac_indexw_reg; // io port 3C7
192 uint8_t dac_indexw_color;
193 uint8_t dac_data_regs[DAC_REG_COUNT]; // io port 3C9
195 /* auxilary fields derived from register values */
196 addr_t activefb_addr;
209 /* IMPORTANT: These are column offsets _NOT_ byte offsets */
210 uint16_t screen_offset; // relative to the framebuffer
211 uint16_t cursor_offset; // relative to the framebuffer
214 struct vm_device * dev;
220 struct v3_console_ops * ops;
227 static void refresh_screen(struct video_internal * state) {
230 PrintDebug("Screen config: framebuf=0x%x-0x%x, gres=%dx%d, tres=%dx%d, %s mode\n",
231 (unsigned) state->activefb_addr,
232 (unsigned) state->activefb_addr + state->activefb_len,
237 state->graphmode ? "graphics" : "text");
239 /* tell the frontend to refresh the screen entirely */
242 if (state->reschanged) {
243 /* resolution change message will trigger update */
244 state->reschanged = 0;
245 PrintDebug("Video: set_text_resolution(%d, %d)\n",
246 state->hchars, state->vchars);
247 if (state->ops && state->ops->set_text_resolution) {
248 state->ops->set_text_resolution(state->hchars, state->vchars, state->private_data);
251 /* send update for full buffer */
252 PrintDebug("Video: update_screen(0, 0, %d * %d * %d)\n", state->vchars, state->hchars, BYTES_PER_COL);
253 screen_size = state->vchars * state->hchars * BYTES_PER_COL;
255 state->ops->update_screen(0, 0, screen_size, state->private_data);
260 static void registers_updated(struct video_internal * state) {
261 struct seq_data_reg_clocking_mode *cm;
262 struct graphc_data_reg_misc *misc;
263 struct crtc_data_reg_max_scan_line *msl;
264 struct crtc_data_reg_overflow *ovf;
266 uint_t activefb_addr, activefb_len, hchars, vchars, vde, hres, vres;
268 /* framebuffer mapping address */
269 misc = (struct graphc_data_reg_misc *)(state->graphc_data_regs + GRAPHC_REGIDX_MISC);
272 activefb_addr = (misc->mm == 3) ? 0xb8000 : 0xb0000;
273 activefb_len = 0x8000;
275 activefb_addr = 0xa0000;
276 activefb_len = (misc->mm == 1) ? 0x10000 : 0x20000;
279 if ((state->activefb_addr != activefb_addr) || (state->activefb_len != activefb_len)) {
280 state->activefb_addr = activefb_addr;
281 state->activefb_len = activefb_len;
283 PrintVerbose("Video: need refresh (activefb=0x%x-0x%x)\n",
284 activefb_addr, activefb_addr + activefb_len);
287 /* mode selection; may be inconclusive, keep old value in that case */
288 if (state->graphmode != misc->gm) {
289 state->graphmode = misc->gm;
291 PrintVerbose("Video: need refresh (graphmode=%d)\n", state->graphmode);
294 /* graphics resolution */
295 if (state->misc_outp_reg.hsp) {
296 vres = (state->misc_outp_reg.vsp) ? 480 : 400;
298 if (!state->misc_outp_reg.vsp) {
299 PrintError("Video: reserved value in misc_outp_reg (0x%x)\n",
300 *(uint8_t *) &state->misc_outp_reg);
304 msl = (struct crtc_data_reg_max_scan_line *) (
305 state->crtc_data_regs + CRTC_REGIDX_MAX_SCAN_LINE);
306 if (msl->dsc) vres /= 2;
307 if (state->vres != vres) {
309 state->reschanged = 1;
310 PrintVerbose("Video: need refresh (vres=%d)\n", vres);
313 switch (state->misc_outp_reg.cs) {
314 case 0: hres = 640; break;
315 case 1: hres = 720; break;
317 PrintError("Video: reserved value in misc_outp_reg (0x%x)\n",
318 *(uint8_t *) &state->misc_outp_reg);
322 cm = (struct seq_data_reg_clocking_mode *) (
323 state->seq_data_regs + SEQ_REGIDX_CLOCKING_MODE);
324 if (cm->dc) hres /= 2;
325 if (state->hres != hres) {
327 state->reschanged = 1;
328 PrintVerbose("Video: need refresh (hres=%d)\n", hres);
331 /* text resolution */
332 ovf = (struct crtc_data_reg_overflow *) (state->crtc_data_regs + CRTC_REGIDX_OVERFLOW);
334 hchars = state->crtc_data_regs[CRTC_REGIDX_HORI_DISPLAY_ENABLE] + 1;
335 lines_per_char = msl->msl + 1;
336 vde = state->crtc_data_regs[CRTC_REGIDX_VERT_DISPLAY_ENABLE_END] |
337 (((unsigned) ovf->vde8) << 8) |
338 (((unsigned) ovf->vde9) << 9);
339 vchars = (vde + 1) / lines_per_char;
340 if (state->hchars != hchars || state->vchars != vchars) {
341 state->hchars = hchars;
342 state->vchars = vchars;
343 state->reschanged = 1;
344 PrintVerbose("Video: need refresh (hchars=%d, vchars=%d)\n", hchars, vchars);
347 /* resolution change implies refresh needed */
348 if (state->reschanged) {
352 /* IO port range selection */
353 state->iorange = state->misc_outp_reg.ios ? 0x3d0 : 0x3b0;
356 static void registers_initialize(struct video_internal * state) {
358 /* initialize the registers; defaults taken from vgatables.h in the VGA
359 * BIOS, mode 3 (which is specified by IBM as the default mode)
361 static const uint8_t seq_defaults[] = {
362 0x03, 0x00, 0x03, 0x00, 0x02,
364 static const uint8_t crtc_defaults[] = {
365 0x5f, 0x4f, 0x50, 0x82, /* 0 - 3 */
366 0x55, 0x81, 0xbf, 0x1f, /* 4 - 7 */
367 0x00, 0x4f, 0x0d, 0x0e, /* 8 - 11 */
368 0x00, 0x00, 0x00, 0x00, /* 12 - 15 */
369 0x9c, 0x8e, 0x8f, 0x28, /* 16 - 19 */
370 0x1f, 0x96, 0xb9, 0xa3, /* 20 - 23 */
373 static const uint8_t graphc_defaults[] = {
374 0x00, 0x00, 0x00, 0x00, /* 0 - 3 */
375 0x00, 0x10, 0x0e, 0x0f, /* 4 - 7 */
378 static const uint8_t attrc_defaults[] = {
379 0x00, 0x01, 0x02, 0x03, /* 0 - 3 */
380 0x04, 0x05, 0x14, 0x07, /* 4 - 7 */
381 0x38, 0x39, 0x3a, 0x3b, /* 8 - 11 */
382 0x3c, 0x3d, 0x3e, 0x3f, /* 12 - 15 */
383 0x0c, 0x00, 0x0f, 0x08, /* 16 - 19 */
387 /* general registers */
388 state->misc_outp_reg.ios = 1;
389 state->misc_outp_reg.eram = 1;
390 state->misc_outp_reg.cs = 1;
391 state->misc_outp_reg.hsp = 1;
393 /* sequencer registers */
394 V3_ASSERT(sizeof(seq_defaults) == sizeof(state->seq_data_regs));
395 memcpy(state->seq_data_regs, seq_defaults, sizeof(state->seq_data_regs));
397 /* CRT controller registers */
398 V3_ASSERT(sizeof(crtc_defaults) == sizeof(state->crtc_data_regs));
399 memcpy(state->crtc_data_regs, crtc_defaults, sizeof(state->crtc_data_regs));
401 /* graphics controller registers */
402 V3_ASSERT(sizeof(graphc_defaults) == sizeof(state->graphc_data_regs));
403 memcpy(state->graphc_data_regs, graphc_defaults, sizeof(state->graphc_data_regs));
405 /* attribute controller registers */
406 V3_ASSERT(sizeof(attrc_defaults) == sizeof(state->attrc_data_regs));
407 memcpy(state->attrc_data_regs, attrc_defaults, sizeof(state->attrc_data_regs));
409 /* initialize auxilary fields */
410 registers_updated(state);
413 static void passthrough_in(uint16_t port, void * src, uint_t length) {
416 *(uint8_t *)src = v3_inb(port);
419 *(uint16_t *)src = v3_inw(port);
422 *(uint32_t *)src = v3_indw(port);
430 static void passthrough_out(uint16_t port, const void * src, uint_t length) {
433 v3_outb(port, *(uint8_t *)src);
436 v3_outw(port, *(uint16_t *)src);
439 v3_outdw(port, *(uint32_t *)src);
446 #if V3_CONFIG_DEBUG_CGA >= 2
447 static unsigned long get_value(const void *ptr, int len) {
448 unsigned long value = 0;
450 if (len > sizeof(value)) len = sizeof(value);
451 memcpy(&value, ptr, len);
456 static char opsize_char(uint_t length) {
467 static int video_write_mem(struct guest_info * core, addr_t guest_addr, void * dest, uint_t length, void * priv_data) {
468 struct vm_device * dev = (struct vm_device *)priv_data;
469 struct video_internal * state = (struct video_internal *)dev->private_data;
470 uint_t length_adjusted, screen_pos, x, y;
471 addr_t framebuf_offset, framebuf_offset_screen, screen_offset;
473 V3_ASSERT(guest_addr >= START_ADDR);
474 V3_ASSERT(guest_addr < END_ADDR);
476 PrintVerbose("Video: write(%p, 0x%lx, %d)\n",
478 get_value(state->framebuf + (guest_addr - START_ADDR), length),
481 /* get data written by passthrough into frame buffer if needed */
482 if (state->passthrough) {
483 memcpy(state->framebuf + (guest_addr - START_ADDR), V3_VAddr((void *)guest_addr), length);
486 /* refresh the entire screen after the registers have been changed */
488 refresh_screen(state);
492 /* the remainder is only needed if there is a front-end */
497 /* write may point into a framebuffer not currently active, for example
498 * preparing a VGA buffer at 0xA0000 while the CGA text mode at 0xB8000
499 * is still on the display; in this case we have to ignore (part of) the
500 * write to avoid buffer overflows
502 length_adjusted = length;
503 if (state->activefb_addr > guest_addr) {
504 uint_t diff = state->activefb_addr - guest_addr;
505 if (diff >= length_adjusted) return length;
507 length_adjusted -= diff;
510 framebuf_offset = guest_addr - state->activefb_addr;
511 if (state->activefb_len <= framebuf_offset) return length;
512 if (length_adjusted > state->activefb_len - framebuf_offset) {
513 length_adjusted = state->activefb_len - framebuf_offset;
516 /* determine position on screen, considering wrapping */
517 framebuf_offset_screen = state->screen_offset * BYTES_PER_COL;
518 if (framebuf_offset > framebuf_offset_screen) {
519 screen_offset = framebuf_offset - framebuf_offset_screen;
521 screen_offset = framebuf_offset + state->activefb_len - framebuf_offset_screen;
524 /* translate to coordinates and pass to the frontend */
525 screen_pos = screen_offset / BYTES_PER_COL;
526 x = screen_pos % state->hchars;
527 y = screen_pos / state->hchars;
528 if (y >= state->vchars) return length;
529 PrintVerbose("Video: update_screen(%d, %d, %d)\n", x, y, length_adjusted);
530 state->ops->update_screen(x, y, length_adjusted, state->private_data);
535 static void debug_port(struct video_internal * video_state, const char *function, uint16_t port, uint_t length, uint_t maxlength)
537 uint16_t portrange = port & 0xfff0;
539 /* report any unexpected guest behaviour, it may explain failures */
540 if (portrange != 0x3c0 && portrange != video_state->iorange) {
541 PrintError("Video %s: got bad port 0x%x\n", function, port);
544 if (!video_state->passthrough && length > maxlength) {
545 PrintError("Video %s: got bad length %d\n", function, length);
547 V3_ASSERT(length >= 1);
550 static void handle_port_read(struct video_internal * video_state, const char *function, uint16_t port, void *dest, uint_t length, uint_t maxlength) {
551 PrintVerbose("Video %s: in%c(0x%x): 0x%lx\n", function, opsize_char(length), port, get_value(dest, length));
552 debug_port(video_state, function, port, length, maxlength);
554 if (video_state->passthrough) {
555 passthrough_in(port, dest, length);
559 static void handle_port_write(struct video_internal * video_state, const char *function, uint16_t port, const void *src, uint_t length, uint_t maxlength) {
560 PrintVerbose("Video %s: out%c(0x%x, 0x%lx)\n", function, opsize_char(length), port, get_value(src, length));
561 debug_port(video_state, function, port, length, maxlength);
563 if (video_state->passthrough) {
564 passthrough_out(port, src, length);
568 static int notimpl_port_read(struct video_internal * video_state, const char *function, uint16_t port, void *dest, uint_t length) {
569 memset(dest, 0xff, length);
570 handle_port_read(video_state, function, port, dest, length, 1);
571 if (!video_state->passthrough) {
572 PrintError("Video %s: not implemented\n", function);
577 static int notimpl_port_write(struct video_internal * video_state, const char *function, uint16_t port, const void *src, uint_t length) {
578 handle_port_write(video_state, function, port, src, length, 1);
579 if (!video_state->passthrough) {
580 PrintError("Video %s: not implemented\n", function);
585 /* general registers */
586 static int misc_outp_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
587 struct video_internal * video_state = priv_data;
589 *(struct misc_outp_reg *) dest = video_state->misc_outp_reg;
591 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
595 static int misc_outp_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
596 struct video_internal * video_state = priv_data;
597 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
599 PrintDebug("Video: misc_outp=0x%x\n", *(uint8_t *) src);
600 video_state->misc_outp_reg = *(struct misc_outp_reg *) src;
601 registers_updated(video_state);
606 static int inp_status0_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
607 return notimpl_port_read(priv_data, __FUNCTION__, port, dest, length);
610 static int inp_status1_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
611 struct video_internal * video_state = priv_data;
613 /* next write to attrc selects the index rather than data */
614 video_state->attrc_index_flipflop = 0;
615 memset(dest, 0x0, length);
617 handle_port_read(priv_data, __FUNCTION__, port, dest, length, 1);
621 static int feat_ctrl_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
622 return notimpl_port_read(priv_data, __FUNCTION__, port, dest, length);
625 static int feat_ctrl_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
626 return notimpl_port_write(priv_data, __FUNCTION__, port, src, length);
629 static int video_enable_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
630 return notimpl_port_read(priv_data, __FUNCTION__, port, dest, length);
633 static int video_enable_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
634 return notimpl_port_write(priv_data, __FUNCTION__, port, src, length);
637 /* sequencer registers */
638 static int seq_data_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
639 struct video_internal * video_state = priv_data;
640 int index = video_state->seq_index_reg;
642 if (index < SEQ_REG_COUNT) {
643 *(uint8_t *) dest = video_state->seq_data_regs[index];
645 PrintError("Video %s: index %d out of range\n", __FUNCTION__, video_state->seq_index_reg);
646 *(uint8_t *) dest = 0;
649 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
653 static int seq_data_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
654 struct video_internal * video_state = priv_data;
655 int index = video_state->seq_index_reg;
656 uint8_t val = *(uint8_t *) src;
657 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
659 if (index < SEQ_REG_COUNT) {
660 PrintDebug("Video: seq[%d]=0x%x\n", index, val);
661 video_state->seq_data_regs[index] = val;
662 registers_updated(video_state);
664 PrintError("Video %s: index %d out of range\n", __FUNCTION__, video_state->seq_index_reg);
670 static int seq_index_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
671 struct video_internal * video_state = priv_data;
673 *(uint8_t *) dest = video_state->seq_index_reg;
675 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
679 static int seq_index_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
680 struct video_internal * video_state = priv_data;
681 handle_port_write(video_state, __FUNCTION__, port, src, length, 2);
683 video_state->seq_index_reg = *(uint8_t *) src;
686 if (seq_data_write(core, port + 1, (uint8_t *) src + 1, length - 1, priv_data) != length - 1) {
694 /* CRT controller registers */
695 static int crtc_data_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
696 struct video_internal * video_state = priv_data;
698 *(uint8_t *) dest = video_state->crtc_index_reg;
700 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
704 static int crtc_data_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
705 struct video_internal * video_state = priv_data;
706 uint8_t val = *(uint8_t *)src;
707 uint_t index = video_state->crtc_index_reg;
709 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
711 PrintError("Invalid write length for port 0x%x\n", port);
715 video_state->crtc_data_regs[index] = val;
716 registers_updated(video_state);
720 case CRTC_REGIDX_START_ADDR_HIGH: break; // Dealt by low-order byte write
721 case CRTC_REGIDX_START_ADDR_LOW: { // Scroll low byte
723 uint_t screen_offset;
726 ((uint16_t) video_state->crtc_data_regs[CRTC_REGIDX_START_ADDR_HIGH] << 8) |
727 ((uint16_t) video_state->crtc_data_regs[CRTC_REGIDX_START_ADDR_LOW]);
728 if ((screen_offset - video_state->screen_offset) % video_state->hchars) {
729 /* diff is not a multiple of column count, need full refresh */
733 /* normal scroll (the common case) */
734 diff = (screen_offset - video_state->screen_offset) / video_state->hchars;
737 PrintVerbose("Video: screen_offset=%d, video_state->screen_offset=%d, video_state->hchars=%d, diff=%d, refresh=%d\n",
738 screen_offset, video_state->screen_offset, video_state->hchars, diff, refresh);
740 // Update the true offset value
741 video_state->screen_offset = screen_offset;
743 if (refresh || video_state->dirty) {
744 refresh_screen(video_state);
745 } else if (diff && video_state->ops) {
746 PrintVerbose("Video: scroll(%d)\n", diff);
747 if (video_state->ops->scroll(diff, video_state->private_data) == -1) {
748 PrintError("Error sending scroll event\n");
754 case CRTC_REGIDX_CURSOR_LOC_HIGH: break; // Dealt by low-order byte write
755 case CRTC_REGIDX_CURSOR_LOC_LOW: { // Cursor adjustment low byte
759 video_state->cursor_offset =
760 ((uint16_t) video_state->crtc_data_regs[CRTC_REGIDX_CURSOR_LOC_HIGH] << 8) |
761 ((uint16_t) video_state->crtc_data_regs[CRTC_REGIDX_CURSOR_LOC_LOW]);
762 x = video_state->cursor_offset % video_state->hchars;
763 y = (video_state->cursor_offset - video_state->screen_offset) / video_state->hchars;
764 PrintVerbose("Video: video_state->cursor_offset=%d, x=%d, y=%d\n",
765 video_state->cursor_offset, x, y);
767 if (video_state->dirty) {
768 refresh_screen(video_state);
771 PrintVerbose("Video: set cursor(%d, %d)\n", x, y);
772 if (video_state->ops) {
773 if (video_state->ops->update_cursor(x, y, video_state->private_data) == -1) {
774 PrintError("Error updating cursor\n");
782 PrintDebug("Video: crtc[%d]=0x%x\n", index, val);
786 if (video_state->passthrough) {
787 passthrough_out(port, src, length);
793 static int crtc_index_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
794 struct video_internal * video_state = priv_data;
796 *(uint8_t *) dest = video_state->crtc_index_reg;
798 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
802 static int crtc_index_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
803 struct video_internal * video_state = priv_data;
805 handle_port_write(video_state, __FUNCTION__, port, src, length, 2);
807 PrintError("Invalid write length for crtc index register port: %d (0x%x)\n",
812 video_state->crtc_index_reg = *(uint8_t *)src;
814 // Only do the passthrough IO for the first byte
815 // the second byte will be done in the data register handler
816 if (video_state->passthrough) {
817 passthrough_out(port, src, 1);
821 if (crtc_data_write(core, port + 1, (uint8_t *) src + 1, length - 1, priv_data) != length - 1) {
829 /* graphics controller registers */
830 static int graphc_data_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
831 struct video_internal * video_state = priv_data;
832 int index = video_state->graphc_index_reg;
834 if (index < GRAPHC_REG_COUNT) {
835 *(uint8_t *) dest = video_state->graphc_data_regs[index];
837 PrintError("Video %s: index %d out of range\n", __FUNCTION__, video_state->graphc_index_reg);
838 *(uint8_t *) dest = 0;
841 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
845 static int graphc_data_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
846 struct video_internal * video_state = priv_data;
847 int index = video_state->graphc_index_reg;
848 uint8_t val = *(uint8_t *) src;
849 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
851 if (index < GRAPHC_REG_COUNT) {
852 PrintDebug("Video: graphc[%d]=0x%x\n", index, val);
853 video_state->graphc_data_regs[index] = val;
854 registers_updated(video_state);
856 PrintError("Video %s: index %d out of range\n", __FUNCTION__, video_state->graphc_index_reg);
862 static int graphc_index_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
863 struct video_internal * video_state = priv_data;
865 *(uint8_t *) dest = video_state->graphc_index_reg;
867 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
871 static int graphc_index_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
872 struct video_internal * video_state = priv_data;
873 handle_port_write(video_state, __FUNCTION__, port, src, length, 2);
875 video_state->graphc_index_reg = *(uint8_t *) src;
878 if (graphc_data_write(core, port + 1, (uint8_t *) src + 1, length - 1, priv_data) != length - 1) {
886 /* attribute controller registers */
887 static int attrc_data_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
888 struct video_internal * video_state = priv_data;
889 int index = video_state->attrc_index_reg;
891 if (index < ATTRC_REG_COUNT) {
892 *(uint8_t *) dest = video_state->attrc_data_regs[index];
894 PrintError("Video %s: index %d out of range\n", __FUNCTION__, video_state->attrc_index_reg);
895 *(uint8_t *) dest = 0;
898 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
902 static int attrc_data_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
903 struct video_internal * video_state = priv_data;
904 int index = video_state->attrc_index_reg;
905 uint8_t val = *(uint8_t *) src;
906 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
908 if (index < ATTRC_REG_COUNT) {
909 PrintDebug("Video: attrc[%d]=0x%x\n", index, val);
910 video_state->attrc_data_regs[index] = val;
912 PrintError("Video %s: index %d out of range\n", __FUNCTION__, video_state->attrc_index_reg);
918 static int attrc_index_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
919 struct video_internal * video_state = priv_data;
921 *(uint8_t *) dest = video_state->attrc_index_reg;
924 if (attrc_data_read(core, port + 1, (uint8_t *) dest + 1, length - 1, priv_data) != length - 1) {
929 handle_port_read(video_state, __FUNCTION__, port, dest, length, 2);
933 static int attrc_index_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
934 struct video_internal * video_state = priv_data;
935 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
937 video_state->attrc_index_reg = *(uint8_t *) src;
942 static int attrc_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
943 struct video_internal * video_state = priv_data;
945 /* two registers in one, written in an alternating fashion */
946 if (video_state->attrc_index_flipflop) {
947 video_state->attrc_index_flipflop = 0;
948 return attrc_data_write(core, port, src, length, priv_data);
950 video_state->attrc_index_flipflop = 1;
951 return attrc_index_write(core, port, src, length, priv_data);
955 /* video DAC palette registers */
956 static int dac_indexw_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
957 struct video_internal * video_state = priv_data;
959 *(uint8_t *) dest = video_state->dac_indexw_reg;
961 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
965 static int dac_indexw_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
966 struct video_internal * video_state = priv_data;
967 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
969 video_state->dac_indexw_reg = *(uint8_t *) src;
970 video_state->dac_indexw_color = 0;
975 static int dac_indexr_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
976 struct video_internal * video_state = priv_data;
977 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
979 video_state->dac_indexr_reg = *(uint8_t *) src;
980 video_state->dac_indexr_color = 0;
985 static int dac_data_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
986 struct video_internal * video_state = priv_data;
990 index = (unsigned) video_state->dac_indexr_reg * DAC_COLOR_COUNT +
991 video_state->dac_indexr_color;
992 V3_ASSERT(index < DAC_REG_COUNT);
993 *(uint8_t *) dest = video_state->dac_data_regs[index];
995 /* move on to next entry/color */
996 if (++video_state->dac_indexr_color > DAC_COLOR_COUNT) {
997 video_state->dac_indexr_reg++;
998 video_state->dac_indexr_color -= DAC_COLOR_COUNT;
1001 handle_port_read(video_state, __FUNCTION__, port, dest, length, 1);
1005 static int dac_data_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
1006 struct video_internal * video_state = priv_data;
1008 handle_port_write(video_state, __FUNCTION__, port, src, length, 1);
1010 /* update palette */
1011 index = (unsigned) video_state->dac_indexw_reg * DAC_COLOR_COUNT +
1012 video_state->dac_indexw_color;
1013 V3_ASSERT(index < DAC_REG_COUNT);
1014 video_state->dac_data_regs[index] = *(uint8_t *) src;
1016 /* move on to next entry/color */
1017 if (++video_state->dac_indexw_color > DAC_COLOR_COUNT) {
1018 video_state->dac_indexw_reg++;
1019 video_state->dac_indexw_color -= DAC_COLOR_COUNT;
1025 static int dac_pelmask_read(struct guest_info * core, uint16_t port, void * dest, uint_t length, void * priv_data) {
1026 return notimpl_port_read(priv_data, __FUNCTION__, port, dest, length);
1029 static int dac_pelmask_write(struct guest_info * core, uint16_t port, void * src, uint_t length, void * priv_data) {
1030 return notimpl_port_write(priv_data, __FUNCTION__, port, src, length);
1034 static int v3_cons_get_fb_graph(struct video_internal * state, uint8_t * dst, uint_t offset, uint_t length) {
1036 uint_t textlength, textoffset, textsize;
1038 /* this call is not intended for graphics mode, tell the user this */
1040 /* center informative text on 10th line */
1041 snprintf(text, sizeof(text), "* * * GRAPHICS MODE %dx%d * * *",
1042 state->hres, state->vres);
1043 textlength = strlen(text);
1044 textoffset = (state->hchars * 9 + (state->hchars - textlength) / 2) * BYTES_PER_COL;
1045 textsize = textlength * BYTES_PER_COL;
1047 /* fill the buffer */
1048 while (length-- > 0) {
1049 if (offset % BYTES_PER_COL) {
1050 c = 0; /* attribute byte */
1051 } else if (offset < textoffset || offset - textoffset >= textsize) {
1052 c = ' '; /* unused character byte */
1054 c = text[(offset - textoffset) / BYTES_PER_COL];
1064 static uint_t min_uint(uint_t x, uint_t y) {
1065 return (x < y) ? x : y;
1068 int v3_cons_get_fb_text(struct video_internal * state, uint8_t * dst, uint_t offset, uint_t length) {
1070 uint_t framebuf_offset, len1, len2;
1071 uint_t screen_byte_offset = state->screen_offset * BYTES_PER_COL;
1073 PrintVerbose("Video: getfb o=%d l=%d so=%d aa=0x%x al=0x%x hc=%d vc=%d\n",
1074 offset, length, state->screen_offset,
1075 (unsigned) state->activefb_addr, (unsigned) state->activefb_len,
1076 state->hchars, state->vchars);
1077 V3_ASSERT(state->activefb_addr >= START_ADDR);
1078 V3_ASSERT(state->activefb_addr + state->activefb_len <= END_ADDR);
1080 /* Copy memory with wrapping (should be configurable, but where else to get the data?) */
1081 framebuf = state->framebuf + (state->activefb_addr - START_ADDR);
1082 framebuf_offset = (screen_byte_offset + offset) % state->activefb_len;
1083 len1 = min_uint(length, state->activefb_len - framebuf_offset);
1084 len2 = length - len1;
1085 if (len1 > 0) memcpy(dst, framebuf + framebuf_offset, len1);
1086 if (len2 > 0) memcpy(dst + len1, framebuf, len2);
1091 int v3_cons_get_fb(struct vm_device * frontend_dev, uint8_t * dst, uint_t offset, uint_t length) {
1092 struct video_internal * state = (struct video_internal *)frontend_dev->private_data;
1094 /* Deal with call depending on mode */
1095 if (state->graphmode) {
1096 return v3_cons_get_fb_graph(state, dst, offset, length);
1098 return v3_cons_get_fb_text(state, dst, offset, length);
1102 static int cga_free(struct video_internal * video_state) {
1104 if (video_state->framebuf_pa) {
1105 PrintError("Freeing framebuffer PA %p\n", (void *)(video_state->framebuf_pa));
1106 V3_FreePages((void *)(video_state->framebuf_pa), (FRAMEBUF_SIZE / 4096));
1109 v3_unhook_mem(video_state->dev->vm, V3_MEM_CORE_ANY, START_ADDR);
1111 V3_Free(video_state);
1117 #ifdef V3_CONFIG_CHECKPOINT
1118 static int cga_save(struct v3_chkpt_ctx * ctx, void * private_data) {
1119 struct video_internal * cga = (struct video_internal *)private_data;
1121 v3_chkpt_save(ctx, "FRAMEBUFFER", FRAMEBUF_SIZE, cga->framebuf);
1123 V3_CHKPT_STD_SAVE(ctx, cga->misc_outp_reg);
1124 V3_CHKPT_STD_SAVE(ctx, cga->seq_index_reg);
1125 V3_CHKPT_STD_SAVE(ctx, cga->seq_data_regs[SEQ_REG_COUNT]);
1126 V3_CHKPT_STD_SAVE(ctx, cga->crtc_index_reg);
1127 V3_CHKPT_STD_SAVE(ctx, cga->crtc_data_regs[CRTC_REG_COUNT]);
1128 V3_CHKPT_STD_SAVE(ctx, cga->graphc_index_reg);
1129 V3_CHKPT_STD_SAVE(ctx, cga->graphc_data_regs[GRAPHC_REG_COUNT]);
1130 V3_CHKPT_STD_SAVE(ctx, cga->attrc_index_flipflop);
1131 V3_CHKPT_STD_SAVE(ctx, cga->attrc_index_reg);
1132 V3_CHKPT_STD_SAVE(ctx, cga->attrc_data_regs[ATTRC_REG_COUNT]);
1133 V3_CHKPT_STD_SAVE(ctx, cga->dac_indexr_reg);
1134 V3_CHKPT_STD_SAVE(ctx, cga->dac_indexr_color);
1135 V3_CHKPT_STD_SAVE(ctx, cga->dac_indexw_reg);
1136 V3_CHKPT_STD_SAVE(ctx, cga->dac_indexw_color);
1137 V3_CHKPT_STD_SAVE(ctx, cga->dac_data_regs[DAC_REG_COUNT]);
1139 V3_CHKPT_STD_SAVE(ctx, cga->activefb_addr);
1140 V3_CHKPT_STD_SAVE(ctx, cga->activefb_len);
1141 V3_CHKPT_STD_SAVE(ctx, cga->iorange);
1142 V3_CHKPT_STD_SAVE(ctx, cga->vres);
1143 V3_CHKPT_STD_SAVE(ctx, cga->hres);
1144 V3_CHKPT_STD_SAVE(ctx, cga->vchars);
1145 V3_CHKPT_STD_SAVE(ctx, cga->hchars);
1146 V3_CHKPT_STD_SAVE(ctx, cga->graphmode);
1148 V3_CHKPT_STD_SAVE(ctx, cga->dirty);
1149 V3_CHKPT_STD_SAVE(ctx, cga->reschanged);
1151 V3_CHKPT_STD_SAVE(ctx, cga->passthrough);
1153 v3_chkpt_save_16(ctx, "SCREEN_OFFSET", &(cga->screen_offset));
1154 v3_chkpt_save_16(ctx, "CURSOR_OFFSET", &(cga->cursor_offset));
1159 static int cga_load(struct v3_chkpt_ctx * ctx, void * private_data) {
1160 struct video_internal * cga = (struct video_internal *)private_data;
1162 v3_chkpt_load(ctx, "FRAMEBUFFER", FRAMEBUF_SIZE, cga->framebuf);
1165 V3_CHKPT_STD_LOAD(ctx, cga->misc_outp_reg);
1166 V3_CHKPT_STD_LOAD(ctx, cga->seq_index_reg);
1167 V3_CHKPT_STD_LOAD(ctx, cga->seq_data_regs[SEQ_REG_COUNT]);
1168 V3_CHKPT_STD_LOAD(ctx, cga->crtc_index_reg);
1169 V3_CHKPT_STD_LOAD(ctx, cga->crtc_data_regs[CRTC_REG_COUNT]);
1170 V3_CHKPT_STD_LOAD(ctx, cga->graphc_index_reg);
1171 V3_CHKPT_STD_LOAD(ctx, cga->graphc_data_regs[GRAPHC_REG_COUNT]);
1172 V3_CHKPT_STD_LOAD(ctx, cga->attrc_index_flipflop);
1173 V3_CHKPT_STD_LOAD(ctx, cga->attrc_index_reg);
1174 V3_CHKPT_STD_LOAD(ctx, cga->attrc_data_regs[ATTRC_REG_COUNT]);
1175 V3_CHKPT_STD_LOAD(ctx, cga->dac_indexr_reg);
1176 V3_CHKPT_STD_LOAD(ctx, cga->dac_indexr_color);
1177 V3_CHKPT_STD_LOAD(ctx, cga->dac_indexw_reg);
1178 V3_CHKPT_STD_LOAD(ctx, cga->dac_indexw_color);
1179 V3_CHKPT_STD_LOAD(ctx, cga->dac_data_regs[DAC_REG_COUNT]);
1181 V3_CHKPT_STD_LOAD(ctx, cga->activefb_addr);
1182 V3_CHKPT_STD_LOAD(ctx, cga->activefb_len);
1183 V3_CHKPT_STD_LOAD(ctx, cga->iorange);
1184 V3_CHKPT_STD_LOAD(ctx, cga->vres);
1185 V3_CHKPT_STD_LOAD(ctx, cga->hres);
1186 V3_CHKPT_STD_LOAD(ctx, cga->vchars);
1187 V3_CHKPT_STD_LOAD(ctx, cga->hchars);
1188 V3_CHKPT_STD_LOAD(ctx, cga->graphmode);
1190 V3_CHKPT_STD_LOAD(ctx, cga->dirty);
1191 V3_CHKPT_STD_LOAD(ctx, cga->reschanged);
1193 V3_CHKPT_STD_LOAD(ctx, cga->passthrough);
1195 v3_chkpt_load_16(ctx, "SCREEN_OFFSET", &(cga->screen_offset));
1196 v3_chkpt_load_16(ctx, "CURSOR_OFFSET", &(cga->cursor_offset));
1205 static struct v3_device_ops dev_ops = {
1206 .free = (int (*)(void *))cga_free,
1207 #ifdef V3_CONFIG_CHECKPOINT
1214 static int cga_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) {
1215 struct video_internal * video_state = NULL;
1216 char * dev_id = v3_cfg_val(cfg, "ID");
1217 char * passthrough_str = v3_cfg_val(cfg, "passthrough");
1220 PrintDebug("video: init_device\n");
1222 video_state = (struct video_internal *)V3_Malloc(sizeof(struct video_internal));
1223 memset(video_state, 0, sizeof(struct video_internal));
1225 struct vm_device * dev = v3_add_device(vm, dev_id, &dev_ops, video_state);
1228 PrintError("Could not attach device %s\n", dev_id);
1229 V3_Free(video_state);
1233 video_state->dev = dev;
1235 video_state->framebuf_pa = (addr_t)V3_AllocPages(FRAMEBUF_SIZE / 4096);
1236 video_state->framebuf = V3_VAddr((void *)(video_state->framebuf_pa));
1237 memset(video_state->framebuf, 0, FRAMEBUF_SIZE);
1239 PrintDebug("PA of array: %p\n", (void *)(video_state->framebuf_pa));
1241 if ((passthrough_str != NULL) &&
1242 (strcasecmp(passthrough_str, "enable") == 0)) {;
1243 video_state->passthrough = 1;
1247 if (video_state->passthrough) {
1248 PrintDebug("Enabling CGA Passthrough\n");
1249 if (v3_hook_write_mem(vm, V3_MEM_CORE_ANY, START_ADDR, END_ADDR,
1250 START_ADDR, &video_write_mem, dev) == -1) {
1251 PrintError("\n\nVideo Hook failed.\n\n");
1255 if (v3_hook_write_mem(vm, V3_MEM_CORE_ANY, START_ADDR, END_ADDR,
1256 video_state->framebuf_pa, &video_write_mem, dev) == -1) {
1257 PrintError("\n\nVideo Hook failed.\n\n");
1262 /* registers according to http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf */
1264 /* general registers */
1265 ret |= v3_dev_hook_io(dev, 0x3cc, &misc_outp_read, NULL);
1266 ret |= v3_dev_hook_io(dev, 0x3c2, &inp_status0_read, &misc_outp_write);
1267 ret |= v3_dev_hook_io(dev, 0x3ba, &inp_status1_read, &feat_ctrl_write);
1268 ret |= v3_dev_hook_io(dev, 0x3da, &inp_status1_read, &feat_ctrl_write);
1269 ret |= v3_dev_hook_io(dev, 0x3ca, &feat_ctrl_read, NULL);
1270 ret |= v3_dev_hook_io(dev, 0x3c3, &video_enable_read, &video_enable_write);
1272 /* sequencer registers */
1273 ret |= v3_dev_hook_io(dev, 0x3c4, &seq_index_read, &seq_index_write);
1274 ret |= v3_dev_hook_io(dev, 0x3c5, &seq_data_read, &seq_data_write);
1276 /* CRT controller registers, both CGA and VGA ranges */
1277 ret |= v3_dev_hook_io(dev, 0x3b4, &crtc_index_read, &crtc_index_write);
1278 ret |= v3_dev_hook_io(dev, 0x3b5, &crtc_data_read, &crtc_data_write);
1279 ret |= v3_dev_hook_io(dev, 0x3d4, &crtc_index_read, &crtc_index_write);
1280 ret |= v3_dev_hook_io(dev, 0x3d5, &crtc_data_read, &crtc_data_write);
1282 /* graphics controller registers */
1283 ret |= v3_dev_hook_io(dev, 0x3ce, &graphc_index_read, &graphc_index_write);
1284 ret |= v3_dev_hook_io(dev, 0x3cf, &graphc_data_read, &graphc_data_write);
1286 /* attribute controller registers */
1287 ret |= v3_dev_hook_io(dev, 0x3c0, &attrc_index_read, &attrc_write);
1288 ret |= v3_dev_hook_io(dev, 0x3c1, &attrc_data_read, NULL);
1290 /* video DAC palette registers */
1291 ret |= v3_dev_hook_io(dev, 0x3c8, &dac_indexw_read, &dac_indexw_write);
1292 ret |= v3_dev_hook_io(dev, 0x3c7, NULL, &dac_indexr_write);
1293 ret |= v3_dev_hook_io(dev, 0x3c9, &dac_data_read, &dac_data_write);
1294 ret |= v3_dev_hook_io(dev, 0x3c6, &dac_pelmask_read, &dac_pelmask_write);
1297 PrintError("Error allocating VGA IO ports\n");
1298 v3_remove_device(dev);
1302 /* initialize the state as it is at boot time */
1303 registers_initialize(video_state);
1308 device_register("CGA_VIDEO", cga_init);
1311 int v3_console_register_cga(struct vm_device * cga_dev, struct v3_console_ops * ops, void * private_data) {
1312 struct video_internal * video_state = (struct video_internal *)cga_dev->private_data;
1314 video_state->ops = ops;
1315 video_state->private_data = private_data;