summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/gpu/drm/Kconfig11
-rw-r--r--drivers/gpu/drm/Makefile1
-rw-r--r--drivers/gpu/drm/drm_crtc.c3
-rw-r--r--drivers/gpu/drm/qxl/Makefile9
-rw-r--r--drivers/gpu/drm/qxl/TODO42
-rw-r--r--drivers/gpu/drm/qxl/qxl_cmd.c349
-rw-r--r--drivers/gpu/drm/qxl/qxl_debugfs.c439
-rw-r--r--drivers/gpu/drm/qxl/qxl_dev.h867
-rw-r--r--drivers/gpu/drm/qxl/qxl_display.c780
-rw-r--r--drivers/gpu/drm/qxl/qxl_draw.c453
-rw-r--r--drivers/gpu/drm/qxl/qxl_drv.c130
-rw-r--r--drivers/gpu/drm/qxl/qxl_drv.h512
-rw-r--r--drivers/gpu/drm/qxl/qxl_dumb.c61
-rw-r--r--drivers/gpu/drm/qxl/qxl_fb.c796
-rw-r--r--drivers/gpu/drm/qxl/qxl_gem.c155
-rw-r--r--drivers/gpu/drm/qxl/qxl_image.c306
-rw-r--r--drivers/gpu/drm/qxl/qxl_ioctl.c276
-rw-r--r--drivers/gpu/drm/qxl/qxl_irq.c72
-rw-r--r--drivers/gpu/drm/qxl/qxl_kms.c244
-rw-r--r--drivers/gpu/drm/qxl/qxl_object.c214
-rw-r--r--drivers/gpu/drm/qxl/qxl_object.h78
-rw-r--r--drivers/gpu/drm/qxl/qxl_ttm.c405
-rw-r--r--include/drm/qxl_drm.h106
23 files changed, 6308 insertions, 1 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 90e28081712..5f47fbdfde8 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -193,3 +193,14 @@ source "drivers/gpu/drm/ast/Kconfig"
source "drivers/gpu/drm/mgag200/Kconfig"
source "drivers/gpu/drm/cirrus/Kconfig"
+
+config DRM_QXL
+ tristate "QXL virtual GPU"
+ depends on DRM && PCI
+ select FB_SYS_FILLRECT
+ select FB_SYS_COPYAREA
+ select FB_SYS_IMAGEBLIT
+ select DRM_KMS_HELPER
+ select DRM_TTM
+ help
+ QXL virtual GPU for Spice virtualization desktop integration.
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index f65f65ed0dd..a730f34520d 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -45,4 +45,5 @@ obj-$(CONFIG_DRM_EXYNOS) +=exynos/
obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
+obj-$(CONFIG_DRM_QXL) += qxl/
obj-y += i2c/
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 6fbfc244748..edbce501aa3 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -271,7 +271,6 @@ struct drm_mode_object *drm_mode_object_find(struct drm_device *dev,
if (!obj || (obj->type != type) || (obj->id != id))
obj = NULL;
mutex_unlock(&dev->mode_config.idr_mutex);
-
return obj;
}
EXPORT_SYMBOL(drm_mode_object_find);
@@ -302,6 +301,7 @@ int drm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *fb,
fb->funcs = funcs;
dev->mode_config.num_fb++;
list_add(&fb->head, &dev->mode_config.fb_list);
+ DRM_INFO("adding fb->base.id %d\n", fb->base.id);
return 0;
}
@@ -350,6 +350,7 @@ void drm_framebuffer_cleanup(struct drm_framebuffer *fb)
}
}
+ DRM_INFO("removing fb object id %d\n", fb->base.id);
drm_mode_object_put(dev, &fb->base);
list_del(&fb->head);
dev->mode_config.num_fb--;
diff --git a/drivers/gpu/drm/qxl/Makefile b/drivers/gpu/drm/qxl/Makefile
new file mode 100644
index 00000000000..3c1f9ade135
--- /dev/null
+++ b/drivers/gpu/drm/qxl/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the drm device driver. This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+
+ccflags-y := -Iinclude/drm
+
+qxl-y := qxl_drv.o qxl_kms.o qxl_display.o qxl_ttm.o qxl_fb.o qxl_object.o qxl_gem.o qxl_cmd.o qxl_image.o qxl_draw.o qxl_debugfs.o qxl_irq.o qxl_dumb.o qxl_ioctl.o
+
+obj-$(CONFIG_DRM_QXL)+= qxl.o
diff --git a/drivers/gpu/drm/qxl/TODO b/drivers/gpu/drm/qxl/TODO
new file mode 100644
index 00000000000..f29c1e9ab5b
--- /dev/null
+++ b/drivers/gpu/drm/qxl/TODO
@@ -0,0 +1,42 @@
+airlied:
+ drop unmap ioctl, userspace can use munmap
+ reorder qxl_drm_command, uint64_t together.
+ implement our own gamma, used for fades.
+ no need to print a kernel git version, we have that in dmesg anyawys
+
+Allocation of on device memory in smaller chunks then PAGE_SIZE.
+For two users:
+ internal: can be done with preallocated lists, or on the fly allocating of chunks of them
+ - specialize for each message.
+ ioctl (xf86-video-qxl): can again do our own memory allocator on top of pages, but now we don't
+ know the sizes in advance.
+ can solve both with mspace, adopted to be on top of TTM.
+
+qxl_image: allocate chunks.
+ we can do this without memory allocator
+
+remove the garbage qxl_io_log
+use QXL_INFO / QXL_DEBUG macros that check a debugfs settable variable to toggle qxl_io_log.
+
+cleanup
+
+running checkpatch.pl
+
+Dynamic output number (QXL_NUM_OUTPUTS define must go)
+
+module parameter log in [0,2] => qxl_log_level
+
+coalesce updates (for fb mainly - let userspace do it for itself)
+
+qxl_image hashing: either implement (good for modesetting) or ditch (xf86-video-qxl
+does it's own hashing, we don't see/don't check for the created images)
+
+====
+Misc
+====
+
+// TODO: why have a seperate mmap and alloc ioctl? also, why not just unmmap on the last
+// decref, i.e. when destroyed? is there a huge performance gain to unmapping stuff before
+// destroying? and can anyone use an allocated but not mapped memory from userspace??
+
+qxlfb_create_pinned_object - 1024x768 limit is actually used?
diff --git a/drivers/gpu/drm/qxl/qxl_cmd.c b/drivers/gpu/drm/qxl/qxl_cmd.c
new file mode 100644
index 00000000000..b3b18b89985
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_cmd.c
@@ -0,0 +1,349 @@
+/* QXL cmd/ring handling */
+
+#include "qxl_drv.h"
+#include "qxl_object.h"
+
+struct ring {
+ struct qxl_ring_header header;
+ uint8_t elements[0];
+};
+
+struct qxl_ring {
+ struct ring *ring;
+ int element_size;
+ int n_elements;
+ int prod_notify;
+ wait_queue_head_t *push_event;
+};
+
+void qxl_ring_free(struct qxl_ring *ring)
+{
+ kfree(ring);
+}
+
+struct qxl_ring *
+qxl_ring_create(struct qxl_ring_header *header,
+ int element_size,
+ int n_elements,
+ int prod_notify,
+ wait_queue_head_t *push_event)
+{
+ struct qxl_ring *ring;
+
+ ring = kmalloc(sizeof(*ring), GFP_KERNEL);
+ if (!ring)
+ return NULL;
+
+ ring->ring = (struct ring *)header;
+ ring->element_size = element_size;
+ ring->n_elements = n_elements;
+ ring->prod_notify = prod_notify;
+ ring->push_event = push_event;
+
+ return ring;
+}
+
+void qxl_ring_push(struct qxl_ring *ring,
+ const void *new_elt)
+{
+ struct qxl_ring_header *header = &(ring->ring->header);
+ uint8_t *elt;
+ int idx;
+
+ if (header->prod - header->cons == header->num_items) {
+ header->notify_on_cons = header->cons + 1;
+ mb();
+ wait_event_interruptible(*ring->push_event,
+ header->prod - header->cons < header->num_items);
+ }
+
+ idx = header->prod & (ring->n_elements - 1);
+ elt = ring->ring->elements + idx * ring->element_size;
+
+ memcpy((void *)elt, new_elt, ring->element_size);
+
+ header->prod++;
+
+ mb();
+
+ if (header->prod == header->notify_on_prod)
+ outb(0, ring->prod_notify);
+}
+
+bool qxl_ring_pop(struct qxl_ring *ring,
+ void *element)
+{
+ volatile struct qxl_ring_header *header = &(ring->ring->header);
+ volatile uint8_t *ring_elt;
+ int idx;
+
+ if (header->cons == header->prod)
+ return false;
+
+ idx = header->cons & (ring->n_elements - 1);
+ ring_elt = ring->ring->elements + idx * ring->element_size;
+
+ memcpy(element, (void *)ring_elt, ring->element_size);
+
+ header->cons++;
+
+ return true;
+}
+
+void qxl_ring_wait_idle(struct qxl_ring *ring)
+{
+ struct qxl_ring_header *header = &(ring->ring->header);
+
+ if (ring->ring->header.cons < ring->ring->header.prod) {
+ header->notify_on_cons = header->prod;
+ mb();
+ wait_event_interruptible(*ring->push_event,
+ header->prod == header->cons);
+ }
+}
+
+void qxl_bo_free(struct qxl_bo *bo)
+{
+ int ret;
+ ret = qxl_bo_reserve(bo, false);
+ if (!ret) {
+ qxl_bo_kunmap(bo);
+ qxl_bo_unpin(bo);
+ qxl_bo_unreserve(bo);
+ }
+ drm_gem_object_release(&bo->gem_base);
+ qxl_bo_unref(&bo);
+}
+
+struct drm_qxl_release *qxl_release_from_id_locked(struct qxl_device *qdev,
+ uint64_t id)
+{
+ struct drm_qxl_release *release;
+
+ release = idr_find(&qdev->release_idr, id);
+ if (!release) {
+ DRM_ERROR("failed to find id in release_idr\n");
+ return NULL;
+ }
+ if (release->bo_count < 1) {
+ DRM_ERROR("read a released resource with 0 bos\n");
+ return NULL;
+ }
+ return release;
+}
+
+int qxl_garbage_collect(struct qxl_device *qdev)
+{
+ struct drm_qxl_release *release;
+ uint64_t id;
+ int i = 0;
+ union qxl_release_info *info;
+
+ mutex_lock(&qdev->release_idr_mutex);
+ while (qxl_ring_pop(qdev->release_ring, &id)) {
+ QXL_INFO(qdev, "popped %lld\n", id);
+ while (id) {
+ release = qxl_release_from_id_locked(qdev, id);
+ if (release == NULL)
+ break;
+ info = (union qxl_release_info *)release->bos[0]->kptr;
+ QXL_INFO(qdev, "popped %lld, next %lld\n", id,
+ info->next);
+
+ switch (release->type) {
+ case QXL_RELEASE_DRAWABLE:
+ case QXL_RELEASE_SURFACE_CMD:
+ case QXL_RELEASE_CURSOR_CMD:
+ break;
+ default:
+ DRM_ERROR("unexpected release type\n");
+ break;
+ }
+ id = info->next;
+ qxl_release_free_locked(qdev, release);
+ ++i;
+ }
+ }
+ mutex_unlock(&qdev->release_idr_mutex);
+ QXL_INFO(qdev, "%s: %lld\n", __func__, i);
+
+ return i;
+}
+
+/* create and pin bo */
+static struct qxl_bo *qxl_create_pinned_bo(struct qxl_device *qdev,
+ unsigned long size)
+{
+ struct qxl_bo *bo;
+ int ret;
+
+ ret = qxl_bo_create(qdev, size, false /* not kernel - device */,
+ QXL_GEM_DOMAIN_VRAM, &bo);
+ if (ret) {
+ DRM_ERROR("failed to allocate VRAM BO\n");
+ return NULL;
+ }
+ ret = qxl_bo_reserve(bo, false);
+ if (unlikely(ret != 0))
+ goto out_unref;
+
+ ret = qxl_bo_pin(bo, QXL_GEM_DOMAIN_VRAM, NULL);
+ if (ret) {
+ DRM_ERROR("failed to pin VRAM BO %d\n", ret);
+ goto out_unref;
+ }
+
+ ret = qxl_bo_kmap(bo, NULL);
+ qxl_bo_unreserve(bo); /* this memory will be reserved via mmap */
+ if (ret)
+ goto out_unref;
+ return bo;
+out_unref:
+ qxl_bo_unref(&bo);
+ return NULL;
+}
+
+void *qxl_allocnf(struct qxl_device *qdev, unsigned long size,
+ struct drm_qxl_release *release)
+{
+ struct qxl_bo *bo;
+
+ qxl_garbage_collect(qdev);
+ bo = qxl_create_pinned_bo(qdev, size);
+ qxl_release_add_res(qdev, release, bo);
+ return bo->kptr;
+}
+
+static void wait_for_io_cmd(struct qxl_device *qdev, uint8_t val, long port)
+{
+ int irq_num = atomic_read(&qdev->irq_received_io_cmd);
+ long addr = qdev->io_base + port;
+ int num_restart = 0;
+
+ mutex_lock(&qdev->async_io_mutex);
+ outb(val, addr);
+ for (; 1 ; ++num_restart) {
+ switch (wait_event_interruptible(qdev->io_cmd_event,
+ atomic_read(&qdev->irq_received_io_cmd) > irq_num)) {
+ case 0:
+ goto done;
+ break;
+ case -ERESTARTSYS:
+ continue;
+ break;
+ }
+ }
+done:
+ qxl_io_log(qdev, "%s: async %d completed after %d wakes\n",
+ __func__, port, num_restart);
+ mutex_unlock(&qdev->async_io_mutex);
+}
+
+void qxl_io_update_area(struct qxl_device *qdev, uint32_t surface_id,
+ const struct qxl_rect *area)
+{
+ qdev->ram_header->update_area = *area;
+ qdev->ram_header->update_surface = surface_id;
+ wait_for_io_cmd(qdev, 0, QXL_IO_UPDATE_AREA_ASYNC);
+}
+
+void qxl_io_notify_oom(struct qxl_device *qdev)
+{
+ outb(0, qdev->io_base + QXL_IO_NOTIFY_OOM);
+}
+
+void qxl_io_update_screen(struct qxl_device *qdev)
+{
+ struct qxl_rect area;
+ u32 height, width;
+
+ height = qdev->fbdev_qfb->base.height;
+ width = qdev->fbdev_qfb->base.width;
+ QXL_INFO(qdev, "%s: bad bad bad %dx%d\n", __func__,
+ width, height);
+ area.left = area.top = 0;
+ area.right = width;
+ area.bottom = height;
+
+ qxl_io_update_area(qdev, 0, &area);
+}
+
+void qxl_io_destroy_primary(struct qxl_device *qdev)
+{
+ if (!qdev->primary_created)
+ return;
+ wait_for_io_cmd(qdev, 0, QXL_IO_DESTROY_PRIMARY_ASYNC);
+ qdev->primary_created = 0;
+}
+
+void qxl_io_create_primary(struct qxl_device *qdev, unsigned width,
+ unsigned height)
+{
+ struct qxl_surface_create *create;
+ int32_t stride = width * 4;
+
+ QXL_INFO(qdev, "%s: qdev %p, ram_header %p\n", __func__, qdev,
+ qdev->ram_header);
+ create = &qdev->ram_header->create_surface;
+ create->format = SPICE_SURFACE_FMT_32_xRGB;
+ create->width = width;
+ create->height = height;
+ create->stride = stride;
+ create->mem = qxl_bo_physical_address(qdev,
+ qdev->surface0_bo, 0,
+ qdev->main_mem_slot);
+ QXL_INFO(qdev, "%s: mem = %llx, from %p\n", __func__, create->mem,
+ qdev->surface0_bo->kptr);
+
+ create->flags = 0;
+ create->type = QXL_SURF_TYPE_PRIMARY;
+
+ qdev->primary_created = 1;
+ qdev->primary_width = width;
+ qdev->primary_height = height;
+
+ wait_for_io_cmd(qdev, 0, QXL_IO_CREATE_PRIMARY_ASYNC);
+}
+
+void qxl_io_memslot_add(struct qxl_device *qdev, uint8_t id)
+{
+ QXL_INFO(qdev, "qxl_memslot_add %d\n", id);
+ wait_for_io_cmd(qdev, id, QXL_IO_MEMSLOT_ADD_ASYNC);
+}
+
+void qxl_io_log(struct qxl_device *qdev, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vsnprintf(qdev->ram_header->log_buf, QXL_LOG_BUF_SIZE, fmt, args);
+ va_end(args);
+ /*
+ * DO not do a DRM output here - this will call printk, which will
+ * call back into qxl for rendering (qxl_fb)
+ */
+ outb(0, qdev->io_base + QXL_IO_LOG);
+}
+
+void qxl_io_reset(struct qxl_device *qdev)
+{
+ outb(0, qdev->io_base + QXL_IO_RESET);
+}
+
+void qxl_io_monitors_config(struct qxl_device *qdev)
+{
+ qxl_io_log(qdev, "%s: %d [%dx%d+%d+%d]\n", __func__,
+ qdev->monitors_config ?
+ qdev->monitors_config->count : -1,
+ qdev->monitors_config && qdev->monitors_config->count ?
+ qdev->monitors_config->heads[0].width : -1,
+ qdev->monitors_config && qdev->monitors_config->count ?
+ qdev->monitors_config->heads[0].height : -1,
+ qdev->monitors_config && qdev->monitors_config->count ?
+ qdev->monitors_config->heads[0].x : -1,
+ qdev->monitors_config && qdev->monitors_config->count ?
+ qdev->monitors_config->heads[0].y : -1
+ );
+
+ wait_for_io_cmd(qdev, 0, QXL_IO_MONITORS_CONFIG_ASYNC);
+}
diff --git a/drivers/gpu/drm/qxl/qxl_debugfs.c b/drivers/gpu/drm/qxl/qxl_debugfs.c
new file mode 100644
index 00000000000..6b71e7aec93
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_debugfs.c
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2009 Red Hat <bskeggs@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/*
+ * Authors:
+ * Alon Levy <alevy@redhat.com>
+ */
+
+#include <linux/debugfs.h>
+
+#include "drmP.h"
+#include "qxl_drv.h"
+#include "qxl_object.h"
+
+int qxl_log_level;
+int qxl_debug_disable_fb;
+
+static void ppm_save(int width, int height, int bytes_per_pixel, int line_size,
+ int bits_per_pixel, uint8_t *d1, struct seq_file *m)
+{
+ uint8_t *d;
+ uint32_t v;
+ int y, x;
+ uint8_t r, g, b;
+ int ret;
+ char *linebuf, *pbuf;
+ int rshift = 16;
+ int gshift = 8;
+ int bshift = 0;
+ int rmax = 255;
+ int gmax = 255;
+ int bmax = 255;
+
+ DRM_INFO("%s: hwd %d,%d,%d bpp %d line %d\n", __func__,
+ height, width, bytes_per_pixel, bits_per_pixel, line_size);
+ seq_printf(m, "P6\n%d %d\n%d\n", width, height, 255);
+ linebuf = kmalloc(width * 3, GFP_KERNEL);
+ for (y = 0; y < height; y++) {
+ d = d1;
+ pbuf = linebuf;
+ for (x = 0; x < width; x++) {
+ if (bits_per_pixel == 32)
+ v = *(uint32_t *)d;
+ else
+ v = (uint32_t) (*(uint16_t *)d);
+ r = ((v >> rshift) & rmax) * 256 / (rmax + 1);
+ g = ((v >> gshift) & gmax) * 256 / (gmax + 1);
+ b = ((v >> bshift) & bmax) * 256 / (bmax + 1);
+ *pbuf++ = r;
+ *pbuf++ = g;
+ *pbuf++ = b;
+ d += bytes_per_pixel;
+ }
+ d1 += line_size;
+ ret = seq_write(m, linebuf, pbuf - linebuf);
+ }
+ kfree(linebuf);
+}
+
+static void ppm_save_qxl_fb(struct qxl_framebuffer *qxl_fb, struct seq_file *m)
+{
+ struct qxl_bo *qobj = gem_to_qxl_bo(qxl_fb->obj);
+ int width = qxl_fb->base.width;
+ int height = qxl_fb->base.height;
+ int bytes_per_pixel = qxl_fb->base.bits_per_pixel / 8;
+ int line_size = qxl_fb->base.pitches[0];
+ int bits_per_pixel = qxl_fb->base.bits_per_pixel;
+ uint8_t *d1 = qobj->kptr;
+
+ ppm_save(width, height, bytes_per_pixel, line_size, bits_per_pixel,
+ d1, m);
+}
+
+static int
+qxl_debugfs_dumbppm(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+ struct qxl_rect area;
+
+ if (qdev->active_user_framebuffer) {
+ ppm_save_qxl_fb(qdev->active_user_framebuffer, m);
+ } else if (qdev->fbdev_qfb) {
+ area.top = area.left = 0;
+ area.right = qdev->fbdev_qfb->base.width;
+ area.bottom = qdev->fbdev_qfb->base.height;
+ qxl_io_update_area(qdev, 0, &area);
+ ppm_save_qxl_fb(qdev->fbdev_qfb, m);
+ }
+ return 0;
+}
+
+struct release_idr_data {
+ struct seq_file *m;
+ int total_bo;
+};
+
+static int idr_iter_fn(int id, void *p, void *data)
+{
+ struct release_idr_data *release_data = data;
+ struct seq_file *m = release_data->m;
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+ struct drm_qxl_release *release = qxl_release_from_id_locked(qdev, id);
+
+ seq_printf(m, "%d, type %d bo %d\n", id, release->type,
+ release->bo_count);
+ release_data->total_bo += release->bo_count;
+ return 0;
+}
+
+static int
+qxl_debugfs_release(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+ struct qxl_ring_header *r = &qdev->ram_header->release_ring_hdr;
+ struct release_idr_data idr_data;
+
+ idr_data.m = m;
+ idr_data.total_bo = 0;
+ mutex_lock(&qdev->release_idr_mutex);
+ idr_for_each(&qdev->release_idr, idr_iter_fn, &idr_data);
+ mutex_unlock(&qdev->release_idr_mutex);
+ seq_printf(m, "ring %d [%d,%d] (%d,%d)\n", r->num_items, r->prod,
+ r->cons, r->notify_on_prod, r->notify_on_cons);
+ seq_printf(m, "collected %d, release bo's %d / %d ttm\n",
+ qxl_garbage_collect(qdev),
+ idr_data.total_bo,
+ atomic_read(&qdev->mman.bdev.glob->bo_count));
+ return 0;
+}
+
+#define DRAW_WIDTH 256
+#define DRAW_HEIGHT 96
+static uint32_t draw_data[DRAW_WIDTH * DRAW_HEIGHT];
+static int draw_line;
+
+static void
+qxl_debugfs_draw_depth(struct seq_file *m, void *data, int depth,
+ uint32_t *palette, int workqueue)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+ struct qxl_fb_image qxl_fb_image;
+ struct fb_image *image = &qxl_fb_image.fb_image;
+ uint32_t stride = DRAW_WIDTH * depth / 32;
+ uint32_t *p = &draw_data[draw_line * stride];
+ int i;
+ static int color_ind;
+ int fg[] = {0xaaaaaa, 0xff55ff, 0xddff33};
+
+ draw_line = (draw_line + 1) % DRAW_HEIGHT;
+
+ for (i = 0 ; i < stride; ++i, ++p)
+ *p = ~*p;
+ qxl_fb_image.qdev = qdev;
+ qxl_fb_image.visual = FB_VISUAL_DIRECTCOLOR;
+ image->dx = 300;
+ image->dy = 100;
+ image->width = DRAW_WIDTH;
+ image->height = DRAW_HEIGHT;
+ image->depth = depth;
+ image->data = (char *)draw_data;
+ if (depth == 1) {
+ if (palette) {
+ memcpy(qxl_fb_image.pseudo_palette, palette,
+ sizeof(qxl_fb_image.pseudo_palette));
+ image->fg_color = 1;
+ image->bg_color = 0;
+ } else {
+ qxl_fb_image.visual = FB_VISUAL_MONO10;
+ image->fg_color = fg[color_ind];
+ image->bg_color = 0;
+ }
+ color_ind = (color_ind + 1) % (sizeof(fg) / sizeof(fg[0]));
+ }
+ if (workqueue)
+ qxl_fb_queue_imageblit(qdev, &qxl_fb_image, NULL, NULL);
+ else
+ qxl_draw_opaque_fb(&qxl_fb_image, 0);
+}
+
+static int
+qxl_debugfs_draw_32(struct seq_file *m, void *data)
+{
+ qxl_debugfs_draw_depth(m, data, 32, NULL, 0);
+ return 0;
+}
+
+static int
+qxl_debugfs_draw_1(struct seq_file *m, void *data)
+{
+ qxl_debugfs_draw_depth(m, data, 1, NULL, 0);
+ return 0;
+}
+
+static int
+qxl_debugfs_oom(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+
+ qxl_io_notify_oom(qdev);
+ return 0;
+}
+
+static int
+qxl_debugfs_debug_enable(struct seq_file *m, void *data)
+{
+ qxl_log_level = 2;
+ return 0;
+}
+
+static int
+qxl_debugfs_debug_disable(struct seq_file *m, void *data)
+{
+ qxl_log_level = 0;
+ return 0;
+}
+
+static int gem_idr_iter_fn(int id, void *p, void *data)
+{
+ ++*(int *)data;
+ return 0;
+}
+
+static int
+qxl_debugfs_mem(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+ int gem_count = 0;
+
+ seq_printf(m, "ttm bo count %d\n",
+ atomic_read(&qdev->mman.bdev.glob->bo_count));
+ gem_count = 0;
+ spin_lock(&qdev->ddev->object_name_lock);
+ idr_for_each(&qdev->ddev->object_name_idr, gem_idr_iter_fn, &gem_count);
+ spin_unlock(&qdev->ddev->object_name_lock);
+ seq_printf(m, "gem object_name count %d\n", gem_count);
+ return 0;
+}
+
+/* test new bo and free of bo */
+static int
+qxl_debugfs_bo_test(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+ struct qxl_bo *bo;
+ int ret;
+
+ ret = qxl_bo_create(qdev, 8192, false /* not kernel, device */,
+ QXL_GEM_DOMAIN_VRAM, &bo);
+ if (unlikely(ret != 0)) {
+ seq_printf(m, "failed to create bo of 8192 bytes\n");
+ return 0;
+ }
+ /* TODO - pin test as well. qxl_allocnf test generally. */
+ qxl_bo_free(bo);
+ return 0;
+}
+
+/* test new idr and release of idr */
+static int
+qxl_debugfs_alloc_test(struct seq_file *m, void *data)
+{
+ seq_printf(m, "implement me\n");
+ return 0;
+}
+
+static int
+qxl_debugfs_fb_wq_image(struct seq_file *m, void *data)
+{
+ uint32_t palette[16] = {0, 0xffffff, 0};
+ qxl_debugfs_draw_depth(m, data, 1, palette, 1);
+ return 0;
+}
+
+static void
+set_rect(struct qxl_rect *r, int top, int left, int bottom, int right)
+{
+ r->top = top;
+ r->left = left;
+ r->bottom = bottom;
+ r->right = right;
+}
+
+static int
+qxl_debugfs_fb_wq_fill(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+ struct qxl_draw_fill qxl_draw_fill_rec;
+
+ qxl_draw_fill_rec.qdev = qdev;
+ set_rect(&qxl_draw_fill_rec.rect, 100, 100, 200, 400);
+ qxl_draw_fill_rec.color = 0x00ff0000;
+ qxl_draw_fill_rec.rop = SPICE_ROPD_OP_PUT;
+ qxl_fb_queue_draw_fill(&qxl_draw_fill_rec);
+ return 0;
+}
+
+
+static int
+qxl_debugfs_fb_enable(struct seq_file *m, void *data)
+{
+ qxl_debug_disable_fb = 0;
+ return 0;
+}
+
+static int
+qxl_debugfs_fb_disable(struct seq_file *m, void *data)
+{
+ qxl_debug_disable_fb = 1;
+ return 0;
+}
+
+static int
+qxl_debugfs_irq_received(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+
+ seq_printf(m, "%d\n", atomic_read(&qdev->irq_received));
+ seq_printf(m, "%d\n", atomic_read(&qdev->irq_received_display));
+ seq_printf(m, "%d\n", atomic_read(&qdev->irq_received_cursor));
+ seq_printf(m, "%d\n", atomic_read(&qdev->irq_received_io_cmd));
+ seq_printf(m, "%d\n", qdev->irq_received_error);
+ return 0;
+}
+
+static int
+qxl_debugfs_read_client_monitors_config(struct seq_file *m, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+
+ qxl_display_read_client_monitors_config(qdev);
+ return 0;
+}
+
+static int
+qxl_debugfs_set_monitors_config(struct seq_file *m, int width, int height)
+{
+ struct drm_info_node *node = (struct drm_info_node *) m->private;
+ struct qxl_device *qdev = node->minor->dev->dev_private;
+
+ qxl_alloc_client_monitors_config(qdev, 1);
+ if (!qdev->client_monitors_config) {
+ qxl_io_log(qdev, "%s: no memory\n", __func__);
+ return 0;
+ }
+
+ qdev->monitors_config->count = 1;
+ qdev->monitors_config->heads[0].width = width;
+ qdev->monitors_config->heads[0].height = height;
+ qdev->monitors_config->heads[0].x = 0;
+ qdev->monitors_config->heads[0].y = 0;
+ qxl_crtc_set_from_monitors_config(qdev);
+ qxl_send_monitors_config(qdev);
+ drm_sysfs_hotplug_event(qdev->ddev);
+ return 0;
+}
+
+static int
+qxl_debugfs_set_monitor_924_668(struct seq_file *m, void *data)
+{
+ return qxl_debugfs_set_monitors_config(m, 924, 668);
+}
+
+static int
+qxl_debugfs_set_monitor_820_620(struct seq_file *m, void *data)
+{
+ return qxl_debugfs_set_monitors_config(m, 820, 620);
+}
+
+static struct drm_info_list qxl_debugfs_list[] = {
+ { "dumbppm", qxl_debugfs_dumbppm, 0, NULL },
+ { "release", qxl_debugfs_release, 0, NULL },
+ { "draw32", qxl_debugfs_draw_32, 0, NULL },
+ { "draw1", qxl_debugfs_draw_1, 0, NULL },
+ { "oom", qxl_debugfs_oom, 0, NULL },
+ { "mem", qxl_debugfs_mem, 0, NULL },
+ { "bo_test", qxl_debugfs_bo_test, 0, NULL },
+ { "alloc_test", qxl_debugfs_alloc_test, 0, NULL },
+ { "fb_wq_image", qxl_debugfs_fb_wq_image, 0, NULL },
+ { "fb_wq_fill", qxl_debugfs_fb_wq_fill, 0, NULL },
+ { "read_client_monitors_config",
+ qxl_debugfs_read_client_monitors_config, 0, NULL },
+ { "mon_924_668", qxl_debugfs_set_monitor_924_668, 0, NULL },
+ { "mon_820_620", qxl_debugfs_set_monitor_820_620, 0, NULL },
+ /* TODO: read int from user; echo debug_level > *
+ * /sys/kernel/debugfs/dri/0/debug */
+ { "debug_enable", qxl_debugfs_debug_enable, 0, NULL },
+ { "debug_disable", qxl_debugfs_debug_disable, 0, NULL },
+ { "fb_enable", qxl_debugfs_fb_enable, 0, NULL },
+ { "fb_disable", qxl_debugfs_fb_disable, 0, NULL },
+ { "irq_received", qxl_debugfs_irq_received, 0, NULL },
+};
+#define NOUVEAU_DEBUGFS_ENTRIES ARRAY_SIZE(qxl_debugfs_list)
+
+int
+qxl_debugfs_init(struct drm_minor *minor)
+{
+ drm_debugfs_create_files(qxl_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES,
+ minor->debugfs_root, minor);
+ return 0;
+}
+
+void
+qxl_debugfs_takedown(struct drm_minor *minor)
+{
+ drm_debugfs_remove_files(qxl_debugfs_list, NOUVEAU_DEBUGFS_ENTRIES,
+ minor);
+}
diff --git a/drivers/gpu/drm/qxl/qxl_dev.h b/drivers/gpu/drm/qxl/qxl_dev.h
new file mode 100644
index 00000000000..532a4d3011e
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_dev.h
@@ -0,0 +1,867 @@
+/*
+ Copyright (C) 2009 Red Hat, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#ifndef H_QXL_DEV
+#define H_QXL_DEV
+
+#include <linux/types.h>
+
+/*
+ * from spice-protocol
+ * Release 0.10.0
+ */
+
+/* enums.h */
+
+enum SpiceImageType {
+ SPICE_IMAGE_TYPE_BITMAP,
+ SPICE_IMAGE_TYPE_QUIC,
+ SPICE_IMAGE_TYPE_RESERVED,
+ SPICE_IMAGE_TYPE_LZ_PLT = 100,
+ SPICE_IMAGE_TYPE_LZ_RGB,
+ SPICE_IMAGE_TYPE_GLZ_RGB,
+ SPICE_IMAGE_TYPE_FROM_CACHE,
+ SPICE_IMAGE_TYPE_SURFACE,
+ SPICE_IMAGE_TYPE_JPEG,
+ SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS,
+ SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB,
+ SPICE_IMAGE_TYPE_JPEG_ALPHA,
+
+ SPICE_IMAGE_TYPE_ENUM_END
+};
+
+enum SpiceBitmapFmt {
+ SPICE_BITMAP_FMT_INVALID,
+ SPICE_BITMAP_FMT_1BIT_LE,
+ SPICE_BITMAP_FMT_1BIT_BE,
+ SPICE_BITMAP_FMT_4BIT_LE,
+ SPICE_BITMAP_FMT_4BIT_BE,
+ SPICE_BITMAP_FMT_8BIT,
+ SPICE_BITMAP_FMT_16BIT,
+ SPICE_BITMAP_FMT_24BIT,
+ SPICE_BITMAP_FMT_32BIT,
+ SPICE_BITMAP_FMT_RGBA,
+
+ SPICE_BITMAP_FMT_ENUM_END
+};
+
+enum SpiceSurfaceFmt {
+ SPICE_SURFACE_FMT_INVALID,
+ SPICE_SURFACE_FMT_1_A,
+ SPICE_SURFACE_FMT_8_A = 8,
+ SPICE_SURFACE_FMT_16_555 = 16,
+ SPICE_SURFACE_FMT_32_xRGB = 32,
+ SPICE_SURFACE_FMT_16_565 = 80,
+ SPICE_SURFACE_FMT_32_ARGB = 96,
+
+ SPICE_SURFACE_FMT_ENUM_END
+};
+
+enum SpiceClipType {
+ SPICE_CLIP_TYPE_NONE,
+ SPICE_CLIP_TYPE_RECTS,
+
+ SPICE_CLIP_TYPE_ENUM_END
+};
+
+enum SpiceRopd {
+ SPICE_ROPD_INVERS_SRC = (1 << 0),
+ SPICE_ROPD_INVERS_BRUSH = (1 << 1),
+ SPICE_ROPD_INVERS_DEST = (1 << 2),
+ SPICE_ROPD_OP_PUT = (1 << 3),
+ SPICE_ROPD_OP_OR = (1 << 4),
+ SPICE_ROPD_OP_AND = (1 << 5),
+ SPICE_ROPD_OP_XOR = (1 << 6),
+ SPICE_ROPD_OP_BLACKNESS = (1 << 7),
+ SPICE_ROPD_OP_WHITENESS = (1 << 8),
+ SPICE_ROPD_OP_INVERS = (1 << 9),
+ SPICE_ROPD_INVERS_RES = (1 << 10),
+
+ SPICE_ROPD_MASK = 0x7ff
+};
+
+enum SpiceBrushType {
+ SPICE_BRUSH_TYPE_NONE,
+ SPICE_BRUSH_TYPE_SOLID,
+ SPICE_BRUSH_TYPE_PATTERN,
+
+ SPICE_BRUSH_TYPE_ENUM_END
+};
+
+/* qxl_dev.h */
+
+#pragma pack(push, 1)
+
+#define REDHAT_PCI_VENDOR_ID 0x1b36
+
+/* 0x100-0x11f reserved for spice, 0x1ff used for unstable work */
+#define QXL_DEVICE_ID_STABLE 0x0100
+
+enum {
+ QXL_REVISION_STABLE_V04 = 0x01,
+ QXL_REVISION_STABLE_V06 = 0x02,
+ QXL_REVISION_STABLE_V10 = 0x03,
+ QXL_REVISION_STABLE_V12 = 0x04,
+};
+
+#define QXL_DEVICE_ID_DEVEL 0x01ff
+#define QXL_REVISION_DEVEL 0x01
+
+#define QXL_ROM_MAGIC (*(uint32_t *)"QXRO")
+#define QXL_RAM_MAGIC (*(uint32_t *)"QXRA")
+
+enum {
+ QXL_RAM_RANGE_INDEX,
+ QXL_VRAM_RANGE_INDEX,
+ QXL_ROM_RANGE_INDEX,
+ QXL_IO_RANGE_INDEX,
+
+ QXL_PCI_RANGES
+};
+
+/* qxl-1 compat: append only */
+enum {
+ QXL_IO_NOTIFY_CMD,
+ QXL_IO_NOTIFY_CURSOR,
+ QXL_IO_UPDATE_AREA,
+ QXL_IO_UPDATE_IRQ,
+ QXL_IO_NOTIFY_OOM,
+ QXL_IO_RESET,
+ QXL_IO_SET_MODE, /* qxl-1 */
+ QXL_IO_LOG,
+ /* appended for qxl-2 */
+ QXL_IO_MEMSLOT_ADD,
+ QXL_IO_MEMSLOT_DEL,
+ QXL_IO_DETACH_PRIMARY,
+ QXL_IO_ATTACH_PRIMARY,
+ QXL_IO_CREATE_PRIMARY,
+ QXL_IO_DESTROY_PRIMARY,
+ QXL_IO_DESTROY_SURFACE_WAIT,
+ QXL_IO_DESTROY_ALL_SURFACES,
+ /* appended for qxl-3 */
+ QXL_IO_UPDATE_AREA_ASYNC,
+ QXL_IO_MEMSLOT_ADD_ASYNC,
+ QXL_IO_CREATE_PRIMARY_ASYNC,
+ QXL_IO_DESTROY_PRIMARY_ASYNC,
+ QXL_IO_DESTROY_SURFACE_ASYNC,
+ QXL_IO_DESTROY_ALL_SURFACES_ASYNC,
+ QXL_IO_FLUSH_SURFACES_ASYNC,
+ QXL_IO_FLUSH_RELEASE,
+ /* appended for qxl-4 */
+ QXL_IO_MONITORS_CONFIG_ASYNC,
+
+ QXL_IO_RANGE_SIZE
+};
+
+typedef uint64_t QXLPHYSICAL;
+typedef int32_t QXLFIXED; /* fixed 28.4 */
+
+struct qxl_point_fix {
+ QXLFIXED x;
+ QXLFIXED y;
+};
+
+struct qxl_point {
+ int32_t x;
+ int32_t y;
+};
+
+struct qxl_point_1_6 {
+ int16_t x;
+ int16_t y;
+};
+
+struct qxl_rect {
+ int32_t top;
+ int32_t left;
+ int32_t bottom;
+ int32_t right;
+};
+
+struct qxl_urect {
+ uint32_t top;
+ uint32_t left;
+ uint32_t bottom;
+ uint32_t right;
+};
+
+/* qxl-1 compat: append only */
+struct qxl_rom {
+ uint32_t magic;
+ uint32_t id;
+ uint32_t update_id;
+ uint32_t compression_level;
+ uint32_t log_level;
+ uint32_t mode; /* qxl-1 */
+ uint32_t modes_offset;
+ uint32_t num_io_pages;
+ uint32_t pages_offset; /* qxl-1 */
+ uint32_t draw_area_offset; /* qxl-1 */
+ uint32_t surface0_area_size; /* qxl-1 name: draw_area_size */
+ uint32_t ram_header_offset;
+ uint32_t mm_clock;
+ /* appended for qxl-2 */
+ uint32_t n_surfaces;
+ uint64_t flags;
+ uint8_t slots_start;
+ uint8_t slots_end;
+ uint8_t slot_gen_bits;
+ uint8_t slot_id_bits;
+ uint8_t slot_generation;
+ /* appended for qxl-4 */
+ uint8_t client_present;
+ uint8_t client_capabilities[58];
+ uint32_t client_monitors_config_crc;
+ struct {
+ uint16_t count;
+ uint16_t padding;
+ struct qxl_urect heads[64];
+ } client_monitors_config;
+};
+
+/* qxl-1 compat: fixed */
+struct qxl_mode {
+ uint32_t id;
+ uint32_t x_res;
+ uint32_t y_res;
+ uint32_t bits;
+ uint32_t stride;
+ uint32_t x_mili;
+ uint32_t y_mili;
+ uint32_t orientation;
+};
+
+/* qxl-1 compat: fixed */
+struct qxl_modes {
+ uint32_t n_modes;
+ struct qxl_mode modes[0];
+};
+
+/* qxl-1 compat: append only */
+enum qxl_cmd_type {
+ QXL_CMD_NOP,
+ QXL_CMD_DRAW,
+ QXL_CMD_UPDATE,
+ QXL_CMD_CURSOR,
+ QXL_CMD_MESSAGE,
+ QXL_CMD_SURFACE,
+};
+
+/* qxl-1 compat: fixed */
+struct qxl_command {
+ QXLPHYSICAL data;
+ uint32_t type;
+ uint32_t padding;
+};
+
+#define QXL_COMMAND_FLAG_COMPAT (1<<0)
+#define QXL_COMMAND_FLAG_COMPAT_16BPP (2<<0)
+
+struct qxl_command_ext {
+ struct qxl_command cmd;
+ uint32_t group_id;
+ uint32_t flags;
+};
+
+struct qxl_mem_slot {
+ uint64_t mem_start;
+ uint64_t mem_end;
+};
+
+#define QXL_SURF_TYPE_PRIMARY 0
+
+#define QXL_SURF_FLAG_KEEP_DATA (1 << 0)
+
+struct qxl_surface_create {
+ uint32_t width;
+ uint32_t height;
+ int32_t stride;
+ uint32_t format;
+ uint32_t position;
+ uint32_t mouse_mode;
+ uint32_t flags;
+ uint32_t type;
+ QXLPHYSICAL mem;
+};
+
+#define QXL_COMMAND_RING_SIZE 32
+#define QXL_CURSOR_RING_SIZE 32
+#define QXL_RELEASE_RING_SIZE 8
+
+#define QXL_LOG_BUF_SIZE 4096
+
+#define QXL_INTERRUPT_DISPLAY (1 << 0)
+#define QXL_INTERRUPT_CURSOR (1 << 1)
+#define QXL_INTERRUPT_IO_CMD (1 << 2)
+#define QXL_INTERRUPT_ERROR (1 << 3)
+#define QXL_INTERRUPT_CLIENT (1 << 4)
+#define QXL_INTERRUPT_CLIENT_MONITORS_CONFIG (1 << 5)
+
+struct qxl_ring_header {
+ uint32_t num_items;
+ uint32_t prod;
+ uint32_t notify_on_prod;
+ uint32_t cons;
+ uint32_t notify_on_cons;
+};
+
+/* qxl-1 compat: append only */
+struct qxl_ram_header {
+ uint32_t magic;
+ uint32_t int_pending;
+ uint32_t int_mask;
+ uint8_t log_buf[QXL_LOG_BUF_SIZE];
+ struct qxl_ring_header cmd_ring_hdr;
+ struct qxl_command cmd_ring[QXL_COMMAND_RING_SIZE];
+ struct qxl_ring_header cursor_ring_hdr;
+ struct qxl_command cursor_ring[QXL_CURSOR_RING_SIZE];
+ struct qxl_ring_header release_ring_hdr;
+ uint64_t release_ring[QXL_RELEASE_RING_SIZE];
+ struct qxl_rect update_area;
+ /* appended for qxl-2 */
+ uint32_t update_surface;
+ struct qxl_mem_slot mem_slot;
+ struct qxl_surface_create create_surface;
+ uint64_t flags;
+
+ /* appended for qxl-4 */
+
+ /* used by QXL_IO_MONITORS_CONFIG_ASYNC */
+ QXLPHYSICAL monitors_config;
+ uint8_t guest_capabilities[64];
+};
+
+union qxl_release_info {
+ uint64_t id; /* in */
+ uint64_t next; /* out */
+};
+
+struct qxl_release_info_ext {
+ union qxl_release_info *info;
+ uint32_t group_id;
+};
+
+struct qxl_data_chunk {
+ uint32_t data_size;
+ QXLPHYSICAL prev_chunk;
+ QXLPHYSICAL next_chunk;
+ uint8_t data[0];
+};
+
+struct qxl_message {
+ union qxl_release_info release_info;
+ uint8_t data[0];
+};
+
+struct qxl_compat_update_cmd {
+ union qxl_release_info release_info;
+ struct qxl_rect area;
+ uint32_t update_id;
+};
+
+struct qxl_update_cmd {
+ union qxl_release_info release_info;
+ struct qxl_rect area;
+ uint32_t update_id;
+ uint32_t surface_id;
+};
+
+struct qxl_cursor_header {
+ uint64_t unique;
+ uint16_t type;
+ uint16_t width;
+ uint16_t height;
+ uint16_t hot_spot_x;
+ uint16_t hot_spot_y;
+};
+
+struct qxl_cursor {
+ struct qxl_cursor_header header;
+ uint32_t data_size;
+ struct qxl_data_chunk chunk;
+};
+
+enum {
+ QXL_CURSOR_SET,
+ QXL_CURSOR_MOVE,
+ QXL_CURSOR_HIDE,
+ QXL_CURSOR_TRAIL,
+};
+
+#define QXL_CURSUR_DEVICE_DATA_SIZE 128
+
+struct qxl_cursor_cmd {
+ union qxl_release_info release_info;
+ uint8_t type;
+ union {
+ struct {
+ struct qxl_point_1_6 position;
+ uint8_t visible;
+ QXLPHYSICAL shape;
+ } set;
+ struct {
+ uint16_t length;
+ uint16_t frequency;
+ } trail;
+ struct qxl_point_1_6 position;
+ } u;
+ /* todo: dynamic size from rom */
+ uint8_t device_data[QXL_CURSUR_DEVICE_DATA_SIZE];
+};
+
+enum {
+ QXL_DRAW_NOP,
+ QXL_DRAW_FILL,
+ QXL_DRAW_OPAQUE,
+ QXL_DRAW_COPY,
+ QXL_COPY_BITS,
+ QXL_DRAW_BLEND,
+ QXL_DRAW_BLACKNESS,
+ QXL_DRAW_WHITENESS,
+ QXL_DRAW_INVERS,
+ QXL_DRAW_ROP3,
+ QXL_DRAW_STROKE,
+ QXL_DRAW_TEXT,
+ QXL_DRAW_TRANSPARENT,
+ QXL_DRAW_ALPHA_BLEND,
+ QXL_DRAW_COMPOSITE
+};
+
+struct qxl_raster_glyph {
+ struct qxl_point render_pos;
+ struct qxl_point glyph_origin;
+ uint16_t width;
+ uint16_t height;
+ uint8_t data[0];
+};
+
+struct qxl_string {
+ uint32_t data_size;
+ uint16_t length;
+ uint16_t flags;
+ struct qxl_data_chunk chunk;
+};
+
+struct qxl_copy_bits {
+ struct qxl_point src_pos;
+};
+
+enum qxl_effect_type {
+ QXL_EFFECT_BLEND = 0,
+ QXL_EFFECT_OPAQUE = 1,
+ QXL_EFFECT_REVERT_ON_DUP = 2,
+ QXL_EFFECT_BLACKNESS_ON_DUP = 3,
+ QXL_EFFECT_WHITENESS_ON_DUP = 4,
+ QXL_EFFECT_NOP_ON_DUP = 5,
+ QXL_EFFECT_NOP = 6,
+ QXL_EFFECT_OPAQUE_BRUSH = 7
+};
+
+struct qxl_pattern {
+ QXLPHYSICAL pat;
+ struct qxl_point pos;
+};
+
+struct qxl_brush {
+ uint32_t type;
+ union {
+ uint32_t color;
+ struct qxl_pattern pattern;
+ } u;
+};
+
+struct qxl_q_mask {
+ uint8_t flags;
+ struct qxl_point pos;
+ QXLPHYSICAL bitmap;
+};
+
+struct qxl_fill {
+ struct qxl_brush brush;
+ uint16_t rop_descriptor;
+ struct qxl_q_mask mask;
+};
+
+struct qxl_opaque {
+ QXLPHYSICAL src_bitmap;
+ struct qxl_rect src_area;
+ struct qxl_brush brush;
+ uint16_t rop_descriptor;
+ uint8_t scale_mode;
+ struct qxl_q_mask mask;
+};
+
+struct qxl_copy {
+ QXLPHYSICAL src_bitmap;
+ struct qxl_rect src_area;
+ uint16_t rop_descriptor;
+ uint8_t scale_mode;
+ struct qxl_q_mask mask;
+};
+
+struct qxl_transparent {
+ QXLPHYSICAL src_bitmap;
+ struct qxl_rect src_area;
+ uint32_t src_color;
+ uint32_t true_color;
+};
+
+struct qxl_alpha_blend {
+ uint16_t alpha_flags;
+ uint8_t alpha;
+ QXLPHYSICAL src_bitmap;
+ struct qxl_rect src_area;
+};
+
+struct qxl_compat_alpha_blend {
+ uint8_t alpha;
+ QXLPHYSICAL src_bitmap;
+ struct qxl_rect src_area;
+};
+
+struct qxl_rop_3 {
+ QXLPHYSICAL src_bitmap;
+ struct qxl_rect src_area;
+ struct qxl_brush brush;
+ uint8_t rop3;
+ uint8_t scale_mode;
+ struct qxl_q_mask mask;
+};
+
+struct qxl_line_attr {
+ uint8_t flags;
+ uint8_t join_style;
+ uint8_t end_style;
+ uint8_t style_nseg;
+ QXLFIXED width;
+ QXLFIXED miter_limit;
+ QXLPHYSICAL style;
+};
+
+struct qxl_stroke {
+ QXLPHYSICAL path;
+ struct qxl_line_attr attr;
+ struct qxl_brush brush;
+ uint16_t fore_mode;
+ uint16_t back_mode;
+};
+
+struct qxl_text {
+ QXLPHYSICAL str;
+ struct qxl_rect back_area;
+ struct qxl_brush fore_brush;
+ struct qxl_brush back_brush;
+ uint16_t fore_mode;
+ uint16_t back_mode;
+};
+
+struct qxl_mask {
+ struct qxl_q_mask mask;
+};
+
+struct qxl_clip {
+ uint32_t type;
+ QXLPHYSICAL data;
+};
+
+enum qxl_operator {
+ QXL_OP_CLEAR = 0x00,
+ QXL_OP_SOURCE = 0x01,
+ QXL_OP_DST = 0x02,
+ QXL_OP_OVER = 0x03,
+ QXL_OP_OVER_REVERSE = 0x04,
+ QXL_OP_IN = 0x05,
+ QXL_OP_IN_REVERSE = 0x06,
+ QXL_OP_OUT = 0x07,
+ QXL_OP_OUT_REVERSE = 0x08,
+ QXL_OP_ATOP = 0x09,
+ QXL_OP_ATOP_REVERSE = 0x0a,
+ QXL_OP_XOR = 0x0b,
+ QXL_OP_ADD = 0x0c,
+ QXL_OP_SATURATE = 0x0d,
+ /* Note the jump here from 0x0d to 0x30 */
+ QXL_OP_MULTIPLY = 0x30,
+ QXL_OP_SCREEN = 0x31,
+ QXL_OP_OVERLAY = 0x32,
+ QXL_OP_DARKEN = 0x33,
+ QXL_OP_LIGHTEN = 0x34,
+ QXL_OP_COLOR_DODGE = 0x35,
+ QXL_OP_COLOR_BURN = 0x36,
+ QXL_OP_HARD_LIGHT = 0x37,
+ QXL_OP_SOFT_LIGHT = 0x38,
+ QXL_OP_DIFFERENCE = 0x39,
+ QXL_OP_EXCLUSION = 0x3a,
+ QXL_OP_HSL_HUE = 0x3b,
+ QXL_OP_HSL_SATURATION = 0x3c,
+ QXL_OP_HSL_COLOR = 0x3d,
+ QXL_OP_HSL_LUMINOSITY = 0x3e
+};
+
+struct qxl_transform {
+ uint32_t t00;
+ uint32_t t01;
+ uint32_t t02;
+ uint32_t t10;
+ uint32_t t11;
+ uint32_t t12;
+};
+
+/* The flags field has the following bit fields:
+ *
+ * operator: [ 0 - 7 ]
+ * src_filter: [ 8 - 10 ]
+ * mask_filter: [ 11 - 13 ]
+ * src_repeat: [ 14 - 15 ]
+ * mask_repeat: [ 16 - 17 ]
+ * component_alpha: [ 18 - 18 ]
+ * reserved: [ 19 - 31 ]
+ *
+ * The repeat and filter values are those of pixman:
+ * REPEAT_NONE = 0
+ * REPEAT_NORMAL = 1
+ * REPEAT_PAD = 2
+ * REPEAT_REFLECT = 3
+ *
+ * The filter values are:
+ * FILTER_NEAREST = 0
+ * FILTER_BILINEAR = 1
+ */
+struct qxl_composite {
+ uint32_t flags;
+
+ QXLPHYSICAL src;
+ QXLPHYSICAL src_transform; /* May be NULL */
+ QXLPHYSICAL mask; /* May be NULL */
+ QXLPHYSICAL mask_transform; /* May be NULL */
+ struct qxl_point_1_6 src_origin;
+ struct qxl_point_1_6 mask_origin;
+};
+
+struct qxl_compat_drawable {
+ union qxl_release_info release_info;
+ uint8_t effect;
+ uint8_t type;
+ uint16_t bitmap_offset;
+ struct qxl_rect bitmap_area;
+ struct qxl_rect bbox;
+ struct qxl_clip clip;
+ uint32_t mm_time;
+ union {
+ struct qxl_fill fill;
+ struct qxl_opaque opaque;
+ struct qxl_copy copy;
+ struct qxl_transparent transparent;
+ struct qxl_compat_alpha_blend alpha_blend;
+ struct qxl_copy_bits copy_bits;
+ struct qxl_copy blend;
+ struct qxl_rop_3 rop3;
+ struct qxl_stroke stroke;
+ struct qxl_text text;
+ struct qxl_mask blackness;
+ struct qxl_mask invers;
+ struct qxl_mask whiteness;
+ } u;
+};
+
+struct qxl_drawable {
+ union qxl_release_info release_info;
+ uint32_t surface_id;
+ uint8_t effect;
+ uint8_t type;
+ uint8_t self_bitmap;
+ struct qxl_rect self_bitmap_area;
+ struct qxl_rect bbox;
+ struct qxl_clip clip;
+ uint32_t mm_time;
+ int32_t surfaces_dest[3];
+ struct qxl_rect surfaces_rects[3];
+ union {
+ struct qxl_fill fill;
+ struct qxl_opaque opaque;
+ struct qxl_copy copy;
+ struct qxl_transparent transparent;
+ struct qxl_alpha_blend alpha_blend;
+ struct qxl_copy_bits copy_bits;
+ struct qxl_copy blend;
+ struct qxl_rop_3 rop3;
+ struct qxl_stroke stroke;
+ struct qxl_text text;
+ struct qxl_mask blackness;
+ struct qxl_mask invers;
+ struct qxl_mask whiteness;
+ struct qxl_composite composite;
+ } u;
+};
+
+enum qxl_surface_cmd_type {
+ QXL_SURFACE_CMD_CREATE,
+ QXL_SURFACE_CMD_DESTROY,
+};
+
+struct qxl_surface {
+ uint32_t format;
+ uint32_t width;
+ uint32_t height;
+ int32_t stride;
+ QXLPHYSICAL data;
+};
+
+struct qxl_surface_cmd {
+ union qxl_release_info release_info;
+ uint32_t surface_id;
+ uint8_t type;
+ uint32_t flags;
+ union {
+ struct qxl_surface surface_create;
+ } u;
+};
+
+struct qxl_clip_rects {
+ uint32_t num_rects;
+ struct qxl_data_chunk chunk;
+};
+
+enum {
+ QXL_PATH_BEGIN = (1 << 0),
+ QXL_PATH_END = (1 << 1),
+ QXL_PATH_CLOSE = (1 << 3),
+ QXL_PATH_BEZIER = (1 << 4),
+};
+
+struct qxl_path_seg {
+ uint32_t flags;
+ uint32_t count;
+ struct qxl_point_fix points[0];
+};
+
+struct qxl_path {
+ uint32_t data_size;
+ struct qxl_data_chunk chunk;
+};
+
+enum {
+ QXL_IMAGE_GROUP_DRIVER,
+ QXL_IMAGE_GROUP_DEVICE,
+ QXL_IMAGE_GROUP_RED,
+ QXL_IMAGE_GROUP_DRIVER_DONT_CACHE,
+};
+
+struct qxl_image_id {
+ uint32_t group;
+ uint32_t unique;
+};
+
+union qxl_image_id_union {
+ struct qxl_image_id id;
+ uint64_t value;
+};
+
+enum qxl_image_flags {
+ QXL_IMAGE_CACHE = (1 << 0),
+ QXL_IMAGE_HIGH_BITS_SET = (1 << 1),
+};
+
+enum qxl_bitmap_flags {
+ QXL_BITMAP_DIRECT = (1 << 0),
+ QXL_BITMAP_UNSTABLE = (1 << 1),
+ QXL_BITMAP_TOP_DOWN = (1 << 2), /* == SPICE_BITMAP_FLAGS_TOP_DOWN */
+};
+
+#define QXL_SET_IMAGE_ID(image, _group, _unique) { \
+ (image)->descriptor.id = (((uint64_t)_unique) << 32) | _group; \
+}
+
+struct qxl_image_descriptor {
+ uint64_t id;
+ uint8_t type;
+ uint8_t flags;
+ uint32_t width;
+ uint32_t height;
+};
+
+struct qxl_palette {
+ uint64_t unique;
+ uint16_t num_ents;
+ uint32_t ents[0];
+};
+
+struct qxl_bitmap {
+ uint8_t format;
+ uint8_t flags;
+ uint32_t x;
+ uint32_t y;
+ uint32_t stride;
+ QXLPHYSICAL palette;
+ QXLPHYSICAL data; /* data[0] ? */
+};
+
+struct qxl_surface_id {
+ uint32_t surface_id;
+};
+
+struct qxl_encoder_data {
+ uint32_t data_size;
+ uint8_t data[0];
+};
+
+struct qxl_image {
+ struct qxl_image_descriptor descriptor;
+ union { /* variable length */
+ struct qxl_bitmap bitmap;
+ struct qxl_encoder_data quic;
+ struct qxl_surface_id surface_image;
+ } u;
+};
+
+/* A QXLHead is a single monitor output backed by a QXLSurface.
+ * x and y offsets are unsigned since they are used in relation to
+ * the given surface, not the same as the x, y coordinates in the guest
+ * screen reference frame. */
+struct qxl_head {
+ uint32_t id;
+ uint32_t surface_id;
+ uint32_t width;
+ uint32_t height;
+ uint32_t x;
+ uint32_t y;
+ uint32_t flags;
+};
+
+struct qxl_monitors_config {
+ uint16_t count;
+ uint16_t max_allowed; /* If it is 0 no fixed limit is given by the
+ driver */
+ struct qxl_head heads[0];
+};
+
+#pragma pack(pop)
+
+#endif /* _H_QXL_DEV */
diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c
new file mode 100644
index 00000000000..5265c8f55da
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_display.c
@@ -0,0 +1,780 @@
+#include "linux/crc32.h"
+
+#include "qxl_drv.h"
+#include "drm_crtc_helper.h"
+
+static void qxl_crtc_set_to_mode(struct qxl_device *qdev,
+ struct drm_connector *connector,
+ struct qxl_head *head)
+{
+ struct drm_device *dev = connector->dev;
+ struct drm_display_mode *mode, *t;
+ int width = head->width;
+ int height = head->height;
+
+ DRM_ERROR("TODO: check against framebuffer size\n");
+ if (width < 320 || height < 240) {
+ qxl_io_log(qdev, "%s: bad head: %dx%d", width, height);
+ width = 1024;
+ height = 768;
+ }
+ if (width * height * 4 > 16*1024*1024) {
+ width = 1024;
+ height = 768;
+ }
+ /* TODO: go over regular modes and removed preferred? */
+ list_for_each_entry_safe(mode, t, &connector->probed_modes, head)
+ drm_mode_remove(connector, mode);
+ mode = drm_cvt_mode(dev, width, height, 60, false, false, false);
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ mode->status = MODE_OK;
+ drm_mode_probed_add(connector, mode);
+ qxl_io_log(qdev, "%s: %d x %d\n", __func__, width, height);
+}
+
+void qxl_crtc_set_from_monitors_config(struct qxl_device *qdev)
+{
+ struct drm_connector *connector;
+ int i;
+ struct drm_device *dev = qdev->ddev;
+
+ i = 0;
+ qxl_io_log(qdev, "%s: %d, %d\n", __func__,
+ dev->mode_config.num_connector,
+ qdev->monitors_config->count);
+ list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+ if (i > qdev->monitors_config->count) {
+ /* crtc will be reported as disabled */
+ continue;
+ }
+ qxl_crtc_set_to_mode(qdev, connector,
+ &qdev->monitors_config->heads[i]);
+ ++i;
+ }
+}
+
+void qxl_alloc_client_monitors_config(struct qxl_device *qdev, unsigned count)
+{
+ if (qdev->client_monitors_config &&
+ count > qdev->client_monitors_config->count) {
+ kfree(qdev->client_monitors_config);
+ }
+ if (!qdev->client_monitors_config) {
+ qdev->client_monitors_config = kzalloc(
+ sizeof(struct qxl_monitors_config) +
+ sizeof(struct qxl_head) * count, GFP_KERNEL);
+ if (!qdev->client_monitors_config) {
+ qxl_io_log(qdev,
+ "%s: allocation failure for %u heads\n",
+ __func__, count);
+ return;
+ }
+ }
+ qdev->client_monitors_config->count = count;
+}
+
+static int qxl_display_copy_rom_client_monitors_config(struct qxl_device *qdev)
+{
+ int i;
+ int num_monitors;
+ uint32_t crc;
+
+ BUG_ON(!qdev->monitors_config);
+ num_monitors = qdev->rom->client_monitors_config.count;
+ crc = crc32(0, (const uint8_t *)&qdev->rom->client_monitors_config,
+ sizeof(qdev->rom->client_monitors_config));
+ if (crc != qdev->rom->client_monitors_config_crc) {
+ qxl_io_log(qdev, "crc mismatch: have %X (%d) != %X\n", crc,
+ sizeof(qdev->rom->client_monitors_config),
+ qdev->rom->client_monitors_config_crc);
+ return 1;
+ }
+ if (num_monitors > qdev->monitors_config->max_allowed) {
+ DRM_INFO("client monitors list will be truncated: %d < %d\n",
+ qdev->monitors_config->max_allowed, num_monitors);
+ num_monitors = qdev->monitors_config->max_allowed;
+ } else {
+ num_monitors = qdev->rom->client_monitors_config.count;
+ }
+ qxl_alloc_client_monitors_config(qdev, num_monitors);
+ /* we copy max from the client but it isn't used */
+ qdev->client_monitors_config->max_allowed =
+ qdev->monitors_config->max_allowed;
+ for (i = 0 ; i < qdev->client_monitors_config->count ; ++i) {
+ struct qxl_urect *c_rect =
+ &qdev->rom->client_monitors_config.heads[i];
+ struct qxl_head *client_head =
+ &qdev->client_monitors_config->heads[i];
+ struct qxl_head *head = &qdev->monitors_config->heads[i];
+ client_head->x = head->x = c_rect->left;
+ client_head->y = head->y = c_rect->top;
+ client_head->width = head->width =
+ c_rect->right - c_rect->left;
+ client_head->height = head->height =
+ c_rect->bottom - c_rect->top;
+ client_head->surface_id = head->surface_id = 0;
+ client_head->id = head->id = i;
+ client_head->flags = head->flags = 0;
+ QXL_DEBUG(qdev, "read %dx%d+%d+%d\n", head->width, head->height,
+ head->x, head->y);
+ }
+ return 0;
+}
+
+void qxl_display_read_client_monitors_config(struct qxl_device *qdev)
+{
+
+ while (qxl_display_copy_rom_client_monitors_config(qdev)) {
+ qxl_io_log(qdev, "failed crc check for client_monitors_config,"
+ " retrying\n");
+ }
+ qxl_crtc_set_from_monitors_config(qdev);
+ /* fire off a uevent and let userspace tell us what to do */
+ qxl_io_log(qdev, "calling drm_sysfs_hotplug_event\n");
+ drm_sysfs_hotplug_event(qdev->ddev);
+}
+
+static int qxl_add_monitors_config_modes(struct drm_connector *connector)
+{
+ struct drm_device *dev = connector->dev;
+ struct qxl_device *qdev = dev->dev_private;
+ struct qxl_output *output = drm_connector_to_qxl_output(connector);
+ int h = output->index;
+ struct drm_display_mode *mode = NULL;
+ struct qxl_head *head;
+
+ if (!qdev->monitors_config)
+ return 0;
+ head = &qdev->monitors_config->heads[h];
+
+ mode = drm_cvt_mode(dev, head->width, head->height, 60, false, false,
+ false);
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ return 1;
+}
+
+static int qxl_add_common_modes(struct drm_connector *connector)
+{
+ struct drm_device *dev = connector->dev;
+ struct drm_display_mode *mode = NULL;
+ int i;
+ struct mode_size {
+ int w;
+ int h;
+ } common_modes[] = {
+ { 640, 480},
+ { 720, 480},
+ { 800, 600},
+ { 848, 480},
+ {1024, 768},
+ {1152, 768},
+ {1280, 720},
+ {1280, 800},
+ {1280, 854},
+ {1280, 960},
+ {1280, 1024},
+ {1440, 900},
+ {1400, 1050},
+ {1680, 1050},
+ {1600, 1200},
+ {1920, 1080},
+ {1920, 1200}
+ };
+
+ for (i = 0; i < ARRAY_SIZE(common_modes); i++) {
+ if (common_modes[i].w < 320 || common_modes[i].h < 200)
+ continue;
+
+ mode = drm_cvt_mode(dev, common_modes[i].w, common_modes[i].h,
+ 60, false, false, false);
+ if (common_modes[i].w == 1024 && common_modes[i].h == 768)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ }
+ return i - 1;
+}
+
+static void qxl_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green,
+ u16 *blue, uint32_t start, uint32_t size)
+{
+ /* TODO */
+}
+
+static void qxl_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct qxl_crtc *qxl_crtc = to_qxl_crtc(crtc);
+
+ drm_crtc_cleanup(crtc);
+ kfree(qxl_crtc);
+}
+
+static const struct drm_crtc_funcs qxl_crtc_funcs = {
+ /* TODO
+ .cursor_set = qxl_crtc_cursor_set,
+ .cursor_move = qxl_crtc_cursor_move,
+ */
+ .gamma_set = qxl_crtc_gamma_set,
+ .set_config = drm_crtc_helper_set_config,
+ .destroy = qxl_crtc_destroy,
+};
+
+static void qxl_user_framebuffer_destroy(struct drm_framebuffer *fb)
+{
+ struct qxl_framebuffer *qxl_fb = to_qxl_framebuffer(fb);
+
+ if (qxl_fb->obj)
+ drm_gem_object_unreference_unlocked(qxl_fb->obj);
+ drm_framebuffer_cleanup(fb);
+ kfree(qxl_fb);
+}
+
+int qxl_framebuffer_surface_dirty(struct drm_framebuffer *fb,
+ struct drm_file *file_priv,
+ unsigned flags, unsigned color,
+ struct drm_clip_rect *clips,
+ unsigned num_clips)
+{
+ /* TODO: vmwgfx where this was cribbed from had locking. Why? */
+ struct qxl_framebuffer *qxl_fb = to_qxl_framebuffer(fb);
+ struct qxl_device *qdev = qxl_fb->base.dev->dev_private;
+ struct drm_clip_rect norect;
+ struct qxl_bo *qobj;
+ int inc = 1;
+
+ qobj = gem_to_qxl_bo(qxl_fb->obj);
+ if (qxl_fb != qdev->active_user_framebuffer) {
+ DRM_INFO("%s: qxl_fb 0x%p != qdev->active_user_framebuffer 0x%p\n",
+ __func__, qxl_fb, qdev->active_user_framebuffer);
+ }
+ if (!num_clips) {
+ num_clips = 1;
+ clips = &norect;
+ norect.x1 = norect.y1 = 0;
+ norect.x2 = fb->width;
+ norect.y2 = fb->height;
+ } else if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY) {
+ num_clips /= 2;
+ inc = 2; /* skip source rects */
+ }
+
+ qxl_draw_dirty_fb(qdev, qxl_fb, qobj, flags, color,
+ clips, num_clips, inc);
+ return 0;
+}
+
+static const struct drm_framebuffer_funcs qxl_fb_funcs = {
+ .destroy = qxl_user_framebuffer_destroy,
+ .dirty = qxl_framebuffer_surface_dirty,
+/* TODO?
+ * .create_handle = qxl_user_framebuffer_create_handle, */
+};
+
+void
+qxl_framebuffer_init(struct drm_device *dev,
+ struct qxl_framebuffer *qfb,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj)
+{
+ qfb->obj = obj;
+ drm_framebuffer_init(dev, &qfb->base, &qxl_fb_funcs);
+ drm_helper_mode_fill_fb_struct(&qfb->base, mode_cmd);
+}
+
+static void qxl_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+}
+
+static bool qxl_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct drm_device *dev = crtc->dev;
+ struct qxl_device *qdev = dev->dev_private;
+
+ qxl_io_log(qdev, "%s: (%d,%d) => (%d,%d)\n",
+ __func__,
+ mode->hdisplay, mode->vdisplay,
+ adjusted_mode->hdisplay,
+ adjusted_mode->vdisplay);
+ return true;
+}
+
+void
+qxl_send_monitors_config(struct qxl_device *qdev)
+{
+ int i;
+
+ BUG_ON(!qdev->ram_header->monitors_config);
+
+ if (qdev->monitors_config->count == 0) {
+ qxl_io_log(qdev, "%s: 0 monitors??\n", __func__);
+ return;
+ }
+ for (i = 0 ; i < qdev->monitors_config->count ; ++i) {
+ struct qxl_head *head = &qdev->monitors_config->heads[i];
+
+ if (head->y > 8192 || head->y < head->x ||
+ head->width > 8192 || head->height > 8192) {
+ DRM_ERROR("head %d wrong: %dx%d+%d+%d\n",
+ i, head->width, head->height,
+ head->x, head->y);
+ return;
+ }
+ }
+ qxl_io_monitors_config(qdev);
+}
+
+static void qxl_monitors_config_set_single(struct qxl_device *qdev,
+ unsigned x, unsigned y,
+ unsigned width, unsigned height)
+{
+ DRM_DEBUG("%dx%d+%d+%d\n", width, height, x, y);
+ qdev->monitors_config->count = 1;
+ qdev->monitors_config->heads[0].x = x;
+ qdev->monitors_config->heads[0].y = y;
+ qdev->monitors_config->heads[0].width = width;
+ qdev->monitors_config->heads[0].height = height;
+}
+
+static int qxl_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct drm_device *dev = crtc->dev;
+ struct qxl_device *qdev = dev->dev_private;
+ struct qxl_mode *m = (void *)mode->private;
+
+ if (!m)
+ /* and do we care? */
+ DRM_DEBUG("%dx%d: not a native mode\n", x, y);
+ else
+ DRM_DEBUG("%dx%d: qxl id %d\n",
+ mode->hdisplay, mode->vdisplay, m->id);
+ DRM_DEBUG("+%d+%d (%d,%d) => (%d,%d)\n",
+ x, y,
+ mode->hdisplay, mode->vdisplay,
+ adjusted_mode->hdisplay,
+ adjusted_mode->vdisplay);
+
+ if (!qdev->primary_created) {
+ qxl_io_log(qdev, "create primary: %dx%d\n",
+ mode->hdisplay + x, mode->vdisplay + y);
+ qxl_io_create_primary(qdev, mode->hdisplay + x,
+ mode->vdisplay + y);
+ } else {
+ unsigned width = x + mode->hdisplay;
+ unsigned height = y + mode->vdisplay;
+ if (width > qdev->primary_width ||
+ height > qdev->primary_height) {
+ width = max(width, qdev->primary_width);
+ height = max(height, qdev->primary_height);
+ qxl_io_destroy_primary(qdev);
+ qxl_io_log(qdev,
+ "recreate primary: %dx%d (was %dx%d)\n",
+ width, height, qdev->primary_width,
+ qdev->primary_height);
+ qxl_io_create_primary(qdev, width, height);
+ }
+ }
+ if (qdev->monitors_config->count == 0) {
+ qxl_monitors_config_set_single(qdev, x, y,
+ mode->hdisplay,
+ mode->vdisplay);
+ }
+ qdev->mode_set = true;
+ return 0;
+}
+
+static int
+qxl_crtc_set_base(struct drm_crtc *crtc, int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ DRM_DEBUG("\n");
+ return 0;
+}
+
+static void qxl_crtc_prepare(struct drm_crtc *crtc)
+{
+ DRM_DEBUG("current: %dx%d+%d+%d (%d).\n",
+ crtc->mode.hdisplay, crtc->mode.vdisplay,
+ crtc->x, crtc->y, crtc->enabled);
+}
+
+static void qxl_crtc_commit(struct drm_crtc *crtc)
+{
+ DRM_DEBUG("\n");
+}
+
+void qxl_crtc_load_lut(struct drm_crtc *crtc)
+{
+ DRM_DEBUG("\n");
+}
+
+static const struct drm_crtc_helper_funcs qxl_crtc_helper_funcs = {
+ .dpms = qxl_crtc_dpms,
+ .mode_fixup = qxl_crtc_mode_fixup,
+ .mode_set = qxl_crtc_mode_set,
+ .mode_set_base = qxl_crtc_set_base,
+ .prepare = qxl_crtc_prepare,
+ .commit = qxl_crtc_commit,
+ .load_lut = qxl_crtc_load_lut,
+};
+
+int qdev_crtc_init(struct drm_device *dev, int num_crtc)
+{
+ struct qxl_crtc *qxl_crtc;
+
+ qxl_crtc = kzalloc(sizeof(struct qxl_crtc), GFP_KERNEL);
+ if (!qxl_crtc)
+ return -ENOMEM;
+
+ drm_crtc_init(dev, &qxl_crtc->base, &qxl_crtc_funcs);
+
+ drm_mode_crtc_set_gamma_size(&qxl_crtc->base, 256);
+ drm_crtc_helper_add(&qxl_crtc->base, &qxl_crtc_helper_funcs);
+ return 0;
+}
+
+static void qxl_enc_dpms(struct drm_encoder *encoder, int mode)
+{
+ DRM_DEBUG("\n");
+}
+
+static bool qxl_enc_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ DRM_DEBUG("\n");
+ return true;
+}
+
+static void qxl_enc_prepare(struct drm_encoder *encoder)
+{
+ DRM_DEBUG("\n");
+}
+
+static void qxl_write_monitors_config_for_encoder(struct qxl_device *qdev,
+ struct drm_encoder *encoder)
+{
+ int i;
+ struct qxl_head *head;
+ struct drm_display_mode *mode;
+
+ BUG_ON(!encoder);
+ /* TODO: ugly, do better */
+ for (i = 0 ; (encoder->possible_crtcs != (1 << i)) && i < 32; ++i)
+ ;
+ if (encoder->possible_crtcs != (1 << i)) {
+ DRM_ERROR("encoder has wrong possible_crtcs: %x\n",
+ encoder->possible_crtcs);
+ return;
+ }
+ if (!qdev->monitors_config ||
+ qdev->monitors_config->max_allowed <= i) {
+ DRM_ERROR(
+ "head number too large or missing monitors config: %p, %d",
+ qdev->monitors_config,
+ qdev->monitors_config ?
+ qdev->monitors_config->max_allowed : -1);
+ return;
+ }
+ if (!encoder->crtc) {
+ DRM_ERROR("missing crtc on encoder %p\n", encoder);
+ return;
+ }
+ if (i != 0)
+ DRM_DEBUG("missing for multiple monitors: no head holes\n");
+ head = &qdev->monitors_config->heads[i];
+ head->id = i;
+ head->surface_id = 0;
+ if (encoder->crtc->enabled) {
+ mode = &encoder->crtc->mode;
+ head->width = mode->hdisplay;
+ head->height = mode->vdisplay;
+ head->x = encoder->crtc->x;
+ head->y = encoder->crtc->y;
+ if (qdev->monitors_config->count < i + 1)
+ qdev->monitors_config->count = i + 1;
+ } else {
+ head->width = 0;
+ head->height = 0;
+ head->x = 0;
+ head->y = 0;
+ }
+ DRM_DEBUG("setting head %d to +%d+%d %dx%d\n",
+ i, head->x, head->y, head->width, head->height);
+ head->flags = 0;
+ /* TODO - somewhere else to call this for multiple monitors
+ * (config_commit?) */
+ qxl_send_monitors_config(qdev);
+}
+
+static void qxl_enc_commit(struct drm_encoder *encoder)
+{
+ struct qxl_device *qdev = encoder->dev->dev_private;
+
+ qxl_write_monitors_config_for_encoder(qdev, encoder);
+ DRM_DEBUG("\n");
+}
+
+static void qxl_enc_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ DRM_DEBUG("\n");
+}
+
+#if 0
+static struct qxl_mode *
+qxl_find_native_mode(struct qxl_device *qdev, struct drm_display_mode *mode,
+ int bpp)
+{
+ int i;
+
+ for (i = 0; i < qdev->mode_info.num_modes; i++) {
+ struct qxl_mode *m = qdev->mode_info.modes + i;
+ if (m->x_res == mode->hdisplay &&
+ m->y_res == mode->vdisplay &&
+ m->bits == bpp)
+ return m;
+ }
+ return NULL;
+}
+#endif
+
+static int qxl_conn_get_modes(struct drm_connector *connector)
+{
+ int ret;
+ struct qxl_device *qdev = connector->dev->dev_private;
+
+ DRM_INFO("monitors_config=%p\n", qdev->monitors_config);
+ /* TODO: what should we do here? only show the configured modes for the
+ * device, or allow the full list, or both? */
+ if (qdev->monitors_config && qdev->monitors_config->count)
+ ret = qxl_add_monitors_config_modes(connector);
+ else
+ ret = qxl_add_common_modes(connector);
+ return ret;
+}
+
+static int qxl_conn_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ /* TODO: is this called for user defined modes? (xrandr --add-mode)
+ * TODO: check that the mode fits in the framebuffer */
+ DRM_DEBUG("%s: %dx%d status=%d\n", mode->name, mode->hdisplay,
+ mode->vdisplay, mode->status);
+ return MODE_OK;
+}
+
+struct drm_encoder *qxl_best_encoder(struct drm_connector *connector)
+{
+ struct qxl_output *qxl_output =
+ drm_connector_to_qxl_output(connector);
+
+ DRM_DEBUG("\n");
+ return &qxl_output->enc;
+}
+
+
+static const struct drm_encoder_helper_funcs qxl_enc_helper_funcs = {
+ .dpms = qxl_enc_dpms,
+ .mode_fixup = qxl_enc_mode_fixup,
+ .prepare = qxl_enc_prepare,
+ .mode_set = qxl_enc_mode_set,
+ .commit = qxl_enc_commit,
+};
+
+static const struct drm_connector_helper_funcs qxl_connector_helper_funcs = {
+ .get_modes = qxl_conn_get_modes,
+ .mode_valid = qxl_conn_mode_valid,
+ .best_encoder = qxl_best_encoder,
+};
+
+static void qxl_conn_save(struct drm_connector *connector)
+{
+ DRM_DEBUG("\n");
+}
+
+static void qxl_conn_restore(struct drm_connector *connector)
+{
+ DRM_DEBUG("\n");
+}
+
+static enum drm_connector_status qxl_conn_detect(
+ struct drm_connector *connector,
+ bool force)
+{
+ struct qxl_output *output =
+ drm_connector_to_qxl_output(connector);
+ struct drm_device *ddev = connector->dev;
+ struct qxl_device *qdev = ddev->dev_private;
+ int connected;
+
+ /* The first monitor is always connected */
+ connected = (output->index == 0) ||
+ (qdev->monitors_config &&
+ qdev->monitors_config->count > output->index);
+
+ DRM_DEBUG("\n");
+ return connected ? connector_status_connected
+ : connector_status_disconnected;
+}
+
+static int qxl_conn_set_property(struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t value)
+{
+ DRM_DEBUG("\n");
+ return 0;
+}
+
+static void qxl_conn_destroy(struct drm_connector *connector)
+{
+ struct qxl_output *qxl_output =
+ drm_connector_to_qxl_output(connector);
+
+ drm_sysfs_connector_remove(connector);
+ drm_connector_cleanup(connector);
+ kfree(qxl_output);
+}
+
+static const struct drm_connector_funcs qxl_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .save = qxl_conn_save,
+ .restore = qxl_conn_restore,
+ .detect = qxl_conn_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .set_property = qxl_conn_set_property,
+ .destroy = qxl_conn_destroy,
+};
+
+static void qxl_enc_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs qxl_enc_funcs = {
+ .destroy = qxl_enc_destroy,
+};
+
+int qdev_output_init(struct drm_device *dev, int num_output)
+{
+ struct qxl_output *qxl_output;
+ struct drm_connector *connector;
+ struct drm_encoder *encoder;
+
+ qxl_output = kzalloc(sizeof(struct qxl_output), GFP_KERNEL);
+ if (!qxl_output)
+ return -ENOMEM;
+
+ qxl_output->index = num_output;
+
+ connector = &qxl_output->base;
+ encoder = &qxl_output->enc;
+ drm_connector_init(dev, &qxl_output->base,
+ &qxl_connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
+
+ drm_encoder_init(dev, &qxl_output->enc, &qxl_enc_funcs,
+ DRM_MODE_ENCODER_VIRTUAL);
+
+ encoder->possible_crtcs = 1 << num_output;
+ drm_mode_connector_attach_encoder(&qxl_output->base,
+ &qxl_output->enc);
+ drm_encoder_helper_add(encoder, &qxl_enc_helper_funcs);
+ drm_connector_helper_add(connector, &qxl_connector_helper_funcs);
+
+ drm_sysfs_connector_add(connector);
+ return 0;
+}
+
+static struct drm_framebuffer *
+qxl_user_framebuffer_create(struct drm_device *dev,
+ struct drm_file *file_priv,
+ struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct drm_gem_object *obj;
+ struct qxl_framebuffer *qxl_fb;
+ struct qxl_device *qdev = dev->dev_private;
+
+ obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]);
+
+ DRM_INFO("%s: ENTRY\n", __func__);
+
+ qxl_fb = kzalloc(sizeof(*qxl_fb), GFP_KERNEL);
+ if (qxl_fb == NULL)
+ return NULL;
+
+ qxl_framebuffer_init(dev, qxl_fb, mode_cmd, obj);
+ if (qdev->active_user_framebuffer) {
+ DRM_INFO("%s: active_user_framebuffer %p -> %p\n",
+ __func__,
+ qdev->active_user_framebuffer, qxl_fb);
+ }
+ qdev->active_user_framebuffer = qxl_fb;
+
+ return &qxl_fb->base;
+}
+
+static const struct drm_mode_config_funcs qxl_mode_funcs = {
+ .fb_create = qxl_user_framebuffer_create,
+};
+
+int qxl_modeset_init(struct qxl_device *qdev)
+{
+ int i;
+ int ret;
+ struct drm_gem_object *gobj;
+ int max_allowed = QXL_NUM_OUTPUTS;
+ int monitors_config_size = sizeof(struct qxl_monitors_config) +
+ max_allowed * sizeof(struct qxl_head);
+
+ drm_mode_config_init(qdev->ddev);
+ ret = qxl_gem_object_create(qdev, monitors_config_size, 0,
+ QXL_GEM_DOMAIN_VRAM,
+ false, false, &gobj);
+ if (ret) {
+ DRM_ERROR("%s: failed to create gem ret=%d\n", __func__, ret);
+ return -ENOMEM;
+ }
+ qdev->monitors_config_bo = gem_to_qxl_bo(gobj);
+ qxl_bo_kmap(qdev->monitors_config_bo, NULL);
+ qdev->monitors_config = qdev->monitors_config_bo->kptr;
+ qdev->ram_header->monitors_config =
+ qxl_bo_physical_address(qdev, qdev->monitors_config_bo, 0,
+ qdev->main_mem_slot);
+ memset(qdev->monitors_config, 0, monitors_config_size);
+ qdev->monitors_config->max_allowed = max_allowed;
+
+ qdev->ddev->mode_config.funcs = (void *)&qxl_mode_funcs;
+
+ /* modes will be validated against the framebuffer size */
+ qdev->ddev->mode_config.max_width = 8192;
+ qdev->ddev->mode_config.max_height = 8192;
+
+ qdev->ddev->mode_config.fb_base = qdev->vram_base;
+ for (i = 0 ; i < QXL_NUM_OUTPUTS; ++i) {
+ qdev_crtc_init(qdev->ddev, i);
+ qdev_output_init(qdev->ddev, i);
+ }
+
+ qdev->mode_info.mode_config_initialized = true;
+
+ /* primary surface must be created by this point, to allow
+ * issuing command queue commands and having them read by
+ * spice server. */
+ qxl_fbdev_init(qdev);
+ return 0;
+}
+
+void qxl_modeset_fini(struct qxl_device *qdev)
+{
+ qxl_fbdev_fini(qdev);
+ if (qdev->mode_info.mode_config_initialized) {
+ drm_mode_config_cleanup(qdev->ddev);
+ qdev->mode_info.mode_config_initialized = false;
+ }
+}
diff --git a/drivers/gpu/drm/qxl/qxl_draw.c b/drivers/gpu/drm/qxl/qxl_draw.c
new file mode 100644
index 00000000000..e7b8ed262cb
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_draw.c
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2011 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * on the rights to use, copy, modify, merge, publish, distribute, sub
+ * license, and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "qxl_drv.h"
+#include "qxl_object.h"
+
+/* returns a pointer to the already allocated qxl_rect array inside
+ * the qxl_clip_rects. This is *not* the same as the memory allocated
+ * on the device, it is offset to qxl_clip_rects.chunk.data */
+static struct qxl_rect *drawable_set_clipping(struct qxl_device *qdev,
+ struct qxl_drawable *drawable,
+ unsigned num_clips,
+ struct drm_qxl_release *release)
+{
+ struct qxl_clip_rects *dev_clips;
+
+ dev_clips = qxl_allocnf(qdev, sizeof(*dev_clips) +
+ sizeof(struct qxl_rect) * num_clips, release);
+ dev_clips->num_rects = num_clips;
+ dev_clips->chunk.next_chunk = 0;
+ dev_clips->chunk.prev_chunk = 0;
+ dev_clips->chunk.data_size = sizeof(struct qxl_rect) * num_clips;
+ drawable->clip.type = SPICE_CLIP_TYPE_RECTS;
+ drawable->clip.data = qxl_fb_physical_address(qdev,
+ release->bos[release->bo_count - 1]);
+ return (struct qxl_rect *)dev_clips->chunk.data;
+}
+
+uint64_t
+qxl_release_alloc(struct qxl_device *qdev, int type,
+ struct drm_qxl_release **ret)
+{
+ struct drm_qxl_release *release;
+ int handle = 0;
+ size_t size = sizeof(*release);
+ int idr_ret;
+
+ release = kmalloc(size, GFP_KERNEL);
+ if (!release) {
+ DRM_ERROR("Out of memory\n");
+ return 0;
+ }
+ memset(release, 0, size);
+ release->type = type;
+ release->bo_count = 0;
+again:
+ mutex_lock(&qdev->release_idr_mutex);
+ if (idr_pre_get(&qdev->release_idr, GFP_KERNEL) == 0) {
+ DRM_ERROR("Out of memory for release idr\n");
+ kfree(release);
+ goto release_fail;
+ }
+ idr_ret = idr_get_new_above(&qdev->release_idr, release, 1, &handle);
+ if (idr_ret == -EAGAIN)
+ goto again;
+ if (ret)
+ *ret = release;
+ QXL_INFO(qdev, "allocated release %lld\n", handle);
+ release->id = handle;
+release_fail:
+ mutex_unlock(&qdev->release_idr_mutex);
+ return handle;
+}
+
+void
+qxl_release_free_locked(struct qxl_device *qdev,
+ struct drm_qxl_release *release)
+{
+ int i;
+
+ QXL_INFO(qdev, "release %d, type %d, %d bos\n", release->id,
+ release->type, release->bo_count);
+ for (i = 0 ; i < release->bo_count; ++i) {
+ QXL_INFO(qdev, "release %llx\n",
+ release->bos[i]->tbo.addr_space_offset
+ - DRM_FILE_OFFSET);
+ qxl_bo_free(release->bos[i]);
+ }
+ idr_remove(&qdev->release_idr, release->id);
+ kfree(release);
+}
+
+void
+qxl_release_add_res(struct qxl_device *qdev, struct drm_qxl_release *release,
+ struct qxl_bo *bo)
+{
+ if (release->bo_count >= QXL_MAX_RES) {
+ DRM_ERROR("exceeded max resource on a drm_qxl_release item\n");
+ return;
+ }
+ release->bos[release->bo_count++] = bo;
+}
+
+void *qxl_alloc_releasable(struct qxl_device *qdev, unsigned long size,
+ int type, struct drm_qxl_release **release,
+ struct qxl_bo **bo)
+{
+ int idr_ret;
+ void *ret;
+
+ idr_ret = qxl_release_alloc(qdev, type, release);
+ ret = qxl_allocnf(qdev, size, *release);
+ ((union qxl_release_info *)ret)->id = idr_ret;
+ *bo = (*release)->bos[(*release)->bo_count - 1];
+ return ret;
+}
+
+static struct qxl_drawable *
+make_drawable(struct qxl_device *qdev, int surface, uint8_t type,
+ const struct qxl_rect *rect,
+ struct drm_qxl_release **release,
+ struct qxl_bo **drawable_bo)
+{
+ struct qxl_drawable *drawable;
+ int i;
+
+ drawable = qxl_alloc_releasable(qdev, sizeof(*drawable),
+ QXL_RELEASE_DRAWABLE, release,
+ drawable_bo);
+
+ drawable->type = type;
+
+ drawable->surface_id = surface; /* Only primary for now */
+ drawable->effect = QXL_EFFECT_OPAQUE;
+ drawable->self_bitmap = 0;
+ drawable->self_bitmap_area.top = 0;
+ drawable->self_bitmap_area.left = 0;
+ drawable->self_bitmap_area.bottom = 0;
+ drawable->self_bitmap_area.right = 0;
+ /* FIXME: add clipping */
+ drawable->clip.type = SPICE_CLIP_TYPE_NONE;
+
+ /*
+ * surfaces_dest[i] should apparently be filled out with the
+ * surfaces that we depend on, and surface_rects should be
+ * filled with the rectangles of those surfaces that we
+ * are going to use.
+ */
+ for (i = 0; i < 3; ++i)
+ drawable->surfaces_dest[i] = -1;
+
+ if (rect)
+ drawable->bbox = *rect;
+
+ drawable->mm_time = qdev->rom->mm_clock;
+
+ return drawable;
+}
+
+/* TODO: bo per command is wasteful. add an offset */
+void
+qxl_push_command_ring(struct qxl_device *qdev, struct qxl_bo *bo, uint32_t type)
+{
+ struct qxl_command cmd;
+
+ cmd.type = type;
+ cmd.data = qxl_fb_physical_address(qdev, bo);
+
+ qxl_ring_push(qdev->command_ring, &cmd);
+}
+
+void
+qxl_push_cursor_ring(struct qxl_device *qdev, struct qxl_bo *bo, uint32_t type)
+{
+ struct qxl_command cmd;
+
+ cmd.type = type;
+ cmd.data = qxl_fb_physical_address(qdev, bo);
+
+ qxl_ring_push(qdev->cursor_ring, &cmd);
+}
+
+static void
+push_drawable(struct qxl_device *qdev, struct qxl_bo *drawable_bo)
+{
+ qxl_push_command_ring(qdev, drawable_bo, QXL_CMD_DRAW);
+}
+
+static struct qxl_palette *qxl_palette_create_1bit(
+ struct drm_qxl_release *release,
+ const struct qxl_fb_image *qxl_fb_image)
+{
+ struct qxl_device *qdev = qxl_fb_image->qdev;
+ const struct fb_image *fb_image = &qxl_fb_image->fb_image;
+ uint32_t visual = qxl_fb_image->visual;
+ const uint32_t *pseudo_palette = qxl_fb_image->pseudo_palette;
+ struct qxl_palette *ret;
+ uint32_t fgcolor, bgcolor;
+ static uint64_t unique; /* we make no attempt to actually set this
+ * correctly globaly, since that would require
+ * tracking all of our palettes. */
+
+ ret = qxl_allocnf(qdev,
+ sizeof(struct qxl_palette) + sizeof(uint32_t) * 2,
+ release);
+ ret->num_ents = 2;
+ ret->unique = unique++;
+ if (visual == FB_VISUAL_TRUECOLOR || visual == FB_VISUAL_DIRECTCOLOR) {
+ /* NB: this is the only used branch currently. */
+ fgcolor = pseudo_palette[fb_image->fg_color];
+ bgcolor = pseudo_palette[fb_image->bg_color];
+ } else {
+ fgcolor = fb_image->fg_color;
+ bgcolor = fb_image->bg_color;
+ }
+ ret->ents[0] = bgcolor;
+ ret->ents[1] = fgcolor;
+ return ret;
+}
+
+void qxl_draw_opaque_fb(const struct qxl_fb_image *qxl_fb_image,
+ int stride /* filled in if 0 */)
+{
+ struct qxl_device *qdev = qxl_fb_image->qdev;
+ struct qxl_drawable *drawable;
+ struct qxl_rect rect;
+ struct qxl_image *image;
+ struct qxl_palette *palette;
+ const struct fb_image *fb_image = &qxl_fb_image->fb_image;
+ int x = fb_image->dx;
+ int y = fb_image->dy;
+ int width = fb_image->width;
+ int height = fb_image->height;
+ const char *src = fb_image->data;
+ int depth = fb_image->depth;
+ struct drm_qxl_release *release;
+ struct qxl_bo *image_bo;
+ struct qxl_bo *drawable_bo;
+
+ if (stride == 0)
+ stride = depth * width / 8;
+
+ rect.left = x;
+ rect.right = x + width;
+ rect.top = y;
+ rect.bottom = y + height;
+
+ drawable = make_drawable(qdev, 0, QXL_DRAW_COPY, &rect, &release,
+ &drawable_bo);
+ QXL_INFO(qdev, "drawable off %llx, release id %lld\n",
+ drawable_bo->tbo.addr_space_offset - DRM_FILE_OFFSET,
+ drawable->release_info.id);
+
+ drawable->u.copy.src_area.top = 0;
+ drawable->u.copy.src_area.bottom = height;
+ drawable->u.copy.src_area.left = 0;
+ drawable->u.copy.src_area.right = width;
+
+ drawable->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT;
+ drawable->u.copy.scale_mode = 0;
+ drawable->u.copy.mask.flags = 0;
+ drawable->u.copy.mask.pos.x = 0;
+ drawable->u.copy.mask.pos.y = 0;
+ drawable->u.copy.mask.bitmap = 0;
+
+ image = qxl_image_create(
+ qdev, release, &image_bo,
+ (const uint8_t *)src, 0, 0, width, height, depth, stride);
+ QXL_INFO(qdev, "image_bo offset %llx\n",
+ image_bo->tbo.addr_space_offset - DRM_FILE_OFFSET);
+ if (depth == 1) {
+ struct qxl_bo *palette_bo;
+
+ palette = qxl_palette_create_1bit(release, qxl_fb_image);
+ palette_bo = release->bos[release->bo_count - 1];
+ image->u.bitmap.palette =
+ qxl_fb_physical_address(qdev, palette_bo);
+ }
+ drawable->u.copy.src_bitmap =
+ qxl_fb_physical_address(qdev, image_bo);
+
+ push_drawable(qdev, drawable_bo);
+}
+
+#if 0
+static void debug_fill_image(uint8_t *data, long width, long height,
+ long stride)
+{
+ int x, y;
+ static int c = 1;
+
+ DRM_INFO("%s: data %p, w %ld, h %ld, stride %ld\n", __func__, data,
+ width, height, stride);
+ for (y = height; y > 0; y--) {
+ /* DRM_INFO("%s: %d, data %p\n", __func__, y, data); */
+ for (x = 2; x < width * 4; x += 4)
+ data[x] += c;
+ data += stride;
+ }
+ c += 127;
+}
+#else
+#define debug_fill_image(...)
+#endif
+
+/* push a draw command using the given clipping rectangles as
+ * the sources from the shadow framebuffer.
+ *
+ * Right now implementing with a single draw and a clip list. Clip
+ * lists are known to be a problem performance wise, this can be solved
+ * by treating them differently in the server.
+ */
+void qxl_draw_dirty_fb(struct qxl_device *qdev,
+ struct qxl_framebuffer *qxl_fb,
+ struct qxl_bo *bo,
+ unsigned flags, unsigned color,
+ struct drm_clip_rect *clips,
+ unsigned num_clips, int inc)
+{
+ /*
+ * TODO: only a single monitor (stole this code from vmwgfx_kms.c)
+ * vmwgfx command created a blit command for each crt that any of
+ * the clip rects targeted.
+ */
+ /*
+ * TODO: if flags & DRM_MODE_FB_DIRTY_ANNOTATE_FILL then we should
+ * send a fill command instead, much cheaper.
+ *
+ * See include/drm/drm_mode.h
+ */
+ struct drm_clip_rect *clips_ptr;
+ int i;
+ int left, right, top, bottom;
+ int width, height;
+ struct qxl_drawable *drawable;
+ struct qxl_rect drawable_rect;
+ struct qxl_rect *rects;
+ struct qxl_image *image;
+ int stride = qxl_fb->base.pitches[0];
+ /* depth is not actually interesting, we don't mask with it */
+ int depth = qxl_fb->base.bits_per_pixel;
+ uint8_t *surface_base = bo->kptr;
+ struct drm_qxl_release *release;
+ struct qxl_bo *image_bo;
+ struct qxl_bo *drawable_bo;
+
+ left = clips->x1;
+ right = clips->x2;
+ top = clips->y1;
+ bottom = clips->y2;
+
+ /* skip the first clip rect */
+ for (i = 1, clips_ptr = clips + inc;
+ i < num_clips; i++, clips_ptr += inc) {
+ left = min_t(int, left, (int)clips_ptr->x1);
+ right = max_t(int, right, (int)clips_ptr->x2);
+ top = min_t(int, top, (int)clips_ptr->y1);
+ bottom = max_t(int, bottom, (int)clips_ptr->y2);
+ }
+
+ width = right - left;
+ height = bottom - top;
+ drawable_rect.left = left;
+ drawable_rect.right = right;
+ drawable_rect.top = top;
+ drawable_rect.bottom = bottom;
+ drawable = make_drawable(qdev, 0, QXL_DRAW_COPY, &drawable_rect,
+ &release, &drawable_bo);
+ rects = drawable_set_clipping(qdev, drawable, num_clips, release);
+
+ drawable->u.copy.src_area.top = 0;
+ drawable->u.copy.src_area.bottom = height;
+ drawable->u.copy.src_area.left = 0;
+ drawable->u.copy.src_area.right = width;
+
+ drawable->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT;
+ drawable->u.copy.scale_mode = 0;
+ drawable->u.copy.mask.flags = 0;
+ drawable->u.copy.mask.pos.x = 0;
+ drawable->u.copy.mask.pos.y = 0;
+ drawable->u.copy.mask.bitmap = 0;
+
+ debug_fill_image((surface_base + (left * 4) + (stride * top)),
+ width, height, stride);
+ image = qxl_image_create(
+ qdev, release, &image_bo, surface_base,
+ left, top, width, height, depth, stride);
+ drawable->u.copy.src_bitmap = qxl_fb_physical_address(qdev, image_bo);
+
+ clips_ptr = clips;
+ for (i = 0; i < num_clips; i++, clips_ptr += inc) {
+ rects[i].left = clips_ptr->x1;
+ rects[i].right = clips_ptr->x2;
+ rects[i].top = clips_ptr->y1;
+ rects[i].bottom = clips_ptr->y2;
+ }
+ push_drawable(qdev, drawable_bo);
+}
+
+void qxl_draw_copyarea(struct qxl_device *qdev,
+ u32 width, u32 height,
+ u32 sx, u32 sy,
+ u32 dx, u32 dy)
+{
+ struct qxl_drawable *drawable;
+ struct qxl_rect rect;
+ struct drm_qxl_release *release;
+ struct qxl_bo *drawable_bo;
+
+ rect.left = dx;
+ rect.top = dy;
+ rect.right = dx + width;
+ rect.bottom = dy + height;
+ drawable = make_drawable(qdev, 0, QXL_COPY_BITS, &rect, &release,
+ &drawable_bo);
+ drawable->u.copy_bits.src_pos.x = sx;
+ drawable->u.copy_bits.src_pos.y = sy;
+
+ push_drawable(qdev, drawable_bo);
+}
+
+void qxl_draw_fill(struct qxl_draw_fill *qxl_draw_fill_rec)
+{
+ struct qxl_device *qdev = qxl_draw_fill_rec->qdev;
+ struct qxl_rect rect = qxl_draw_fill_rec->rect;
+ uint32_t color = qxl_draw_fill_rec->color;
+ uint16_t rop = qxl_draw_fill_rec->rop;
+ struct qxl_drawable *drawable;
+ struct drm_qxl_release *release;
+ struct qxl_bo *drawable_bo;
+
+ drawable = make_drawable(qdev, 0, QXL_DRAW_FILL, &rect, &release,
+ &drawable_bo);
+
+ drawable->u.fill.brush.type = SPICE_BRUSH_TYPE_SOLID;
+ drawable->u.fill.brush.u.color = color;
+ drawable->u.fill.rop_descriptor = rop;
+ drawable->u.fill.mask.flags = 0;
+ drawable->u.fill.mask.pos.x = 0;
+ drawable->u.fill.mask.pos.y = 0;
+ drawable->u.fill.mask.bitmap = 0;
+
+ push_drawable(qdev, drawable_bo);
+}
diff --git a/drivers/gpu/drm/qxl/qxl_drv.c b/drivers/gpu/drm/qxl/qxl_drv.c
new file mode 100644
index 00000000000..89e73b5d76a
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_drv.c
@@ -0,0 +1,130 @@
+/* vim: set ts=8 sw=8 tw=78 ai noexpandtab */
+/* qxl_drv.c -- QXL driver -*- linux-c -*-
+ *
+ * Copyright 2011 Red Hat, Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Dave Airlie <airlie@redhat.com>
+ * Alon Levy <alevy@redhat.com>
+ */
+
+#include <linux/module.h>
+
+#include "drmP.h"
+#include "drm.h"
+
+#include "qxl_drv.h"
+
+extern int qxl_max_ioctls;
+static DEFINE_PCI_DEVICE_TABLE(pciidlist) = {
+ { 0x1b36, 0x100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8,
+ 0xffff00, 0 },
+ { 0x1b36, 0x100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_OTHER << 8,
+ 0xffff00, 0 },
+ { 0, 0, 0 },
+};
+
+static struct drm_driver qxl_driver;
+static struct pci_driver qxl_pci_driver;
+
+static int __devinit
+qxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ if (pdev->revision < 4) {
+ DRM_ERROR("qxl too old, doesn't support client_monitors_config,"
+ " use xf86-video-qxl in user mode");
+ return -EINVAL; /* TODO: ENODEV ? */
+ }
+ return drm_get_pci_dev(pdev, ent, &qxl_driver);
+}
+
+static void
+qxl_pci_remove(struct pci_dev *pdev)
+{
+ struct drm_device *dev = pci_get_drvdata(pdev);
+
+ drm_put_dev(dev);
+}
+
+static struct pci_driver qxl_pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ .probe = qxl_pci_probe,
+ .remove = qxl_pci_remove,
+};
+
+static const struct file_operations qxl_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ .mmap = qxl_mmap,
+};
+
+static struct drm_driver qxl_driver = {
+ .driver_features = DRIVER_GEM | DRIVER_MODESET |
+ DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED,
+ .dev_priv_size = 0,
+ .load = qxl_driver_load,
+ .unload = qxl_driver_unload,
+ .dumb_create = qxl_mode_dumb_create,
+ .dumb_map_offset = qxl_mode_dumb_mmap,
+ .dumb_destroy = qxl_mode_dumb_destroy,
+#if defined(CONFIG_DEBUG_FS)
+ .debugfs_init = qxl_debugfs_init,
+ .debugfs_cleanup = qxl_debugfs_takedown,
+#endif
+ .gem_init_object = qxl_gem_object_init,
+ .gem_free_object = qxl_gem_object_free,
+ .gem_open_object = qxl_gem_object_open,
+ .gem_close_object = qxl_gem_object_close,
+ .fops = &qxl_fops,
+ .ioctls = qxl_ioctls,
+ .irq_handler = qxl_irq_handler,
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = 0,
+ .minor = 1,
+ .patchlevel = 0,
+};
+
+static int __init qxl_init(void)
+{
+ qxl_driver.num_ioctls = qxl_max_ioctls;
+ return drm_pci_init(&qxl_driver, &qxl_pci_driver);
+}
+
+static void __exit qxl_exit(void)
+{
+ drm_pci_exit(&qxl_driver, &qxl_pci_driver);
+}
+
+module_init(qxl_init);
+module_exit(qxl_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/gpu/drm/qxl/qxl_drv.h b/drivers/gpu/drm/qxl/qxl_drv.h
new file mode 100644
index 00000000000..a15ed9da1df
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_drv.h
@@ -0,0 +1,512 @@
+#ifndef QXL_DRV_H
+#define QXL_DRV_H
+
+/*
+ * Definitions taken from spice-protocol, plus kernel driver specific bits.
+ */
+
+#include <linux/workqueue.h>
+#include <linux/firmware.h>
+#include <linux/platform_device.h>
+
+#include "drmP.h"
+#include "drm_crtc.h"
+#include <ttm/ttm_bo_api.h>
+#include <ttm/ttm_bo_driver.h>
+#include <ttm/ttm_placement.h>
+#include <ttm/ttm_module.h>
+
+#include "qxl_drm.h"
+#include "qxl_dev.h"
+
+#define DRIVER_AUTHOR "Dave Airlie"
+
+#define DRIVER_NAME "qxl"
+#define DRIVER_DESC "RH QXL"
+#define DRIVER_DATE "20120117"
+
+#define DRIVER_MAJOR 0
+#define DRIVER_MINOR 1
+#define DRIVER_PATCHLEVEL 0
+
+#define QXL_NUM_OUTPUTS 1
+
+extern int qxl_log_level;
+extern int qxl_debug_disable_fb;
+
+enum {
+ QXL_INFO_LEVEL = 1,
+ QXL_DEBUG_LEVEL = 2,
+};
+
+#define QXL_INFO(qdev, fmt, ...) do { \
+ if (qxl_log_level >= QXL_INFO_LEVEL) { \
+ qxl_io_log(qdev, fmt, __VA_ARGS__); \
+ } \
+ } while (0)
+#define QXL_DEBUG(qdev, fmt, ...) do { \
+ if (qxl_log_level >= QXL_DEBUG_LEVEL) { \
+ qxl_io_log(qdev, fmt, __VA_ARGS__); \
+ } \
+ } while (0)
+#define QXL_INFO_ONCE(qdev, fmt, ...) do { \
+ static int done; \
+ if (!done) { \
+ done = 1; \
+ QXL_INFO(qdev, fmt, __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define DRM_FILE_OFFSET 0x100000000ULL
+#define DRM_FILE_PAGE_OFFSET (DRM_FILE_OFFSET >> PAGE_SHIFT)
+
+#define QXL_INTERRUPT_MASK (\
+ QXL_INTERRUPT_DISPLAY |\
+ QXL_INTERRUPT_CURSOR |\
+ QXL_INTERRUPT_IO_CMD |\
+ QXL_INTERRUPT_CLIENT_MONITORS_CONFIG)
+/* TODO QXL_INTERRUPT_CLIENT */
+
+struct qxl_bo {
+ /* Protected by gem.mutex */
+ struct list_head list;
+ /* Protected by tbo.reserved */
+ u32 placements[3];
+ struct ttm_placement placement;
+ struct ttm_buffer_object tbo;
+ struct ttm_bo_kmap_obj kmap;
+ unsigned pin_count;
+ void *kptr;
+ u32 pitch;
+ int surface_reg;
+ /* Constant after initialization */
+ struct qxl_device *qdev;
+ struct drm_gem_object gem_base;
+};
+#define gem_to_qxl_bo(gobj) container_of((gobj), struct qxl_bo, gem_base)
+
+
+struct qxl_fence_driver {
+ atomic_t seq;
+ uint32_t last_seq;
+ wait_queue_head_t queue;
+ rwlock_t lock;
+ struct list_head created;
+ struct list_head emited;
+ struct list_head signaled;
+};
+
+struct qxl_fence {
+ struct qxl_device *qdev;
+ struct kref kref;
+ struct list_head list;
+ uint32_t seq;
+ unsigned long timeout;
+ bool emited;
+ bool signaled;
+};
+
+int qxl_fence_driver_init(struct qxl_device *qdev);
+void qxl_fence_driver_fini(struct qxl_device *qdev);
+
+struct qxl_gem {
+ struct mutex mutex;
+ struct list_head objects;
+};
+
+struct qxl_crtc {
+ struct drm_crtc base;
+};
+
+struct qxl_output {
+ int index;
+ struct drm_connector base;
+ struct drm_encoder enc;
+};
+
+struct qxl_framebuffer {
+ struct drm_framebuffer base;
+ struct drm_gem_object *obj;
+};
+
+#define to_qxl_crtc(x) container_of(x, struct qxl_crtc, base)
+#define drm_connector_to_qxl_output(x) container_of(x, struct qxl_output, base)
+#define drm_encoder_to_qxl_output(x) container_of(x, struct qxl_output, base)
+#define to_qxl_framebuffer(x) container_of(x, struct qxl_framebuffer, base)
+
+struct qxl_mman {
+ struct ttm_bo_global_ref bo_global_ref;
+ struct drm_global_reference mem_global_ref;
+ bool mem_global_referenced;
+ struct ttm_bo_device bdev;
+};
+
+struct qxl_mode_info {
+ int num_modes;
+ struct qxl_mode *modes;
+ bool mode_config_initialized;
+
+ /* pointer to fbdev info structure */
+ struct qxl_fbdev *qfbdev;
+};
+
+
+struct qxl_memslot {
+ uint8_t generation;
+ uint64_t start_phys_addr;
+ uint64_t end_phys_addr;
+ uint64_t high_bits;
+};
+
+enum {
+ QXL_RELEASE_DRAWABLE,
+ QXL_RELEASE_SURFACE_CMD,
+ QXL_RELEASE_CURSOR_CMD,
+};
+
+/* drm_ prefix to differentiate from qxl_release_info in
+ * spice-protocol/qxl_dev.h */
+#define QXL_MAX_RES 6
+struct drm_qxl_release {
+ int id;
+ int type;
+ int bo_count;
+ struct qxl_bo *bos[QXL_MAX_RES];
+};
+
+/* all information required for an image blit. used instead
+ * of fb_image & fb_info to do a single allocation when we need
+ * to queue the work. */
+struct qxl_fb_image {
+ struct qxl_device *qdev;
+ uint32_t pseudo_palette[16];
+ struct fb_image fb_image;
+ uint32_t visual;
+};
+
+struct qxl_draw_fill {
+ struct qxl_device *qdev;
+ struct qxl_rect rect;
+ uint32_t color;
+ uint16_t rop;
+};
+
+enum {
+ QXL_FB_WORK_ITEM_INVALID,
+ QXL_FB_WORK_ITEM_IMAGE,
+ QXL_FB_WORK_ITEM_DRAW_FILL,
+};
+
+struct qxl_fb_work_item {
+ struct list_head head;
+ int type;
+ union {
+ struct qxl_fb_image qxl_fb_image;
+ struct qxl_draw_fill qxl_draw_fill;
+ };
+};
+
+struct qxl_device {
+ struct device *dev;
+ struct drm_device *ddev;
+ struct pci_dev *pdev;
+ unsigned long flags;
+
+ resource_size_t vram_base, vram_size;
+ resource_size_t surfaceram_base, surfaceram_size;
+ resource_size_t rom_base, rom_size;
+ struct qxl_rom *rom;
+
+ struct qxl_mode *modes;
+ struct qxl_bo *monitors_config_bo;
+ struct qxl_monitors_config *monitors_config;
+
+ /* last received client_monitors_config */
+ struct qxl_monitors_config *client_monitors_config;
+
+ int io_base;
+ void *ram;
+ struct qxl_bo *surface0_bo;
+ void *surface0_shadow;
+ struct qxl_mman mman;
+ struct qxl_gem gem;
+ struct qxl_mode_info mode_info;
+
+ /*
+ * last created framebuffer with fb_create
+ * only used by debugfs dumbppm
+ */
+ struct qxl_framebuffer *active_user_framebuffer;
+
+ struct fb_info *fbdev_info;
+ struct qxl_framebuffer *fbdev_qfb;
+ void *ram_physical;
+
+ struct qxl_ring *release_ring;
+ struct qxl_ring *command_ring;
+ struct qxl_ring *cursor_ring;
+
+ struct qxl_ram_header *ram_header;
+ bool mode_set;
+
+ bool primary_created;
+ unsigned primary_width;
+ unsigned primary_height;
+
+ struct qxl_memslot *mem_slots;
+ uint8_t n_mem_slots;
+
+ uint8_t main_mem_slot;
+ uint8_t surfaces_mem_slot;
+ uint8_t slot_id_bits;
+ uint8_t slot_gen_bits;
+ uint64_t va_slot_mask;
+
+ uint8_t vram_mem_slot;
+
+ struct idr release_idr;
+ struct mutex release_idr_mutex;
+ struct mutex async_io_mutex;
+
+ /* framebuffer. workqueue to avoid bo allocation in interrupt context */
+ struct workqueue_struct *fb_workqueue;
+ spinlock_t fb_workqueue_spinlock;
+ struct work_struct fb_work;
+ struct list_head fb_work_item_pending;
+ struct list_head fb_work_item_free;
+ struct qxl_fb_work_item fb_work_items[16];
+
+ /* interrupt handling */
+ atomic_t irq_received;
+ atomic_t irq_received_display;
+ atomic_t irq_received_cursor;
+ atomic_t irq_received_io_cmd;
+ unsigned irq_received_error;
+ wait_queue_head_t display_event;
+ wait_queue_head_t cursor_event;
+ wait_queue_head_t io_cmd_event;
+ struct work_struct client_monitors_config_work;
+};
+
+/* forward declaration for QXL_INFO_IO */
+void qxl_io_log(struct qxl_device *qdev, const char *fmt, ...);
+
+extern struct drm_ioctl_desc qxl_ioctls[];
+extern int qxl_max_ioctl;
+
+int qxl_driver_load(struct drm_device *dev, unsigned long flags);
+int qxl_driver_unload(struct drm_device *dev);
+
+int qxl_modeset_init(struct qxl_device *qdev);
+void qxl_modeset_fini(struct qxl_device *qdev);
+
+int qxl_bo_init(struct qxl_device *qdev);
+void qxl_bo_fini(struct qxl_device *qdev);
+
+struct qxl_ring *qxl_ring_create(struct qxl_ring_header *header,
+ int element_size,
+ int n_elements,
+ int prod_notify,
+ wait_queue_head_t *push_event);
+void qxl_ring_free(struct qxl_ring *ring);
+extern void *qxl_allocnf(struct qxl_device *qdev, unsigned long size,
+ struct drm_qxl_release *release);
+
+static inline uint64_t
+qxl_physical_address(struct qxl_device *qdev, uint64_t kptr)
+{
+ struct qxl_memslot *slot = &(qdev->mem_slots[qdev->main_mem_slot]);
+
+ return slot->high_bits | kptr;
+}
+
+static inline uint64_t
+qxl_fb_physical_address(struct qxl_device *qdev, struct qxl_bo *bo)
+{
+ uint64_t offset = bo->tbo.offset;
+
+ QXL_INFO(qdev, "physical from tbo %llx (%llx, %llx)\n",
+ bo->tbo.addr_space_offset, offset,
+ bo->tbo.offset);
+ BUG_ON(bo->tbo.addr_space_offset & 0xf000000000000000);
+ return qxl_physical_address(qdev, offset);
+}
+
+static inline void *
+qxl_fb_virtual_address(struct qxl_device *qdev, unsigned long physical)
+{
+ QXL_INFO(qdev, "not implemented (%lu)\n", physical);
+ return 0;
+}
+
+static inline uint64_t
+qxl_bo_physical_address(struct qxl_device *qdev, struct qxl_bo *bo,
+ unsigned long offset, uint8_t slot_id)
+{
+ struct qxl_memslot *slot = &(qdev->mem_slots[slot_id]);
+
+ /* TODO - need to hold one of the locks to read tbo.offset */
+ return slot->high_bits | (bo->tbo.offset + offset);
+}
+
+/* qxl_fb.c */
+#define QXLFB_CONN_LIMIT 1
+
+int qxl_fbdev_init(struct qxl_device *qdev);
+void qxl_fbdev_fini(struct qxl_device *qdev);
+int qxl_get_handle_for_primary_fb(struct qxl_device *qdev,
+ struct drm_file *file_priv,
+ uint32_t *handle);
+
+/* qxl_display.c */
+void
+qxl_framebuffer_init(struct drm_device *dev,
+ struct qxl_framebuffer *rfb,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object *obj);
+void qxl_display_read_client_monitors_config(struct qxl_device *qdev);
+void qxl_send_monitors_config(struct qxl_device *qdev);
+
+/* used by qxl_debugfs only */
+void qxl_crtc_set_from_monitors_config(struct qxl_device *qdev);
+void qxl_alloc_client_monitors_config(struct qxl_device *qdev, unsigned count);
+
+/* qxl_gem.c */
+int qxl_gem_init(struct qxl_device *qdev);
+void qxl_gem_fini(struct qxl_device *qdev);
+int qxl_gem_object_create(struct qxl_device *qdev, int size,
+ int alignment, int initial_domain,
+ bool discardable, bool kernel,
+ struct drm_gem_object **obj);
+int qxl_gem_object_pin(struct drm_gem_object *obj, uint32_t pin_domain,
+ uint64_t *gpu_addr);
+void qxl_gem_object_unpin(struct drm_gem_object *obj);
+int qxl_gem_object_create_with_handle(struct qxl_device *qdev,
+ struct drm_file *file_priv,
+ u32 domain,
+ size_t size,
+ struct qxl_bo **qobj,
+ uint32_t *handle);
+int qxl_gem_object_init(struct drm_gem_object *obj);
+void qxl_gem_object_free(struct drm_gem_object *gobj);
+int qxl_gem_object_open(struct drm_gem_object *obj, struct drm_file *file_priv);
+void qxl_gem_object_close(struct drm_gem_object *obj,
+ struct drm_file *file_priv);
+void qxl_bo_force_delete(struct qxl_device *qdev);
+int qxl_bo_kmap(struct qxl_bo *bo, void **ptr);
+
+/* qxl_dumb.c */
+int qxl_mode_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+int qxl_mode_dumb_destroy(struct drm_file *file_priv,
+ struct drm_device *dev,
+ uint32_t handle);
+int qxl_mode_dumb_mmap(struct drm_file *filp,
+ struct drm_device *dev,
+ uint32_t handle, uint64_t *offset_p);
+
+
+/* qxl ttm */
+int qxl_ttm_init(struct qxl_device *qdev);
+void qxl_ttm_fini(struct qxl_device *qdev);
+int qxl_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* qxl image */
+
+struct qxl_image *qxl_image_create(struct qxl_device *qdev,
+ struct drm_qxl_release *release,
+ struct qxl_bo **image_bo,
+ const uint8_t *data,
+ int x, int y, int width, int height,
+ int depth, int stride);
+void qxl_image_destroy(struct qxl_device *qdev,
+ struct qxl_image *image);
+void qxl_update_screen(struct qxl_device *qxl);
+
+/* qxl io operations (qxl_cmd.c) */
+
+void qxl_io_create_primary(struct qxl_device *qdev,
+ unsigned width, unsigned height);
+void qxl_io_destroy_primary(struct qxl_device *qdev);
+void qxl_io_memslot_add(struct qxl_device *qdev, uint8_t id);
+void qxl_io_notify_oom(struct qxl_device *qdev);
+void qxl_io_update_area(struct qxl_device *qdev, uint32_t surface_id,
+ const struct qxl_rect *area);
+void qxl_io_reset(struct qxl_device *qdev);
+void qxl_io_monitors_config(struct qxl_device *qdev);
+void qxl_ring_push(struct qxl_ring *ring, const void *new_elt);
+
+
+/*
+ * qxl_bo_add_resource.
+ *
+ */
+void qxl_bo_add_resource(struct qxl_bo *main_bo, struct qxl_bo *resource);
+
+/* used both directly via qxl_draw and via ioctl execbuffer */
+void *qxl_alloc_releasable(struct qxl_device *qdev, unsigned long size,
+ int type, struct drm_qxl_release **release,
+ struct qxl_bo **bo);
+void
+qxl_push_command_ring(struct qxl_device *qdev, struct qxl_bo *bo,
+ uint32_t type);
+void
+qxl_push_cursor_ring(struct qxl_device *qdev, struct qxl_bo *bo,
+ uint32_t type);
+
+/* qxl drawing commands */
+
+void qxl_draw_opaque_fb(const struct qxl_fb_image *qxl_fb_image,
+ int stride /* filled in if 0 */);
+
+void qxl_draw_dirty_fb(struct qxl_device *qdev,
+ struct qxl_framebuffer *qxl_fb,
+ struct qxl_bo *bo,
+ unsigned flags, unsigned color,
+ struct drm_clip_rect *clips,
+ unsigned num_clips, int inc);
+
+void qxl_draw_fill(struct qxl_draw_fill *qxl_draw_fill_rec);
+
+void qxl_draw_copyarea(struct qxl_device *qdev,
+ u32 width, u32 height,
+ u32 sx, u32 sy,
+ u32 dx, u32 dy);
+
+uint64_t
+qxl_release_alloc(struct qxl_device *qdev, int type,
+ struct drm_qxl_release **ret);
+
+void qxl_release_free_locked(struct qxl_device *qdev,
+ struct drm_qxl_release *release);
+void qxl_release_add_res(struct qxl_device *qdev,
+ struct drm_qxl_release *release,
+ struct qxl_bo *bo);
+
+/* used by qxl_debugfs_release */
+struct drm_qxl_release *qxl_release_from_id_locked(struct qxl_device *qdev,
+ uint64_t id);
+
+int qxl_garbage_collect(struct qxl_device *qdev);
+
+/* debugfs */
+
+int qxl_debugfs_init(struct drm_minor *minor);
+void qxl_debugfs_takedown(struct drm_minor *minor);
+
+/* qxl_irq.c */
+int qxl_irq_init(struct qxl_device *qdev);
+irqreturn_t qxl_irq_handler(DRM_IRQ_ARGS);
+
+/* qxl_fb.c */
+int qxl_fb_init(struct qxl_device *qdev);
+
+/* not static for debugfs */
+int qxl_fb_queue_imageblit(struct qxl_device *qdev,
+ struct qxl_fb_image *qxl_fb_image,
+ struct fb_info *info,
+ const struct fb_image *image);
+int qxl_fb_queue_draw_fill(struct qxl_draw_fill *qxl_draw_fill_rec);
+
+#endif
diff --git a/drivers/gpu/drm/qxl/qxl_dumb.c b/drivers/gpu/drm/qxl/qxl_dumb.c
new file mode 100644
index 00000000000..668611581ff
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_dumb.c
@@ -0,0 +1,61 @@
+#include "qxl_drv.h"
+#include "qxl_object.h"
+
+/* dumb ioctls implementation */
+
+int qxl_mode_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ struct qxl_device *qdev = dev->dev_private;
+ struct qxl_bo *qobj;
+ uint32_t handle;
+ int r;
+
+ args->pitch = args->width * ((args->bpp + 1) / 8);
+ args->size = args->pitch * args->height;
+ args->size = ALIGN(args->size, PAGE_SIZE);
+
+ r = qxl_gem_object_create_with_handle(qdev, file_priv,
+ QXL_GEM_DOMAIN_VRAM,
+ args->size, &qobj,
+ &handle);
+ DRM_INFO("%s: width %d, height %d, bpp %d, pitch %d, size %lld, %s\n",
+ __func__, args->width, args->height, args->bpp,
+ args->pitch, args->size, r ? "failed" : "success");
+ if (r)
+ return r;
+ args->handle = handle;
+ DRM_INFO("%s: kptr %p\n", __func__, qobj->kptr);
+ return 0;
+}
+
+int qxl_mode_dumb_destroy(struct drm_file *file_priv,
+ struct drm_device *dev,
+ uint32_t handle)
+{
+ return drm_gem_handle_delete(file_priv, handle);
+}
+
+int qxl_mode_dumb_mmap(struct drm_file *file_priv,
+ struct drm_device *dev,
+ uint32_t handle, uint64_t *offset_p)
+{
+ struct drm_gem_object *gobj;
+ struct qxl_bo *qobj;
+
+ BUG_ON(!offset_p);
+ gobj = drm_gem_object_lookup(dev, file_priv, handle);
+ DRM_INFO("%s: %d, %s\n", __func__, handle, gobj ? "success" : "failed");
+ if (gobj == NULL)
+ return -ENOENT;
+ qobj = gem_to_qxl_bo(gobj);
+ *offset_p = qxl_bo_mmap_offset(qobj);
+ DRM_INFO("%s: %p, %lld| %lld, %ld, %ld\n", __func__, gobj, *offset_p,
+ qobj->tbo.addr_space_offset,
+ qobj->tbo.vm_node ?
+ qobj->tbo.vm_node->start : -1,
+ qobj->tbo.num_pages);
+ drm_gem_object_unreference_unlocked(gobj);
+ return 0;
+}
diff --git a/drivers/gpu/drm/qxl/qxl_fb.c b/drivers/gpu/drm/qxl/qxl_fb.c
new file mode 100644
index 00000000000..018d88de8d2
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_fb.c
@@ -0,0 +1,796 @@
+/*
+ * Copyright © 2007 David Airlie
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * David Airlie
+ */
+ /*
+ * Modularization
+ */
+
+#include <linux/module.h>
+#include <linux/fb.h>
+
+#include "drmP.h"
+#include "drm.h"
+#include "drm_crtc.h"
+#include "drm_crtc_helper.h"
+#include "qxl_drm.h"
+#include "qxl_drv.h"
+
+#include "qxl_object.h"
+#include "drm_fb_helper.h"
+
+#define QXL_DIRTY_DELAY (HZ / 30)
+
+struct qxl_fbdev {
+ struct drm_fb_helper helper;
+ struct qxl_framebuffer qfb;
+ struct list_head fbdev_list;
+ struct qxl_device *qdev;
+
+ /* dirty memory logging */
+ struct {
+ spinlock_t lock;
+ bool active;
+ unsigned x1;
+ unsigned y1;
+ unsigned x2;
+ unsigned y2;
+ } dirty;
+};
+
+static void qxl_fb_image_init(struct qxl_fb_image *qxl_fb_image,
+ struct qxl_device *qdev, struct fb_info *info,
+ const struct fb_image *image)
+{
+ qxl_fb_image->qdev = qdev;
+ if (info) {
+ qxl_fb_image->visual = info->fix.visual;
+ if (qxl_fb_image->visual == FB_VISUAL_TRUECOLOR ||
+ qxl_fb_image->visual == FB_VISUAL_DIRECTCOLOR)
+ memcpy(&qxl_fb_image->pseudo_palette,
+ info->pseudo_palette,
+ sizeof(qxl_fb_image->pseudo_palette));
+ } else {
+ /* fallback */
+ if (image->depth == 1)
+ qxl_fb_image->visual = FB_VISUAL_MONO10;
+ else
+ qxl_fb_image->visual = FB_VISUAL_DIRECTCOLOR;
+ }
+ if (image) {
+ memcpy(&qxl_fb_image->fb_image, image,
+ sizeof(qxl_fb_image->fb_image));
+ }
+}
+
+static void qxl_fb_dirty_flush(struct fb_info *info)
+{
+ struct qxl_fbdev *qfbdev = info->par;
+ struct qxl_device *qdev = qfbdev->qdev;
+ struct qxl_fb_image qxl_fb_image;
+ struct fb_image *image = &qxl_fb_image.fb_image;
+ u32 x1, x2, y1, y2;
+
+ /* TODO: hard coding 32 bpp */
+ int stride = qfbdev->qfb.base.pitches[0] * 4;
+
+ x1 = qfbdev->dirty.x1;
+ x2 = qfbdev->dirty.x2;
+ y1 = qfbdev->dirty.y1;
+ y2 = qfbdev->dirty.y2;
+ /*
+ * we are using a shadow draw buffer, at qdev->surface0_shadow
+ */
+ qxl_io_log(qdev, "dirty x[%d, %d], y[%d, %d]", x1, x2, y1, y2);
+ image->dx = x1;
+ image->dy = y1;
+ image->width = x2 - x1;
+ image->height = y2 - y1;
+ image->fg_color = 0xffffffff; /* unused, just to avoid uninitialized
+ warnings */
+ image->bg_color = 0;
+ image->depth = 32; /* TODO: take from somewhere? */
+ image->cmap.start = 0;
+ image->cmap.len = 0;
+ image->cmap.red = NULL;
+ image->cmap.green = NULL;
+ image->cmap.blue = NULL;
+ image->cmap.transp = NULL;
+ image->data = qdev->surface0_shadow + (x1 * 4) + (stride * y1);
+
+ qxl_fb_image_init(&qxl_fb_image, qdev, info, NULL);
+ qxl_draw_opaque_fb(&qxl_fb_image, stride);
+ qfbdev->dirty.x1 = 0;
+ qfbdev->dirty.x2 = 0;
+ qfbdev->dirty.y1 = 0;
+ qfbdev->dirty.y2 = 0;
+}
+
+static void qxl_deferred_io(struct fb_info *info,
+ struct list_head *pagelist)
+{
+ struct qxl_fbdev *qfbdev = info->par;
+ unsigned long start, end, min, max;
+ struct page *page;
+ int y1, y2;
+
+ min = ULONG_MAX;
+ max = 0;
+ list_for_each_entry(page, pagelist, lru) {
+ start = page->index << PAGE_SHIFT;
+ end = start + PAGE_SIZE - 1;
+ min = min(min, start);
+ max = max(max, end);
+ }
+
+ if (min < max) {
+ y1 = min / info->fix.line_length;
+ y2 = (max / info->fix.line_length) + 1;
+
+ /* TODO: add spin lock? */
+ /* spin_lock_irqsave(&qfbdev->dirty.lock, flags); */
+ qfbdev->dirty.x1 = 0;
+ qfbdev->dirty.y1 = y1;
+ qfbdev->dirty.x2 = info->var.xres;
+ qfbdev->dirty.y2 = y2;
+ /* spin_unlock_irqrestore(&qfbdev->dirty.lock, flags); */
+ }
+
+ qxl_fb_dirty_flush(info);
+};
+
+
+struct fb_deferred_io qxl_defio = {
+ .delay = QXL_DIRTY_DELAY,
+ .deferred_io = qxl_deferred_io,
+};
+
+static void qxl_fb_do_work_items(struct qxl_device *qdev);
+
+static void qxl_fb_fillrect(struct fb_info *info,
+ const struct fb_fillrect *fb_rect)
+{
+ struct qxl_fbdev *qfbdev = info->par;
+ struct qxl_device *qdev = qfbdev->qdev;
+ struct qxl_rect rect;
+ uint32_t color;
+ int x = fb_rect->dx;
+ int y = fb_rect->dy;
+ int width = fb_rect->width;
+ int height = fb_rect->height;
+ uint16_t rop;
+ struct qxl_draw_fill qxl_draw_fill_rec;
+
+ if (qxl_debug_disable_fb) {
+ QXL_INFO_ONCE(qdev, "(skipped) %s:%d\n", __func__, __LINE__);
+ return;
+ }
+
+ if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
+ info->fix.visual == FB_VISUAL_DIRECTCOLOR)
+ color = ((u32 *) (info->pseudo_palette))[fb_rect->color];
+ else
+ color = fb_rect->color;
+ rect.left = x;
+ rect.right = x + width;
+ rect.top = y;
+ rect.bottom = y + height;
+ switch (fb_rect->rop) {
+ case ROP_XOR:
+ rop = SPICE_ROPD_OP_XOR;
+ break;
+ case ROP_COPY:
+ rop = SPICE_ROPD_OP_PUT;
+ break;
+ default:
+ pr_err("qxl_fb_fillrect(): unknown rop, "
+ "defaulting to SPICE_ROPD_OP_PUT\n");
+ rop = SPICE_ROPD_OP_PUT;
+ }
+ qxl_draw_fill_rec.qdev = qdev;
+ qxl_draw_fill_rec.rect = rect;
+ qxl_draw_fill_rec.color = color;
+ qxl_draw_fill_rec.rop = rop;
+ if (in_interrupt()) {
+ qxl_io_log(qdev,
+ "%s: TODO use RCU, mysterious locks with spin_lock\n",
+ __func__);
+ return;
+#if 0
+ unsigned long flags;
+ spin_lock_irqsave(&qdev->fb_workqueue_spinlock, flags);
+ if (qxl_fb_queue_draw_fill(&qxl_draw_fill_rec)) {
+ qxl_io_log(qdev,
+ "%s: failed to queue work item in interrupt context.\n"
+ "fb_fillrect = %x\n", __func__, fb_rect);
+ }
+ spin_unlock_irqrestore(&qdev->fb_workqueue_spinlock, flags);
+ return;
+#endif
+ }
+ /* ensure proper order of rendering operations - TODO: must do this
+ * for everything. */
+ qxl_fb_do_work_items(qdev);
+ qxl_draw_fill(&qxl_draw_fill_rec);
+}
+
+static void qxl_fb_copyarea(struct fb_info *info,
+ const struct fb_copyarea *region)
+{
+ struct qxl_fbdev *qfbdev = info->par;
+
+ if (qxl_debug_disable_fb) {
+ QXL_INFO_ONCE(qfbdev->qdev, "skipped %s:%d\n",
+ __func__, __LINE__);
+ return;
+ }
+ qxl_draw_copyarea(qfbdev->qdev,
+ region->width, region->height,
+ region->sx, region->sy,
+ region->dx, region->dy);
+}
+
+#if 0
+static u32 checksum(struct fb_info *info)
+{
+ u8 *addr;
+ u32 sum = 0;
+ /* for now checksum the pixmap - later check other stuff */
+ for (addr = info->pixmap.addr;
+ addr < info->pixmap.addr + info->pixmap.size; ++addr)
+ sum += *addr;
+ return sum;
+}
+#endif
+
+static struct qxl_fb_work_item *get_work_item(struct qxl_device *qdev, int type)
+{
+ struct qxl_fb_work_item *item;
+
+ if (list_empty(&qdev->fb_work_item_free))
+ /* not much we can do, really - log this to qemu at least */
+ return NULL;
+ item = list_first_entry(&qdev->fb_work_item_free,
+ struct qxl_fb_work_item, head);
+ list_del(&item->head);
+ item->type = type;
+ return item;
+}
+
+static void push_fb_work_item(struct qxl_device *qdev,
+ struct qxl_fb_work_item *item)
+{
+ list_add_tail(&item->head, &qdev->fb_work_item_pending);
+ queue_work(qdev->fb_workqueue, &qdev->fb_work);
+}
+
+/* not static for debugfs */
+int qxl_fb_queue_draw_fill(struct qxl_draw_fill *qxl_draw_fill_rec)
+{
+ struct qxl_device *qdev = qxl_draw_fill_rec->qdev;
+ struct qxl_fb_work_item *item =
+ get_work_item(qdev, QXL_FB_WORK_ITEM_DRAW_FILL);
+
+ if (!item)
+ return -ENOMEM;
+ item->qxl_draw_fill = *qxl_draw_fill_rec;
+ push_fb_work_item(qdev, item);
+ return 0;
+}
+
+/* not static for debugfs */
+int qxl_fb_queue_imageblit(struct qxl_device *qdev,
+ struct qxl_fb_image *qxl_fb_image,
+ struct fb_info *info,
+ const struct fb_image *image)
+{
+ struct qxl_fb_work_item *item =
+ get_work_item(qdev, QXL_FB_WORK_ITEM_IMAGE);
+
+ if (!item)
+ return -ENOMEM;
+ if (!qxl_fb_image)
+ qxl_fb_image_init(&item->qxl_fb_image, qdev, info, image);
+ else /* debugfs path */
+ memcpy(&item->qxl_fb_image, qxl_fb_image,
+ sizeof(*qxl_fb_image));
+ push_fb_work_item(qdev, item);
+ return 0;
+}
+
+static void qxl_fb_imageblit_safe(struct qxl_fb_image *qxl_fb_image)
+{
+ qxl_draw_opaque_fb(qxl_fb_image, 0);
+}
+
+static void qxl_fb_imageblit(struct fb_info *info,
+ const struct fb_image *image)
+{
+ struct qxl_fbdev *qfbdev = info->par;
+ struct qxl_device *qdev = qfbdev->qdev;
+ struct qxl_fb_image qxl_fb_image;
+
+ if (qxl_debug_disable_fb) {
+ QXL_INFO_ONCE(qfbdev->qdev, "%s: skipping\n", __func__);
+ return;
+ }
+ if (in_interrupt()) {
+ /* we cannot do any ttm_bo allocation since that will fail on
+ * ioremap_wc..__get_vm_area_node, so queue the work item
+ * instead This can happen from printk inside an interrupt
+ * context, i.e.: smp_apic_timer_interrupt..check_cpu_stall */
+ qxl_io_log(qdev,
+ "%s: TODO use RCU, mysterious locks with spin_lock\n",
+ __func__);
+ return;
+#if 0
+ unsigned long flags;
+ spin_lock_irqsave(&qdev->fb_workqueue_spinlock, flags);
+ if (qxl_fb_queue_imageblit(qdev, NULL, info, image))
+ qxl_io_log(qdev,
+ "%s: failed to queue work item in interrupt context.\n"
+ "info = %x, image = %x\n", __func__, info, image);
+ spin_unlock_irqrestore(&qdev->fb_workqueue_spinlock, flags);
+ return;
+#endif
+ }
+ /* ensure proper order of rendering operations - TODO: must do this
+ * for everything. */
+ qxl_fb_do_work_items(qfbdev->qdev);
+ qxl_fb_image_init(&qxl_fb_image, qfbdev->qdev, info, image);
+ qxl_fb_imageblit_safe(&qxl_fb_image);
+}
+
+static void qxl_fb_do_work_items(struct qxl_device *qdev)
+{
+ struct qxl_fb_work_item *item;
+ struct qxl_fb_work_item *n;
+ unsigned long flags;
+
+ if (in_interrupt()) {
+ qxl_io_log(qdev, "%s: wtf - should not happen\n", __func__);
+ return;
+ }
+
+ spin_lock_irqsave(&qdev->fb_workqueue_spinlock, flags);
+ list_for_each_entry_safe(item, n, &qdev->fb_work_item_pending, head) {
+ spin_unlock_irqrestore(&qdev->fb_workqueue_spinlock, flags);
+ switch (item->type) {
+ case QXL_FB_WORK_ITEM_IMAGE:
+ qxl_fb_imageblit_safe(&item->qxl_fb_image);
+ break;
+ case QXL_FB_WORK_ITEM_DRAW_FILL:
+ qxl_draw_fill(&item->qxl_draw_fill);
+ break;
+ default:
+ qxl_io_log(qdev, "%s: invalid fb work item type %d\n",
+ item->type);
+ }
+ spin_lock_irqsave(&qdev->fb_workqueue_spinlock, flags);
+ list_del(&item->head);
+ list_add_tail(&item->head, &qdev->fb_work_item_free);
+ spin_unlock_irqrestore(&qdev->fb_workqueue_spinlock, flags);
+ }
+ spin_unlock_irqrestore(&qdev->fb_workqueue_spinlock, flags);
+}
+
+static void qxl_fb_work(struct work_struct *work)
+{
+ struct qxl_device *qdev =
+ container_of(work, struct qxl_device, fb_work);
+
+ qxl_fb_do_work_items(qdev);
+}
+
+int qxl_fb_init(struct qxl_device *qdev)
+{
+ int i;
+
+ qdev->fb_workqueue = alloc_workqueue("qxl_fb", WQ_NON_REENTRANT, 0);
+ if (!qdev->fb_workqueue)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&qdev->fb_work_item_pending);
+ INIT_LIST_HEAD(&qdev->fb_work_item_free);
+ INIT_WORK(&qdev->fb_work, qxl_fb_work);
+ spin_lock_init(&qdev->fb_workqueue_spinlock);
+ /* no kmalloc in interrupt context - not sure if I'm fixing a problem
+ * or introducing a limitation */
+ for (i = 0 ; i < sizeof(qdev->fb_work_items)
+ / sizeof(qdev->fb_work_items[0]) ; ++i) {
+ list_add_tail(&qdev->fb_work_items[i].head,
+ &qdev->fb_work_item_free);
+ }
+ return 0;
+}
+
+
+struct qxl_fb_par {
+ u32 pseudo_palette[17]; /* why not 257? Xorg wants 256 elements. */
+};
+
+static int qxl_fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ return 0;
+}
+
+static int qxl_fb_blank(int blank, struct fb_info *info)
+{
+ return 0;
+}
+
+static struct fb_ops qxlfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = drm_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par, /* TODO: copy vmwgfx */
+ .fb_fillrect = qxl_fb_fillrect,
+ .fb_copyarea = qxl_fb_copyarea,
+ .fb_imageblit = qxl_fb_imageblit,
+ .fb_pan_display = qxl_fb_pan_display,
+ .fb_blank = qxl_fb_blank,
+ .fb_setcmap = drm_fb_helper_setcmap,
+ .fb_debug_enter = drm_fb_helper_debug_enter,
+ .fb_debug_leave = drm_fb_helper_debug_leave,
+};
+
+static void qxlfb_destroy_pinned_object(struct drm_gem_object *gobj)
+{
+ struct qxl_bo *qbo = gem_to_qxl_bo(gobj);
+ int ret;
+
+ ret = qxl_bo_reserve(qbo, false);
+ if (likely(ret == 0)) {
+ qxl_bo_kunmap(qbo);
+ qxl_bo_unpin(qbo);
+ qxl_bo_unreserve(qbo);
+ }
+ drm_gem_object_unreference_unlocked(gobj);
+}
+
+int qxl_get_handle_for_primary_fb(struct qxl_device *qdev,
+ struct drm_file *file_priv,
+ uint32_t *handle)
+{
+ int r;
+ struct drm_gem_object *gobj = qdev->fbdev_qfb->obj;
+
+ BUG_ON(!gobj);
+ /* drm_get_handle_create adds a reference - good */
+ r = drm_gem_handle_create(file_priv, gobj, handle);
+ if (r)
+ return r;
+ return 0;
+}
+
+static int qxlfb_create_pinned_object(struct qxl_fbdev *qfbdev,
+ struct drm_mode_fb_cmd2 *mode_cmd,
+ struct drm_gem_object **gobj_p)
+{
+ struct qxl_device *qdev = qfbdev->qdev;
+ struct drm_gem_object *gobj = NULL;
+ struct qxl_bo *qbo = NULL;
+ int ret;
+ int aligned_size, size;
+ int height = mode_cmd->height;
+ int bpp;
+ int depth;
+
+ drm_fb_get_bpp_depth(mode_cmd->pixel_format, &bpp, &depth);
+
+ size = mode_cmd->pitches[0] * height;
+ aligned_size = ALIGN(size, PAGE_SIZE);
+ /* TODO: unallocate and reallocate surface0 for real. Hack to just
+ * have a large enough surface0 for 1024x768 Xorg 32bpp mode */
+ ret = qxl_gem_object_create(qdev, 1024*768*4 /* aligned_size */, 0,
+ QXL_GEM_DOMAIN_VRAM,
+ false, /* is discardable */
+ false, /* is kernel (false means device) */
+ &gobj);
+ if (ret) {
+ pr_err("failed to allocate framebuffer (%d)\n",
+ aligned_size);
+ return -ENOMEM;
+ }
+ qbo = gem_to_qxl_bo(gobj);
+
+ ret = qxl_bo_reserve(qbo, false);
+ if (unlikely(ret != 0))
+ goto out_unref;
+ ret = qxl_bo_pin(qbo, QXL_GEM_DOMAIN_VRAM, NULL);
+ if (ret) {
+ qxl_bo_unreserve(qbo);
+ goto out_unref;
+ }
+ ret = qxl_bo_kmap(qbo, NULL);
+ qxl_bo_unreserve(qbo); /* unreserve, will be mmaped */
+ if (ret)
+ goto out_unref;
+
+ *gobj_p = gobj;
+ return 0;
+out_unref:
+ qxlfb_destroy_pinned_object(gobj);
+ *gobj_p = NULL;
+ return ret;
+}
+
+static int qxlfb_create(struct qxl_fbdev *qfbdev,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct qxl_device *qdev = qfbdev->qdev;
+ struct fb_info *info;
+ struct drm_framebuffer *fb = NULL;
+ struct drm_mode_fb_cmd2 mode_cmd;
+ struct drm_gem_object *gobj = NULL;
+ struct qxl_bo *qbo = NULL;
+ struct device *device = &qdev->pdev->dev;
+ int ret;
+ int size;
+ int bpp = sizes->surface_bpp;
+ int depth = sizes->surface_depth;
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+
+ mode_cmd.pitches[0] = ALIGN(mode_cmd.width * ((bpp + 1) / 8), 64);
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp, depth);
+
+ ret = qxlfb_create_pinned_object(qfbdev, &mode_cmd, &gobj);
+ qdev->surface0_bo = qbo = gem_to_qxl_bo(gobj);
+ QXL_INFO(qdev, "%s: %dx%d %d\n", __func__, mode_cmd.width,
+ mode_cmd.height, mode_cmd.pitches[0]);
+ qdev->surface0_shadow =
+ kmalloc(mode_cmd.pitches[0] * mode_cmd.height, GFP_KERNEL);
+ /* TODO: what's the usual response to memory allocation errors? */
+ BUG_ON(!qdev->surface0_shadow);
+ QXL_INFO(qdev,
+ "surface0 at gpu offset %lld, mmap_offset %lld (virt %p, shadow %p)\n",
+ qxl_bo_gpu_offset(qdev->surface0_bo),
+ qxl_bo_mmap_offset(qdev->surface0_bo),
+ qdev->surface0_bo->kptr,
+ qdev->surface0_shadow);
+ size = mode_cmd.pitches[0] * mode_cmd.height;
+
+ info = framebuffer_alloc(0, device);
+ if (info == NULL) {
+ ret = -ENOMEM;
+ goto out_unref;
+ }
+
+ info->par = qfbdev;
+
+ qxl_framebuffer_init(qdev->ddev, &qfbdev->qfb, &mode_cmd, gobj);
+
+ fb = &qfbdev->qfb.base;
+
+ /* setup helper with fb data */
+ qfbdev->helper.fb = fb;
+ qfbdev->helper.fbdev = info;
+
+ strcpy(info->fix.id, "qxldrmfb");
+
+ drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
+
+ info->flags = FBINFO_DEFAULT;
+ info->fbops = &qxlfb_ops;
+
+ /*
+ * TODO: using gobj->size in various places in this function. Not sure
+ * what the difference between the different sizes is.
+ */
+ info->fix.smem_start = qdev->vram_base; /* TODO - correct? */
+ info->fix.smem_len = gobj->size;
+ info->screen_base = qdev->surface0_shadow;
+ info->screen_size = gobj->size;
+
+ drm_fb_helper_fill_var(info, &qfbdev->helper, sizes->fb_width,
+ sizes->fb_height);
+
+ /* setup aperture base/size for vesafb takeover */
+ info->apertures = alloc_apertures(1);
+ if (!info->apertures) {
+ ret = -ENOMEM;
+ goto out_unref;
+ }
+ info->apertures->ranges[0].base = qdev->ddev->mode_config.fb_base;
+ info->apertures->ranges[0].size = qdev->vram_size;
+
+ info->fix.mmio_start = 0;
+ info->fix.mmio_len = 0;
+ /* TODO: not correct - must be taken from pci */
+ info->pixmap.size = 64*1024;
+ info->pixmap.buf_align = 8;
+ info->pixmap.access_align = 32;
+ info->pixmap.flags = FB_PIXMAP_SYSTEM;
+ info->pixmap.scan_align = 1;
+ if (info->screen_base == NULL) {
+ ret = -ENOSPC;
+ goto out_unref;
+ }
+
+ ret = fb_alloc_cmap(&info->cmap, 256, 0);
+ if (ret) {
+ ret = -ENOMEM;
+ goto out_unref;
+ }
+
+ info->fbdefio = &qxl_defio;
+ fb_deferred_io_init(info);
+
+ qdev->fbdev_info = info;
+ qdev->fbdev_qfb = &qfbdev->qfb;
+ DRM_INFO("fb mappable at 0x%lX\n", info->fix.smem_start);
+ DRM_INFO("size %lu\n", (unsigned long)info->screen_size);
+ DRM_INFO("fb depth is %d\n", fb->depth);
+ DRM_INFO(" pitch is %d\n", fb->pitches[0]);
+ DRM_INFO(" width is %d\n", fb->width);
+ DRM_INFO(" height is %d\n", fb->height);
+
+ return 0;
+
+out_unref:
+ if (qbo) {
+ ret = qxl_bo_reserve(qbo, false);
+ if (likely(ret == 0)) {
+ qxl_bo_kunmap(qbo);
+ qxl_bo_unpin(qbo);
+ qxl_bo_unreserve(qbo);
+ }
+ }
+ if (fb && ret) {
+ drm_gem_object_unreference(gobj);
+ drm_framebuffer_cleanup(fb);
+ kfree(fb);
+ }
+ drm_gem_object_unreference(gobj);
+ return ret;
+}
+
+static int qxl_fb_find_or_create_single(
+ struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct qxl_fbdev *qfbdev = (struct qxl_fbdev *)helper;
+ int new_fb = 0;
+ int ret;
+
+ if (!helper->fb) {
+ pr_info("%s:%d\n", __func__, __LINE__);
+ ret = qxlfb_create(qfbdev, sizes);
+ pr_info("%s:%d %d\n", __func__, __LINE__, ret);
+ if (ret)
+ return ret;
+ new_fb = 1;
+ }
+ return new_fb;
+}
+
+static char *mode_option;
+int qxl_parse_options(char *options)
+{
+ char *this_opt;
+
+ if (!options || !*options)
+ return 0;
+
+ while ((this_opt = strsep(&options, ",")) != NULL) {
+ if (!*this_opt)
+ continue;
+ mode_option = this_opt;
+ }
+ return 0;
+}
+
+static int qxl_fbdev_destroy(struct drm_device *dev, struct qxl_fbdev *qfbdev)
+{
+ struct fb_info *info;
+ struct qxl_framebuffer *qfb = &qfbdev->qfb;
+
+ if (qfbdev->helper.fbdev) {
+ info = qfbdev->helper.fbdev;
+
+ unregister_framebuffer(info);
+ framebuffer_release(info);
+ }
+ if (qfb->obj) {
+ qxlfb_destroy_pinned_object(qfb->obj);
+ qfb->obj = NULL;
+ }
+ drm_fb_helper_fini(&qfbdev->helper);
+ drm_framebuffer_cleanup(&qfb->base);
+
+ return 0;
+}
+
+static struct drm_fb_helper_funcs qxl_fb_helper_funcs = {
+ /* TODO
+ .gamma_set = qxl_crtc_fb_gamma_set,
+ .gamma_get = qxl_crtc_fb_gamma_get,
+ */
+ .fb_probe = qxl_fb_find_or_create_single,
+};
+
+/* TODO: defio
+ * How does that work? can I get the changed data to send as opaques?
+ * Otherwise I need to invalidate an area - counter to qxl phylosophy.
+ */
+
+int qxl_fbdev_init(struct qxl_device *qdev)
+{
+ struct qxl_fbdev *qfbdev;
+ int bpp_sel = 32; /* TODO: parameter from somewhere? */
+ int ret;
+
+ qfbdev = kzalloc(sizeof(struct qxl_fbdev), GFP_KERNEL);
+ if (!qfbdev)
+ return -ENOMEM;
+
+ qfbdev->qdev = qdev;
+ qdev->mode_info.qfbdev = qfbdev;
+ qfbdev->helper.funcs = &qxl_fb_helper_funcs;
+
+ ret = drm_fb_helper_init(qdev->ddev, &qfbdev->helper,
+ 1 /* num_crtc - QXL supports just 1 */,
+ QXLFB_CONN_LIMIT);
+ if (ret) {
+ kfree(qfbdev);
+ return ret;
+ }
+
+ drm_fb_helper_single_add_all_connectors(&qfbdev->helper);
+ drm_fb_helper_initial_config(&qfbdev->helper, bpp_sel);
+ return 0;
+}
+
+void qxl_fbdev_fini(struct qxl_device *qdev)
+{
+ if (!qdev->mode_info.qfbdev)
+ return;
+
+ qxl_fbdev_destroy(qdev->ddev, qdev->mode_info.qfbdev);
+ kfree(qdev->mode_info.qfbdev);
+ qdev->mode_info.qfbdev = NULL;
+}
+
+void qxl_fbdev_set_suspend(struct qxl_device *qdev, int state)
+{
+ fb_set_suspend(qdev->mode_info.qfbdev->helper.fbdev, state);
+}
+
+int qxl_fbdev_total_size(struct qxl_device *qdev)
+{
+ struct qxl_bo *robj;
+ int size = 0;
+
+ robj = gem_to_qxl_bo(qdev->mode_info.qfbdev->qfb.obj);
+ size += qxl_bo_size(robj);
+ return size;
+}
+
+bool qxl_fbdev_robj_is_fb(struct qxl_device *qdev, struct qxl_bo *robj)
+{
+ if (robj == gem_to_qxl_bo(qdev->mode_info.qfbdev->qfb.obj))
+ return true;
+ return false;
+}
diff --git a/drivers/gpu/drm/qxl/qxl_gem.c b/drivers/gpu/drm/qxl/qxl_gem.c
new file mode 100644
index 00000000000..7ef7b6f1963
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_gem.c
@@ -0,0 +1,155 @@
+#include "drmP.h"
+#include "drm.h"
+#include "qxl_drm.h"
+#include "qxl_drv.h"
+#include "qxl_object.h"
+
+int qxl_gem_object_init(struct drm_gem_object *obj)
+{
+ /* we do nothings here */
+ return 0;
+}
+
+void qxl_gem_object_free(struct drm_gem_object *gobj)
+{
+ struct qxl_bo *qobj = gem_to_qxl_bo(gobj);
+
+ if (qobj) {
+ /* or unref? */
+ qxl_bo_free(qobj);
+ }
+}
+
+int qxl_gem_object_create(struct qxl_device *qdev, int size,
+ int alignment, int initial_domain,
+ bool discardable, bool kernel,
+ struct drm_gem_object **obj)
+{
+ struct qxl_bo *qbo;
+ int r;
+
+ *obj = NULL;
+ /* At least align on page size */
+ if (alignment < PAGE_SIZE)
+ alignment = PAGE_SIZE;
+ r = qxl_bo_create(qdev, size, kernel, initial_domain, &qbo);
+ if (r) {
+ if (r != -ERESTARTSYS)
+ DRM_ERROR(
+ "Failed to allocate GEM object (%d, %d, %u, %d)\n",
+ size, initial_domain, alignment, r);
+ return r;
+ }
+ *obj = &qbo->gem_base;
+
+ mutex_lock(&qbo->qdev->gem.mutex);
+ list_add_tail(&qbo->list, &qdev->gem.objects);
+ mutex_unlock(&qbo->qdev->gem.mutex);
+
+ return 0;
+}
+
+int qxl_gem_object_create_with_handle(struct qxl_device *qdev,
+ struct drm_file *file_priv,
+ u32 domain,
+ size_t size,
+ struct qxl_bo **qobj,
+ uint32_t *handle)
+{
+ struct drm_gem_object *gobj;
+ int r;
+
+ BUG_ON(!qobj);
+ BUG_ON(!handle);
+
+ r = qxl_gem_object_create(qdev, size, 0,
+ domain,
+ false, false,
+ &gobj);
+ if (r)
+ return -ENOMEM;
+ r = drm_gem_handle_create(file_priv, gobj, handle);
+ if (r)
+ return r;
+ /* drop reference from allocate - handle holds it now */
+ drm_gem_object_unreference_unlocked(gobj);
+ *qobj = gem_to_qxl_bo(gobj);
+ qxl_bo_kmap(*qobj, NULL);
+ return 0;
+}
+
+int qxl_gem_object_pin(struct drm_gem_object *obj, uint32_t pin_domain,
+ uint64_t *gpu_addr)
+{
+ struct qxl_bo *qobj = obj->driver_private;
+ int r;
+
+ r = qxl_bo_reserve(qobj, false);
+ if (unlikely(r != 0))
+ return r;
+ r = qxl_bo_pin(qobj, pin_domain, gpu_addr);
+ qxl_bo_unreserve(qobj);
+ return r;
+}
+
+void qxl_gem_object_unpin(struct drm_gem_object *obj)
+{
+ struct qxl_bo *qobj = obj->driver_private;
+ int r;
+
+ r = qxl_bo_reserve(qobj, false);
+ if (likely(r == 0)) {
+ qxl_bo_unpin(qobj);
+ qxl_bo_unreserve(qobj);
+ }
+}
+
+int qxl_gem_set_domain(struct drm_gem_object *gobj,
+ uint32_t rdomain, uint32_t wdomain)
+{
+ struct qxl_bo *qobj;
+ uint32_t domain;
+ int r;
+
+ /* FIXME: reeimplement */
+ qobj = gobj->driver_private;
+ /* work out where to validate the buffer to */
+ domain = wdomain;
+ if (!domain)
+ domain = rdomain;
+ if (!domain) {
+ /* Do nothings */
+ pr_warn("Set domain withou domain !\n");
+ return 0;
+ }
+ if (domain == QXL_GEM_DOMAIN_CPU) {
+ /* Asking for cpu access wait for object idle */
+ r = qxl_bo_wait(qobj, NULL, false);
+ if (r) {
+ pr_err("Failed to wait for object !\n");
+ return r;
+ }
+ }
+ return 0;
+}
+
+int qxl_gem_object_open(struct drm_gem_object *obj, struct drm_file *file_priv)
+{
+ return 0;
+}
+
+void qxl_gem_object_close(struct drm_gem_object *obj,
+ struct drm_file *file_priv)
+{
+}
+
+int qxl_gem_init(struct qxl_device *qdev)
+{
+ INIT_LIST_HEAD(&qdev->gem.objects);
+ return 0;
+}
+
+void qxl_gem_fini(struct qxl_device *qdev)
+{
+ qxl_bo_force_delete(qdev);
+}
diff --git a/drivers/gpu/drm/qxl/qxl_image.c b/drivers/gpu/drm/qxl/qxl_image.c
new file mode 100644
index 00000000000..1b5941ba077
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_image.c
@@ -0,0 +1,306 @@
+#include <linux/gfp.h>
+#include <linux/slab.h>
+#if 0
+#include <linux/jhash.h>
+#endif
+
+#include "qxl_drv.h"
+
+struct image_info {
+ struct qxl_image *image;
+ int ref_count;
+ struct image_info *next;
+};
+
+#define HASH_SIZE 4096
+static struct image_info *image_table[HASH_SIZE];
+
+#if 0
+static unsigned int
+hash_and_copy(const uint8_t *src, int src_stride,
+ uint8_t *dest, int dest_stride,
+ int width, int height)
+{
+ int i, j;
+ unsigned int hash = 0;
+
+ for (i = 0; i < height; ++i) {
+ const uint8_t *src_line = src + i * src_stride;
+ uint8_t *dest_line = dest + i * dest_stride;
+
+ for (j = 0; j < width; ++j) {
+ uint32_t *s = (uint32_t *)src_line;
+ uint32_t *d = (uint32_t *)dest_line;
+
+ if (dest)
+ d[j] = s[j];
+ }
+
+ hash = jhash(src_line, width * sizeof(uint32_t), hash);
+ }
+
+ return hash;
+}
+
+static struct image_info *
+lookup_image_info(unsigned int hash,
+ int width,
+ int height)
+{
+ struct image_info *info = image_table[hash % HASH_SIZE];
+
+ while (info) {
+ struct qxl_image *image = info->image;
+
+ if (image->descriptor.id == hash &&
+ image->descriptor.width == width &&
+ image->descriptor.height == height) {
+ return info;
+ }
+
+ info = info->next;
+ }
+
+ return NULL;
+}
+#endif
+
+static struct image_info *
+insert_image_info(unsigned int hash)
+{
+ struct image_info *info =
+ kmalloc(sizeof(struct image_info), GFP_KERNEL);
+
+ if (!info)
+ return NULL;
+
+ info->next = image_table[hash % HASH_SIZE];
+ image_table[hash % HASH_SIZE] = info;
+
+ return info;
+}
+
+#if 0
+static void
+remove_image_info(struct image_info *info)
+{
+ struct image_info **location =
+ &image_table[info->image->descriptor.id % HASH_SIZE];
+
+ while (*location && (*location) != info)
+ location = &((*location)->next);
+
+ if (*location)
+ *location = info->next;
+
+ kfree(info);
+}
+#endif
+
+#if 0
+void debug_fill(uint8_t *data, int width, int height)
+{
+ int xx, yy;
+ uint32_t *p;
+ static int count;
+
+ for (yy = 0, p = data ; yy < height; ++yy) {
+ for (xx = 0 ; xx < width ; ++xx) {
+ (*p++) = (((count + yy) & 0xff) << 24) |
+ (((count + yy) & 0xff) << 16) |
+ (((count + yy) & 0xff) << 8) |
+ ((count + yy) & 0xff);
+ }
+ }
+ ++count;
+}
+#endif
+
+
+static struct qxl_image *
+qxl_image_create_helper(struct qxl_device *qdev,
+ struct drm_qxl_release *release,
+ struct qxl_bo **image_bo,
+ const uint8_t *data,
+ int width, int height,
+ int depth, unsigned int hash,
+ int stride)
+{
+ struct qxl_image *image;
+ struct qxl_data_chunk *chunk;
+ struct image_info *info;
+ int i;
+ int chunk_stride;
+ int linesize = width * depth / 8;
+ struct qxl_bo *chunk_bo;
+
+ /* Chunk */
+ /* FIXME: Check integer overflow */
+ /* TODO: variable number of chunks */
+ chunk_stride = stride; /* TODO: should use linesize, but it renders
+ wrong (check the bitmaps are sent correctly
+ first) */
+ chunk = qxl_allocnf(qdev, sizeof(*chunk) + height * chunk_stride,
+ release);
+ chunk_bo = release->bos[release->bo_count - 1];
+
+ chunk->data_size = height * chunk_stride;
+ chunk->prev_chunk = 0;
+ chunk->next_chunk = 0;
+
+#if 0
+ qxl_io_log(qdev, "%s: stride %d, chunk_s %d, linesize %d; "
+ "chunk %p, chunk->data %p, data %p\n",
+ __func__, stride, chunk_stride, linesize, chunk,
+ chunk ? chunk->data : NULL, data);
+#endif
+ /* TODO: put back hashing */
+ if (stride == linesize && chunk_stride == stride)
+ memcpy(chunk->data, data, linesize * height);
+ else
+ for (i = 0 ; i < height ; ++i)
+ memcpy(chunk->data + i*chunk_stride, data + i*stride,
+ linesize);
+ /* Image */
+ image = qxl_allocnf(qdev, sizeof(*image), release);
+ *image_bo = release->bos[release->bo_count - 1];
+
+ image->descriptor.id = 0;
+ image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
+
+ image->descriptor.flags = 0;
+ image->descriptor.width = width;
+ image->descriptor.height = height;
+
+ switch (depth) {
+ case 1:
+ /* TODO: BE? check by arch? */
+ image->u.bitmap.format = SPICE_BITMAP_FMT_1BIT_BE;
+ break;
+ case 24:
+ image->u.bitmap.format = SPICE_BITMAP_FMT_24BIT;
+ break;
+ case 32:
+ image->u.bitmap.format = SPICE_BITMAP_FMT_32BIT;
+ break;
+ default:
+ DRM_ERROR("unsupported image bit depth\n");
+ return NULL; /* TODO: cleanup */
+ }
+ image->u.bitmap.flags = QXL_BITMAP_TOP_DOWN;
+ image->u.bitmap.x = width;
+ image->u.bitmap.y = height;
+ image->u.bitmap.stride = chunk_stride;
+ image->u.bitmap.palette = 0;
+ image->u.bitmap.data = qxl_fb_physical_address(qdev, chunk_bo);
+
+ /* Add to hash table */
+ if (!hash) {
+ QXL_INFO(qdev, "%s: id %d has size %d %d\n", __func__,
+ image->descriptor.id, width, height);
+
+ return image;
+ }
+ info = insert_image_info(hash);
+ if (info) {
+ info->image = image;
+ info->ref_count = 1;
+
+ image->descriptor.id = hash;
+ image->descriptor.flags = QXL_IMAGE_CACHE;
+
+ }
+ QXL_INFO(qdev, "%s: id %d has size %d %d\n", __func__,
+ image->descriptor.id, width, height);
+ return image;
+}
+
+struct qxl_image *qxl_image_create(struct qxl_device *qdev,
+ struct drm_qxl_release *release,
+ struct qxl_bo **image_bo,
+ const uint8_t *data,
+ int x, int y, int width, int height,
+ int depth, int stride)
+{
+#if 0
+ unsigned int hash;
+ struct image_info *info;
+#endif
+
+ data += y * stride + x * (depth / 8);
+ /* TODO: return hashing */
+ return qxl_image_create_helper(qdev, release, image_bo, data,
+ width, height, depth, 0, stride);
+
+#if 0
+ hash = hash_and_copy(data, stride, NULL, -1, width, height);
+
+ info = lookup_image_info(hash, width, height);
+ if (info) {
+ int i, j;
+ qxl_io_log(qdev, "%s: image found in cache\n", __func__);
+
+ info->ref_count++;
+
+ for (i = 0; i < height; ++i) {
+ struct qxl_data_chunk *chunk;
+ const uint8_t *src_line = data + i * stride;
+ uint32_t *dest_line;
+
+ chunk = qxl_fb_virtual_address(qdev,
+ info->image->u.bitmap.data);
+
+ dest_line = (uint32_t *)chunk->data + width * i;
+
+ for (j = 0; j < width; ++j) {
+ uint32_t *s = (uint32_t *)src_line;
+ uint32_t *d = (uint32_t *)dest_line;
+
+ if (d[j] != s[j])
+ goto out;
+ }
+ }
+out:
+ return info->image;
+ } else
+ return qxl_image_create_helper(qdev, release, image_bo, data,
+ width, height, depth, hash,
+ stride);
+#endif
+}
+
+void qxl_image_destroy(struct qxl_device *qdev,
+ struct qxl_image *image)
+{
+#if 0
+ struct qxl_data_chunk *chunk;
+ struct image_info *info;
+
+ chunk = qxl_virtual_address(qdev, (void *)image->u.bitmap.data);
+
+ info = lookup_image_info(image->descriptor.id,
+ image->descriptor.width,
+ image->descriptor.height);
+
+ if (info && info->image == image) {
+ --info->ref_count;
+
+ if (info->ref_count != 0)
+ return;
+
+#if 0
+ ErrorF("removed %p from hash table\n", info->image);
+#endif
+
+ remove_image_info(info);
+ }
+
+ qxl_free(qdev->mem, chunk);
+ qxl_free(qdev->mem, image);
+#endif
+}
+
+void qxl_drop_image_cache(struct qxl_device *qdev)
+{
+ memset(image_table, 0, HASH_SIZE * sizeof(struct image_info *));
+}
diff --git a/drivers/gpu/drm/qxl/qxl_ioctl.c b/drivers/gpu/drm/qxl/qxl_ioctl.c
new file mode 100644
index 00000000000..b5ec64d0f00
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_ioctl.c
@@ -0,0 +1,276 @@
+#include "qxl_drv.h"
+
+/*
+ * TODO: allocating a new gem(in qxl_bo) for each request.
+ * This is wasteful since bo's are page aligned.
+ */
+int qxl_alloc_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct qxl_device *qdev = dev->dev_private;
+ struct drm_qxl_alloc *qxl_alloc = data;
+ int ret;
+ /*
+ * TODO: actually take note of the drm_qxl_alloc->type flag, except for
+ * the primary
+ * surface creation (i.e. use the surfaces bar)
+ */
+
+ if (qxl_alloc->size == 0) {
+ DRM_ERROR("invalid size %d\n", qxl_alloc->size);
+ return -EINVAL;
+ }
+ switch (qxl_alloc->type) {
+ case QXL_ALLOC_TYPE_SURFACE_PRIMARY:
+ /*
+ * TODO: would be nice if the primary always had a handle of 1,
+ * but for that we would need to change drm gem code a little,
+ * so probably not worth it. Note: The size is a actually
+ * ignored here, we return a handle to the primary surface BO
+ */
+ ret = qxl_get_handle_for_primary_fb(qdev, file_priv,
+ &qxl_alloc->handle);
+ if (ret) {
+ DRM_ERROR("%s: failed to allocate handle for primary"
+ "fb gem object %p\n", __func__,
+ qdev->fbdev_qfb->obj);
+ }
+ break;
+ case QXL_ALLOC_TYPE_SURFACE:
+ case QXL_ALLOC_TYPE_DATA: {
+ struct qxl_bo *qobj;
+ uint32_t handle;
+ u32 domain = qxl_alloc->type == QXL_ALLOC_TYPE_SURFACE ?
+ QXL_GEM_DOMAIN_SURFACE : QXL_GEM_DOMAIN_VRAM;
+ ret = qxl_gem_object_create_with_handle(qdev, file_priv,
+ domain,
+ qxl_alloc->size,
+ &qobj, &handle);
+ if (ret) {
+ DRM_ERROR("%s: failed to create gem ret=%d\n",
+ __func__, ret);
+ return -ENOMEM;
+ }
+ qxl_alloc->handle = handle;
+ break;
+ }
+ default:
+ DRM_ERROR("%s: unexpected alloc type %d\n", __func__,
+ qxl_alloc->type);
+ return -EINVAL;
+ break;
+ }
+ return 0;
+}
+
+int qxl_incref_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_qxl_incref *incref = data;
+ struct drm_gem_object *gobj;
+
+ /* this takes a reference */
+ gobj = drm_gem_object_lookup(dev, file_priv, incref->handle);
+
+ if (!gobj) {
+ DRM_ERROR("%s: invalid handle %u\n", __func__, incref->handle);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int qxl_decref_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct drm_qxl_decref *decref = data;
+ struct drm_gem_object *gobj;
+
+ gobj = drm_gem_object_lookup(dev, file_priv, decref->handle);
+ if (!gobj) {
+ DRM_ERROR("%s: invalid handle %u\n", __func__, decref->handle);
+ return -EINVAL;
+ }
+ /* remove reference taken by lookup */
+ drm_gem_object_unreference_unlocked(gobj);
+
+ drm_gem_object_unreference_unlocked(gobj);
+ return 0;
+}
+
+int qxl_map_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct qxl_device *qdev = dev->dev_private;
+ struct drm_qxl_map *qxl_map = data;
+
+ DRM_INFO("%s: handle in %d\n", __func__, qxl_map->handle);
+ return qxl_mode_dumb_mmap(file_priv, qdev->ddev, qxl_map->handle,
+ &qxl_map->offset);
+}
+
+/*
+ * dst must be validated, i.e. whole bo on vram/surfacesram (right now all bo's
+ * are on vram).
+ * *(src + src_off) = qxl_bo_physical_address(dst, dst_off)
+ */
+static void
+apply_reloc(struct qxl_device *qdev, struct qxl_bo *src, uint64_t src_off,
+ struct qxl_bo *dst, uint64_t dst_off)
+{
+ *(uint64_t *)(src->kptr + src_off) = qxl_bo_physical_address(qdev,
+ dst, dst_off, qdev->main_mem_slot);
+}
+
+struct qxl_bo *qxlhw_handle_to_bo(struct qxl_device *qdev,
+ struct drm_file *file_priv, uint64_t handle,
+ struct qxl_bo *handle_0_bo)
+{
+ struct drm_gem_object *gobj;
+ struct qxl_bo *qobj;
+
+ if (handle == 0)
+ return handle_0_bo;
+ gobj = drm_gem_object_lookup(qdev->ddev, file_priv, handle);
+ if (!gobj) {
+ DRM_ERROR("bad bo handle %lld\n", handle);
+ return NULL;
+ }
+ qobj = gem_to_qxl_bo(gobj);
+ drm_gem_object_unreference_unlocked(gobj);
+ return qobj;
+}
+
+/*
+ * Usage of execbuffer:
+ * Relocations need to take into account the full QXLDrawable size.
+ * However, the command as passed from user space must *not* contain the initial
+ * QXLReleaseInfo struct (first XXX bytes)
+ */
+int qxl_execbuffer_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct qxl_device *qdev = dev->dev_private;
+ struct drm_qxl_execbuffer *execbuffer = data;
+ struct drm_qxl_command user_cmd;
+ int cmd_num;
+ struct qxl_bo *reloc_src_bo;
+ struct qxl_bo *reloc_dst_bo;
+ struct drm_qxl_reloc reloc;
+ void *fb_cmd;
+ int i;
+
+ for (cmd_num = 0; cmd_num < execbuffer->commands_num; ++cmd_num) {
+ struct drm_qxl_release *release;
+ struct qxl_bo *cmd_bo;
+ int release_type;
+ int is_cursor;
+ struct drm_qxl_command *commands =
+ (struct drm_qxl_command *)execbuffer->commands;
+
+ if (DRM_COPY_FROM_USER(&user_cmd, &commands[cmd_num],
+ sizeof(user_cmd)))
+ return -EFAULT;
+ switch (user_cmd.type) {
+ case QXL_CMD_DRAW:
+ release_type = QXL_RELEASE_DRAWABLE;
+ is_cursor = 0;
+ break;
+ case QXL_CMD_SURFACE:
+ release_type = QXL_RELEASE_SURFACE_CMD;
+ is_cursor = 0;
+ break;
+ case QXL_CMD_CURSOR:
+ release_type = QXL_RELEASE_CURSOR_CMD;
+ is_cursor = 1;
+ break;
+ default:
+ qxl_io_log(qdev,
+ "%s: bad command %d not in {%d, %d, %d}\n",
+ __func__, user_cmd.type,
+ QXL_CMD_DRAW, QXL_CMD_SURFACE,
+ QXL_CMD_CURSOR);
+ return -EFAULT;
+ }
+ fb_cmd = qxl_alloc_releasable(qdev,
+ sizeof(union qxl_release_info) +
+ user_cmd.command_size,
+ release_type,
+ &release,
+ &cmd_bo);
+ if (DRM_COPY_FROM_USER(fb_cmd + sizeof(union qxl_release_info),
+ (void *)user_cmd.command,
+ user_cmd.command_size))
+ return -EFAULT;
+ qxl_io_log(qdev, "%s: type %d, size %d, #relocs %d\n",
+ __func__, user_cmd.type,
+ user_cmd.command_size, user_cmd.relocs_num);
+ for (i = 0 ; i < user_cmd.relocs_num; ++i) {
+ if (DRM_COPY_FROM_USER(&reloc,
+ &((struct drm_qxl_reloc *)user_cmd.relocs)[i],
+ sizeof(reloc)))
+ return -EFAULT;
+ qxl_io_log(qdev, "%s: r#%d: %d+%d->%d+%d\n",
+ __func__, i, reloc.src_handle,
+ reloc.src_offset, reloc.dst_handle,
+ reloc.dst_offset);
+ reloc_src_bo =
+ qxlhw_handle_to_bo(qdev, file_priv,
+ reloc.src_handle, cmd_bo);
+ reloc_dst_bo =
+ qxlhw_handle_to_bo(qdev, file_priv,
+ reloc.dst_handle, cmd_bo);
+ if (!reloc_src_bo || !reloc_dst_bo)
+ return -EINVAL;
+ apply_reloc(qdev, reloc_src_bo, reloc.src_offset,
+ reloc_dst_bo, reloc.dst_offset);
+ }
+ /* TODO: multiple commands in a single push (introduce new
+ * QXLCommandBunch ?) */
+ if (is_cursor)
+ qxl_push_cursor_ring(qdev, cmd_bo, user_cmd.type);
+ else
+ qxl_push_command_ring(qdev, cmd_bo, user_cmd.type);
+ }
+ return 0;
+}
+
+/* TODO: this should be defined in ram or rom */
+#define NUM_SURFACES 1024
+
+int qxl_update_area_ioctl(struct drm_device *dev, void *data,
+ struct drm_file *file_priv)
+{
+ struct qxl_device *qdev = dev->dev_private;
+ struct drm_qxl_update_area *update_area = data;
+ struct qxl_rect area = {.left = update_area->left,
+ .top = update_area->top,
+ .right = update_area->right,
+ .bottom = update_area->bottom};
+
+ if (update_area->surface_id > NUM_SURFACES ||
+ update_area->left >= update_area->right ||
+ update_area->top >= update_area->bottom)
+ return -EINVAL;
+
+ qxl_io_update_area(qdev, update_area->surface_id, &area);
+ return 0;
+}
+
+struct drm_ioctl_desc qxl_ioctls[] = {
+ DRM_IOCTL_DEF_DRV(QXL_ALLOC, qxl_alloc_ioctl, DRM_AUTH|DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(QXL_INCREF, qxl_incref_ioctl, DRM_AUTH|DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(QXL_DECREF, qxl_decref_ioctl, DRM_AUTH|DRM_UNLOCKED),
+
+ /* NB: QXL_MAP doesn't really map, it is similar to DUMPMAP in that it
+ * provides the caller (userspace) with an offset to give to the mmap
+ * system call, which ends up in qxl_mmap, which calls ttm_bo_mmap,
+ * which actually mmaps. */
+ DRM_IOCTL_DEF_DRV(QXL_MAP, qxl_map_ioctl, DRM_AUTH|DRM_UNLOCKED),
+
+ DRM_IOCTL_DEF_DRV(QXL_EXECBUFFER, qxl_execbuffer_ioctl,
+ DRM_AUTH|DRM_UNLOCKED),
+ DRM_IOCTL_DEF_DRV(QXL_UPDATE_AREA, qxl_update_area_ioctl,
+ DRM_AUTH|DRM_UNLOCKED),
+};
+
+int qxl_max_ioctls = DRM_ARRAY_SIZE(qxl_ioctls);
diff --git a/drivers/gpu/drm/qxl/qxl_irq.c b/drivers/gpu/drm/qxl/qxl_irq.c
new file mode 100644
index 00000000000..4b2b1cd19e0
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_irq.c
@@ -0,0 +1,72 @@
+#include "qxl_drv.h"
+
+irqreturn_t qxl_irq_handler(DRM_IRQ_ARGS)
+{
+ struct drm_device *dev = (struct drm_device *) arg;
+ struct qxl_device *qdev = (struct qxl_device *)dev->dev_private;
+ uint32_t pending;
+
+ pending = xchg(&qdev->ram_header->int_pending, 0);
+
+ atomic_inc(&qdev->irq_received);
+
+ if (pending & QXL_INTERRUPT_DISPLAY) {
+ atomic_inc(&qdev->irq_received_display);
+ wake_up_all(&qdev->display_event);
+ }
+ if (pending & QXL_INTERRUPT_CURSOR) {
+ atomic_inc(&qdev->irq_received_cursor);
+ wake_up_all(&qdev->cursor_event);
+ }
+ if (pending & QXL_INTERRUPT_IO_CMD) {
+ atomic_inc(&qdev->irq_received_io_cmd);
+ wake_up_all(&qdev->io_cmd_event);
+ }
+ if (pending & QXL_INTERRUPT_ERROR) {
+ /* TODO: log it, reset device (only way to exit this condition)
+ * (do it a certain number of times, afterwards admit defeat,
+ * to avoid endless loops).
+ */
+ qdev->irq_received_error++;
+ qxl_io_log(qdev, "%s: driver is in bug mode.\n", __func__);
+ }
+ if (pending & QXL_INTERRUPT_CLIENT_MONITORS_CONFIG) {
+ qxl_io_log(qdev, "QXL_INTERRUPT_CLIENT_MONITORS_CONFIG\n");
+ schedule_work(&qdev->client_monitors_config_work);
+ }
+ qxl_io_log(qdev, "%s: pending %x\n", __func__, pending);
+ qdev->ram_header->int_mask = QXL_INTERRUPT_MASK;
+ outb(0, qdev->io_base + QXL_IO_UPDATE_IRQ);
+ return IRQ_HANDLED;
+}
+
+static void qxl_client_monitors_config_work_func(struct work_struct *work)
+{
+ struct qxl_device *qdev = container_of(work, struct qxl_device,
+ client_monitors_config_work);
+
+ qxl_display_read_client_monitors_config(qdev);
+}
+
+int qxl_irq_init(struct qxl_device *qdev)
+{
+ int ret;
+
+ init_waitqueue_head(&qdev->display_event);
+ init_waitqueue_head(&qdev->cursor_event);
+ init_waitqueue_head(&qdev->io_cmd_event);
+ INIT_WORK(&qdev->client_monitors_config_work,
+ qxl_client_monitors_config_work_func);
+ atomic_set(&qdev->irq_received, 0);
+ atomic_set(&qdev->irq_received_display, 0);
+ atomic_set(&qdev->irq_received_cursor, 0);
+ atomic_set(&qdev->irq_received_io_cmd, 0);
+ qdev->irq_received_error = 0;
+ ret = drm_irq_install(qdev->ddev);
+ qdev->ram_header->int_mask = QXL_INTERRUPT_MASK;
+ if (unlikely(ret != 0)) {
+ DRM_ERROR("Failed installing irq: %d\n", ret);
+ return 1;
+ }
+ return 0;
+}
diff --git a/drivers/gpu/drm/qxl/qxl_kms.c b/drivers/gpu/drm/qxl/qxl_kms.c
new file mode 100644
index 00000000000..dcc4f60911f
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_kms.c
@@ -0,0 +1,244 @@
+#include "qxl_drv.h"
+#include "qxl_object.h"
+
+static void qxl_dump_mode(struct qxl_device *qdev, void *p)
+{
+ struct qxl_mode *m = p;
+ DRM_INFO("%d: %dx%d %d bits, stride %d, %dmm x %dmm, orientation %d\n",
+ m->id, m->x_res, m->y_res, m->bits, m->stride, m->x_mili,
+ m->y_mili, m->orientation);
+}
+
+static bool qxl_check_device(struct qxl_device *qdev)
+{
+ struct qxl_rom *rom = qdev->rom;
+ int mode_offset;
+ int i;
+
+ if (rom->magic != 0x4f525851) {
+ DRM_ERROR("bad rom signature %x\n", rom->magic);
+ return false;
+ }
+
+ DRM_INFO("Device Version %d.%d\n", rom->id, rom->update_id);
+ DRM_INFO("Compression level %d log level %d\n", rom->compression_level,
+ rom->log_level);
+ DRM_INFO("Currently using mode #%d, list at 0x%x\n",
+ rom->mode, rom->modes_offset);
+ DRM_INFO("%d io pages at offset 0x%x\n",
+ rom->num_io_pages, rom->pages_offset);
+ DRM_INFO("%d byte draw area at offset 0x%x\n",
+ rom->surface0_area_size, rom->draw_area_offset);
+
+ qdev->vram_size = rom->surface0_area_size;
+ DRM_INFO("RAM header offset: 0x%x\n", rom->ram_header_offset);
+
+ mode_offset = rom->modes_offset / 4;
+ qdev->mode_info.num_modes = ((u32 *)rom)[mode_offset];
+ DRM_INFO("rom modes offset 0x%x for %d modes\n", rom->modes_offset,
+ qdev->mode_info.num_modes);
+ qdev->mode_info.modes = (void *)((uint32_t *)rom + mode_offset + 1);
+ for (i = 0; i < qdev->mode_info.num_modes; i++)
+ qxl_dump_mode(qdev, qdev->mode_info.modes + i);
+ return true;
+}
+
+static uint8_t setup_slot(struct qxl_device *qdev, uint8_t slot_index_offset,
+ unsigned long start_phys_addr, unsigned long end_phys_addr)
+{
+ uint64_t high_bits;
+ struct qxl_memslot *slot;
+ uint8_t slot_index;
+ struct qxl_ram_header *ram_header = qdev->ram_header;
+
+ slot_index = qdev->rom->slots_start + slot_index_offset;
+ slot = &qdev->mem_slots[slot_index];
+ slot->start_phys_addr = start_phys_addr;
+ slot->end_phys_addr = end_phys_addr;
+ ram_header->mem_slot.mem_start = slot->start_phys_addr;
+ ram_header->mem_slot.mem_end = slot->end_phys_addr;
+ qxl_io_memslot_add(qdev, slot_index);
+ slot->generation = qdev->rom->slot_generation;
+ high_bits = slot_index << qdev->slot_gen_bits;
+ high_bits |= slot->generation;
+ high_bits <<= (64 - (qdev->slot_gen_bits + qdev->slot_id_bits));
+ slot->high_bits = high_bits;
+ return slot_index;
+}
+
+int qxl_device_init(struct qxl_device *qdev,
+ struct drm_device *ddev,
+ struct pci_dev *pdev,
+ unsigned long flags)
+{
+ int r;
+
+ DRM_INFO("qxl: Initialising\n");
+
+ qdev->dev = &pdev->dev;
+ qdev->ddev = ddev;
+ qdev->pdev = pdev;
+ qdev->flags = flags;
+
+ mutex_init(&qdev->gem.mutex);
+ INIT_LIST_HEAD(&qdev->gem.objects);
+
+ qdev->rom_base = pci_resource_start(pdev, 2);
+ qdev->rom_size = pci_resource_len(pdev, 2);
+ qdev->vram_base = pci_resource_start(pdev, 0);
+ qdev->surfaceram_base = pci_resource_start(pdev, 1);
+ qdev->surfaceram_size = pci_resource_len(pdev, 1);
+ qdev->io_base = pci_resource_start(pdev, 3);
+
+ DRM_INFO("qxl: vram %p-%p(%dM %dk), surface %p-%p(%dM %dk)\n",
+ (void *)qdev->vram_base, (void *)pci_resource_end(pdev, 0),
+ (int)pci_resource_len(pdev, 0) / 1024 / 1024,
+ (int)pci_resource_len(pdev, 0) / 1024,
+ (void *)qdev->surfaceram_base,
+ (void *)pci_resource_end(pdev, 1),
+ (int)qdev->surfaceram_size / 1024 / 1024,
+ (int)qdev->surfaceram_size / 1024);
+
+ qdev->rom = ioremap(qdev->rom_base, qdev->rom_size);
+ if (!qdev->rom) {
+ pr_err("Unable to ioremap ROM\n");
+ return -ENOMEM;
+ }
+
+ qxl_check_device(qdev);
+
+ r = qxl_bo_init(qdev);
+ if (r) {
+ DRM_ERROR("bo init failed %d\n", r);
+ return r;
+ }
+
+ qdev->ram_header = ioremap(qdev->vram_base +
+ qdev->rom->ram_header_offset,
+ sizeof(*qdev->ram_header));
+
+ qdev->command_ring = qxl_ring_create(&(qdev->ram_header->cmd_ring_hdr),
+ sizeof(struct qxl_command),
+ QXL_COMMAND_RING_SIZE,
+ qdev->io_base + QXL_IO_NOTIFY_CMD,
+ &qdev->display_event);
+
+ qdev->cursor_ring = qxl_ring_create(
+ &(qdev->ram_header->cursor_ring_hdr),
+ sizeof(struct qxl_command),
+ QXL_CURSOR_RING_SIZE,
+ qdev->io_base + QXL_IO_NOTIFY_CMD,
+ &qdev->cursor_event);
+
+ qdev->release_ring = qxl_ring_create(
+ &(qdev->ram_header->release_ring_hdr),
+ sizeof(uint64_t),
+ QXL_RELEASE_RING_SIZE, 0,
+ NULL);
+
+ /* TODO - slot initialization should happen on reset. where is our
+ * reset handler? */
+ qdev->n_mem_slots = qdev->rom->slots_end;
+ qdev->slot_gen_bits = qdev->rom->slot_gen_bits;
+ qdev->slot_id_bits = qdev->rom->slot_id_bits;
+ qdev->va_slot_mask =
+ (~(uint64_t)0) >> (qdev->slot_id_bits + qdev->slot_gen_bits);
+
+ qdev->mem_slots =
+ kmalloc(qdev->n_mem_slots * sizeof(struct qxl_memslot),
+ GFP_KERNEL);
+
+ idr_init(&qdev->release_idr);
+ mutex_init(&qdev->release_idr_mutex);
+ mutex_init(&qdev->async_io_mutex);
+
+ /* reset the device into a known state - no memslots, no primary
+ * created, no surfaces. */
+ qxl_io_reset(qdev);
+
+ /* must initialize irq before first async io - slot creation */
+ r = qxl_irq_init(qdev);
+ if (r)
+ return r;
+
+ /*
+ * Note that virtual is surface0. We rely on the single ioremap done
+ * before.
+ */
+ qdev->main_mem_slot = setup_slot(qdev, 0,
+ (unsigned long)qdev->vram_base,
+ (unsigned long)qdev->vram_base + qdev->rom->ram_header_offset);
+ qdev->surfaces_mem_slot = setup_slot(qdev, 1,
+ (unsigned long)qdev->surfaceram_base,
+ (unsigned long)qdev->surfaceram_base + qdev->surfaceram_size);
+ DRM_INFO("main mem slot %d [%lx,%x)\n",
+ qdev->main_mem_slot,
+ (unsigned long)qdev->vram_base, qdev->rom->ram_header_offset);
+
+ r = qxl_fb_init(qdev);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+void qxl_device_fini(struct qxl_device *qdev)
+{
+ qxl_ring_free(qdev->command_ring);
+ qxl_ring_free(qdev->release_ring);
+ qxl_bo_fini(qdev);
+ iounmap(qdev->ram_header);
+ iounmap(qdev->rom);
+ qdev->rom = NULL;
+ qdev->mode_info.modes = NULL;
+ qdev->mode_info.num_modes = 0;
+}
+
+int qxl_driver_unload(struct drm_device *dev)
+{
+ struct qxl_device *qdev = dev->dev_private;
+
+ if (qdev == NULL)
+ return 0;
+ qxl_modeset_fini(qdev);
+ qxl_device_fini(qdev);
+
+ kfree(qdev);
+ dev->dev_private = NULL;
+ return 0;
+}
+
+int qxl_driver_load(struct drm_device *dev, unsigned long flags)
+{
+ struct qxl_device *qdev;
+ int r;
+
+ /* globals defined in qxl_debugfs.c */
+ qxl_log_level = 0;
+ qxl_debug_disable_fb = 0;
+
+ /* require kms */
+ if (!drm_core_check_feature(dev, DRIVER_MODESET))
+ return -ENODEV;
+
+ qdev = kzalloc(sizeof(struct qxl_device), GFP_KERNEL);
+ if (qdev == NULL)
+ return -ENOMEM;
+
+ dev->dev_private = qdev;
+
+ r = qxl_device_init(qdev, dev, dev->pdev, flags);
+ if (r)
+ goto out;
+
+ r = qxl_modeset_init(qdev);
+ if (r) {
+ qxl_driver_unload(dev);
+ goto out;
+ }
+
+ return 0;
+out:
+ kfree(qdev);
+ return r;
+}
diff --git a/drivers/gpu/drm/qxl/qxl_object.c b/drivers/gpu/drm/qxl/qxl_object.c
new file mode 100644
index 00000000000..ad32b408b7e
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_object.c
@@ -0,0 +1,214 @@
+#include "qxl_drv.h"
+#include "qxl_drm.h"
+#include "qxl_object.h"
+
+static void qxl_ttm_bo_destroy(struct ttm_buffer_object *tbo)
+{
+ struct qxl_bo *bo;
+
+ bo = container_of(tbo, struct qxl_bo, tbo);
+ mutex_lock(&bo->qdev->gem.mutex);
+ list_del_init(&bo->list);
+ mutex_unlock(&bo->qdev->gem.mutex);
+ kfree(bo);
+}
+
+bool qxl_ttm_bo_is_qxl_bo(struct ttm_buffer_object *bo)
+{
+ if (bo->destroy == &qxl_ttm_bo_destroy)
+ return true;
+ return false;
+}
+
+void qxl_ttm_placement_from_domain(struct qxl_bo *qbo, u32 domain)
+{
+ u32 c = 0;
+
+ qbo->placement.fpfn = 0;
+ qbo->placement.lpfn = 0;
+ qbo->placement.placement = qbo->placements;
+ qbo->placement.busy_placement = qbo->placements;
+ if (domain & QXL_GEM_DOMAIN_VRAM)
+ qbo->placements[c++] = TTM_PL_FLAG_CACHED | TTM_PL_FLAG_VRAM;
+ if (domain & QXL_GEM_DOMAIN_SURFACE)
+ qbo->placements[c++] = TTM_PL_FLAG_CACHED | TTM_PL_FLAG_PRIV0;
+ if (domain & QXL_GEM_DOMAIN_CPU)
+ qbo->placements[c++] = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+ if (!c)
+ qbo->placements[c++] = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+ qbo->placement.num_placement = c;
+ qbo->placement.num_busy_placement = c;
+}
+
+
+int qxl_bo_create(struct qxl_device *qdev,
+ unsigned long size, bool kernel, u32 domain,
+ struct qxl_bo **bo_ptr)
+{
+ struct qxl_bo *bo;
+ enum ttm_bo_type type;
+ int r;
+ static int max_size;
+
+ if (unlikely(qdev->mman.bdev.dev_mapping == NULL))
+ qdev->mman.bdev.dev_mapping = qdev->ddev->dev_mapping;
+ if (kernel)
+ type = ttm_bo_type_kernel;
+ else
+ type = ttm_bo_type_device;
+ *bo_ptr = NULL;
+ bo = kzalloc(sizeof(struct qxl_bo), GFP_KERNEL);
+ if (bo == NULL)
+ return -ENOMEM;
+ if ((size & (PAGE_SIZE - 1)) != 0) {
+ if (size > PAGE_SIZE)
+ size = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
+ else {
+ printk_once("lost %d bytes (%ld -> %ld)\n",
+ PAGE_SIZE - size, size, PAGE_SIZE);
+ size = PAGE_SIZE;
+ }
+ }
+ if (size > max_size) {
+ max_size = size;
+ DRM_INFO("%s: max_size = %d\n", __func__, max_size);
+ }
+ r = drm_gem_object_init(qdev->ddev, &bo->gem_base, size);
+ if (unlikely(r)) {
+ kfree(bo);
+ return r;
+ }
+ bo->gem_base.driver_private = NULL;
+ bo->qdev = qdev;
+ bo->surface_reg = -1;
+ INIT_LIST_HEAD(&bo->list);
+
+ qxl_ttm_placement_from_domain(bo, domain);
+ /* Kernel allocation are uninterruptible */
+ /* TODO radeon has a vram_mutex here */
+ r = ttm_bo_init(&qdev->mman.bdev, &bo->tbo, size, type,
+ &bo->placement, 0, 0, !kernel, NULL, size,
+ NULL, &qxl_ttm_bo_destroy);
+ if (unlikely(r != 0)) {
+ if (r != -ERESTARTSYS)
+ dev_err(qdev->dev,
+ "object_init failed for (%lu, 0x%08X)\n",
+ size, domain);
+ return r;
+ }
+ *bo_ptr = bo;
+ return 0;
+}
+
+int qxl_bo_kmap(struct qxl_bo *bo, void **ptr)
+{
+ bool is_iomem;
+ int r;
+
+ if (bo->kptr) {
+ if (ptr)
+ *ptr = bo->kptr;
+ return 0;
+ }
+ r = ttm_bo_kmap(&bo->tbo, 0, bo->tbo.num_pages, &bo->kmap);
+ if (r)
+ return r;
+ bo->kptr = ttm_kmap_obj_virtual(&bo->kmap, &is_iomem);
+ if (ptr)
+ *ptr = bo->kptr;
+ return 0;
+}
+
+void qxl_bo_kunmap(struct qxl_bo *bo)
+{
+ if (bo->kptr == NULL)
+ return;
+ bo->kptr = NULL;
+ ttm_bo_kunmap(&bo->kmap);
+}
+
+void qxl_bo_unref(struct qxl_bo **bo)
+{
+ struct ttm_buffer_object *tbo;
+
+ if ((*bo) == NULL)
+ return;
+ tbo = &((*bo)->tbo);
+ ttm_bo_unref(&tbo);
+ if (tbo == NULL)
+ *bo = NULL;
+}
+
+int qxl_bo_pin(struct qxl_bo *bo, u32 domain, u64 *gpu_addr)
+{
+ int r, i;
+
+ if (bo->pin_count) {
+ bo->pin_count++;
+ if (gpu_addr)
+ *gpu_addr = qxl_bo_gpu_offset(bo);
+ return 0;
+ }
+ qxl_ttm_placement_from_domain(bo, domain);
+ for (i = 0; i < bo->placement.num_placement; i++)
+ bo->placements[i] |= TTM_PL_FLAG_NO_EVICT;
+ r = ttm_bo_validate(&bo->tbo, &bo->placement, false, false, false);
+ if (likely(r == 0)) {
+ bo->pin_count = 1;
+ if (gpu_addr != NULL)
+ *gpu_addr = qxl_bo_gpu_offset(bo);
+ }
+ if (unlikely(r != 0))
+ dev_err(bo->qdev->dev, "%p pin failed\n", bo);
+ return r;
+}
+
+int qxl_bo_unpin(struct qxl_bo *bo)
+{
+ int r, i;
+
+ if (!bo->pin_count) {
+ dev_warn(bo->qdev->dev, "%p unpin not necessary\n", bo);
+ return 0;
+ }
+ bo->pin_count--;
+ if (bo->pin_count)
+ return 0;
+ for (i = 0; i < bo->placement.num_placement; i++)
+ bo->placements[i] &= ~TTM_PL_FLAG_NO_EVICT;
+ r = ttm_bo_validate(&bo->tbo, &bo->placement, false, false, false);
+ if (unlikely(r != 0))
+ dev_err(bo->qdev->dev, "%p validate failed for unpin\n", bo);
+ return r;
+}
+
+void qxl_bo_force_delete(struct qxl_device *qdev)
+{
+ struct qxl_bo *bo, *n;
+
+ if (list_empty(&qdev->gem.objects))
+ return;
+ dev_err(qdev->dev, "Userspace still has active objects !\n");
+ list_for_each_entry_safe(bo, n, &qdev->gem.objects, list) {
+ mutex_lock(&qdev->ddev->struct_mutex);
+ dev_err(qdev->dev, "%p %p %lu %lu force free\n",
+ &bo->gem_base, bo, (unsigned long)bo->gem_base.size,
+ *((unsigned long *)&bo->gem_base.refcount));
+ mutex_lock(&bo->qdev->gem.mutex);
+ list_del_init(&bo->list);
+ mutex_unlock(&bo->qdev->gem.mutex);
+ /* this should unref the ttm bo */
+ drm_gem_object_unreference(&bo->gem_base);
+ mutex_unlock(&qdev->ddev->struct_mutex);
+ }
+}
+
+int qxl_bo_init(struct qxl_device *qdev)
+{
+ return qxl_ttm_init(qdev);
+}
+
+void qxl_bo_fini(struct qxl_device *qdev)
+{
+ qxl_ttm_fini(qdev);
+}
diff --git a/drivers/gpu/drm/qxl/qxl_object.h b/drivers/gpu/drm/qxl/qxl_object.h
new file mode 100644
index 00000000000..967e890b773
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_object.h
@@ -0,0 +1,78 @@
+#ifndef QXL_OBJECT_H
+#define QXL_OBJECT_H
+
+#include "qxl_drv.h"
+
+static inline int qxl_bo_reserve(struct qxl_bo *bo, bool no_wait)
+{
+ int r;
+
+ r = ttm_bo_reserve(&bo->tbo, true, no_wait, false, 0);
+ if (unlikely(r != 0)) {
+ if (r != -ERESTARTSYS)
+ dev_err(bo->qdev->dev, "%p reserve failed\n", bo);
+ return r;
+ }
+ return 0;
+}
+
+static inline void qxl_bo_unreserve(struct qxl_bo *bo)
+{
+ ttm_bo_unreserve(&bo->tbo);
+}
+
+static inline u64 qxl_bo_gpu_offset(struct qxl_bo *bo)
+{
+ return bo->tbo.offset;
+}
+
+static inline unsigned long qxl_bo_size(struct qxl_bo *bo)
+{
+ return bo->tbo.num_pages << PAGE_SHIFT;
+}
+
+static inline bool qxl_bo_is_reserved(struct qxl_bo *bo)
+{
+ return !!atomic_read(&bo->tbo.reserved);
+}
+
+static inline u64 qxl_bo_mmap_offset(struct qxl_bo *bo)
+{
+ return bo->tbo.addr_space_offset;
+}
+
+static inline int qxl_bo_wait(struct qxl_bo *bo, u32 *mem_type,
+ bool no_wait)
+{
+ int r;
+
+ r = ttm_bo_reserve(&bo->tbo, true, no_wait, false, 0);
+ if (unlikely(r != 0)) {
+ if (r != -ERESTARTSYS)
+ dev_err(bo->qdev->dev, "%p reserve failed for wait\n",
+ bo);
+ return r;
+ }
+ spin_lock(&bo->tbo.bdev->fence_lock);
+ if (mem_type)
+ *mem_type = bo->tbo.mem.mem_type;
+ if (bo->tbo.sync_obj)
+ r = ttm_bo_wait(&bo->tbo, true, true, no_wait);
+ spin_unlock(&bo->tbo.bdev->fence_lock);
+ ttm_bo_unreserve(&bo->tbo);
+ return r;
+}
+
+extern int qxl_bo_create(struct qxl_device *qdev,
+ unsigned long size,
+ bool kernel, u32 domain,
+ struct qxl_bo **bo_ptr);
+extern void qxl_bo_free(struct qxl_bo *bo);
+extern int qxl_bo_kmap(struct qxl_bo *bo, void **ptr);
+extern void qxl_bo_kunmap(struct qxl_bo *bo);
+extern void qxl_bo_unref(struct qxl_bo **bo);
+extern int qxl_bo_pin(struct qxl_bo *bo, u32 domain, u64 *gpu_addr);
+extern int qxl_bo_unpin(struct qxl_bo *bo);
+extern void qxl_ttm_placement_from_domain(struct qxl_bo *qbo, u32 domain);
+extern bool qxl_ttm_bo_is_qxl_bo(struct ttm_buffer_object *bo);
+#endif
diff --git a/drivers/gpu/drm/qxl/qxl_ttm.c b/drivers/gpu/drm/qxl/qxl_ttm.c
new file mode 100644
index 00000000000..b119afe8243
--- /dev/null
+++ b/drivers/gpu/drm/qxl/qxl_ttm.c
@@ -0,0 +1,405 @@
+
+#include <ttm/ttm_bo_api.h>
+#include <ttm/ttm_bo_driver.h>
+#include <ttm/ttm_placement.h>
+#include <ttm/ttm_module.h>
+#include <drm/drmP.h>
+#include <drm/drm.h>
+#include <drm/qxl_drm.h>
+#include "qxl_drv.h"
+#include "qxl_object.h"
+
+static struct qxl_device *qxl_get_qdev(struct ttm_bo_device *bdev)
+{
+ struct qxl_mman *mman;
+ struct qxl_device *qdev;
+
+ mman = container_of(bdev, struct qxl_mman, bdev);
+ qdev = container_of(mman, struct qxl_device, mman);
+ return qdev;
+}
+
+static int qxl_ttm_mem_global_init(struct drm_global_reference *ref)
+{
+ return ttm_mem_global_init(ref->object);
+}
+
+static void qxl_ttm_mem_global_release(struct drm_global_reference *ref)
+{
+ ttm_mem_global_release(ref->object);
+}
+
+static int qxl_ttm_global_init(struct qxl_device *qdev)
+{
+ struct drm_global_reference *global_ref;
+ int r;
+
+ qdev->mman.mem_global_referenced = false;
+ global_ref = &qdev->mman.mem_global_ref;
+ global_ref->global_type = DRM_GLOBAL_TTM_MEM;
+ global_ref->size = sizeof(struct ttm_mem_global);
+ global_ref->init = &qxl_ttm_mem_global_init;
+ global_ref->release = &qxl_ttm_mem_global_release;
+
+ r = drm_global_item_ref(global_ref);
+ if (r != 0) {
+ DRM_ERROR("Failed setting up TTM memory accounting "
+ "subsystem.\n");
+ return r;
+ }
+
+ qdev->mman.bo_global_ref.mem_glob =
+ qdev->mman.mem_global_ref.object;
+ global_ref = &qdev->mman.bo_global_ref.ref;
+ global_ref->global_type = DRM_GLOBAL_TTM_BO;
+ global_ref->size = sizeof(struct ttm_bo_global);
+ global_ref->init = &ttm_bo_global_init;
+ global_ref->release = &ttm_bo_global_release;
+ r = drm_global_item_ref(global_ref);
+ if (r != 0) {
+ DRM_ERROR("Failed setting up TTM BO subsystem.\n");
+ drm_global_item_unref(&qdev->mman.mem_global_ref);
+ return r;
+ }
+
+ qdev->mman.mem_global_referenced = true;
+ return 0;
+}
+
+static void qxl_ttm_global_fini(struct qxl_device *qdev)
+{
+ if (qdev->mman.mem_global_referenced) {
+ drm_global_item_unref(&qdev->mman.bo_global_ref.ref);
+ drm_global_item_unref(&qdev->mman.mem_global_ref);
+ qdev->mman.mem_global_referenced = false;
+ }
+}
+
+static struct vm_operations_struct qxl_ttm_vm_ops;
+static const struct vm_operations_struct *ttm_vm_ops;
+
+static int qxl_ttm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ struct ttm_buffer_object *bo;
+ struct qxl_device *qdev;
+ int r;
+
+ bo = (struct ttm_buffer_object *)vma->vm_private_data;
+ if (bo == NULL)
+ return VM_FAULT_NOPAGE;
+ qdev = qxl_get_qdev(bo->bdev);
+ r = ttm_vm_ops->fault(vma, vmf);
+ return r;
+}
+
+int qxl_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_file *file_priv;
+ struct qxl_device *qdev;
+ int r;
+
+ if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET)) {
+ pr_info("%s: vma->vm_pgoff (%ld) < DRM_FILE_PAGE_OFFSET\n",
+ __func__, vma->vm_pgoff);
+ return drm_mmap(filp, vma);
+ }
+
+ file_priv = filp->private_data;
+ qdev = file_priv->minor->dev->dev_private;
+ if (qdev == NULL) {
+ DRM_ERROR(
+ "filp->private_data->minor->dev->dev_private == NULL\n");
+ return -EINVAL;
+ }
+ QXL_INFO(qdev, "%s: filp->private_data = 0x%p, vma->vm_pgoff = %ld\n",
+ __func__, filp->private_data, vma->vm_pgoff);
+
+ r = ttm_bo_mmap(filp, vma, &qdev->mman.bdev);
+ if (unlikely(r != 0))
+ return r;
+ if (unlikely(ttm_vm_ops == NULL)) {
+ ttm_vm_ops = vma->vm_ops;
+ qxl_ttm_vm_ops = *ttm_vm_ops;
+ qxl_ttm_vm_ops.fault = &qxl_ttm_fault;
+ }
+ vma->vm_ops = &qxl_ttm_vm_ops;
+ return 0;
+}
+
+static int qxl_invalidate_caches(struct ttm_bo_device *bdev, uint32_t flags)
+{
+ return 0;
+}
+
+static int qxl_bo_man_init(struct ttm_mem_type_manager *man,
+ unsigned long p_size)
+{
+ return ttm_bo_manager_func.init(man, p_size);
+}
+
+static int qxl_bo_man_takedown(struct ttm_mem_type_manager *man)
+{
+ return ttm_bo_manager_func.takedown(man);
+}
+
+static int qxl_bo_man_get_node(struct ttm_mem_type_manager *man,
+ struct ttm_buffer_object *bo,
+ struct ttm_placement *placement,
+ struct ttm_mem_reg *mem)
+{
+ struct qxl_device *qdev = container_of(man->bdev, struct qxl_device,
+ mman.bdev);
+ int ret;
+ int res = 0;
+
+ ret = ttm_bo_manager_func.get_node(man, bo, placement, mem);
+ while (unlikely(ret || mem->mm_node == NULL)) {
+ qxl_io_notify_oom(qdev);
+ res += qxl_garbage_collect(qdev);
+ /* TODO - sleep here */
+ ret = ttm_bo_manager_func.get_node(man, bo, placement, mem);
+ }
+ if (unlikely(res > 0))
+ QXL_INFO(qdev, "%s: released %d\n", res);
+ return ret;
+}
+
+static void qxl_bo_man_put_node(struct ttm_mem_type_manager *man,
+ struct ttm_mem_reg *mem)
+{
+ ttm_bo_manager_func.put_node(man, mem);
+}
+
+static void qxl_bo_man_debug(struct ttm_mem_type_manager *man,
+ const char *prefix)
+{
+ ttm_bo_manager_func.debug(man, prefix);
+}
+
+static const struct ttm_mem_type_manager_func qxl_bo_manager_func = {
+ qxl_bo_man_init,
+ qxl_bo_man_takedown,
+ qxl_bo_man_get_node,
+ qxl_bo_man_put_node,
+ qxl_bo_man_debug,
+};
+
+static int qxl_init_mem_type(struct ttm_bo_device *bdev, uint32_t type,
+ struct ttm_mem_type_manager *man)
+{
+ struct qxl_device *qdev;
+
+ qdev = qxl_get_qdev(bdev);
+
+ switch (type) {
+ case TTM_PL_SYSTEM:
+ /* System memory */
+ man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->available_caching = TTM_PL_MASK_CACHING;
+ man->default_caching = TTM_PL_FLAG_CACHED;
+ break;
+ case TTM_PL_VRAM:
+ case TTM_PL_PRIV0:
+ /* "On-card" video ram */
+ man->func = &qxl_bo_manager_func;
+ man->gpu_offset = 0;
+ man->flags = TTM_MEMTYPE_FLAG_FIXED |
+ TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->available_caching = TTM_PL_MASK_CACHING;
+ man->default_caching = TTM_PL_FLAG_CACHED;
+ break;
+ default:
+ DRM_ERROR("Unsupported memory type %u\n", (unsigned)type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void qxl_evict_flags(struct ttm_buffer_object *bo,
+ struct ttm_placement *placement)
+{
+ struct qxl_bo *qbo;
+ static u32 placements = TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+
+ if (!qxl_ttm_bo_is_qxl_bo(bo)) {
+ placement->fpfn = 0;
+ placement->lpfn = 0;
+ placement->placement = &placements;
+ placement->busy_placement = &placements;
+ placement->num_placement = 1;
+ placement->num_busy_placement = 1;
+ return;
+ }
+ qbo = container_of(bo, struct qxl_bo, tbo);
+ qxl_ttm_placement_from_domain(qbo, QXL_GEM_DOMAIN_CPU);
+ *placement = qbo->placement;
+}
+
+static int qxl_verify_access(struct ttm_buffer_object *bo, struct file *filp)
+{
+ return 0;
+}
+
+static int qxl_ttm_io_mem_reserve(struct ttm_bo_device *bdev,
+ struct ttm_mem_reg *mem)
+{
+ struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type];
+ struct qxl_device *qdev = qxl_get_qdev(bdev);
+
+ mem->bus.addr = NULL;
+ mem->bus.offset = 0;
+ mem->bus.size = mem->num_pages << PAGE_SHIFT;
+ mem->bus.base = 0;
+ mem->bus.is_iomem = false;
+ if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
+ return -EINVAL;
+ switch (mem->mem_type) {
+ case TTM_PL_SYSTEM:
+ /* system memory */
+ return 0;
+ case TTM_PL_VRAM:
+ mem->bus.is_iomem = true;
+ mem->bus.base = qdev->vram_base;
+ mem->bus.offset = mem->start << PAGE_SHIFT;
+ break;
+ case TTM_PL_PRIV0:
+ qxl_io_log(qdev, "PRIV0 reservation\n");
+ mem->bus.is_iomem = true;
+ mem->bus.base = qdev->surfaceram_base;
+ mem->bus.offset = mem->start << PAGE_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void qxl_ttm_io_mem_free(struct ttm_bo_device *bdev,
+ struct ttm_mem_reg *mem)
+{
+}
+
+/*
+ * TTM backend functions.
+ */
+struct qxl_ttm_tt {
+ struct ttm_dma_tt ttm;
+ struct qxl_device *qdev;
+ u64 offset;
+};
+
+static int qxl_ttm_backend_bind(struct ttm_tt *ttm,
+ struct ttm_mem_reg *bo_mem)
+{
+ struct qxl_ttm_tt *gtt = (void *)ttm;
+
+ gtt->offset = (unsigned long)(bo_mem->start << PAGE_SHIFT);
+ if (!ttm->num_pages) {
+ WARN(1, "nothing to bind %lu pages for mreg %p back %p!\n",
+ ttm->num_pages, bo_mem, ttm);
+ }
+ /* Not implemented */
+ return -1;
+}
+
+static int qxl_ttm_backend_unbind(struct ttm_tt *ttm)
+{
+ /* Not implemented */
+ return -1;
+}
+
+static void qxl_ttm_backend_destroy(struct ttm_tt *ttm)
+{
+ struct qxl_ttm_tt *gtt = (void *)ttm;
+
+ ttm_dma_tt_fini(&gtt->ttm);
+ kfree(gtt);
+}
+
+static struct ttm_backend_func qxl_backend_func = {
+ .bind = &qxl_ttm_backend_bind,
+ .unbind = &qxl_ttm_backend_unbind,
+ .destroy = &qxl_ttm_backend_destroy,
+};
+
+
+struct ttm_tt *qxl_ttm_tt_create(struct ttm_bo_device *bdev,
+ unsigned long size, uint32_t page_flags,
+ struct page *dummy_read_page)
+{
+ struct qxl_device *qdev;
+ struct qxl_ttm_tt *gtt;
+
+ qdev = qxl_get_qdev(bdev);
+ gtt = kzalloc(sizeof(struct qxl_ttm_tt), GFP_KERNEL);
+ if (gtt == NULL)
+ return NULL;
+ gtt->ttm.ttm.func = &qxl_backend_func;
+ gtt->qdev = qdev;
+ if (ttm_dma_tt_init(&gtt->ttm, bdev, size, page_flags,
+ dummy_read_page)) {
+ kfree(gtt);
+ return NULL;
+ }
+ return &gtt->ttm.ttm;
+}
+
+static struct ttm_bo_driver qxl_bo_driver = {
+ .ttm_tt_create = &qxl_ttm_tt_create,
+ .invalidate_caches = &qxl_invalidate_caches,
+ .init_mem_type = &qxl_init_mem_type,
+ .evict_flags = &qxl_evict_flags,
+ .verify_access = &qxl_verify_access,
+ .io_mem_reserve = &qxl_ttm_io_mem_reserve,
+ .io_mem_free = &qxl_ttm_io_mem_free,
+};
+
+int qxl_ttm_init(struct qxl_device *qdev)
+{
+ int r;
+ int num_io_pages; /* != rom->num_io_pages, we include surface0 */
+
+ r = qxl_ttm_global_init(qdev);
+ if (r)
+ return r;
+ /* No others user of address space so set it to 0 */
+ r = ttm_bo_device_init(&qdev->mman.bdev,
+ qdev->mman.bo_global_ref.ref.object,
+ &qxl_bo_driver, DRM_FILE_PAGE_OFFSET, 0);
+ if (r) {
+ DRM_ERROR("failed initializing buffer object driver(%d).\n", r);
+ return r;
+ }
+ pr_info("%s: qdev->mman.bdev.glob == %p\n", __func__,
+ qdev->mman.bdev.glob);
+ /* NOTE: this includes the framebuffer (aka surface 0) */
+ num_io_pages = qdev->rom->ram_header_offset / PAGE_SIZE;
+ r = ttm_bo_init_mm(&qdev->mman.bdev, TTM_PL_VRAM,
+ num_io_pages);
+ if (r) {
+ DRM_ERROR("Failed initializing VRAM heap.\n");
+ return r;
+ }
+ r = ttm_bo_init_mm(&qdev->mman.bdev, TTM_PL_PRIV0,
+ qdev->surfaceram_size / PAGE_SIZE);
+ if (r) {
+ DRM_ERROR("Failed initializing Surfaces heap.\n");
+ return r;
+ }
+ DRM_INFO("qxl: %uM of VRAM memory size\n",
+ (unsigned)qdev->vram_size / (1024 * 1024));
+ DRM_INFO("qxl: %luM of IO pages memory ready (VRAM domain)\n",
+ ((unsigned)num_io_pages * PAGE_SIZE) / (1024 * 1024));
+ if (unlikely(qdev->mman.bdev.dev_mapping == NULL))
+ qdev->mman.bdev.dev_mapping = qdev->ddev->dev_mapping;
+ return 0;
+}
+
+void qxl_ttm_fini(struct qxl_device *qdev)
+{
+ ttm_bo_clean_mm(&qdev->mman.bdev, TTM_PL_VRAM);
+ ttm_bo_clean_mm(&qdev->mman.bdev, TTM_PL_PRIV0);
+ ttm_bo_device_release(&qdev->mman.bdev);
+ qxl_ttm_global_fini(qdev);
+ DRM_INFO("qxl: ttm finalized\n");
+}
diff --git a/include/drm/qxl_drm.h b/include/drm/qxl_drm.h
new file mode 100644
index 00000000000..2847675c980
--- /dev/null
+++ b/include/drm/qxl_drm.h
@@ -0,0 +1,106 @@
+#ifndef QXL_DRM_H
+#define QXL_DRM_H
+
+#include <stddef.h>
+#include "drm.h"
+
+/* Please note that modifications to all structs defined here are
+ * subject to backwards-compatibility constraints.
+ *
+ * Do not use pointers, use uint64_t instead for 32 bit / 64 bit user/kernel
+ * compatibility Keep fields aligned to their size
+ */
+
+#define QXL_GEM_DOMAIN_CPU 0
+#define QXL_GEM_DOMAIN_VRAM 1
+#define QXL_GEM_DOMAIN_SURFACE 2
+
+#define DRM_QXL_ALLOC 0x00
+#define DRM_QXL_INCREF 0x01
+#define DRM_QXL_DECREF 0x02
+#define DRM_QXL_MAP 0x03
+#define DRM_QXL_UNMAP 0x04
+#define DRM_QXL_EXECBUFFER 0x05
+#define DRM_QXL_UPDATE_AREA 0x06
+
+enum {
+ QXL_ALLOC_TYPE_DATA,
+ QXL_ALLOC_TYPE_SURFACE,
+ QXL_ALLOC_TYPE_SURFACE_PRIMARY,
+};
+
+struct drm_qxl_alloc {
+ uint32_t type;
+ uint32_t size;
+ uint32_t handle; /* 0 is an invalid handle */
+};
+
+struct drm_qxl_incref {
+ uint32_t handle;
+};
+
+struct drm_qxl_decref {
+ uint32_t handle;
+};
+
+struct drm_qxl_map {
+ uint64_t offset; /* use for mmap system call */
+ uint32_t handle;
+};
+
+/*
+ * *(src_handle.base_addr + src_offset) = physical_address(dst_handle.addr +
+ * dst_offset)
+ */
+struct drm_qxl_reloc {
+ uint64_t src_offset; /* offset into src_handle or src buffer */
+ uint64_t dst_offset; /* offset in dest handle */
+ uint32_t src_handle; /* 0 if to command buffer */
+ uint32_t dst_handle; /* dest handle to compute address from */
+};
+
+struct drm_qxl_command {
+ uint32_t type;
+ uint32_t command_size;
+ uint64_t __user command; /* void* */
+ uint32_t relocs_num;
+ uint64_t __user relocs; /* struct drm_qxl_reloc* */
+};
+
+/* XXX: call it drm_qxl_commands? */
+struct drm_qxl_execbuffer {
+ uint32_t flags; /* for future use */
+ uint32_t commands_num;
+ uint64_t __user commands; /* struct drm_qxl_command* */
+ uint8_t spare[16]; /* spare for future */
+};
+
+struct drm_qxl_update_area {
+ uint32_t surface_id;
+ uint32_t top;
+ uint32_t left;
+ uint32_t bottom;
+ uint32_t right;
+};
+
+#define DRM_IOCTL_QXL_ALLOC \
+ DRM_IOWR(DRM_COMMAND_BASE + DRM_QXL_ALLOC, struct drm_qxl_alloc)
+
+#define DRM_IOCTL_QXL_INCREF \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_QXL_INCREF, struct drm_qxl_incref)
+
+#define DRM_IOCTL_QXL_DECREF \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_QXL_DECREF, struct drm_qxl_decref)
+
+#define DRM_IOCTL_QXL_MAP \
+ DRM_IOWR(DRM_COMMAND_BASE + DRM_QXL_MAP, struct drm_qxl_map)
+
+#define DRM_IOCTL_QXL_EXECBUFFER \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_QXL_EXECBUFFER,\
+ struct drm_qxl_execbuffer)
+
+#define DRM_IOCTL_QXL_UPDATE_AREA \
+ DRM_IOW(DRM_COMMAND_BASE + DRM_QXL_UPDATE_AREA,\
+ struct drm_qxl_update_area)
+
+#endif