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