xref: /haiku/src/add-ons/kernel/drivers/graphics/intel_extreme/intel_extreme.cpp (revision 1d9d47fc72028bb71b5f232a877231e59cfe2438)
1 /*
2  * Copyright 2006, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include "intel_extreme.h"
11 #include "driver.h"
12 #include "utility.h"
13 
14 #include <driver_settings.h>
15 #include <util/kernel_cpp.h>
16 
17 #include <unistd.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <errno.h>
21 
22 
23 class AreaKeeper {
24 	public:
25 		AreaKeeper();
26 		~AreaKeeper();
27 
28 		area_id Create(const char *name, void **_virtualAddress, uint32 spec,
29 			size_t size, uint32 lock, uint32 protection);
30 		area_id Map(const char *name, void *physicalAddress, size_t numBytes,
31 			uint32 spec, uint32 protection, void **_virtualAddress);
32 
33 		status_t InitCheck() { return fArea < B_OK ? (status_t)fArea : B_OK; }
34 		void Detach();
35 
36 	private:
37 		area_id	fArea;
38 };
39 
40 
41 AreaKeeper::AreaKeeper()
42 	:
43 	fArea(-1)
44 {
45 }
46 
47 
48 AreaKeeper::~AreaKeeper()
49 {
50 	if (fArea >= B_OK)
51 		delete_area(fArea);
52 }
53 
54 
55 area_id
56 AreaKeeper::Create(const char *name, void **_virtualAddress, uint32 spec,
57 	size_t size, uint32 lock, uint32 protection)
58 {
59 	fArea = create_area(name, _virtualAddress, spec, size, lock, protection);
60 	return fArea;
61 }
62 
63 
64 area_id
65 AreaKeeper::Map(const char *name, void *physicalAddress, size_t numBytes,
66 	uint32 spec, uint32 protection, void **_virtualAddress)
67 {
68 	fArea = map_physical_memory(name, physicalAddress, numBytes, spec, protection,
69 		_virtualAddress);
70 	return fArea;
71 }
72 
73 
74 void
75 AreaKeeper::Detach()
76 {
77 	fArea = -1;
78 }
79 
80 
81 //	#pragma mark -
82 
83 
84 static void
85 init_overlay_registers(overlay_registers *registers)
86 {
87 	memset(registers, 0, B_PAGE_SIZE);
88 
89 	registers->contrast_correction = 0x40;
90 	registers->saturation_cos_correction = 0x40;
91 		// this by-passes contrast and saturation correction
92 }
93 
94 
95 static void
96 read_settings(size_t &memorySize, bool &hardwareCursor, bool &ignoreAllocated)
97 {
98 	size_t size = 8; // 8 MB
99 	hardwareCursor = true;
100 	ignoreAllocated = false;
101 
102 	void *settings = load_driver_settings("intel_extreme");
103 	if (settings != NULL) {
104 		size_t specified = 0;
105 		const char *string = get_driver_parameter(settings,
106 			"graphics_memory_size", "0", "0");
107 		if (string != NULL)
108 			specified = atoi(string);
109 
110 		hardwareCursor = get_driver_boolean_parameter(settings,
111 			"hardware_cursor", true, true);
112 
113 		ignoreAllocated = get_driver_boolean_parameter(settings,
114 			"ignore_bios_allocated_memory", false, false);
115 
116 		unload_driver_settings(settings);
117 
118 		if (specified != 0) {
119 			// take the next power of two
120 			size_t desired = 1;
121 			while (desired < specified) {
122 				desired *= 2;
123 			}
124 
125 			if (desired < 128)
126 				size = desired;
127 		}
128 	}
129 
130 	memorySize = size << 20;
131 }
132 
133 
134 static size_t
135 determine_stolen_memory_size(intel_info &info)
136 {
137 	// read stolen memory from the PCI configuration of the PCI bridge
138 	uint16 memoryConfig = gPCI->read_pci_config(0, 0, 0, INTEL_GRAPHICS_MEMORY_CONTROL, 2);
139 	size_t memorySize = 1 << 20; // 1 MB
140 
141 	// TODO: test with different models!
142 
143 	if (info.device_type == (INTEL_TYPE_8xx | INTEL_TYPE_83x)) {
144 		switch (memoryConfig & STOLEN_MEMORY_MASK) {
145 			case i830_LOCAL_MEMORY_ONLY:
146 				// TODO: determine its size!
147 				break;
148 			case i830_STOLEN_512K:
149 				memorySize >>= 1;
150 				break;
151 			case i830_STOLEN_8M:
152 				memorySize *= 8;
153 				break;
154 		}
155 	} else if (info.device_type == (INTEL_TYPE_8xx | INTEL_TYPE_85x)) {
156 		switch (memoryConfig & STOLEN_MEMORY_MASK) {
157 			case i855_STOLEN_MEMORY_4M:
158 				memorySize *= 4;
159 				break;
160 			case i855_STOLEN_MEMORY_8M:
161 				memorySize *= 8;
162 				break;
163 			case i855_STOLEN_MEMORY_16M:
164 				memorySize *= 16;
165 				break;
166 			case i855_STOLEN_MEMORY_32M:
167 				memorySize *= 32;
168 				break;
169 			case i855_STOLEN_MEMORY_48M:
170 				memorySize *= 48;
171 				break;
172 			case i855_STOLEN_MEMORY_64M:
173 				memorySize *= 64;
174 				break;
175 		}
176 	}
177 
178 	return memorySize;
179 }
180 
181 
182 static void
183 set_gtt_entry(intel_info &info, uint32 offset, uint8 *physicalAddress)
184 {
185 	write32(info.registers + INTEL_GTT_BASE + (offset >> 10),
186 		(uint32)physicalAddress | GTT_ENTRY_VALID);
187 }
188 
189 
190 static int32
191 release_vblank_sem(intel_info &info)
192 {
193 	int32 count;
194 	if (get_sem_count(info.shared_info->vblank_sem, &count) == B_OK
195 		&& count < 0) {
196 		release_sem_etc(info.shared_info->vblank_sem, -count, B_DO_NOT_RESCHEDULE);
197 		return B_INVOKE_SCHEDULER;
198 	}
199 
200 	return B_HANDLED_INTERRUPT;
201 }
202 
203 
204 static int32
205 intel_interrupt_handler(void *data)
206 {
207 	intel_info &info = *(intel_info *)data;
208 
209 	uint32 identity = read16(info.registers + INTEL_INTERRUPT_IDENTITY);
210 	if (identity == 0)
211 		return B_UNHANDLED_INTERRUPT;
212 
213 	int32 handled = B_HANDLED_INTERRUPT;
214 
215 	if ((identity & INTERRUPT_VBLANK) != 0) {
216 		handled = release_vblank_sem(info);
217 
218 		// make sure we'll get another one of those
219 		write32(info.registers + INTEL_DISPLAY_A_PIPE_STATUS, DISPLAY_PIPE_VBLANK_STATUS);
220 	}
221 
222 	// setting the bit clears it!
223 	write16(info.registers + INTEL_INTERRUPT_IDENTITY, identity);
224 
225 	return handled;
226 }
227 
228 
229 static void
230 init_interrupt_handler(intel_info &info)
231 {
232 	info.shared_info->vblank_sem = create_sem(0, "intel extreme vblank");
233 	if (info.shared_info->vblank_sem < B_OK)
234 		return;
235 
236 	status_t status = B_OK;
237 
238 	// We need to change the owner of the sem to the calling team (usually the
239 	// app_server), because userland apps cannot acquire kernel semaphores
240 	thread_id thread = find_thread(NULL);
241 	thread_info threadInfo;
242 	if (get_thread_info(thread, &threadInfo) != B_OK
243 		|| set_sem_owner(info.shared_info->vblank_sem, threadInfo.team) != B_OK) {
244 		status = B_ERROR;
245 	}
246 
247 	if (status == B_OK
248 		&& info.pci->u.h0.interrupt_pin != 0x00 && info.pci->u.h0.interrupt_line != 0xff) {
249 		// we've gotten an interrupt line for us to use
250 
251 		info.fake_interrupts = false;
252 
253 		status = install_io_interrupt_handler(info.pci->u.h0.interrupt_line,
254 			&intel_interrupt_handler, (void *)&info, 0);
255 		if (status == B_OK) {
256 			// enable interrupts - we only want VBLANK interrupts
257 			write16(info.registers + INTEL_INTERRUPT_ENABLED,
258 				read16(info.registers + INTEL_INTERRUPT_ENABLED) | INTERRUPT_VBLANK);
259 			write16(info.registers + INTEL_INTERRUPT_MASK, ~INTERRUPT_VBLANK);
260 
261 			write32(info.registers + INTEL_DISPLAY_A_PIPE_STATUS,
262 				DISPLAY_PIPE_VBLANK_STATUS);
263 			write16(info.registers + INTEL_INTERRUPT_IDENTITY, ~0);
264 		}
265 	}
266 	if (status < B_OK) {
267 		// there is no interrupt reserved for us, or we couldn't install our interrupt
268 		// handler, let's fake the vblank interrupt for our clients using a timer
269 		// interrupt
270 		info.fake_interrupts = true;
271 
272 		// TODO: fake interrupts!
273 		status = B_ERROR;
274 	}
275 
276 	if (status < B_OK) {
277 		delete_sem(info.shared_info->vblank_sem);
278 		info.shared_info->vblank_sem = B_ERROR;
279 	}
280 }
281 
282 
283 //	#pragma mark -
284 
285 
286 status_t
287 intel_extreme_init(intel_info &info)
288 {
289 	AreaKeeper sharedCreator;
290 	info.shared_area = sharedCreator.Create("intel extreme shared info",
291 		(void **)&info.shared_info, B_ANY_KERNEL_ADDRESS,
292 		ROUND_TO_PAGE_SIZE(sizeof(intel_shared_info)) + 3 * B_PAGE_SIZE,
293 		B_FULL_LOCK, 0);
294 	if (info.shared_area < B_OK)
295 		return info.shared_area;
296 
297 	memset((void *)info.shared_info, 0, sizeof(intel_shared_info));
298 
299 	int fbIndex = 0;
300 	int mmioIndex = 1;
301 	if ((info.device_type & INTEL_TYPE_FAMILY_MASK) == INTEL_TYPE_9xx) {
302 		// for some reason Intel saw the need to change the order of the mappings
303 		// with the introduction of the i9xx family
304 		mmioIndex = 0;
305 		fbIndex = 2;
306 	}
307 
308 	// evaluate driver settings, if any
309 
310 	bool ignoreBIOSAllocated;
311 	bool hardwareCursor;
312 	size_t totalSize;
313 	read_settings(totalSize, hardwareCursor, ignoreBIOSAllocated);
314 
315 	// Determine the amount of "stolen" (ie. reserved by the BIOS) graphics memory
316 	// and see if we need to allocate some more.
317 	// TODO: make it allocate the memory on demand!
318 
319 	size_t stolenSize = ignoreBIOSAllocated ? 0 : determine_stolen_memory_size(info);
320 	totalSize = max_c(totalSize, stolenSize);
321 
322 	dprintf(DEVICE_NAME ": detected %ld MB of stolen memory, reserving %ld MB total\n",
323 		stolenSize >> 20, totalSize >> 20);
324 
325 	AreaKeeper additionalMemoryCreator;
326 	uint8 *additionalMemory;
327 
328 	if (stolenSize < totalSize) {
329 		// Every device should have at least 8 MB - we could also allocate them
330 		// on demand only, but we're lazy here...
331 		// TODO: overlay seems to have problem when the memory pages are too
332 		//	far spreaded - that's why we're using B_CONTIGUOUS for now.
333 		info.additional_memory_area = additionalMemoryCreator.Create("intel additional memory",
334 			(void **)&additionalMemory, B_ANY_KERNEL_ADDRESS,
335 			totalSize - stolenSize, B_CONTIGUOUS /*B_FULL_LOCK*/, 0);
336 		if (info.additional_memory_area < B_OK)
337 			return info.additional_memory_area;
338 	} else
339 		info.additional_memory_area = B_ERROR;
340 
341 	// map frame buffer, try to map it write combined
342 
343 	AreaKeeper graphicsMapper;
344 	info.graphics_memory_area = graphicsMapper.Map("intel extreme graphics memory",
345 		(void *)info.pci->u.h0.base_registers[fbIndex],
346 		totalSize, B_ANY_KERNEL_BLOCK_ADDRESS | B_MTR_WC,
347 		B_READ_AREA | B_WRITE_AREA, (void **)&info.graphics_memory);
348 	if (graphicsMapper.InitCheck() < B_OK) {
349 		// try again without write combining
350 		dprintf(DEVICE_NAME ": enabling write combined mode failed.\n");
351 
352 		info.graphics_memory_area = graphicsMapper.Map("intel extreme graphics memory",
353 			(void *)info.pci->u.h0.base_registers[fbIndex],
354 			totalSize/*info.pci->u.h0.base_register_sizes[0]*/, B_ANY_KERNEL_BLOCK_ADDRESS,
355 			B_READ_AREA | B_WRITE_AREA, (void **)&info.graphics_memory);
356 	}
357 	if (graphicsMapper.InitCheck() < B_OK) {
358 		dprintf(DEVICE_NAME ": could not map frame buffer!\n");
359 		return info.graphics_memory_area;
360 	}
361 
362 	// memory mapped I/O
363 
364 	AreaKeeper mmioMapper;
365 	info.registers_area = mmioMapper.Map("intel extreme mmio",
366 		(void *)info.pci->u.h0.base_registers[mmioIndex],
367 		info.pci->u.h0.base_register_sizes[mmioIndex],
368 		B_ANY_KERNEL_ADDRESS, B_READ_AREA | B_WRITE_AREA /*0*/, (void **)&info.registers);
369 	if (mmioMapper.InitCheck() < B_OK) {
370 		dprintf(DEVICE_NAME ": could not map memory I/O!\n");
371 		return info.registers_area;
372 	}
373 
374 	// init graphics memory manager
375 
376 	info.memory_manager = mem_init("intel extreme memory manager", 0, totalSize, 1024,
377 		min_c(totalSize / 1024, 512));
378 	if (info.memory_manager == NULL)
379 		return B_NO_MEMORY;
380 
381 	// reserve ring buffer memory (currently, this memory is placed in
382 	// the graphics memory), but this could bring us problems with
383 	// write combining...
384 
385 	ring_buffer &primary = info.shared_info->primary_ring_buffer;
386 	if (mem_alloc(info.memory_manager, 4 * B_PAGE_SIZE, &info,
387 			&primary.handle, &primary.offset) == B_OK) {
388 		primary.register_base = INTEL_PRIMARY_RING_BUFFER;
389 		primary.size = 4 * B_PAGE_SIZE;
390 		primary.base = info.graphics_memory + primary.offset;
391 	}
392 
393 	ring_buffer &secondary = info.shared_info->secondary_ring_buffer;
394 	if (mem_alloc(info.memory_manager, B_PAGE_SIZE, &info,
395 			&secondary.handle, &secondary.offset) == B_OK) {
396 		secondary.register_base = INTEL_SECONDARY_RING_BUFFER_0;
397 		secondary.size = B_PAGE_SIZE;
398 		secondary.base = info.graphics_memory + secondary.offset;
399 	}
400 
401 	// no errors, so keep areas and mappings
402 	sharedCreator.Detach();
403 	additionalMemoryCreator.Detach();
404 	graphicsMapper.Detach();
405 	mmioMapper.Detach();
406 
407 	info.shared_info->graphics_memory_area = info.graphics_memory_area;
408 	info.shared_info->registers_area = info.registers_area;
409 	info.shared_info->graphics_memory = info.graphics_memory;
410 	info.shared_info->physical_graphics_memory = (uint8 *)info.pci->u.h0.base_registers[0];
411 
412 	info.shared_info->graphics_memory_size = totalSize;
413 	info.shared_info->frame_buffer_offset = 0;
414 	info.shared_info->dpms_mode = B_DPMS_ON;
415 	info.shared_info->pll_info.reference_frequency = 48000;	// 48 kHz
416 	info.shared_info->pll_info.min_frequency = 25000;		// 25 MHz (not tested)
417 	info.shared_info->pll_info.max_frequency = 350000;		// 350 MHz RAM DAC speed
418 	info.shared_info->pll_info.divisor_register = INTEL_DISPLAY_A_PLL_DIVISOR_0;
419 
420 	info.shared_info->device_type = info.device_type;
421 #ifdef __HAIKU__
422 	strlcpy(info.shared_info->device_identifier, info.device_identifier,
423 		sizeof(info.shared_info->device_identifier));
424 #else
425 	strcpy(info.shared_info->device_identifier, info.device_identifier);
426 #endif
427 
428 	// setup overlay registers
429 
430 	info.overlay_registers = (overlay_registers *)((uint8 *)info.shared_info
431 		+ ROUND_TO_PAGE_SIZE(sizeof(intel_shared_info)));
432 	init_overlay_registers(info.overlay_registers);
433 
434 	physical_entry physicalEntry;
435 	get_memory_map(info.overlay_registers, sizeof(overlay_registers), &physicalEntry, 1);
436 	info.shared_info->physical_overlay_registers = (uint8 *)physicalEntry.address;
437 
438 	// The hardware status page and the cursor memory share one area with
439 	// the overlay registers and the shared info
440 
441 	get_memory_map((uint8 *)info.overlay_registers + B_PAGE_SIZE,
442 		B_PAGE_SIZE, &physicalEntry, 1);
443 	info.shared_info->physical_status_page = (uint8 *)physicalEntry.address;
444 
445 	get_memory_map((uint8 *)info.overlay_registers + 2 * B_PAGE_SIZE,
446 		B_PAGE_SIZE, &physicalEntry, 1);
447 	info.shared_info->physical_cursor_memory = (uint8 *)physicalEntry.address;
448 
449 	// setup the GTT to point to include the additional memory
450 
451 	if (stolenSize < totalSize) {
452 		for (size_t offset = stolenSize; offset < totalSize;) {
453 			physical_entry physicalEntry;
454 			get_memory_map(additionalMemory + offset - stolenSize,
455 				totalSize - offset, &physicalEntry, 1);
456 
457 			for (size_t i = 0; i < physicalEntry.size; i += B_PAGE_SIZE) {
458 				set_gtt_entry(info, offset + i, (uint8 *)physicalEntry.address + i);
459 			}
460 
461 			offset += physicalEntry.size;
462 		}
463 	}
464 
465 	// We also need to map the cursor memory into the GTT
466 
467 	info.shared_info->hardware_cursor_enabled = hardwareCursor;
468 	if (hardwareCursor) {
469 		// This could also be part of the usual graphics memory, but since we
470 		// need to make sure it's not in the graphics local memory (and I don't
471 		// even know yet how to determine that a chip has local memory...), we
472 		// keep the current strategy and put it into the shared area.
473 		// Unfortunately, the GTT is not readable until it has been written into
474 		// the double buffered register set; we cannot get its original contents.
475 
476 		set_gtt_entry(info, totalSize, info.shared_info->physical_cursor_memory);
477 		info.shared_info->cursor_buffer_offset = totalSize;
478 	}
479 
480 	init_interrupt_handler(info);
481 
482 	info.cookie_magic = INTEL_COOKIE_MAGIC;
483 		// this makes the cookie valid to be used
484 
485 	dprintf(DEVICE_NAME "intel_extreme_init() completed successfully!\n");
486 
487 	return B_OK;
488 }
489 
490 
491 void
492 intel_extreme_uninit(intel_info &info)
493 {
494 	dprintf(DEVICE_NAME": intel_extreme_uninit()\n");
495 
496 	if (!info.fake_interrupts && info.shared_info->vblank_sem > 0) {
497 		// disable interrupt generation
498 		write16(info.registers + INTEL_INTERRUPT_ENABLED, 0);
499 		write16(info.registers + INTEL_INTERRUPT_MASK, ~0);
500 
501 		remove_io_interrupt_handler(info.pci->u.h0.interrupt_line,
502 			intel_interrupt_handler, &info);
503 	}
504 
505 	mem_destroy(info.memory_manager);
506 
507 	delete_area(info.graphics_memory_area);
508 	delete_area(info.registers_area);
509 	delete_area(info.shared_area);
510 
511 	// we may or may not have allocated additional graphics memory
512 	if (info.additional_memory_area >= B_OK)
513 		delete_area(info.additional_memory_area);
514 }
515 
516