1 | /* |
2 | * Copyright © 2008-2012 Intel Corporation |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright notice and this permission notice (including the next |
12 | * paragraph) shall be included in all copies or substantial portions of the |
13 | * Software. |
14 | * |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
21 | * IN THE SOFTWARE. |
22 | * |
23 | * Authors: |
24 | * Eric Anholt <eric@anholt.net> |
25 | * Chris Wilson <chris@chris-wilson.co.uk> |
26 | * |
27 | */ |
28 | |
29 | #include <linux/printk.h> |
30 | #include <linux/err.h> |
31 | #include <drm/drmP.h> |
32 | #include <drm/i915_drm.h> |
33 | #include "i915_drv.h" |
34 | |
35 | /* |
36 | * The BIOS typically reserves some of the system's memory for the exclusive |
37 | * use of the integrated graphics. This memory is no longer available for |
38 | * use by the OS and so the user finds that his system has less memory |
39 | * available than he put in. We refer to this memory as stolen. |
40 | * |
41 | * The BIOS will allocate its framebuffer from the stolen memory. Our |
42 | * goal is try to reuse that object for our own fbcon which must always |
43 | * be available for panics. Anything else we can reuse the stolen memory |
44 | * for is a boon. |
45 | */ |
46 | |
47 | static unsigned long i915_stolen_to_physical(struct drm_device *dev) |
48 | { |
49 | #ifndef __NetBSD__ |
50 | struct drm_i915_private *dev_priv = dev->dev_private; |
51 | struct resource *r; |
52 | #endif |
53 | u32 base; |
54 | |
55 | /* Almost universally we can find the Graphics Base of Stolen Memory |
56 | * at offset 0x5c in the igfx configuration space. On a few (desktop) |
57 | * machines this is also mirrored in the bridge device at different |
58 | * locations, or in the MCHBAR. On gen2, the layout is again slightly |
59 | * different with the Graphics Segment immediately following Top of |
60 | * Memory (or Top of Usable DRAM). Note it appears that TOUD is only |
61 | * reported by 865g, so we just use the top of memory as determined |
62 | * by the e820 probe. |
63 | * |
64 | * XXX However gen2 requires an unavailable symbol. |
65 | */ |
66 | base = 0; |
67 | if (INTEL_INFO(dev)->gen >= 3) { |
68 | #ifndef __NetBSD__ /* XXX disable this for now */ |
69 | /* Read Graphics Base of Stolen Memory directly */ |
70 | pci_read_config_dword(dev->pdev, 0x5c, &base); |
71 | base &= ~((1<<20) - 1); |
72 | #endif |
73 | } else { /* GEN2 */ |
74 | #if 0 |
75 | /* Stolen is immediately above Top of Memory */ |
76 | base = max_low_pfn_mapped << PAGE_SHIFT; |
77 | #endif |
78 | } |
79 | |
80 | if (base == 0) |
81 | return 0; |
82 | |
83 | #ifndef __NetBSD__ /* XXX */ |
84 | /* Verify that nothing else uses this physical address. Stolen |
85 | * memory should be reserved by the BIOS and hidden from the |
86 | * kernel. So if the region is already marked as busy, something |
87 | * is seriously wrong. |
88 | */ |
89 | r = devm_request_mem_region(dev->dev, base, dev_priv->gtt.stolen_size, |
90 | "Graphics Stolen Memory" ); |
91 | if (r == NULL) { |
92 | /* |
93 | * One more attempt but this time requesting region from |
94 | * base + 1, as we have seen that this resolves the region |
95 | * conflict with the PCI Bus. |
96 | * This is a BIOS w/a: Some BIOS wrap stolen in the root |
97 | * PCI bus, but have an off-by-one error. Hence retry the |
98 | * reservation starting from 1 instead of 0. |
99 | */ |
100 | r = devm_request_mem_region(dev->dev, base + 1, |
101 | dev_priv->gtt.stolen_size - 1, |
102 | "Graphics Stolen Memory" ); |
103 | if (r == NULL) { |
104 | DRM_ERROR("conflict detected with stolen region: [0x%08x - 0x%08x]\n" , |
105 | base, base + (uint32_t)dev_priv->gtt.stolen_size); |
106 | base = 0; |
107 | } |
108 | } |
109 | #endif |
110 | |
111 | return base; |
112 | } |
113 | |
114 | static int i915_setup_compression(struct drm_device *dev, int size) |
115 | { |
116 | struct drm_i915_private *dev_priv = dev->dev_private; |
117 | struct drm_mm_node *compressed_fb, *uninitialized_var(compressed_llb); |
118 | int ret; |
119 | |
120 | compressed_fb = kzalloc(sizeof(*compressed_fb), GFP_KERNEL); |
121 | if (!compressed_fb) |
122 | goto err_llb; |
123 | |
124 | /* Try to over-allocate to reduce reallocations and fragmentation */ |
125 | ret = drm_mm_insert_node(&dev_priv->mm.stolen, compressed_fb, |
126 | size <<= 1, 4096, DRM_MM_SEARCH_DEFAULT); |
127 | if (ret) |
128 | ret = drm_mm_insert_node(&dev_priv->mm.stolen, compressed_fb, |
129 | size >>= 1, 4096, |
130 | DRM_MM_SEARCH_DEFAULT); |
131 | if (ret) |
132 | goto err_llb; |
133 | |
134 | if (HAS_PCH_SPLIT(dev)) |
135 | I915_WRITE(ILK_DPFC_CB_BASE, compressed_fb->start); |
136 | else if (IS_GM45(dev)) { |
137 | I915_WRITE(DPFC_CB_BASE, compressed_fb->start); |
138 | } else { |
139 | compressed_llb = kzalloc(sizeof(*compressed_llb), GFP_KERNEL); |
140 | if (!compressed_llb) |
141 | goto err_fb; |
142 | |
143 | ret = drm_mm_insert_node(&dev_priv->mm.stolen, compressed_llb, |
144 | 4096, 4096, DRM_MM_SEARCH_DEFAULT); |
145 | if (ret) |
146 | goto err_fb; |
147 | |
148 | dev_priv->fbc.compressed_llb = compressed_llb; |
149 | |
150 | I915_WRITE(FBC_CFB_BASE, |
151 | dev_priv->mm.stolen_base + compressed_fb->start); |
152 | I915_WRITE(FBC_LL_BASE, |
153 | dev_priv->mm.stolen_base + compressed_llb->start); |
154 | } |
155 | |
156 | dev_priv->fbc.compressed_fb = compressed_fb; |
157 | dev_priv->fbc.size = size; |
158 | |
159 | DRM_DEBUG_KMS("reserved %d bytes of contiguous stolen space for FBC\n" , |
160 | size); |
161 | |
162 | return 0; |
163 | |
164 | err_fb: |
165 | kfree(compressed_llb); |
166 | drm_mm_remove_node(compressed_fb); |
167 | err_llb: |
168 | kfree(compressed_fb); |
169 | pr_info_once("drm: not enough stolen space for compressed buffer (need %d more bytes), disabling. Hint: you may be able to increase stolen memory size in the BIOS to avoid this.\n" , size); |
170 | return -ENOSPC; |
171 | } |
172 | |
173 | int i915_gem_stolen_setup_compression(struct drm_device *dev, int size) |
174 | { |
175 | struct drm_i915_private *dev_priv = dev->dev_private; |
176 | |
177 | if (!drm_mm_initialized(&dev_priv->mm.stolen)) |
178 | return -ENODEV; |
179 | |
180 | if (size < dev_priv->fbc.size) |
181 | return 0; |
182 | |
183 | /* Release any current block */ |
184 | i915_gem_stolen_cleanup_compression(dev); |
185 | |
186 | return i915_setup_compression(dev, size); |
187 | } |
188 | |
189 | void i915_gem_stolen_cleanup_compression(struct drm_device *dev) |
190 | { |
191 | struct drm_i915_private *dev_priv = dev->dev_private; |
192 | |
193 | if (dev_priv->fbc.size == 0) |
194 | return; |
195 | |
196 | if (dev_priv->fbc.compressed_fb) { |
197 | drm_mm_remove_node(dev_priv->fbc.compressed_fb); |
198 | kfree(dev_priv->fbc.compressed_fb); |
199 | } |
200 | |
201 | if (dev_priv->fbc.compressed_llb) { |
202 | drm_mm_remove_node(dev_priv->fbc.compressed_llb); |
203 | kfree(dev_priv->fbc.compressed_llb); |
204 | } |
205 | |
206 | dev_priv->fbc.size = 0; |
207 | } |
208 | |
209 | void i915_gem_cleanup_stolen(struct drm_device *dev) |
210 | { |
211 | struct drm_i915_private *dev_priv = dev->dev_private; |
212 | |
213 | if (!drm_mm_initialized(&dev_priv->mm.stolen)) |
214 | return; |
215 | |
216 | i915_gem_stolen_cleanup_compression(dev); |
217 | drm_mm_takedown(&dev_priv->mm.stolen); |
218 | } |
219 | |
220 | int i915_gem_init_stolen(struct drm_device *dev) |
221 | { |
222 | struct drm_i915_private *dev_priv = dev->dev_private; |
223 | int bios_reserved = 0; |
224 | |
225 | #ifdef CONFIG_INTEL_IOMMU |
226 | if (intel_iommu_gfx_mapped && INTEL_INFO(dev)->gen < 8) { |
227 | DRM_INFO("DMAR active, disabling use of stolen memory\n" ); |
228 | return 0; |
229 | } |
230 | #endif |
231 | |
232 | if (dev_priv->gtt.stolen_size == 0) |
233 | return 0; |
234 | |
235 | dev_priv->mm.stolen_base = i915_stolen_to_physical(dev); |
236 | if (dev_priv->mm.stolen_base == 0) |
237 | return 0; |
238 | |
239 | DRM_DEBUG_KMS("found %zd bytes of stolen memory at %08lx\n" , |
240 | dev_priv->gtt.stolen_size, dev_priv->mm.stolen_base); |
241 | |
242 | if (IS_VALLEYVIEW(dev)) |
243 | bios_reserved = 1024*1024; /* top 1M on VLV/BYT */ |
244 | |
245 | if (WARN_ON(bios_reserved > dev_priv->gtt.stolen_size)) |
246 | return 0; |
247 | |
248 | /* Basic memrange allocator for stolen space */ |
249 | drm_mm_init(&dev_priv->mm.stolen, 0, dev_priv->gtt.stolen_size - |
250 | bios_reserved); |
251 | |
252 | return 0; |
253 | } |
254 | |
255 | #ifndef __NetBSD__ |
256 | static struct sg_table * |
257 | i915_pages_create_for_stolen(struct drm_device *dev, |
258 | u32 offset, u32 size) |
259 | { |
260 | struct drm_i915_private *dev_priv = dev->dev_private; |
261 | struct sg_table *st; |
262 | struct scatterlist *sg; |
263 | |
264 | DRM_DEBUG_DRIVER("offset=0x%x, size=%d\n" , offset, size); |
265 | BUG_ON(offset > dev_priv->gtt.stolen_size - size); |
266 | |
267 | /* We hide that we have no struct page backing our stolen object |
268 | * by wrapping the contiguous physical allocation with a fake |
269 | * dma mapping in a single scatterlist. |
270 | */ |
271 | |
272 | st = kmalloc(sizeof(*st), GFP_KERNEL); |
273 | if (st == NULL) |
274 | return NULL; |
275 | |
276 | if (sg_alloc_table(st, 1, GFP_KERNEL)) { |
277 | kfree(st); |
278 | return NULL; |
279 | } |
280 | |
281 | sg = st->sgl; |
282 | sg->offset = 0; |
283 | sg->length = size; |
284 | |
285 | sg_dma_address(sg) = (dma_addr_t)dev_priv->mm.stolen_base + offset; |
286 | sg_dma_len(sg) = size; |
287 | |
288 | return st; |
289 | } |
290 | #endif |
291 | |
292 | static int i915_gem_object_get_pages_stolen(struct drm_i915_gem_object *obj) |
293 | { |
294 | BUG(); |
295 | return -EINVAL; |
296 | } |
297 | |
298 | static void i915_gem_object_put_pages_stolen(struct drm_i915_gem_object *obj) |
299 | { |
300 | /* Should only be called during free */ |
301 | #ifdef __NetBSD__ |
302 | bus_dmamap_unload(obj->base.dev->dmat, obj->igo_dmamap); |
303 | bus_dmamap_destroy(obj->base.dev->dmat, obj->igo_dmamap); |
304 | kmem_free(obj->pages, (obj->igo_nsegs * sizeof(obj->pages[0]))); |
305 | #else |
306 | sg_free_table(obj->pages); |
307 | kfree(obj->pages); |
308 | #endif |
309 | } |
310 | |
311 | static const struct drm_i915_gem_object_ops i915_gem_object_stolen_ops = { |
312 | .get_pages = i915_gem_object_get_pages_stolen, |
313 | .put_pages = i915_gem_object_put_pages_stolen, |
314 | }; |
315 | |
316 | static struct drm_i915_gem_object * |
317 | _i915_gem_object_create_stolen(struct drm_device *dev, |
318 | struct drm_mm_node *stolen) |
319 | { |
320 | struct drm_i915_gem_object *obj; |
321 | #ifdef __NetBSD__ |
322 | struct drm_i915_private *const dev_priv = dev->dev_private; |
323 | unsigned i; |
324 | int error; |
325 | #endif |
326 | |
327 | obj = i915_gem_object_alloc(dev); |
328 | if (obj == NULL) |
329 | return NULL; |
330 | |
331 | drm_gem_private_object_init(dev, &obj->base, stolen->size); |
332 | i915_gem_object_init(obj, &i915_gem_object_stolen_ops); |
333 | |
334 | #ifdef __NetBSD__ |
335 | TAILQ_INIT(&obj->igo_pageq); /* XXX Need to fill this... */ |
336 | KASSERT((stolen->size % PAGE_SIZE) == 0); |
337 | obj->igo_nsegs = (stolen->size / PAGE_SIZE); |
338 | obj->pages = kmem_alloc((obj->igo_nsegs * sizeof(obj->pages[0])), |
339 | KM_SLEEP); |
340 | /* |
341 | * x86 bus_dmamap_load_raw fails to respect the maxsegsz we |
342 | * pass to bus_dmamap_create, so we have to create page-sized |
343 | * segments to begin with. |
344 | */ |
345 | for (i = 0; i < obj->igo_nsegs; i++) { |
346 | obj->pages[i].ds_addr = (bus_addr_t)dev_priv->mm.stolen_base + |
347 | stolen->start + (i*PAGE_SIZE); |
348 | obj->pages[i].ds_len = PAGE_SIZE; |
349 | } |
350 | error = bus_dmamap_create(dev->dmat, obj->base.size, obj->igo_nsegs, |
351 | PAGE_SIZE, 0, BUS_DMA_WAITOK, &obj->igo_dmamap); |
352 | if (error) { |
353 | DRM_ERROR("failed to create DMA map for stolen object: %d\n" , |
354 | error); |
355 | kmem_free(obj->pages, sizeof(obj->pages[0])); |
356 | obj->pages = NULL; |
357 | goto cleanup; |
358 | } |
359 | error = bus_dmamap_load_raw(dev->dmat, obj->igo_dmamap, obj->pages, |
360 | obj->igo_nsegs, stolen->size, BUS_DMA_WAITOK); |
361 | if (error) { |
362 | DRM_ERROR("failed to load DMA map for stolen object: %d\n" , |
363 | error); |
364 | bus_dmamap_destroy(dev->dmat, obj->igo_dmamap); |
365 | kmem_free(obj->pages, sizeof(obj->pages[0])); |
366 | obj->pages = NULL; |
367 | goto cleanup; |
368 | } |
369 | #else |
370 | obj->pages = i915_pages_create_for_stolen(dev, |
371 | stolen->start, stolen->size); |
372 | if (obj->pages == NULL) |
373 | goto cleanup; |
374 | #endif |
375 | |
376 | obj->has_dma_mapping = true; |
377 | i915_gem_object_pin_pages(obj); |
378 | obj->stolen = stolen; |
379 | |
380 | obj->base.read_domains = I915_GEM_DOMAIN_CPU | I915_GEM_DOMAIN_GTT; |
381 | obj->cache_level = HAS_LLC(dev) ? I915_CACHE_LLC : I915_CACHE_NONE; |
382 | |
383 | return obj; |
384 | |
385 | cleanup: |
386 | i915_gem_object_free(obj); |
387 | return NULL; |
388 | } |
389 | |
390 | struct drm_i915_gem_object * |
391 | i915_gem_object_create_stolen(struct drm_device *dev, u32 size) |
392 | { |
393 | struct drm_i915_private *dev_priv = dev->dev_private; |
394 | struct drm_i915_gem_object *obj; |
395 | struct drm_mm_node *stolen; |
396 | int ret; |
397 | |
398 | if (!drm_mm_initialized(&dev_priv->mm.stolen)) |
399 | return NULL; |
400 | |
401 | DRM_DEBUG_KMS("creating stolen object: size=%x\n" , size); |
402 | if (size == 0) |
403 | return NULL; |
404 | |
405 | stolen = kzalloc(sizeof(*stolen), GFP_KERNEL); |
406 | if (!stolen) |
407 | return NULL; |
408 | |
409 | ret = drm_mm_insert_node(&dev_priv->mm.stolen, stolen, size, |
410 | 4096, DRM_MM_SEARCH_DEFAULT); |
411 | if (ret) { |
412 | kfree(stolen); |
413 | return NULL; |
414 | } |
415 | |
416 | obj = _i915_gem_object_create_stolen(dev, stolen); |
417 | if (obj) |
418 | return obj; |
419 | |
420 | drm_mm_remove_node(stolen); |
421 | kfree(stolen); |
422 | return NULL; |
423 | } |
424 | |
425 | struct drm_i915_gem_object * |
426 | i915_gem_object_create_stolen_for_preallocated(struct drm_device *dev, |
427 | u32 stolen_offset, |
428 | u32 gtt_offset, |
429 | u32 size) |
430 | { |
431 | struct drm_i915_private *dev_priv = dev->dev_private; |
432 | struct i915_address_space *ggtt = &dev_priv->gtt.base; |
433 | struct drm_i915_gem_object *obj; |
434 | struct drm_mm_node *stolen; |
435 | struct i915_vma *vma; |
436 | int ret; |
437 | |
438 | if (!drm_mm_initialized(&dev_priv->mm.stolen)) |
439 | return NULL; |
440 | |
441 | DRM_DEBUG_KMS("creating preallocated stolen object: stolen_offset=%x, gtt_offset=%x, size=%x\n" , |
442 | stolen_offset, gtt_offset, size); |
443 | |
444 | /* KISS and expect everything to be page-aligned */ |
445 | BUG_ON(stolen_offset & 4095); |
446 | BUG_ON(size & 4095); |
447 | |
448 | if (WARN_ON(size == 0)) |
449 | return NULL; |
450 | |
451 | stolen = kzalloc(sizeof(*stolen), GFP_KERNEL); |
452 | if (!stolen) |
453 | return NULL; |
454 | |
455 | stolen->start = stolen_offset; |
456 | stolen->size = size; |
457 | ret = drm_mm_reserve_node(&dev_priv->mm.stolen, stolen); |
458 | if (ret) { |
459 | DRM_DEBUG_KMS("failed to allocate stolen space\n" ); |
460 | kfree(stolen); |
461 | return NULL; |
462 | } |
463 | |
464 | obj = _i915_gem_object_create_stolen(dev, stolen); |
465 | if (obj == NULL) { |
466 | DRM_DEBUG_KMS("failed to allocate stolen object\n" ); |
467 | drm_mm_remove_node(stolen); |
468 | kfree(stolen); |
469 | return NULL; |
470 | } |
471 | |
472 | /* Some objects just need physical mem from stolen space */ |
473 | if (gtt_offset == I915_GTT_OFFSET_NONE) |
474 | return obj; |
475 | |
476 | vma = i915_gem_obj_lookup_or_create_vma(obj, ggtt); |
477 | if (IS_ERR(vma)) { |
478 | ret = PTR_ERR(vma); |
479 | goto err_out; |
480 | } |
481 | |
482 | /* To simplify the initialisation sequence between KMS and GTT, |
483 | * we allow construction of the stolen object prior to |
484 | * setting up the GTT space. The actual reservation will occur |
485 | * later. |
486 | */ |
487 | vma->node.start = gtt_offset; |
488 | vma->node.size = size; |
489 | if (drm_mm_initialized(&ggtt->mm)) { |
490 | ret = drm_mm_reserve_node(&ggtt->mm, &vma->node); |
491 | if (ret) { |
492 | DRM_DEBUG_KMS("failed to allocate stolen GTT space\n" ); |
493 | goto err_vma; |
494 | } |
495 | } |
496 | |
497 | obj->has_global_gtt_mapping = 1; |
498 | |
499 | list_add_tail(&obj->global_list, &dev_priv->mm.bound_list); |
500 | list_add_tail(&vma->mm_list, &ggtt->inactive_list); |
501 | i915_gem_object_pin_pages(obj); |
502 | |
503 | return obj; |
504 | |
505 | err_vma: |
506 | i915_gem_vma_destroy(vma); |
507 | err_out: |
508 | drm_mm_remove_node(stolen); |
509 | kfree(stolen); |
510 | drm_gem_object_unreference(&obj->base); |
511 | return NULL; |
512 | } |
513 | |
514 | void |
515 | i915_gem_object_release_stolen(struct drm_i915_gem_object *obj) |
516 | { |
517 | if (obj->stolen) { |
518 | drm_mm_remove_node(obj->stolen); |
519 | kfree(obj->stolen); |
520 | obj->stolen = NULL; |
521 | } |
522 | } |
523 | |