1/* $NetBSD: vbe.c,v 1.6 2010/06/25 15:35:08 tsutsui Exp $ */
2
3/*-
4 * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * VESA BIOS Extensions routines
31 */
32
33#include <lib/libsa/stand.h>
34#include <lib/libkern/libkern.h>
35#include <machine/bootinfo.h>
36#include "libi386.h"
37#include "vbe.h"
38
39extern const uint8_t rasops_cmap[];
40static uint8_t *vbe_edid = NULL;
41static int vbe_edid_valid = 0;
42
43static struct _vbestate {
44	int		available;
45	int		modenum;
46} vbestate;
47
48static int
49vbe_mode_is_supported(struct modeinfoblock *mi)
50{
51	if ((mi->ModeAttributes & 0x01) == 0)
52		return 0;	/* mode not supported by hardware */
53	if ((mi->ModeAttributes & 0x08) == 0)
54		return 0;	/* linear fb not available */
55	if ((mi->ModeAttributes & 0x10) == 0)
56		return 0;	/* text mode */
57	if (mi->NumberOfPlanes != 1)
58		return 0;	/* planar mode not supported */
59	if (mi->MemoryModel != 0x04 /* Packed pixel */ &&
60	    mi->MemoryModel != 0x06 /* Direct Color */)
61		return 0;	/* unsupported pixel format */
62	return 1;
63}
64
65static bool
66vbe_check(void)
67{
68	if (!vbestate.available) {
69		printf("VBE not available\n");
70		return false;
71	}
72	return true;
73}
74
75void
76vbe_init(void)
77{
78	struct vbeinfoblock vbe;
79
80	memset(&vbe, 0, sizeof(vbe));
81	memcpy(vbe.VbeSignature, "VBE2", 4);
82	if (biosvbe_info(&vbe) != 0x004f)
83		return;
84	if (memcmp(vbe.VbeSignature, "VESA", 4) != 0)
85		return;
86
87	vbestate.available = 1;
88	vbestate.modenum = 0;
89}
90
91int
92vbe_available(void)
93{
94	return vbestate.available;
95}
96
97int
98vbe_set_palette(const uint8_t *cmap, int slot)
99{
100	struct paletteentry pe;
101	int ret;
102
103	if (!vbe_check())
104		return 1;
105
106	pe.Blue = cmap[2] >> 2;
107	pe.Green = cmap[1] >> 2;
108	pe.Red = cmap[0] >> 2;
109	pe.Alignment = 0;
110
111	ret = biosvbe_palette_data(0x0600, slot, &pe);
112
113	return ret == 0x004f ? 0 : 1;
114}
115
116int
117vbe_set_mode(int modenum)
118{
119	struct modeinfoblock mi;
120	struct btinfo_framebuffer fb;
121	int ret, i;
122
123	if (!vbe_check())
124		return 1;
125
126	ret = biosvbe_get_mode_info(modenum, &mi);
127	if (ret != 0x004f) {
128		printf("mode 0x%x invalid\n", modenum);
129		return 1;
130	}
131
132	if (!vbe_mode_is_supported(&mi)) {
133		printf("mode 0x%x not supported\n", modenum);
134		return 1;
135	}
136
137	ret = biosvbe_set_mode(modenum);
138	if (ret != 0x004f) {
139		printf("mode 0x%x could not be set\n", modenum);
140		return 1;
141	}
142
143	/* Setup palette for packed pixel mode */
144	if (mi.MemoryModel == 0x04)
145		for (i = 0; i < 256; i++)
146			vbe_set_palette(&rasops_cmap[i * 3], i);
147
148	fb.physaddr = (uint64_t)mi.PhysBasePtr & 0xffffffff;
149	fb.width = mi.XResolution;
150	fb.height = mi.YResolution;
151	fb.stride = mi.BytesPerScanLine;
152	fb.depth = mi.BitsPerPixel;
153	fb.flags = 0;
154	fb.rnum = mi.RedMaskSize;
155	fb.rpos = mi.RedFieldPosition;
156	fb.gnum = mi.GreenMaskSize;
157	fb.gpos = mi.GreenFieldPosition;
158	fb.bnum = mi.BlueMaskSize;
159	fb.bpos = mi.BlueFieldPosition;
160	fb.vbemode = modenum;
161
162	framebuffer_configure(&fb);
163
164	return 0;
165}
166
167int
168vbe_commit(void)
169{
170	int ret = 1;
171
172	if (vbestate.modenum > 0) {
173		ret = vbe_set_mode(vbestate.modenum);
174		if (ret) {
175			printf("WARNING: failed to set VBE mode 0x%x\n",
176			    vbestate.modenum);
177			wait_sec(5);
178		}
179	}
180	return ret;
181}
182
183static void *
184vbe_farptr(uint32_t farptr)
185{
186	return VBEPHYPTR((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff)));
187}
188
189static int
190vbe_parse_mode_str(char *str, int *x, int *y, int *depth)
191{
192	char *p;
193
194	p = str;
195	*x = strtoul(p, NULL, 0);
196	if (*x == 0)
197		return 0;
198	p = strchr(p, 'x');
199	if (!p)
200		return 0;
201	++p;
202	*y = strtoul(p, NULL, 0);
203	if (*y == 0)
204		return 0;
205	p = strchr(p, 'x');
206	if (!p)
207		*depth = 8;
208	else {
209		++p;
210		*depth = strtoul(p, NULL, 0);
211		if (*depth == 0)
212			return 0;
213	}
214
215	return 1;
216}
217
218static int
219vbe_find_mode_xyd(int x, int y, int depth)
220{
221	struct vbeinfoblock vbe;
222	struct modeinfoblock mi;
223	uint32_t farptr;
224	uint16_t mode;
225	int safety = 0;
226
227	memset(&vbe, 0, sizeof(vbe));
228	memcpy(vbe.VbeSignature, "VBE2", 4);
229	if (biosvbe_info(&vbe) != 0x004f)
230		return 0;
231	if (memcmp(vbe.VbeSignature, "VESA", 4) != 0)
232		return 0;
233	farptr = vbe.VideoModePtr;
234	if (farptr == 0)
235		return 0;
236
237	while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
238		safety++;
239		farptr += 2;
240		if (safety == 100)
241			return 0;
242		if (biosvbe_get_mode_info(mode, &mi) != 0x004f)
243			continue;
244		/* we only care about linear modes here */
245		if (vbe_mode_is_supported(&mi) == 0)
246			continue;
247		safety = 0;
248		if (mi.XResolution == x &&
249		    mi.YResolution == y &&
250		    mi.BitsPerPixel == depth)
251			return mode;
252	}
253
254	return 0;
255}
256
257static int
258vbe_find_mode(char *str)
259{
260	int x, y, depth;
261
262	if (!vbe_parse_mode_str(str, &x, &y, &depth))
263		return 0;
264
265	return vbe_find_mode_xyd(x, y, depth);
266}
267
268static void
269vbe_dump_mode(int modenum, struct modeinfoblock *mi)
270{
271	printf("0x%x=%dx%dx%d", modenum,
272	    mi->XResolution, mi->YResolution, mi->BitsPerPixel);
273}
274
275static int
276vbe_get_edid(int *pwidth, int *pheight)
277{
278	const uint8_t magic[] = EDID_MAGIC;
279	int ddc_caps, ret;
280
281	ddc_caps = biosvbe_ddc_caps();
282	if (ddc_caps == 0) {
283		return 1;
284	}
285
286	if (vbe_edid == NULL) {
287		vbe_edid = alloc(128);
288	}
289	if (vbe_edid_valid == 0) {
290		ret = biosvbe_ddc_read_edid(0, vbe_edid);
291		if (ret != 0x004f)
292			return 1;
293		if (memcmp(vbe_edid, magic, sizeof(magic)) != 0)
294			return 1;
295		vbe_edid_valid = 1;
296	}
297
298	*pwidth = vbe_edid[EDID_DESC_BLOCK + 2] |
299	    (((int)vbe_edid[EDID_DESC_BLOCK + 4] & 0xf0) << 4);
300	*pheight = vbe_edid[EDID_DESC_BLOCK + 5] |
301	    (((int)vbe_edid[EDID_DESC_BLOCK + 7] & 0xf0) << 4);
302
303	return 0;
304}
305
306void
307vbe_modelist(void)
308{
309	struct vbeinfoblock vbe;
310	struct modeinfoblock mi;
311	uint32_t farptr;
312	uint16_t mode;
313	int nmodes = 0, safety = 0;
314	int ddc_caps, edid_width, edid_height;
315
316	if (!vbe_check())
317		return;
318
319	ddc_caps = biosvbe_ddc_caps();
320	if (ddc_caps & 3) {
321		printf("DDC");
322		if (ddc_caps & 1)
323			printf(" [DDC1]");
324		if (ddc_caps & 2)
325			printf(" [DDC2]");
326
327		if (vbe_get_edid(&edid_width, &edid_height) != 0)
328			printf(": no EDID information\n");
329		else
330			printf(": EDID %dx%d\n", edid_width, edid_height);
331	}
332
333	printf("Modes: ");
334	memset(&vbe, 0, sizeof(vbe));
335	memcpy(vbe.VbeSignature, "VBE2", 4);
336	if (biosvbe_info(&vbe) != 0x004f)
337		goto done;
338	if (memcmp(vbe.VbeSignature, "VESA", 4) != 0)
339		goto done;
340	farptr = vbe.VideoModePtr;
341	if (farptr == 0)
342		goto done;
343
344	while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
345		safety++;
346		farptr += 2;
347		if (safety == 100) {
348			printf("[?] ");
349			break;
350		}
351		if (biosvbe_get_mode_info(mode, &mi) != 0x004f)
352			continue;
353		/* we only care about linear modes here */
354		if (vbe_mode_is_supported(&mi) == 0)
355			continue;
356		safety = 0;
357		if (nmodes % 4 == 0)
358			printf("\n");
359		else
360			printf("  ");
361		vbe_dump_mode(mode, &mi);
362		nmodes++;
363	}
364
365done:
366	if (nmodes == 0)
367		printf("none found");
368	printf("\n");
369}
370
371void
372command_vesa(char *cmd)
373{
374	char arg[20];
375	int modenum, edid_width, edid_height;
376
377	if (!vbe_check())
378		return;
379
380	strlcpy(arg, cmd, sizeof(arg));
381
382	if (strcmp(arg, "list") == 0) {
383		vbe_modelist();
384		return;
385	}
386
387	if (strcmp(arg, "disabled") == 0 || strcmp(arg, "off") == 0) {
388		vbestate.modenum = 0;
389		return;
390	}
391
392	if (strcmp(arg, "enabled") == 0 || strcmp(arg, "on") == 0) {
393		if (vbe_get_edid(&edid_width, &edid_height) != 0) {
394			modenum = VBE_DEFAULT_MODE;
395		} else {
396			modenum = vbe_find_mode_xyd(edid_width, edid_height, 8);
397			if (modenum == 0)
398				modenum = VBE_DEFAULT_MODE;
399		}
400	} else if (strncmp(arg, "0x", 2) == 0) {
401		modenum = strtoul(arg, NULL, 0);
402	} else if (strchr(arg, 'x') != NULL) {
403		modenum = vbe_find_mode(arg);
404		if (modenum == 0) {
405			printf("mode %s not supported by firmware\n", arg);
406			return;
407		}
408	} else {
409		modenum = 0;
410	}
411
412	if (modenum >= 0x100) {
413		vbestate.modenum = modenum;
414		return;
415	}
416
417	printf("invalid flag, must be 'on', 'off', "
418	    "a display mode, or a VBE mode number\n");
419}
420