662 lines · c
1/*2 * Copyright 2013 Red Hat Inc.3 *4 * Permission is hereby granted, free of charge, to any person obtaining a5 * copy of this software and associated documentation files (the "Software"),6 * to deal in the Software without restriction, including without limitation7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,8 * and/or sell copies of the Software, and to permit persons to whom the9 * Software is furnished to do so, subject to the following conditions:10 *11 * The above copyright notice and this permission notice shall be included in12 * all copies or substantial portions of the Software.13 *14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL17 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR18 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR20 * OTHER DEALINGS IN THE SOFTWARE.21 *22 * Authors: Dave Airlie23 * Alon Levy24 */25 26/* QXL cmd/ring handling */27 28#include <linux/delay.h>29 30#include <drm/drm_util.h>31 32#include "qxl_drv.h"33#include "qxl_object.h"34 35static int qxl_reap_surface_id(struct qxl_device *qdev, int max_to_reap);36 37struct ring {38 struct qxl_ring_header header;39 uint8_t elements[];40};41 42struct qxl_ring {43 struct ring *ring;44 int element_size;45 int n_elements;46 int prod_notify;47 wait_queue_head_t *push_event;48 spinlock_t lock;49};50 51void qxl_ring_free(struct qxl_ring *ring)52{53 kfree(ring);54}55 56struct qxl_ring *57qxl_ring_create(struct qxl_ring_header *header,58 int element_size,59 int n_elements,60 int prod_notify,61 wait_queue_head_t *push_event)62{63 struct qxl_ring *ring;64 65 ring = kmalloc(sizeof(*ring), GFP_KERNEL);66 if (!ring)67 return NULL;68 69 ring->ring = (struct ring *)header;70 ring->element_size = element_size;71 ring->n_elements = n_elements;72 ring->prod_notify = prod_notify;73 ring->push_event = push_event;74 spin_lock_init(&ring->lock);75 return ring;76}77 78static int qxl_check_header(struct qxl_ring *ring)79{80 int ret;81 struct qxl_ring_header *header = &(ring->ring->header);82 unsigned long flags;83 84 spin_lock_irqsave(&ring->lock, flags);85 ret = header->prod - header->cons < header->num_items;86 if (ret == 0)87 header->notify_on_cons = header->cons + 1;88 spin_unlock_irqrestore(&ring->lock, flags);89 return ret;90}91 92int qxl_check_idle(struct qxl_ring *ring)93{94 int ret;95 struct qxl_ring_header *header = &(ring->ring->header);96 unsigned long flags;97 98 spin_lock_irqsave(&ring->lock, flags);99 ret = header->prod == header->cons;100 spin_unlock_irqrestore(&ring->lock, flags);101 return ret;102}103 104int qxl_ring_push(struct qxl_ring *ring,105 const void *new_elt, bool interruptible)106{107 struct qxl_ring_header *header = &(ring->ring->header);108 uint8_t *elt;109 int idx, ret;110 unsigned long flags;111 112 spin_lock_irqsave(&ring->lock, flags);113 if (header->prod - header->cons == header->num_items) {114 header->notify_on_cons = header->cons + 1;115 mb();116 spin_unlock_irqrestore(&ring->lock, flags);117 if (!drm_can_sleep()) {118 while (!qxl_check_header(ring))119 udelay(1);120 } else {121 if (interruptible) {122 ret = wait_event_interruptible(*ring->push_event,123 qxl_check_header(ring));124 if (ret)125 return ret;126 } else {127 wait_event(*ring->push_event,128 qxl_check_header(ring));129 }130 131 }132 spin_lock_irqsave(&ring->lock, flags);133 }134 135 idx = header->prod & (ring->n_elements - 1);136 elt = ring->ring->elements + idx * ring->element_size;137 138 memcpy((void *)elt, new_elt, ring->element_size);139 140 header->prod++;141 142 mb();143 144 if (header->prod == header->notify_on_prod)145 outb(0, ring->prod_notify);146 147 spin_unlock_irqrestore(&ring->lock, flags);148 return 0;149}150 151static bool qxl_ring_pop(struct qxl_ring *ring,152 void *element)153{154 volatile struct qxl_ring_header *header = &(ring->ring->header);155 volatile uint8_t *ring_elt;156 int idx;157 unsigned long flags;158 159 spin_lock_irqsave(&ring->lock, flags);160 if (header->cons == header->prod) {161 header->notify_on_prod = header->cons + 1;162 spin_unlock_irqrestore(&ring->lock, flags);163 return false;164 }165 166 idx = header->cons & (ring->n_elements - 1);167 ring_elt = ring->ring->elements + idx * ring->element_size;168 169 memcpy(element, (void *)ring_elt, ring->element_size);170 171 header->cons++;172 173 spin_unlock_irqrestore(&ring->lock, flags);174 return true;175}176 177int178qxl_push_command_ring_release(struct qxl_device *qdev, struct qxl_release *release,179 uint32_t type, bool interruptible)180{181 struct qxl_command cmd;182 183 cmd.type = type;184 cmd.data = qxl_bo_physical_address(qdev, release->release_bo, release->release_offset);185 186 return qxl_ring_push(qdev->command_ring, &cmd, interruptible);187}188 189int190qxl_push_cursor_ring_release(struct qxl_device *qdev, struct qxl_release *release,191 uint32_t type, bool interruptible)192{193 struct qxl_command cmd;194 195 cmd.type = type;196 cmd.data = qxl_bo_physical_address(qdev, release->release_bo, release->release_offset);197 198 return qxl_ring_push(qdev->cursor_ring, &cmd, interruptible);199}200 201bool qxl_queue_garbage_collect(struct qxl_device *qdev, bool flush)202{203 if (!qxl_check_idle(qdev->release_ring)) {204 schedule_work(&qdev->gc_work);205 if (flush)206 flush_work(&qdev->gc_work);207 return true;208 }209 return false;210}211 212int qxl_garbage_collect(struct qxl_device *qdev)213{214 struct qxl_release *release;215 uint64_t id, next_id;216 int i = 0;217 union qxl_release_info *info;218 219 while (qxl_ring_pop(qdev->release_ring, &id)) {220 DRM_DEBUG_DRIVER("popped %lld\n", id);221 while (id) {222 release = qxl_release_from_id_locked(qdev, id);223 if (release == NULL)224 break;225 226 info = qxl_release_map(qdev, release);227 next_id = info->next;228 qxl_release_unmap(qdev, release, info);229 230 DRM_DEBUG_DRIVER("popped %lld, next %lld\n", id,231 next_id);232 233 switch (release->type) {234 case QXL_RELEASE_DRAWABLE:235 case QXL_RELEASE_SURFACE_CMD:236 case QXL_RELEASE_CURSOR_CMD:237 break;238 default:239 DRM_ERROR("unexpected release type\n");240 break;241 }242 id = next_id;243 244 qxl_release_free(qdev, release);245 ++i;246 }247 }248 249 wake_up_all(&qdev->release_event);250 DRM_DEBUG_DRIVER("%d\n", i);251 252 return i;253}254 255int qxl_alloc_bo_reserved(struct qxl_device *qdev,256 struct qxl_release *release,257 unsigned long size,258 struct qxl_bo **_bo)259{260 struct qxl_bo *bo;261 int ret;262 263 ret = qxl_bo_create(qdev, size, false /* not kernel - device */,264 false, QXL_GEM_DOMAIN_VRAM, 0, NULL, &bo);265 if (ret) {266 DRM_ERROR("failed to allocate VRAM BO\n");267 return ret;268 }269 ret = qxl_release_list_add(release, bo);270 if (ret)271 goto out_unref;272 273 *_bo = bo;274 return 0;275out_unref:276 qxl_bo_unref(&bo);277 return ret;278}279 280static int wait_for_io_cmd_user(struct qxl_device *qdev, uint8_t val, long port, bool intr)281{282 int irq_num;283 long addr = qdev->io_base + port;284 int ret;285 286 mutex_lock(&qdev->async_io_mutex);287 irq_num = atomic_read(&qdev->irq_received_io_cmd);288 if (qdev->last_sent_io_cmd > irq_num) {289 if (intr)290 ret = wait_event_interruptible_timeout(qdev->io_cmd_event,291 atomic_read(&qdev->irq_received_io_cmd) > irq_num, 5*HZ);292 else293 ret = wait_event_timeout(qdev->io_cmd_event,294 atomic_read(&qdev->irq_received_io_cmd) > irq_num, 5*HZ);295 /* 0 is timeout, just bail the "hw" has gone away */296 if (ret <= 0)297 goto out;298 irq_num = atomic_read(&qdev->irq_received_io_cmd);299 }300 outb(val, addr);301 qdev->last_sent_io_cmd = irq_num + 1;302 if (intr)303 ret = wait_event_interruptible_timeout(qdev->io_cmd_event,304 atomic_read(&qdev->irq_received_io_cmd) > irq_num, 5*HZ);305 else306 ret = wait_event_timeout(qdev->io_cmd_event,307 atomic_read(&qdev->irq_received_io_cmd) > irq_num, 5*HZ);308out:309 if (ret > 0)310 ret = 0;311 mutex_unlock(&qdev->async_io_mutex);312 return ret;313}314 315static void wait_for_io_cmd(struct qxl_device *qdev, uint8_t val, long port)316{317 int ret;318 319restart:320 ret = wait_for_io_cmd_user(qdev, val, port, false);321 if (ret == -ERESTARTSYS)322 goto restart;323}324 325int qxl_io_update_area(struct qxl_device *qdev, struct qxl_bo *surf,326 const struct qxl_rect *area)327{328 int surface_id;329 uint32_t surface_width, surface_height;330 int ret;331 332 if (!surf->hw_surf_alloc)333 DRM_ERROR("got io update area with no hw surface\n");334 335 if (surf->is_primary)336 surface_id = 0;337 else338 surface_id = surf->surface_id;339 surface_width = surf->surf.width;340 surface_height = surf->surf.height;341 342 if (area->left < 0 || area->top < 0 ||343 area->right > surface_width || area->bottom > surface_height)344 return -EINVAL;345 346 mutex_lock(&qdev->update_area_mutex);347 qdev->ram_header->update_area = *area;348 qdev->ram_header->update_surface = surface_id;349 ret = wait_for_io_cmd_user(qdev, 0, QXL_IO_UPDATE_AREA_ASYNC, true);350 mutex_unlock(&qdev->update_area_mutex);351 return ret;352}353 354void qxl_io_notify_oom(struct qxl_device *qdev)355{356 outb(0, qdev->io_base + QXL_IO_NOTIFY_OOM);357}358 359void qxl_io_flush_release(struct qxl_device *qdev)360{361 outb(0, qdev->io_base + QXL_IO_FLUSH_RELEASE);362}363 364void qxl_io_flush_surfaces(struct qxl_device *qdev)365{366 wait_for_io_cmd(qdev, 0, QXL_IO_FLUSH_SURFACES_ASYNC);367}368 369void qxl_io_destroy_primary(struct qxl_device *qdev)370{371 wait_for_io_cmd(qdev, 0, QXL_IO_DESTROY_PRIMARY_ASYNC);372 qdev->primary_bo->is_primary = false;373 drm_gem_object_put(&qdev->primary_bo->tbo.base);374 qdev->primary_bo = NULL;375}376 377void qxl_io_create_primary(struct qxl_device *qdev, struct qxl_bo *bo)378{379 struct qxl_surface_create *create;380 381 if (WARN_ON(qdev->primary_bo))382 return;383 384 DRM_DEBUG_DRIVER("qdev %p, ram_header %p\n", qdev, qdev->ram_header);385 create = &qdev->ram_header->create_surface;386 create->format = bo->surf.format;387 create->width = bo->surf.width;388 create->height = bo->surf.height;389 create->stride = bo->surf.stride;390 create->mem = qxl_bo_physical_address(qdev, bo, 0);391 392 DRM_DEBUG_DRIVER("mem = %llx, from %p\n", create->mem, bo->kptr);393 394 create->flags = QXL_SURF_FLAG_KEEP_DATA;395 create->type = QXL_SURF_TYPE_PRIMARY;396 397 wait_for_io_cmd(qdev, 0, QXL_IO_CREATE_PRIMARY_ASYNC);398 qdev->primary_bo = bo;399 qdev->primary_bo->is_primary = true;400 drm_gem_object_get(&qdev->primary_bo->tbo.base);401}402 403void qxl_io_memslot_add(struct qxl_device *qdev, uint8_t id)404{405 DRM_DEBUG_DRIVER("qxl_memslot_add %d\n", id);406 wait_for_io_cmd(qdev, id, QXL_IO_MEMSLOT_ADD_ASYNC);407}408 409void qxl_io_reset(struct qxl_device *qdev)410{411 outb(0, qdev->io_base + QXL_IO_RESET);412}413 414void qxl_io_monitors_config(struct qxl_device *qdev)415{416 wait_for_io_cmd(qdev, 0, QXL_IO_MONITORS_CONFIG_ASYNC);417}418 419int qxl_surface_id_alloc(struct qxl_device *qdev,420 struct qxl_bo *surf)421{422 uint32_t handle;423 int idr_ret;424again:425 idr_preload(GFP_ATOMIC);426 spin_lock(&qdev->surf_id_idr_lock);427 idr_ret = idr_alloc(&qdev->surf_id_idr, NULL, 1, 0, GFP_NOWAIT);428 spin_unlock(&qdev->surf_id_idr_lock);429 idr_preload_end();430 if (idr_ret < 0)431 return idr_ret;432 handle = idr_ret;433 434 if (handle >= qdev->rom->n_surfaces) {435 spin_lock(&qdev->surf_id_idr_lock);436 idr_remove(&qdev->surf_id_idr, handle);437 spin_unlock(&qdev->surf_id_idr_lock);438 qxl_reap_surface_id(qdev, 2);439 goto again;440 }441 surf->surface_id = handle;442 443 spin_lock(&qdev->surf_id_idr_lock);444 qdev->last_alloced_surf_id = handle;445 spin_unlock(&qdev->surf_id_idr_lock);446 return 0;447}448 449void qxl_surface_id_dealloc(struct qxl_device *qdev,450 uint32_t surface_id)451{452 spin_lock(&qdev->surf_id_idr_lock);453 idr_remove(&qdev->surf_id_idr, surface_id);454 spin_unlock(&qdev->surf_id_idr_lock);455}456 457int qxl_hw_surface_alloc(struct qxl_device *qdev,458 struct qxl_bo *surf)459{460 struct qxl_surface_cmd *cmd;461 struct qxl_release *release;462 int ret;463 464 if (surf->hw_surf_alloc)465 return 0;466 467 ret = qxl_alloc_surface_release_reserved(qdev, QXL_SURFACE_CMD_CREATE,468 NULL,469 &release);470 if (ret)471 return ret;472 473 ret = qxl_release_reserve_list(release, true);474 if (ret) {475 qxl_release_free(qdev, release);476 return ret;477 }478 cmd = (struct qxl_surface_cmd *)qxl_release_map(qdev, release);479 cmd->type = QXL_SURFACE_CMD_CREATE;480 cmd->flags = QXL_SURF_FLAG_KEEP_DATA;481 cmd->u.surface_create.format = surf->surf.format;482 cmd->u.surface_create.width = surf->surf.width;483 cmd->u.surface_create.height = surf->surf.height;484 cmd->u.surface_create.stride = surf->surf.stride;485 cmd->u.surface_create.data = qxl_bo_physical_address(qdev, surf, 0);486 cmd->surface_id = surf->surface_id;487 qxl_release_unmap(qdev, release, &cmd->release_info);488 489 surf->surf_create = release;490 491 /* no need to add a release to the fence for this surface bo,492 since it is only released when we ask to destroy the surface493 and it would never signal otherwise */494 qxl_release_fence_buffer_objects(release);495 qxl_push_command_ring_release(qdev, release, QXL_CMD_SURFACE, false);496 497 surf->hw_surf_alloc = true;498 spin_lock(&qdev->surf_id_idr_lock);499 idr_replace(&qdev->surf_id_idr, surf, surf->surface_id);500 spin_unlock(&qdev->surf_id_idr_lock);501 return 0;502}503 504int qxl_hw_surface_dealloc(struct qxl_device *qdev,505 struct qxl_bo *surf)506{507 struct qxl_surface_cmd *cmd;508 struct qxl_release *release;509 int ret;510 int id;511 512 if (!surf->hw_surf_alloc)513 return 0;514 515 ret = qxl_alloc_surface_release_reserved(qdev, QXL_SURFACE_CMD_DESTROY,516 surf->surf_create,517 &release);518 if (ret)519 return ret;520 521 surf->surf_create = NULL;522 /* remove the surface from the idr, but not the surface id yet */523 spin_lock(&qdev->surf_id_idr_lock);524 idr_replace(&qdev->surf_id_idr, NULL, surf->surface_id);525 spin_unlock(&qdev->surf_id_idr_lock);526 surf->hw_surf_alloc = false;527 528 id = surf->surface_id;529 surf->surface_id = 0;530 531 release->surface_release_id = id;532 cmd = (struct qxl_surface_cmd *)qxl_release_map(qdev, release);533 cmd->type = QXL_SURFACE_CMD_DESTROY;534 cmd->surface_id = id;535 qxl_release_unmap(qdev, release, &cmd->release_info);536 537 qxl_release_fence_buffer_objects(release);538 qxl_push_command_ring_release(qdev, release, QXL_CMD_SURFACE, false);539 540 return 0;541}542 543static int qxl_update_surface(struct qxl_device *qdev, struct qxl_bo *surf)544{545 struct qxl_rect rect;546 int ret;547 548 /* if we are evicting, we need to make sure the surface is up549 to date */550 rect.left = 0;551 rect.right = surf->surf.width;552 rect.top = 0;553 rect.bottom = surf->surf.height;554retry:555 ret = qxl_io_update_area(qdev, surf, &rect);556 if (ret == -ERESTARTSYS)557 goto retry;558 return ret;559}560 561static void qxl_surface_evict_locked(struct qxl_device *qdev, struct qxl_bo *surf, bool do_update_area)562{563 /* no need to update area if we are just freeing the surface normally */564 if (do_update_area)565 qxl_update_surface(qdev, surf);566 567 /* nuke the surface id at the hw */568 qxl_hw_surface_dealloc(qdev, surf);569}570 571void qxl_surface_evict(struct qxl_device *qdev, struct qxl_bo *surf, bool do_update_area)572{573 mutex_lock(&qdev->surf_evict_mutex);574 qxl_surface_evict_locked(qdev, surf, do_update_area);575 mutex_unlock(&qdev->surf_evict_mutex);576}577 578static int qxl_reap_surf(struct qxl_device *qdev, struct qxl_bo *surf, bool stall)579{580 long ret;581 582 ret = qxl_bo_reserve(surf);583 if (ret)584 return ret;585 586 if (stall)587 mutex_unlock(&qdev->surf_evict_mutex);588 589 if (stall) {590 ret = dma_resv_wait_timeout(surf->tbo.base.resv,591 DMA_RESV_USAGE_BOOKKEEP, true,592 15 * HZ);593 if (ret > 0)594 ret = 0;595 else if (ret == 0)596 ret = -EBUSY;597 } else {598 ret = dma_resv_test_signaled(surf->tbo.base.resv,599 DMA_RESV_USAGE_BOOKKEEP);600 ret = ret ? -EBUSY : 0;601 }602 603 if (stall)604 mutex_lock(&qdev->surf_evict_mutex);605 if (ret) {606 qxl_bo_unreserve(surf);607 return ret;608 }609 610 qxl_surface_evict_locked(qdev, surf, true);611 qxl_bo_unreserve(surf);612 return 0;613}614 615static int qxl_reap_surface_id(struct qxl_device *qdev, int max_to_reap)616{617 int num_reaped = 0;618 int i, ret;619 bool stall = false;620 int start = 0;621 622 mutex_lock(&qdev->surf_evict_mutex);623again:624 625 spin_lock(&qdev->surf_id_idr_lock);626 start = qdev->last_alloced_surf_id + 1;627 spin_unlock(&qdev->surf_id_idr_lock);628 629 for (i = start; i < start + qdev->rom->n_surfaces; i++) {630 void *objptr;631 int surfid = i % qdev->rom->n_surfaces;632 633 /* this avoids the case where the objects is in the634 idr but has been evicted half way - its makes635 the idr lookup atomic with the eviction */636 spin_lock(&qdev->surf_id_idr_lock);637 objptr = idr_find(&qdev->surf_id_idr, surfid);638 spin_unlock(&qdev->surf_id_idr_lock);639 640 if (!objptr)641 continue;642 643 ret = qxl_reap_surf(qdev, objptr, stall);644 if (ret == 0)645 num_reaped++;646 if (num_reaped >= max_to_reap)647 break;648 }649 if (num_reaped == 0 && stall == false) {650 stall = true;651 goto again;652 }653 654 mutex_unlock(&qdev->surf_evict_mutex);655 if (num_reaped) {656 usleep_range(500, 1000);657 qxl_queue_garbage_collect(qdev, true);658 }659 660 return 0;661}662