diff options
author | Thomas Hellstrom <thellstrom@vmware.com> | 2013-10-24 04:49:26 -0400 |
---|---|---|
committer | Thomas Hellstrom <thellstrom@vmware.com> | 2013-11-06 06:57:16 -0500 |
commit | d92d985177c495aab53c7167f310a7efb1853918 (patch) | |
tree | b8af3af9373dd0bbcb7df30b5e5a7b66dbf98801 /drivers/gpu/drm/vmwgfx/vmwgfx_buffer.c | |
parent | 7aeb7448d8d02868ef30a6d08e856b2220319273 (diff) |
drm/vmwgfx: Use the linux DMA api to get valid device addresses of pages
The code handles three different cases:
1) physical page addresses. The ttm page array is used.
2) DMA subsystem addresses. A scatter-gather list is used.
3) Coherent pages. The ttm dma pool is used, together with the dma_ttm
array os dma_addr_t
Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
Reviewed-by: Jakob Bornecrantz <jakob@vmware.com>
Diffstat (limited to 'drivers/gpu/drm/vmwgfx/vmwgfx_buffer.c')
-rw-r--r-- | drivers/gpu/drm/vmwgfx/vmwgfx_buffer.c | 379 |
1 files changed, 362 insertions, 17 deletions
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_buffer.c b/drivers/gpu/drm/vmwgfx/vmwgfx_buffer.c index 96dc84dc34d0..7776e6f0aef6 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_buffer.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_buffer.c | |||
@@ -141,37 +141,374 @@ struct ttm_placement vmw_srf_placement = { | |||
141 | }; | 141 | }; |
142 | 142 | ||
143 | struct vmw_ttm_tt { | 143 | struct vmw_ttm_tt { |
144 | struct ttm_tt ttm; | 144 | struct ttm_dma_tt dma_ttm; |
145 | struct vmw_private *dev_priv; | 145 | struct vmw_private *dev_priv; |
146 | int gmr_id; | 146 | int gmr_id; |
147 | struct sg_table sgt; | ||
148 | struct vmw_sg_table vsgt; | ||
149 | uint64_t sg_alloc_size; | ||
150 | bool mapped; | ||
147 | }; | 151 | }; |
148 | 152 | ||
153 | /** | ||
154 | * Helper functions to advance a struct vmw_piter iterator. | ||
155 | * | ||
156 | * @viter: Pointer to the iterator. | ||
157 | * | ||
158 | * These functions return false if past the end of the list, | ||
159 | * true otherwise. Functions are selected depending on the current | ||
160 | * DMA mapping mode. | ||
161 | */ | ||
162 | static bool __vmw_piter_non_sg_next(struct vmw_piter *viter) | ||
163 | { | ||
164 | return ++(viter->i) < viter->num_pages; | ||
165 | } | ||
166 | |||
167 | static bool __vmw_piter_sg_next(struct vmw_piter *viter) | ||
168 | { | ||
169 | return __sg_page_iter_next(&viter->iter); | ||
170 | } | ||
171 | |||
172 | |||
173 | /** | ||
174 | * Helper functions to return a pointer to the current page. | ||
175 | * | ||
176 | * @viter: Pointer to the iterator | ||
177 | * | ||
178 | * These functions return a pointer to the page currently | ||
179 | * pointed to by @viter. Functions are selected depending on the | ||
180 | * current mapping mode. | ||
181 | */ | ||
182 | static struct page *__vmw_piter_non_sg_page(struct vmw_piter *viter) | ||
183 | { | ||
184 | return viter->pages[viter->i]; | ||
185 | } | ||
186 | |||
187 | static struct page *__vmw_piter_sg_page(struct vmw_piter *viter) | ||
188 | { | ||
189 | return sg_page_iter_page(&viter->iter); | ||
190 | } | ||
191 | |||
192 | |||
193 | /** | ||
194 | * Helper functions to return the DMA address of the current page. | ||
195 | * | ||
196 | * @viter: Pointer to the iterator | ||
197 | * | ||
198 | * These functions return the DMA address of the page currently | ||
199 | * pointed to by @viter. Functions are selected depending on the | ||
200 | * current mapping mode. | ||
201 | */ | ||
202 | static dma_addr_t __vmw_piter_phys_addr(struct vmw_piter *viter) | ||
203 | { | ||
204 | return page_to_phys(viter->pages[viter->i]); | ||
205 | } | ||
206 | |||
207 | static dma_addr_t __vmw_piter_dma_addr(struct vmw_piter *viter) | ||
208 | { | ||
209 | return viter->addrs[viter->i]; | ||
210 | } | ||
211 | |||
212 | static dma_addr_t __vmw_piter_sg_addr(struct vmw_piter *viter) | ||
213 | { | ||
214 | return sg_page_iter_dma_address(&viter->iter); | ||
215 | } | ||
216 | |||
217 | |||
218 | /** | ||
219 | * vmw_piter_start - Initialize a struct vmw_piter. | ||
220 | * | ||
221 | * @viter: Pointer to the iterator to initialize | ||
222 | * @vsgt: Pointer to a struct vmw_sg_table to initialize from | ||
223 | * | ||
224 | * Note that we're following the convention of __sg_page_iter_start, so that | ||
225 | * the iterator doesn't point to a valid page after initialization; it has | ||
226 | * to be advanced one step first. | ||
227 | */ | ||
228 | void vmw_piter_start(struct vmw_piter *viter, const struct vmw_sg_table *vsgt, | ||
229 | unsigned long p_offset) | ||
230 | { | ||
231 | viter->i = p_offset - 1; | ||
232 | viter->num_pages = vsgt->num_pages; | ||
233 | switch (vsgt->mode) { | ||
234 | case vmw_dma_phys: | ||
235 | viter->next = &__vmw_piter_non_sg_next; | ||
236 | viter->dma_address = &__vmw_piter_phys_addr; | ||
237 | viter->page = &__vmw_piter_non_sg_page; | ||
238 | viter->pages = vsgt->pages; | ||
239 | break; | ||
240 | case vmw_dma_alloc_coherent: | ||
241 | viter->next = &__vmw_piter_non_sg_next; | ||
242 | viter->dma_address = &__vmw_piter_dma_addr; | ||
243 | viter->page = &__vmw_piter_non_sg_page; | ||
244 | viter->addrs = vsgt->addrs; | ||
245 | break; | ||
246 | case vmw_dma_map_populate: | ||
247 | case vmw_dma_map_bind: | ||
248 | viter->next = &__vmw_piter_sg_next; | ||
249 | viter->dma_address = &__vmw_piter_sg_addr; | ||
250 | viter->page = &__vmw_piter_sg_page; | ||
251 | __sg_page_iter_start(&viter->iter, vsgt->sgt->sgl, | ||
252 | vsgt->sgt->orig_nents, p_offset); | ||
253 | break; | ||
254 | default: | ||
255 | BUG(); | ||
256 | } | ||
257 | } | ||
258 | |||
259 | /** | ||
260 | * vmw_ttm_unmap_from_dma - unmap device addresses previsouly mapped for | ||
261 | * TTM pages | ||
262 | * | ||
263 | * @vmw_tt: Pointer to a struct vmw_ttm_backend | ||
264 | * | ||
265 | * Used to free dma mappings previously mapped by vmw_ttm_map_for_dma. | ||
266 | */ | ||
267 | static void vmw_ttm_unmap_from_dma(struct vmw_ttm_tt *vmw_tt) | ||
268 | { | ||
269 | struct device *dev = vmw_tt->dev_priv->dev->dev; | ||
270 | |||
271 | dma_unmap_sg(dev, vmw_tt->sgt.sgl, vmw_tt->sgt.nents, | ||
272 | DMA_BIDIRECTIONAL); | ||
273 | vmw_tt->sgt.nents = vmw_tt->sgt.orig_nents; | ||
274 | } | ||
275 | |||
276 | /** | ||
277 | * vmw_ttm_map_for_dma - map TTM pages to get device addresses | ||
278 | * | ||
279 | * @vmw_tt: Pointer to a struct vmw_ttm_backend | ||
280 | * | ||
281 | * This function is used to get device addresses from the kernel DMA layer. | ||
282 | * However, it's violating the DMA API in that when this operation has been | ||
283 | * performed, it's illegal for the CPU to write to the pages without first | ||
284 | * unmapping the DMA mappings, or calling dma_sync_sg_for_cpu(). It is | ||
285 | * therefore only legal to call this function if we know that the function | ||
286 | * dma_sync_sg_for_cpu() is a NOP, and dma_sync_sg_for_device() is at most | ||
287 | * a CPU write buffer flush. | ||
288 | */ | ||
289 | static int vmw_ttm_map_for_dma(struct vmw_ttm_tt *vmw_tt) | ||
290 | { | ||
291 | struct device *dev = vmw_tt->dev_priv->dev->dev; | ||
292 | int ret; | ||
293 | |||
294 | ret = dma_map_sg(dev, vmw_tt->sgt.sgl, vmw_tt->sgt.orig_nents, | ||
295 | DMA_BIDIRECTIONAL); | ||
296 | if (unlikely(ret == 0)) | ||
297 | return -ENOMEM; | ||
298 | |||
299 | vmw_tt->sgt.nents = ret; | ||
300 | |||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | /** | ||
305 | * vmw_ttm_map_dma - Make sure TTM pages are visible to the device | ||
306 | * | ||
307 | * @vmw_tt: Pointer to a struct vmw_ttm_tt | ||
308 | * | ||
309 | * Select the correct function for and make sure the TTM pages are | ||
310 | * visible to the device. Allocate storage for the device mappings. | ||
311 | * If a mapping has already been performed, indicated by the storage | ||
312 | * pointer being non NULL, the function returns success. | ||
313 | */ | ||
314 | static int vmw_ttm_map_dma(struct vmw_ttm_tt *vmw_tt) | ||
315 | { | ||
316 | struct vmw_private *dev_priv = vmw_tt->dev_priv; | ||
317 | struct ttm_mem_global *glob = vmw_mem_glob(dev_priv); | ||
318 | struct vmw_sg_table *vsgt = &vmw_tt->vsgt; | ||
319 | struct vmw_piter iter; | ||
320 | dma_addr_t old; | ||
321 | int ret = 0; | ||
322 | static size_t sgl_size; | ||
323 | static size_t sgt_size; | ||
324 | |||
325 | if (vmw_tt->mapped) | ||
326 | return 0; | ||
327 | |||
328 | vsgt->mode = dev_priv->map_mode; | ||
329 | vsgt->pages = vmw_tt->dma_ttm.ttm.pages; | ||
330 | vsgt->num_pages = vmw_tt->dma_ttm.ttm.num_pages; | ||
331 | vsgt->addrs = vmw_tt->dma_ttm.dma_address; | ||
332 | vsgt->sgt = &vmw_tt->sgt; | ||
333 | |||
334 | switch (dev_priv->map_mode) { | ||
335 | case vmw_dma_map_bind: | ||
336 | case vmw_dma_map_populate: | ||
337 | if (unlikely(!sgl_size)) { | ||
338 | sgl_size = ttm_round_pot(sizeof(struct scatterlist)); | ||
339 | sgt_size = ttm_round_pot(sizeof(struct sg_table)); | ||
340 | } | ||
341 | vmw_tt->sg_alloc_size = sgt_size + sgl_size * vsgt->num_pages; | ||
342 | ret = ttm_mem_global_alloc(glob, vmw_tt->sg_alloc_size, false, | ||
343 | true); | ||
344 | if (unlikely(ret != 0)) | ||
345 | return ret; | ||
346 | |||
347 | ret = sg_alloc_table_from_pages(&vmw_tt->sgt, vsgt->pages, | ||
348 | vsgt->num_pages, 0, | ||
349 | (unsigned long) | ||
350 | vsgt->num_pages << PAGE_SHIFT, | ||
351 | GFP_KERNEL); | ||
352 | if (unlikely(ret != 0)) | ||
353 | goto out_sg_alloc_fail; | ||
354 | |||
355 | if (vsgt->num_pages > vmw_tt->sgt.nents) { | ||
356 | uint64_t over_alloc = | ||
357 | sgl_size * (vsgt->num_pages - | ||
358 | vmw_tt->sgt.nents); | ||
359 | |||
360 | ttm_mem_global_free(glob, over_alloc); | ||
361 | vmw_tt->sg_alloc_size -= over_alloc; | ||
362 | } | ||
363 | |||
364 | ret = vmw_ttm_map_for_dma(vmw_tt); | ||
365 | if (unlikely(ret != 0)) | ||
366 | goto out_map_fail; | ||
367 | |||
368 | break; | ||
369 | default: | ||
370 | break; | ||
371 | } | ||
372 | |||
373 | old = ~((dma_addr_t) 0); | ||
374 | vmw_tt->vsgt.num_regions = 0; | ||
375 | for (vmw_piter_start(&iter, vsgt, 0); vmw_piter_next(&iter);) { | ||
376 | dma_addr_t cur = vmw_piter_dma_addr(&iter); | ||
377 | |||
378 | if (cur != old + PAGE_SIZE) | ||
379 | vmw_tt->vsgt.num_regions++; | ||
380 | old = cur; | ||
381 | } | ||
382 | |||
383 | vmw_tt->mapped = true; | ||
384 | return 0; | ||
385 | |||
386 | out_map_fail: | ||
387 | sg_free_table(vmw_tt->vsgt.sgt); | ||
388 | vmw_tt->vsgt.sgt = NULL; | ||
389 | out_sg_alloc_fail: | ||
390 | ttm_mem_global_free(glob, vmw_tt->sg_alloc_size); | ||
391 | return ret; | ||
392 | } | ||
393 | |||
394 | /** | ||
395 | * vmw_ttm_unmap_dma - Tear down any TTM page device mappings | ||
396 | * | ||
397 | * @vmw_tt: Pointer to a struct vmw_ttm_tt | ||
398 | * | ||
399 | * Tear down any previously set up device DMA mappings and free | ||
400 | * any storage space allocated for them. If there are no mappings set up, | ||
401 | * this function is a NOP. | ||
402 | */ | ||
403 | static void vmw_ttm_unmap_dma(struct vmw_ttm_tt *vmw_tt) | ||
404 | { | ||
405 | struct vmw_private *dev_priv = vmw_tt->dev_priv; | ||
406 | |||
407 | if (!vmw_tt->vsgt.sgt) | ||
408 | return; | ||
409 | |||
410 | switch (dev_priv->map_mode) { | ||
411 | case vmw_dma_map_bind: | ||
412 | case vmw_dma_map_populate: | ||
413 | vmw_ttm_unmap_from_dma(vmw_tt); | ||
414 | sg_free_table(vmw_tt->vsgt.sgt); | ||
415 | vmw_tt->vsgt.sgt = NULL; | ||
416 | ttm_mem_global_free(vmw_mem_glob(dev_priv), | ||
417 | vmw_tt->sg_alloc_size); | ||
418 | break; | ||
419 | default: | ||
420 | break; | ||
421 | } | ||
422 | vmw_tt->mapped = false; | ||
423 | } | ||
424 | |||
149 | static int vmw_ttm_bind(struct ttm_tt *ttm, struct ttm_mem_reg *bo_mem) | 425 | static int vmw_ttm_bind(struct ttm_tt *ttm, struct ttm_mem_reg *bo_mem) |
150 | { | 426 | { |
151 | struct vmw_ttm_tt *vmw_be = container_of(ttm, struct vmw_ttm_tt, ttm); | 427 | struct vmw_ttm_tt *vmw_be = |
428 | container_of(ttm, struct vmw_ttm_tt, dma_ttm.ttm); | ||
429 | int ret; | ||
430 | |||
431 | ret = vmw_ttm_map_dma(vmw_be); | ||
432 | if (unlikely(ret != 0)) | ||
433 | return ret; | ||
152 | 434 | ||
153 | vmw_be->gmr_id = bo_mem->start; | 435 | vmw_be->gmr_id = bo_mem->start; |
154 | 436 | ||
155 | return vmw_gmr_bind(vmw_be->dev_priv, ttm->pages, | 437 | return vmw_gmr_bind(vmw_be->dev_priv, &vmw_be->vsgt, |
156 | ttm->num_pages, vmw_be->gmr_id); | 438 | ttm->num_pages, vmw_be->gmr_id); |
157 | } | 439 | } |
158 | 440 | ||
159 | static int vmw_ttm_unbind(struct ttm_tt *ttm) | 441 | static int vmw_ttm_unbind(struct ttm_tt *ttm) |
160 | { | 442 | { |
161 | struct vmw_ttm_tt *vmw_be = container_of(ttm, struct vmw_ttm_tt, ttm); | 443 | struct vmw_ttm_tt *vmw_be = |
444 | container_of(ttm, struct vmw_ttm_tt, dma_ttm.ttm); | ||
162 | 445 | ||
163 | vmw_gmr_unbind(vmw_be->dev_priv, vmw_be->gmr_id); | 446 | vmw_gmr_unbind(vmw_be->dev_priv, vmw_be->gmr_id); |
447 | |||
448 | if (vmw_be->dev_priv->map_mode == vmw_dma_map_bind) | ||
449 | vmw_ttm_unmap_dma(vmw_be); | ||
450 | |||
164 | return 0; | 451 | return 0; |
165 | } | 452 | } |
166 | 453 | ||
167 | static void vmw_ttm_destroy(struct ttm_tt *ttm) | 454 | static void vmw_ttm_destroy(struct ttm_tt *ttm) |
168 | { | 455 | { |
169 | struct vmw_ttm_tt *vmw_be = container_of(ttm, struct vmw_ttm_tt, ttm); | 456 | struct vmw_ttm_tt *vmw_be = |
170 | 457 | container_of(ttm, struct vmw_ttm_tt, dma_ttm.ttm); | |
171 | ttm_tt_fini(ttm); | 458 | |
459 | vmw_ttm_unmap_dma(vmw_be); | ||
460 | if (vmw_be->dev_priv->map_mode == vmw_dma_alloc_coherent) | ||
461 | ttm_dma_tt_fini(&vmw_be->dma_ttm); | ||
462 | else | ||
463 | ttm_tt_fini(ttm); | ||
172 | kfree(vmw_be); | 464 | kfree(vmw_be); |
173 | } | 465 | } |
174 | 466 | ||
467 | static int vmw_ttm_populate(struct ttm_tt *ttm) | ||
468 | { | ||
469 | struct vmw_ttm_tt *vmw_tt = | ||
470 | container_of(ttm, struct vmw_ttm_tt, dma_ttm.ttm); | ||
471 | struct vmw_private *dev_priv = vmw_tt->dev_priv; | ||
472 | struct ttm_mem_global *glob = vmw_mem_glob(dev_priv); | ||
473 | int ret; | ||
474 | |||
475 | if (ttm->state != tt_unpopulated) | ||
476 | return 0; | ||
477 | |||
478 | if (dev_priv->map_mode == vmw_dma_alloc_coherent) { | ||
479 | size_t size = | ||
480 | ttm_round_pot(ttm->num_pages * sizeof(dma_addr_t)); | ||
481 | ret = ttm_mem_global_alloc(glob, size, false, true); | ||
482 | if (unlikely(ret != 0)) | ||
483 | return ret; | ||
484 | |||
485 | ret = ttm_dma_populate(&vmw_tt->dma_ttm, dev_priv->dev->dev); | ||
486 | if (unlikely(ret != 0)) | ||
487 | ttm_mem_global_free(glob, size); | ||
488 | } else | ||
489 | ret = ttm_pool_populate(ttm); | ||
490 | |||
491 | return ret; | ||
492 | } | ||
493 | |||
494 | static void vmw_ttm_unpopulate(struct ttm_tt *ttm) | ||
495 | { | ||
496 | struct vmw_ttm_tt *vmw_tt = container_of(ttm, struct vmw_ttm_tt, | ||
497 | dma_ttm.ttm); | ||
498 | struct vmw_private *dev_priv = vmw_tt->dev_priv; | ||
499 | struct ttm_mem_global *glob = vmw_mem_glob(dev_priv); | ||
500 | |||
501 | vmw_ttm_unmap_dma(vmw_tt); | ||
502 | if (dev_priv->map_mode == vmw_dma_alloc_coherent) { | ||
503 | size_t size = | ||
504 | ttm_round_pot(ttm->num_pages * sizeof(dma_addr_t)); | ||
505 | |||
506 | ttm_dma_unpopulate(&vmw_tt->dma_ttm, dev_priv->dev->dev); | ||
507 | ttm_mem_global_free(glob, size); | ||
508 | } else | ||
509 | ttm_pool_unpopulate(ttm); | ||
510 | } | ||
511 | |||
175 | static struct ttm_backend_func vmw_ttm_func = { | 512 | static struct ttm_backend_func vmw_ttm_func = { |
176 | .bind = vmw_ttm_bind, | 513 | .bind = vmw_ttm_bind, |
177 | .unbind = vmw_ttm_unbind, | 514 | .unbind = vmw_ttm_unbind, |
@@ -183,20 +520,28 @@ struct ttm_tt *vmw_ttm_tt_create(struct ttm_bo_device *bdev, | |||
183 | struct page *dummy_read_page) | 520 | struct page *dummy_read_page) |
184 | { | 521 | { |
185 | struct vmw_ttm_tt *vmw_be; | 522 | struct vmw_ttm_tt *vmw_be; |
523 | int ret; | ||
186 | 524 | ||
187 | vmw_be = kmalloc(sizeof(*vmw_be), GFP_KERNEL); | 525 | vmw_be = kzalloc(sizeof(*vmw_be), GFP_KERNEL); |
188 | if (!vmw_be) | 526 | if (!vmw_be) |
189 | return NULL; | 527 | return NULL; |
190 | 528 | ||
191 | vmw_be->ttm.func = &vmw_ttm_func; | 529 | vmw_be->dma_ttm.ttm.func = &vmw_ttm_func; |
192 | vmw_be->dev_priv = container_of(bdev, struct vmw_private, bdev); | 530 | vmw_be->dev_priv = container_of(bdev, struct vmw_private, bdev); |
193 | 531 | ||
194 | if (ttm_tt_init(&vmw_be->ttm, bdev, size, page_flags, dummy_read_page)) { | 532 | if (vmw_be->dev_priv->map_mode == vmw_dma_alloc_coherent) |
195 | kfree(vmw_be); | 533 | ret = ttm_dma_tt_init(&vmw_be->dma_ttm, bdev, size, page_flags, |
196 | return NULL; | 534 | dummy_read_page); |
197 | } | 535 | else |
198 | 536 | ret = ttm_tt_init(&vmw_be->dma_ttm.ttm, bdev, size, page_flags, | |
199 | return &vmw_be->ttm; | 537 | dummy_read_page); |
538 | if (unlikely(ret != 0)) | ||
539 | goto out_no_init; | ||
540 | |||
541 | return &vmw_be->dma_ttm.ttm; | ||
542 | out_no_init: | ||
543 | kfree(vmw_be); | ||
544 | return NULL; | ||
200 | } | 545 | } |
201 | 546 | ||
202 | int vmw_invalidate_caches(struct ttm_bo_device *bdev, uint32_t flags) | 547 | int vmw_invalidate_caches(struct ttm_bo_device *bdev, uint32_t flags) |
@@ -332,8 +677,8 @@ static int vmw_sync_obj_wait(void *sync_obj, bool lazy, bool interruptible) | |||
332 | 677 | ||
333 | struct ttm_bo_driver vmw_bo_driver = { | 678 | struct ttm_bo_driver vmw_bo_driver = { |
334 | .ttm_tt_create = &vmw_ttm_tt_create, | 679 | .ttm_tt_create = &vmw_ttm_tt_create, |
335 | .ttm_tt_populate = &ttm_pool_populate, | 680 | .ttm_tt_populate = &vmw_ttm_populate, |
336 | .ttm_tt_unpopulate = &ttm_pool_unpopulate, | 681 | .ttm_tt_unpopulate = &vmw_ttm_unpopulate, |
337 | .invalidate_caches = vmw_invalidate_caches, | 682 | .invalidate_caches = vmw_invalidate_caches, |
338 | .init_mem_type = vmw_init_mem_type, | 683 | .init_mem_type = vmw_init_mem_type, |
339 | .evict_flags = vmw_evict_flags, | 684 | .evict_flags = vmw_evict_flags, |