xref: /haiku/src/system/boot/platform/bios_ia32/video.cpp (revision aff60bb217827097c13d643275fdf1f1c66e7f17)
1 /*
2  * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "video.h"
8 #include "bios.h"
9 #include "vesa.h"
10 #include "vga.h"
11 #include "mmu.h"
12 #include "images.h"
13 
14 #include <arch/cpu.h>
15 #include <boot/stage2.h>
16 #include <boot/platform.h>
17 #include <boot/menu.h>
18 #include <boot/kernel_args.h>
19 #include <util/list.h>
20 #include <drivers/driver_settings.h>
21 
22 #include <stdio.h>
23 #include <string.h>
24 
25 
26 //#define TRACE_VIDEO
27 #ifdef TRACE_VIDEO
28 #	define TRACE(x) dprintf x
29 #else
30 #	define TRACE(x) ;
31 #endif
32 
33 
34 struct video_mode {
35 	list_link	link;
36 	uint16		mode;
37 	int32		width, height, bits_per_pixel;
38 };
39 
40 static vbe_info_block sInfo;
41 static video_mode *sMode, *sDefaultMode;
42 static bool sVesaCompatible;
43 struct list sModeList;
44 static addr_t sFrameBuffer;
45 static bool sModeChosen;
46 static bool sSettingsLoaded;
47 
48 
49 static void
50 vga_set_palette(const uint8 *palette, int32 firstIndex, int32 numEntries)
51 {
52 	out8(firstIndex, VGA_COLOR_WRITE_MODE);
53 	// write VGA palette
54 	for (int32 i = firstIndex; i < numEntries; i++) {
55 		// VGA (usually) has only 6 bits per gun
56 		out8(palette[i * 3 + 0] >> 2, VGA_COLOR_DATA);
57 		out8(palette[i * 3 + 1] >> 2, VGA_COLOR_DATA);
58 		out8(palette[i * 3 + 2] >> 2, VGA_COLOR_DATA);
59 	}
60 }
61 
62 
63 static void
64 vga_enable_bright_background_colors(void)
65 {
66 	// reset attribute controller
67 	in8(VGA_INPUT_STATUS_1);
68 
69 	// select mode control register
70 	out8(0x30, VGA_ATTRIBUTE_WRITE);
71 
72 	// read mode control register, change it (we need to clear bit 3), and write it back
73 	uint8 mode = in8(VGA_ATTRIBUTE_READ) & 0xf7;
74 	out8(mode, VGA_ATTRIBUTE_WRITE);
75 }
76 
77 
78 //	#pragma mark - vesa
79 //	VESA functions
80 
81 
82 static status_t
83 vesa_get_mode_info(uint16 mode, struct vbe_mode_info *modeInfo)
84 {
85 	memset(modeInfo, 0, sizeof(vbe_mode_info));
86 
87 	struct bios_regs regs;
88 	regs.eax = 0x4f01;
89 	regs.ecx = mode;
90 	regs.es = ADDRESS_SEGMENT(modeInfo);
91 	regs.edi = ADDRESS_OFFSET(modeInfo);
92 	call_bios(0x10, &regs);
93 
94 	// %ah contains the error code
95 	if ((regs.eax & 0xff00) != 0)
96 		return B_ENTRY_NOT_FOUND;
97 
98 	return B_OK;
99 }
100 
101 
102 static status_t
103 vesa_get_vbe_info_block(vbe_info_block *info)
104 {
105 	memset(info, 0, sizeof(vbe_info_block));
106 	info->signature = VBE2_SIGNATURE;
107 
108 	struct bios_regs regs;
109 	regs.eax = 0x4f00;
110 	regs.es = ADDRESS_SEGMENT(info);
111 	regs.edi = ADDRESS_OFFSET(info);
112 	call_bios(0x10, &regs);
113 
114 	// %ah contains the error code
115 	if ((regs.eax & 0xff00) != 0)
116 		return B_ERROR;
117 
118 	if (info->signature != VESA_SIGNATURE)
119 		return B_ERROR;
120 
121 	dprintf("VESA version = %lx\n", info->version);
122 
123 	if (info->version.major < 2) {
124 		dprintf("VESA support too old\n", info->version);
125 		return B_ERROR;
126 	}
127 
128 	info->oem_string = SEGMENTED_TO_LINEAR(info->oem_string);
129 	info->mode_list = SEGMENTED_TO_LINEAR(info->mode_list);
130 	dprintf("oem string: %s\n", (const char *)info->oem_string);
131 
132 	return B_OK;
133 }
134 
135 
136 static status_t
137 vesa_init(vbe_info_block *info, video_mode **_standardMode)
138 {
139 	if (vesa_get_vbe_info_block(info) != B_OK)
140 		return B_ERROR;
141 
142 	// fill mode list
143 
144 	video_mode *standardMode = NULL;
145 
146 	for (int32 i = 0; true; i++) {
147 		uint16 mode = ((uint16 *)info->mode_list)[i];
148 		if (mode == 0xffff)
149 			break;
150 
151 		TRACE(("  %lx: ", mode));
152 
153 		struct vbe_mode_info modeInfo;
154 		if (vesa_get_mode_info(mode, &modeInfo) == B_OK) {
155 			TRACE(("%ld x %ld x %ld (a = %ld, mem = %ld, phy = %lx, p = %ld, b = %ld)\n",
156 				modeInfo.width, modeInfo.height, modeInfo.bits_per_pixel, modeInfo.attributes,
157 				modeInfo.memory_model, modeInfo.physical_base, modeInfo.num_planes,
158 				modeInfo.num_banks));
159 
160 			const uint32 requiredAttributes = MODE_ATTR_AVAILABLE | MODE_ATTR_GRAPHICS_MODE
161 								| MODE_ATTR_COLOR_MODE | MODE_ATTR_LINEAR_BUFFER;
162 
163 			if (modeInfo.width >= 640
164 				&& modeInfo.physical_base != 0
165 				&& modeInfo.num_planes == 1
166 				&& (modeInfo.memory_model == MODE_MEMORY_PACKED_PIXEL
167 					|| modeInfo.memory_model == MODE_MEMORY_DIRECT_COLOR)
168 				&& (modeInfo.attributes & requiredAttributes) == requiredAttributes) {
169 				// this mode fits our needs
170 				video_mode *videoMode = (video_mode *)malloc(sizeof(struct video_mode));
171 				if (videoMode == NULL)
172 					continue;
173 
174 				videoMode->mode = mode;
175 				videoMode->width = modeInfo.width;
176 				videoMode->height = modeInfo.height;
177 				videoMode->bits_per_pixel = modeInfo.bits_per_pixel;
178 
179 				// ToDo: for now, only accept 8 bit modes as standard
180 				if (standardMode == NULL && modeInfo.bits_per_pixel == 8)
181 					standardMode = videoMode;
182 				else if (standardMode != NULL) {
183 					// switch to the one with the higher resolution
184 					// ToDo: is that always a good idea? for now we'll use 800x600
185 					if (modeInfo.width > standardMode->width && modeInfo.width <= 800)
186 						standardMode = videoMode;
187 				}
188 
189 				list_add_item(&sModeList, videoMode);
190 			}
191 		} else
192 			TRACE(("(failed)\n"));
193 	}
194 
195 	if (standardMode == NULL) {
196 		// no usable VESA mode found...
197 		return B_ERROR;
198 	}
199 
200 	*_standardMode = standardMode;
201 	return B_OK;
202 }
203 
204 
205 #if 0
206 static status_t
207 vesa_get_mode(uint16 *_mode)
208 {
209 	struct bios_regs regs;
210 	regs.eax = 0x4f03;
211 	call_bios(0x10, &regs);
212 
213 	if ((regs.eax & 0xffff) != 0x4f)
214 		return B_ERROR;
215 
216 	*_mode = regs.ebx & 0xffff;
217 	return B_OK;
218 }
219 #endif
220 
221 
222 static status_t
223 vesa_set_mode(uint16 mode)
224 {
225 	struct bios_regs regs;
226 	regs.eax = 0x4f02;
227 	regs.ebx = (mode & SET_MODE_MASK) | SET_MODE_LINEAR_BUFFER;
228 	call_bios(0x10, &regs);
229 
230 	if ((regs.eax & 0xffff) != 0x4f)
231 		return B_ERROR;
232 
233 #if 0
234 	// make sure we have 8 bits per color channel
235 	regs.eax = 0x4f08;
236 	regs.ebx = 8 << 8;
237 	call_bios(0x10, &regs);
238 #endif
239 
240 	return B_OK;
241 }
242 
243 
244 static status_t
245 vesa_set_palette(const uint8 *palette, int32 firstIndex, int32 numEntries)
246 {
247 	// is this an 8 bit indexed color mode?
248 	if (gKernelArgs.frame_buffer.depth != 8)
249 		return B_BAD_TYPE;
250 
251 #if 0
252 	struct bios_regs regs;
253 	regs.eax = 0x4f09;
254 	regs.ebx = 0;
255 	regs.ecx = numEntries;
256 	regs.edx = firstIndex;
257 	regs.es = (addr_t)palette >> 4;
258 	regs.edi = (addr_t)palette & 0xf;
259 	call_bios(0x10, &regs);
260 
261 	if ((regs.eax & 0xffff) != 0x4f) {
262 #endif
263 		// the VESA call does not work, just try good old VGA mechanism
264 		vga_set_palette(palette, firstIndex, numEntries);
265 #if 0
266 		return B_ERROR;
267 	}
268 #endif
269 	return B_OK;
270 }
271 
272 
273 //	#pragma mark -
274 
275 
276 bool
277 video_mode_hook(Menu *menu, MenuItem *item)
278 {
279 	// find selected mode
280 	video_mode *mode = NULL;
281 
282 	menu = item->Submenu();
283 	item = menu->FindMarked();
284 	if (item != NULL) {
285 		switch (menu->IndexOf(item)) {
286 			case 0:
287 				// "Default" mode special
288 				sMode = sDefaultMode;
289 				sModeChosen = false;
290 				return true;
291 			case 1:
292 				// "Standard VGA" mode special
293 				// sets sMode to NULL which triggers VGA mode
294 				break;
295 			default:
296 				mode = (video_mode *)item->Data();
297 				break;
298 		}
299 	}
300 
301 	if (mode != sMode) {
302 		// update standard mode
303 		// ToDo: update fb settings!
304 		sMode = mode;
305 	}
306 
307 	sModeChosen = true;
308 	return true;
309 }
310 
311 
312 Menu *
313 video_mode_menu()
314 {
315 	Menu *menu = new Menu(CHOICE_MENU, "Select Video Mode");
316 	MenuItem *item;
317 
318 	menu->AddItem(item = new MenuItem("Default"));
319 	item->SetMarked(true);
320 	item->Select(true);
321 	item->SetHelpText("The Default video mode is the one currently configured in "
322 		"the system. If there is no mode configured yet, a viable mode will be chosen "
323 		"automatically.");
324 
325 	menu->AddItem(new MenuItem("Standard VGA"));
326 
327 	video_mode *mode = NULL;
328 	while ((mode = (video_mode *)list_get_next_item(&sModeList, mode)) != NULL) {
329 		char label[64];
330 		sprintf(label, "%ldx%ld %ld bit", mode->width, mode->height, mode->bits_per_pixel);
331 
332 		menu->AddItem(item = new MenuItem(label));
333 		item->SetData(mode);
334 	}
335 
336 	menu->AddSeparatorItem();
337 	menu->AddItem(item = new MenuItem("Return to main menu"));
338 	item->SetType(MENU_ITEM_NO_CHOICE);
339 
340 	return menu;
341 }
342 
343 
344 static video_mode *
345 find_video_mode(int32 width, int32 height, int32 depth)
346 {
347 	video_mode *mode = NULL;
348 	while ((mode = (video_mode *)list_get_next_item(&sModeList, mode)) != NULL) {
349 		if (mode->width == width
350 			&& mode->height == height
351 			&& mode->bits_per_pixel == depth) {
352 			return mode;
353 		}
354 	}
355 
356 	return NULL;
357 }
358 
359 
360 static void
361 get_mode_from_settings(void)
362 {
363 	if (sSettingsLoaded)
364 		return;
365 
366 	void *handle = load_driver_settings("vesa");
367 	if (handle == NULL)
368 		return;
369 
370 	const driver_settings *settings = get_driver_settings(handle);
371 	if (settings == NULL)
372 		goto out;
373 
374 	sSettingsLoaded = true;
375 
376 	for (int32 i = 0; i < settings->parameter_count; i++) {
377 		driver_parameter &parameter = settings->parameters[i];
378 
379 		if (!strcmp(parameter.name, "mode") && parameter.value_count > 2) {
380 			// parameter found, now get its values
381 			int32 width = strtol(parameter.values[0], NULL, 0);
382 			int32 height = strtol(parameter.values[1], NULL, 0);
383 			int32 depth = strtol(parameter.values[2], NULL, 0);
384 
385 			// search mode that fits
386 
387 			video_mode *mode = find_video_mode(width, height, depth);
388 			if (mode != NULL)
389 				sMode = mode;
390 		}
391 	}
392 
393 out:
394 	unload_driver_settings(handle);
395 }
396 
397 
398 static void
399 set_vga_mode(void)
400 {
401 	// sets 640x480 16 colors graphics mode
402 	bios_regs regs;
403 	regs.eax = 0x12;
404 	call_bios(0x10, &regs);
405 }
406 
407 
408 static void
409 set_text_mode(void)
410 {
411 	// sets 80x25 text console
412 	bios_regs regs;
413 	regs.eax = 3;
414 	call_bios(0x10, &regs);
415 }
416 
417 
418 //	#pragma mark - blit
419 
420 
421 static void
422 blit32(const uint8 *data, uint16 width, uint16 height,
423 	const uint8 *palette, uint16 left, uint16 top)
424 {
425 	uint32 *start = (uint32 *)sFrameBuffer + gKernelArgs.frame_buffer.width * top + left;
426 
427 	for (int32 y = 0; y < height; y++) {
428 		for (int32 x = 0; x < width; x++) {
429 			uint16 color = data[y * width + x] * 3;
430 
431 			start[x] = (palette[color + 0] << 16) | (palette[color + 1] << 8) | (palette[color + 2]);
432 		}
433 
434 		start += gKernelArgs.frame_buffer.width;
435 	}
436 }
437 
438 
439 static void
440 blit24(const uint8 *data, uint16 width, uint16 height,
441 	const uint8 *palette, uint16 left, uint16 top)
442 {
443 	uint8 *start = (uint8 *)sFrameBuffer + gKernelArgs.frame_buffer.width * 3 * top + 3 * left;
444 
445 	for (int32 y = 0; y < height; y++) {
446 		for (int32 x = 0; x < width; x++) {
447 			uint16 color = data[y * width + x] * 3;
448 			uint32 index = x * 3;
449 
450 			start[index + 0] = palette[color + 2];
451 			start[index + 1] = palette[color + 1];
452 			start[index + 2] = palette[color + 0];
453 		}
454 
455 		start += gKernelArgs.frame_buffer.width * 3;
456 	}
457 }
458 
459 
460 static void
461 blit16(const uint8 *data, uint16 width, uint16 height,
462 	const uint8 *palette, uint16 left, uint16 top)
463 {
464 	uint16 *start = (uint16 *)sFrameBuffer + gKernelArgs.frame_buffer.width * top + left;
465 
466 	for (int32 y = 0; y < height; y++) {
467 		for (int32 x = 0; x < width; x++) {
468 			uint16 color = data[y * width + x] * 3;
469 
470 			start[x] = ((palette[color + 0] >> 3) << 11) | ((palette[color + 1] >> 2) << 5)
471 				| ((palette[color + 2] >> 3));
472 		}
473 
474 		start += gKernelArgs.frame_buffer.width;
475 	}
476 }
477 
478 
479 static void
480 blit15(const uint8 *data, uint16 width, uint16 height,
481 	const uint8 *palette, uint16 left, uint16 top)
482 {
483 	uint16 *start = (uint16 *)sFrameBuffer + gKernelArgs.frame_buffer.width * top + left;
484 
485 	for (int32 y = 0; y < height; y++) {
486 		for (int32 x = 0; x < width; x++) {
487 			uint16 color = data[y * width + x] * 3;
488 
489 			start[x] = ((palette[color + 0] >> 3) << 10) | ((palette[color + 1] >> 3) << 5)
490 				| ((palette[color + 2] >> 3));
491 		}
492 
493 		start += gKernelArgs.frame_buffer.width;
494 	}
495 }
496 
497 
498 static void
499 blit8(const uint8 *data, uint16 width, uint16 height,
500 	const uint8 *palette, uint16 left, uint16 top)
501 {
502 	if (vesa_set_palette((const uint8 *)kPalette, 0, 256) != B_OK)
503 		dprintf("set palette failed!\n");
504 
505 	addr_t start = sFrameBuffer + gKernelArgs.frame_buffer.width * top + left;
506 
507 	for (int32 i = 0; i < height; i++) {
508 		memcpy((void *)(start + gKernelArgs.frame_buffer.width * i),
509 			&data[i * width], width);
510 	}
511 }
512 
513 
514 static void
515 blit4(const uint8 *data, uint16 width, uint16 height,
516 	const uint8 *palette, uint16 left, uint16 top)
517 {
518 	//	vga_set_palette((const uint8 *)kPalette16, 0, 16);
519 	// ToDo: no boot logo yet in VGA mode
520 #if 1
521 // this draws 16 big rectangles in all the available colors
522 	uint8 *bits = (uint8 *)sFrameBuffer;
523 	uint32 bytesPerRow = 80;
524 	for (int32 i = 0; i < 32; i++) {
525 		bits[9 * bytesPerRow + i + 2] = 0x55;
526 		bits[30 * bytesPerRow + i + 2] = 0xaa;
527 	}
528 
529 	for (int32 y = 10; y < 30; y++) {
530 		for (int32 i = 0; i < 16; i++) {
531 			out16((15 << 8) | 0x02, VGA_SEQUENCER_INDEX);
532 			bits[32 * bytesPerRow + i*2 + 2] = i;
533 
534 			if (i & 1) {
535 				out16((1 << 8) | 0x02, VGA_SEQUENCER_INDEX);
536 				bits[y * bytesPerRow + i*2 + 2] = 0xff;
537 				bits[y * bytesPerRow + i*2 + 3] = 0xff;
538 			}
539 			if (i & 2) {
540 				out16((2 << 8) | 0x02, VGA_SEQUENCER_INDEX);
541 				bits[y * bytesPerRow + i*2 + 2] = 0xff;
542 				bits[y * bytesPerRow + i*2 + 3] = 0xff;
543 			}
544 			if (i & 4) {
545 				out16((4 << 8) | 0x02, VGA_SEQUENCER_INDEX);
546 				bits[y * bytesPerRow + i*2 + 2] = 0xff;
547 				bits[y * bytesPerRow + i*2 + 3] = 0xff;
548 			}
549 			if (i & 8) {
550 				out16((8 << 8) | 0x02, VGA_SEQUENCER_INDEX);
551 				bits[y * bytesPerRow + i*2 + 2] = 0xff;
552 				bits[y * bytesPerRow + i*2 + 3] = 0xff;
553 			}
554 		}
555 	}
556 
557 	// enable all planes again
558 	out16((15 << 8) | 0x02, VGA_SEQUENCER_INDEX);
559 #endif
560 }
561 
562 
563 static void
564 blit_8bit_image(const uint8 *data, uint16 width, uint16 height,
565 	const uint8 *palette, uint16 left, uint16 top)
566 {
567 	switch (gKernelArgs.frame_buffer.depth) {
568 		case 4:
569 			return blit4(data, width, height, palette, left, top);
570 		case 8:
571 			return blit8(data, width, height, palette, left, top);
572 		case 15:
573 			return blit15(data, width, height, palette, left, top);
574 		case 16:
575 			return blit16(data, width, height, palette, left, top);
576 		case 24:
577 			return blit24(data, width, height, palette, left, top);
578 		case 32:
579 			return blit32(data, width, height, palette, left, top);
580 	}
581 }
582 
583 
584 //	#pragma mark -
585 
586 
587 extern "C" void
588 platform_switch_to_logo(void)
589 {
590 	// in debug mode, we'll never show the logo
591 	if ((platform_boot_options() & BOOT_OPTION_DEBUG_OUTPUT) != 0)
592 		return;
593 
594 	addr_t lastBase = gKernelArgs.frame_buffer.physical_buffer.start;
595 	size_t lastSize = gKernelArgs.frame_buffer.physical_buffer.size;
596 	int32 bytesPerPixel = 1;
597 
598 	if (sVesaCompatible && sMode != NULL) {
599 		if (!sModeChosen)
600 			get_mode_from_settings();
601 
602 		if (vesa_set_mode(sMode->mode) != B_OK)
603 			goto fallback;
604 
605 		struct vbe_mode_info modeInfo;
606 		if (vesa_get_mode_info(sMode->mode, &modeInfo) != B_OK)
607 			goto fallback;
608 
609 		bytesPerPixel = (modeInfo.bits_per_pixel + 7) / 8;
610 
611 		gKernelArgs.frame_buffer.width = modeInfo.width;
612 		gKernelArgs.frame_buffer.height = modeInfo.height;
613 		gKernelArgs.frame_buffer.depth = modeInfo.bits_per_pixel;
614 		gKernelArgs.frame_buffer.physical_buffer.size = gKernelArgs.frame_buffer.width
615 			* gKernelArgs.frame_buffer.height * bytesPerPixel;
616 		gKernelArgs.frame_buffer.physical_buffer.start = modeInfo.physical_base;
617 	} else {
618 fallback:
619 		// use standard VGA mode 640x480x4
620 		set_vga_mode();
621 
622 		gKernelArgs.frame_buffer.width = 640;
623 		gKernelArgs.frame_buffer.height = 480;
624 		gKernelArgs.frame_buffer.depth = 4;
625 		gKernelArgs.frame_buffer.physical_buffer.size = gKernelArgs.frame_buffer.width
626 			* gKernelArgs.frame_buffer.height / 2;
627 		gKernelArgs.frame_buffer.physical_buffer.start = 0xa0000;
628 	}
629 
630 	gKernelArgs.frame_buffer.enabled = 1;
631 
632 	// If the new frame buffer is either larger than the old one or located at
633 	// a different address, we need to remap it, so we first have to throw
634 	// away its previous mapping
635 	if (lastBase != 0
636 		&& (lastBase != gKernelArgs.frame_buffer.physical_buffer.start
637 			|| lastSize < gKernelArgs.frame_buffer.physical_buffer.size)) {
638 		mmu_free((void *)sFrameBuffer, lastSize);
639 		lastBase = 0;
640 	}
641 	if (lastBase == 0) {
642 		// the graphics memory has not been mapped yet!
643 		sFrameBuffer = mmu_map_physical_memory(gKernelArgs.frame_buffer.physical_buffer.start,
644 							gKernelArgs.frame_buffer.physical_buffer.size, kDefaultPageFlags);
645 	}
646 
647 	// clear the video memory
648 	// ToDo: this shouldn't be necessary on real hardware (and Bochs), but
649 	//	at least booting with Qemu looks ugly when this is missing
650 	memset((void *)sFrameBuffer, 0, gKernelArgs.frame_buffer.physical_buffer.size);
651 
652 	// ToDo: the boot image is only a temporary solution - it should be
653 	//	provided by the loader itself, as well as the blitting routines.
654 	//	The image should be compressed, too.
655 
656 	blit_8bit_image(kImageData, kWidth, kHeight, kPalette,
657 		gKernelArgs.frame_buffer.width - kWidth - 40,
658 		gKernelArgs.frame_buffer.height - kHeight - 60);
659 }
660 
661 
662 extern "C" void
663 platform_switch_to_text_mode(void)
664 {
665 	if (!gKernelArgs.frame_buffer.enabled) {
666 		vga_enable_bright_background_colors();
667 		return;
668 	}
669 
670 	set_text_mode();
671 	gKernelArgs.frame_buffer.enabled = 0;
672 
673 	vga_enable_bright_background_colors();
674 }
675 
676 
677 extern "C" status_t
678 platform_init_video(void)
679 {
680 	gKernelArgs.frame_buffer.enabled = 0;
681 	list_init(&sModeList);
682 
683 	set_text_mode();
684 		// You may wonder why we do this here:
685 		// Obviously, some graphics card BIOS implementations don't
686 		// report all available modes unless you've done this before
687 		// getting the VESA information.
688 		// One example of those is the SiS 630 chipset in my laptop.
689 
690 	sVesaCompatible = vesa_init(&sInfo, &sDefaultMode) == B_OK;
691 	if (!sVesaCompatible) {
692 		TRACE(("No VESA compatible graphics!\n"));
693 		return B_ERROR;
694 	}
695 
696 	sMode = sDefaultMode;
697 
698 	TRACE(("VESA compatible graphics!\n"));
699 
700 	return B_OK;
701 }
702 
703