xref: /haiku/src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp (revision 52f7c9389475e19fc21487b38064b4390eeb6fea)
1 /*
2  * Copyright 2012-2014, Artem Falcon <lomka@gero.in>
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <string.h>
8 
9 #include <KernelExport.h>
10 #include <directories.h>
11 #include <stdio.h>
12 #include <driver_settings.h>
13 
14 #include "intel_extreme.h"
15 #include "driver.h"
16 
17 
18 #define TRACE_BIOS
19 #ifdef TRACE_BIOS
20 #	define TRACE(x) dprintf x
21 #else
22 #	define TRACE(x) ;
23 #endif
24 
25 
26 struct vbt_header {
27 	uint8 signature[20];
28 	uint16 version;
29 	uint16 header_size;
30 	uint16 vbt_size;
31 	uint8 vbt_checksum;
32 	uint8 reserved0;
33 	uint32 bdb_offset;
34 	uint32 aim_offset[4];
35 } __attribute__((packed));
36 
37 
38 struct bdb_header {
39 	uint8 signature[16];
40 	uint16 version;
41 	uint16 header_size;
42 	uint16 bdb_size;
43 } __attribute__((packed));
44 
45 
46 enum bdb_block_id {
47 	BDB_LVDS_OPTIONS = 40,
48 	BDB_LVDS_LFP_DATA_PTRS = 41,
49 	BDB_GENERIC_DTD = 58
50 };
51 
52 
53 // FIXME the struct definition for the bdb_header is not complete, so we rely
54 // on direct access with hardcoded offsets to get the timings out of it.
55 #define _PIXEL_CLOCK(x) (x[0] + (x[1] << 8)) * 10000
56 #define _H_ACTIVE(x) (x[2] + ((x[4] & 0xF0) << 4))
57 #define _H_BLANK(x) (x[3] + ((x[4] & 0x0F) << 8))
58 #define _H_SYNC_OFF(x) (x[8] + ((x[11] & 0xC0) << 2))
59 #define _H_SYNC_WIDTH(x) (x[9] + ((x[11] & 0x30) << 4))
60 #define _V_ACTIVE(x) (x[5] + ((x[7] & 0xF0) << 4))
61 #define _V_BLANK(x) (x[6] + ((x[7] & 0x0F) << 8))
62 #define _V_SYNC_OFF(x) ((x[10] >> 4) + ((x[11] & 0x0C) << 2))
63 #define _V_SYNC_WIDTH(x) ((x[10] & 0x0F) + ((x[11] & 0x03) << 4))
64 
65 
66 struct lvds_bdb1 {
67 	uint8 id;
68 	uint16 size;
69 	uint8 panel_type;
70 	uint8 reserved0;
71 	uint16 caps;
72 } __attribute__((packed));
73 
74 
75 struct lvds_bdb2_entry {
76 	uint16 lfp_info_offset;
77 	uint8 lfp_info_size;
78 	uint16 lfp_edid_dtd_offset;
79 	uint8 lfp_edid_dtd_size;
80 	uint16 lfp_edid_pid_offset;
81 	uint8 lfp_edid_pid_size;
82 } __attribute__((packed));
83 
84 
85 struct lvds_bdb2 {
86 	uint8 id;
87 	uint16 size;
88 	uint8 table_size; /* followed by one or more lvds_data_ptr structs */
89 	struct lvds_bdb2_entry panels[16];
90 } __attribute__((packed));
91 
92 
93 struct lvds_bdb2_lfp_info {
94 	uint16 x_res;
95 	uint16 y_res;
96 	uint32 lvds_reg;
97 	uint32 lvds_reg_val;
98 	uint32 pp_on_reg;
99 	uint32 pp_on_reg_val;
100 	uint32 pp_off_reg;
101 	uint32 pp_off_reg_val;
102 	uint32 pp_cycle_reg;
103 	uint32 pp_cycle_reg_val;
104 	uint32 pfit_reg;
105 	uint32 pfit_reg_val;
106 	uint16 terminator;
107 } __attribute__((packed));
108 
109 
110 
111 struct generic_dtd_entry {
112 	uint32 pixel_clock;
113 	uint16 hactive;
114 	uint16 hblank;
115 	uint16 hfront_porch;
116 	uint16 hsync;
117 	uint16 vactive;
118 	uint16 vblank;
119 	uint16 vfront_porch;
120 	uint16 vsync;
121 	uint16 width_mm;
122 	uint16 height_mm;
123 
124 	uint8 rsvd_flags:6;
125 	uint8 vsync_positive_polarity:1;
126 	uint8 hsync_positive_polarity:1;
127 
128 	uint8 rsvd[3];
129 } __attribute__((packed));
130 
131 
132 struct bdb_generic_dtd {
133 	uint8 id;
134 	uint16 size;
135 	uint16 gdtd_size;
136 	struct generic_dtd_entry dtd[];
137 } __attribute__((packed));
138 
139 
140 static struct vbios {
141 	area_id			area;
142 	uint8*			memory;
143 	uint16_t ReadWord(off_t address)
144 	{
145 		return memory[address] | memory[address + 1] << 8;
146 	}
147 } vbios;
148 
149 
150 static bool
151 read_settings_dumpRom(void)
152 {
153 	bool dumpRom = false;
154 
155 	void* settings = load_driver_settings("intel_extreme");
156 	if (settings != NULL) {
157 		dumpRom = get_driver_boolean_parameter(settings,
158 			"dump_rom", false, false);
159 
160 		unload_driver_settings(settings);
161 	}
162 	return dumpRom;
163 }
164 
165 
166 static void
167 dumprom(void *rom, uint32 size, intel_info &info)
168 {
169 	int fd;
170 	uint32 cnt;
171 	char fname[64];
172 
173 	/* determine the romfile name: we need split-up per card in the system */
174 	sprintf (fname, kUserDirectory "//intel_extreme.%04x_%04x_%02x%02x%02x.rom",
175 		info.pci->vendor_id, info.pci->device_id, info.pci->bus, info.pci->device, info.pci->function);
176 
177 	fd = open (fname, O_WRONLY | O_CREAT, 0666);
178 	if (fd < 0) return;
179 
180 	/* The ROM size is a multiple of 1kb.. */
181 	for (cnt = 0; (cnt < size); cnt += 1024)
182 		write (fd, ((void *)(((uint8 *)rom) + cnt)), 1024);
183 	close (fd);
184 }
185 
186 
187 /*!	This is reimplementation, Haiku uses BIOS call and gets most current panel
188 	info, we're, otherwise, digging in VBIOS memory and parsing VBT tables to
189 	get native panel timings. This will allow to get non-updated,
190 	PROM-programmed timings info when compensation mode is off on your machine.
191 */
192 
193 // outdated: https://01.org/sites/default/files/documentation/skl_opregion_rev0p5.pdf
194 
195 #define ASLS 0xfc // ASL Storage.
196 #define OPREGION_SIGNATURE "IntelGraphicsMem"
197 #define OPREGION_ASLE_OFFSET   0x300
198 #define OPREGION_VBT_OFFSET   0x400
199 #define MBOX_ACPI (1 << 0)
200 #define MBOX_SWSCI (1 << 1)
201 #define MBOX_ASLE (1 << 2)
202 #define MBOX_ASLE_EXT (1 << 3)
203 
204 
205 struct opregion_header {
206 	uint8 signature[16];
207 	uint32 size;
208 	uint8 reserved;
209 	uint8 revision_version;
210 	uint8 minor_version;
211 	uint8 major_version;
212 	uint8 sver[32];
213 	uint8 vver[16];
214 	uint8 gver[16];
215 	uint32 mboxes;
216 	uint32 driver_model;
217 	uint32 platform_configuration;
218 	uint8 gop_version[32];
219 	uint8 rsvd[124];
220 } __attribute__((packed));
221 
222 
223 struct opregion_asle {
224 	uint32 ardy;
225 	uint32 aslc;
226 	uint32 tche;
227 	uint32 alsi;
228 	uint32 bclp;
229 	uint32 pfit;
230 	uint32 cblv;
231 	uint16 bclm[20];
232 	uint32 cpfm;
233 	uint32 epfm;
234 	uint8 plut[74];
235 	uint32 pfmb;
236 	uint32 cddv;
237 	uint32 pcft;
238 	uint32 srot;
239 	uint32 iuer;
240 	uint64 fdss;
241 	uint32 fdsp;
242 	uint32 stat;
243 	uint64 rvda;
244 	uint32 rvds;
245 	uint8 rsvd[58];
246 } __attribute__((packed));
247 
248 
249 static bool
250 get_bios(int* vbtOffset)
251 {
252 	STATIC_ASSERT(sizeof(opregion_header) == 0x100);
253 	STATIC_ASSERT(sizeof(opregion_asle) == 0x100);
254 
255 	intel_info &info = *gDeviceInfo[0];
256 	// first try to fetch Intel OpRegion which should be populated by the BIOS at start
257 	uint32 kVBIOSSize;
258 
259 	for (uint32 romMethod = 0; romMethod < 2; romMethod++) {
260 		switch(romMethod) {
261 			case 0:
262 			{
263 				// get OpRegion - see Intel ACPI IGD info in acpi_igd_opregion_spec_0.pdf
264 				uint64 kVBIOSAddress = get_pci_config(info.pci, ASLS, 4);
265 				if (kVBIOSAddress == 0) {
266 					TRACE((DEVICE_NAME ": ACPI OpRegion not supported!\n"));
267 					continue;
268 				}
269 				kVBIOSSize = 8 * 1024;
270 				TRACE((DEVICE_NAME ": Graphic OpRegion physical addr: 0x%" B_PRIx64
271 						"; size: 0x%" B_PRIx32 "\n", kVBIOSAddress, kVBIOSSize));
272 				vbios.area = map_physical_memory("ASLS mapping", kVBIOSAddress,
273 					kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void **)&vbios.memory);
274 				if (vbios.area < 0)
275 					continue;
276 				TRACE((DEVICE_NAME ": mapping OpRegion: 0x%" B_PRIx64 " -> %p\n",
277 					kVBIOSAddress, vbios.memory));
278 				// check if we got the OpRegion and signature
279 				if (memcmp(vbios.memory, OPREGION_SIGNATURE, 16) != 0) {
280 					TRACE((DEVICE_NAME ": OpRegion signature mismatch\n"));
281 					delete_area(vbios.area);
282 					vbios.area = -1;
283 					continue;
284 				}
285 				opregion_header* header = (opregion_header*)vbios.memory;
286 				opregion_asle* asle = (opregion_asle*)(vbios.memory + OPREGION_ASLE_OFFSET);
287 				if (header->major_version < 2 || (header->mboxes & MBOX_ASLE) == 0
288 					|| asle->rvda == 0 || asle->rvds == 0) {
289 					vbios.memory += OPREGION_VBT_OFFSET;
290 					kVBIOSSize -= OPREGION_VBT_OFFSET;
291 					break;
292 				}
293 				uint64 rvda = asle->rvda;
294 				kVBIOSSize = asle->rvds;
295 				if (header->major_version > 3 || header->minor_version >= 1) {
296 					rvda += kVBIOSAddress;
297 				}
298 				TRACE((DEVICE_NAME ": RVDA physical addr: 0x%" B_PRIx64
299 						"; size: 0x%" B_PRIx32 "\n", rvda, kVBIOSSize));
300 				delete_area(vbios.area);
301 				vbios.area = map_physical_memory("RVDA mapping", rvda,
302 					kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void **)&vbios.memory);
303 				if (vbios.area < 0)
304 					continue;
305 				break;
306 			}
307 			case 1:
308 			{
309 				uint64 kVBIOSAddress = 0xc0000;
310 				kVBIOSSize = 64 * 1024;
311 				/* !!!DANGER!!!: mapping of BIOS using legacy location as a fallback,
312 				  hence, if panel mode will be set using info from VBT this way, it will
313 				  be taken from primary card's VBIOS */
314 				vbios.area = map_physical_memory("VBIOS mapping", kVBIOSAddress,
315 					kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void**)&vbios.memory);
316 				if (vbios.area < 0)
317 					continue;
318 
319 				TRACE((DEVICE_NAME ": mapping VBIOS: 0x%" B_PRIx64 " -> %p\n",
320 					kVBIOSAddress, vbios.memory));
321 				break;
322 			}
323 		}
324 
325 		// dump ROM to file if selected in settings file
326 		if (read_settings_dumpRom())
327 			dumprom(vbios.memory, kVBIOSSize, info);
328 
329 		// scan BIOS for VBT signature
330 		*vbtOffset = kVBIOSSize;
331 		for (uint32 i = 0; i + 4 < kVBIOSSize; i += 4) {
332 			if (memcmp(vbios.memory + i, "$VBT", 4) == 0) {
333 				*vbtOffset = i;
334 				break;
335 			}
336 		}
337 
338 		if ((*vbtOffset + (uint32)sizeof(vbt_header)) >= kVBIOSSize) {
339 			TRACE((DEVICE_NAME": bad VBT offset : 0x%x\n", *vbtOffset));
340 			delete_area(vbios.area);
341 			continue;
342 		}
343 
344 		struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + *vbtOffset);
345 		if (memcmp(vbt->signature, "$VBT", 4) != 0) {
346 			TRACE((DEVICE_NAME": bad VBT signature: %20s\n", vbt->signature));
347 			delete_area(vbios.area);
348 			continue;
349 		}
350 		return true;
351 	}
352 
353 	return false;
354 }
355 
356 
357 static void
358 sanitize_panel_timing(display_timing& timing)
359 {
360 	bool bogus = false;
361 
362 	/* handle bogus h/vtotal values, if got such */
363 	if (timing.h_sync_end > timing.h_total) {
364 		timing.h_total = timing.h_sync_end + 1;
365 		bogus = true;
366 		TRACE((DEVICE_NAME": got bogus htotal. Fixing\n"));
367 	}
368 	if (timing.v_sync_end > timing.v_total) {
369 		timing.v_total = timing.v_sync_end + 1;
370 		bogus = true;
371 		TRACE((DEVICE_NAME": got bogus vtotal. Fixing\n"));
372 	}
373 
374 	if (bogus) {
375 		TRACE((DEVICE_NAME": adjusted LFP modeline: %" B_PRIu32 " KHz,\t"
376 			"%d %d %d %d   %d %d %d %d\n",
377 			timing.pixel_clock / (timing.h_total * timing.v_total),
378 			timing.h_display, timing.h_sync_start,
379 			timing.h_sync_end, timing.h_total,
380 			timing.v_display, timing.v_sync_start,
381 			timing.v_sync_end, timing.v_total));
382 	}
383 }
384 
385 
386 bool
387 get_lvds_mode_from_bios(display_timing* panelTiming)
388 {
389 	int vbtOffset = 0;
390 	if (!get_bios(&vbtOffset))
391 		return false;
392 
393 	struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + vbtOffset);
394 	int bdbOffset = vbtOffset + vbt->bdb_offset;
395 
396 	struct bdb_header* bdb = (struct bdb_header*)(vbios.memory + bdbOffset);
397 	if (memcmp(bdb->signature, "BIOS_DATA_BLOCK ", 16) != 0) {
398 		TRACE((DEVICE_NAME": bad BDB signature\n"));
399 		delete_area(vbios.area);
400 	}
401 	TRACE((DEVICE_NAME ": VBT signature \"%.*s\", BDB version %d\n",
402 			(int)sizeof(vbt->signature), vbt->signature, bdb->version));
403 
404 	int blockSize;
405 	int panelType = -1;
406 	bool panelTimingFound = false;
407 
408 	for (int bdbBlockOffset = bdb->header_size; bdbBlockOffset < bdb->bdb_size;
409 			bdbBlockOffset += blockSize) {
410 		int start = bdbOffset + bdbBlockOffset;
411 
412 		int id = vbios.memory[start];
413 		blockSize = vbios.ReadWord(start + 1) + 3;
414 		switch (id) {
415 			case BDB_LVDS_OPTIONS:
416 			{
417 				struct lvds_bdb1 *lvds1;
418 				lvds1 = (struct lvds_bdb1 *)(vbios.memory + start);
419 				panelType = lvds1->panel_type;
420 				if (panelType > 0xf) {
421 					TRACE((DEVICE_NAME ": invalid panel type %d\n", panelType));
422 					panelType = -1;
423 					break;
424 				}
425 				TRACE((DEVICE_NAME ": panel type: %d\n", panelType));
426 				break;
427 			}
428 			case BDB_LVDS_LFP_DATA_PTRS:
429 			{
430 				// First make sure we found block BDB_LVDS_OPTIONS and the panel type
431 				if (panelType == -1)
432 					break;
433 
434 				struct lvds_bdb2 *lvds2;
435 				struct lvds_bdb2_lfp_info *lvds2_lfp_info;
436 
437 				lvds2 = (struct lvds_bdb2 *)(vbios.memory + start);
438 				lvds2_lfp_info = (struct lvds_bdb2_lfp_info *)
439 					(vbios.memory + bdbOffset
440 					+ lvds2->panels[panelType].lfp_info_offset);
441 				/* Show terminator: Check not done in drm i915 driver: Assuming chk not valid. */
442 				TRACE((DEVICE_NAME ": LFP info terminator %x\n", lvds2_lfp_info->terminator));
443 
444 				uint8_t* timing_data = vbios.memory + bdbOffset
445 					+ lvds2->panels[panelType].lfp_edid_dtd_offset;
446 				TRACE((DEVICE_NAME ": found LFP of size %d x %d "
447 					"in BIOS VBT tables\n",
448 					lvds2_lfp_info->x_res, lvds2_lfp_info->y_res));
449 
450 				panelTiming->pixel_clock = _PIXEL_CLOCK(timing_data) / 1000;
451 				panelTiming->h_sync_start = _H_ACTIVE(timing_data) + _H_SYNC_OFF(timing_data);
452 				panelTiming->h_sync_end = panelTiming->h_sync_start + _H_SYNC_WIDTH(timing_data);
453 				panelTiming->h_total = _H_ACTIVE(timing_data) + _H_BLANK(timing_data);
454 				panelTiming->h_display = _H_ACTIVE(timing_data);
455 				panelTiming->v_sync_start = _V_ACTIVE(timing_data) + _V_SYNC_OFF(timing_data);
456 				panelTiming->v_sync_end = panelTiming->v_sync_start + _V_SYNC_WIDTH(timing_data);
457 				panelTiming->v_total = _V_ACTIVE(timing_data) + _V_BLANK(timing_data);
458 				panelTiming->v_display = _V_ACTIVE(timing_data);
459 				panelTiming->flags = 0;
460 
461 				sanitize_panel_timing(*panelTiming);
462 
463 				// on newer versions, check also generic DTD, use LFP panel DTD as a fallback
464 				if (bdb->version >= 229) {
465 					panelTimingFound = true;
466 					break;
467 				}
468 				delete_area(vbios.area);
469 				return true;
470 			}
471 			case BDB_GENERIC_DTD:
472 			{
473 				// First make sure we found block BDB_LVDS_OPTIONS and the panel type
474 				if (panelType == -1)
475 					break;
476 
477 				bdb_generic_dtd* generic_dtd = (bdb_generic_dtd*)(vbios.memory + start);
478 				if (generic_dtd->gdtd_size < sizeof(bdb_generic_dtd)) {
479 					TRACE((DEVICE_NAME ": invalid gdtd_size %d\n", generic_dtd->gdtd_size));
480 					break;
481 				}
482 				int32 count = (blockSize - sizeof(bdb_generic_dtd)) / generic_dtd->gdtd_size;
483 				if (panelType >= count) {
484 					TRACE((DEVICE_NAME ": panel type not found %d in %" B_PRId32 " dtds\n",
485 						panelType, count));
486 					break;
487 				}
488 				generic_dtd_entry* dtd = &generic_dtd->dtd[panelType];
489 				TRACE((DEVICE_NAME ": pixel_clock %" B_PRId32 " "
490 					"hactive %d hfront_porch %d hsync %d hblank %d "
491 					"vactive %d vfront_porch %d vsync %d vblank %d\n",
492 						dtd->pixel_clock, dtd->hactive, dtd->hfront_porch, dtd->hsync, dtd->hblank,
493 						dtd->vactive, dtd->vfront_porch, dtd->vsync, dtd->vblank));
494 
495 				TRACE((DEVICE_NAME ": found generic dtd entry of size %d x %d "
496 					"in BIOS VBT tables\n", dtd->hactive, dtd->vactive));
497 
498 				panelTiming->pixel_clock = dtd->pixel_clock;
499 				panelTiming->h_sync_start = dtd->hactive + dtd->hfront_porch;
500 				panelTiming->h_sync_end = panelTiming->h_sync_start + dtd->hsync;
501 				panelTiming->h_total = dtd->hactive + dtd->hblank;
502 				panelTiming->h_display = dtd->hactive;
503 				panelTiming->v_sync_start = dtd->vactive + dtd->vfront_porch;
504 				panelTiming->v_sync_end = panelTiming->v_sync_start + dtd->vsync;
505 				panelTiming->v_total = dtd->vactive + dtd->vblank;
506 				panelTiming->v_display = dtd->vactive;
507 				panelTiming->flags = 0;
508 				if (dtd->hsync_positive_polarity)
509 					panelTiming->flags |= B_POSITIVE_HSYNC;
510 				if (dtd->vsync_positive_polarity)
511 					panelTiming->flags |= B_POSITIVE_VSYNC;
512 
513 				sanitize_panel_timing(*panelTiming);
514 				delete_area(vbios.area);
515 				return true;
516 
517 			}
518 		}
519 	}
520 
521 	delete_area(vbios.area);
522 	return panelTimingFound;
523 }
524