diff --git a/drivers/gpu/drm/drm_bufs.c b/drivers/gpu/drm/drm_bufs.c index bde64b8..5492d5b 100644 --- a/drivers/gpu/drm/drm_bufs.c +++ b/drivers/gpu/drm/drm_bufs.c @@ -419,6 +419,9 @@ int drm_rmmap_locked(struct drm_device *dev, drm_local_map_t *map) dmah.size = map->size; __drm_pci_free(dev, &dmah); break; + case _DRM_GEM: + DRM_ERROR("tried to rmmap GEM object\n"); + break; } drm_free(map, sizeof(*map), DRM_MEM_MAPS); diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index f934414..43aacca 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -315,6 +315,7 @@ static void drm_cleanup(struct drm_device * dev) dev->driver->unload(dev); drm_ht_remove(&dev->map_hash); + drm_mm_takedown(&dev->offset_manager); drm_ctxbitmap_cleanup(dev); drm_put_minor(&dev->primary); diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index 434155b..c2fcf01 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -418,3 +418,23 @@ drm_gem_object_handle_free(struct kref *kref) } EXPORT_SYMBOL(drm_gem_object_handle_free); +int +drm_gem_mmap(struct vm_area_struct *vma, struct file *filp, struct drm_map *map) +{ + struct drm_gem_object *obj = map->handle; + + DRM_ERROR("drm_gem_mmap on object %d, vm_ops %p\n", obj->name, + obj->vm_ops); + + if (!obj->vm_ops) + return -EINVAL; + + vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP; + vma->vm_ops = obj->vm_ops; + vma->vm_private_data = map->handle; + vma->vm_file = obj->filp; + + DRM_DEBUG("set vm_ops for BO %d\n", obj->name); + + return 0; +} diff --git a/drivers/gpu/drm/drm_hashtab.c b/drivers/gpu/drm/drm_hashtab.c index 3316067..af539f7 100644 --- a/drivers/gpu/drm/drm_hashtab.c +++ b/drivers/gpu/drm/drm_hashtab.c @@ -127,6 +127,7 @@ int drm_ht_insert_item(struct drm_open_hash *ht, struct drm_hash_item *item) } return 0; } +EXPORT_SYMBOL(drm_ht_insert_item); /* * Just insert an item and return any "bits" bit key that hasn't been @@ -188,6 +189,7 @@ int drm_ht_remove_item(struct drm_open_hash *ht, struct drm_hash_item *item) ht->fill--; return 0; } +EXPORT_SYMBOL(drm_ht_remove_item); void drm_ht_remove(struct drm_open_hash *ht) { diff --git a/drivers/gpu/drm/drm_stub.c b/drivers/gpu/drm/drm_stub.c index 82f4657..498463f 100644 --- a/drivers/gpu/drm/drm_stub.c +++ b/drivers/gpu/drm/drm_stub.c @@ -113,6 +113,12 @@ static int drm_fill_in_dev(struct drm_device * dev, struct pci_dev *pdev, return -ENOMEM; } + if (drm_mm_init(&dev->offset_manager, DRM_FILE_PAGE_OFFSET_START, + DRM_FILE_PAGE_OFFSET_SIZE)) { + drm_ht_remove(&dev->map_hash); + return -ENOMEM; + } + /* the DRM has 6 basic counters */ dev->counters = 6; dev->types[0] = _DRM_STAT_LOCK; diff --git a/drivers/gpu/drm/drm_vm.c b/drivers/gpu/drm/drm_vm.c index c234c6f..f4dfe8e 100644 --- a/drivers/gpu/drm/drm_vm.c +++ b/drivers/gpu/drm/drm_vm.c @@ -267,6 +267,9 @@ static void drm_vm_shm_close(struct vm_area_struct *vma) dmah.size = map->size; __drm_pci_free(dev, &dmah); break; + case _DRM_GEM: + DRM_ERROR("tried to rmmap GEM object\n"); + break; } drm_free(map, sizeof(*map), DRM_MEM_MAPS); } @@ -547,6 +550,7 @@ static int drm_mmap_locked(struct file *filp, struct vm_area_struct *vma) struct drm_map *map = NULL; unsigned long offset = 0; struct drm_hash_item *hash; + int ret; DRM_DEBUG("start = 0x%lx, end = 0x%lx, page offset = 0x%lx\n", vma->vm_start, vma->vm_end, vma->vm_pgoff); @@ -566,6 +570,7 @@ static int drm_mmap_locked(struct file *filp, struct vm_area_struct *vma) ) return drm_mmap_dma(filp, vma); + DRM_ERROR("looking for object at 0x%08lx\n", vma->vm_pgoff); if (drm_ht_find_item(&dev->map_hash, vma->vm_pgoff, &hash)) { DRM_ERROR("Could not find map\n"); return -EINVAL; @@ -647,6 +652,12 @@ static int drm_mmap_locked(struct file *filp, struct vm_area_struct *vma) vma->vm_flags |= VM_RESERVED; vma->vm_page_prot = drm_dma_prot(map->type, vma); break; + case _DRM_GEM: + DRM_ERROR("found GEM object, setting up vm_ops\n"); + ret = drm_gem_mmap(vma, filp, map); + if (ret) + return ret; + break; default: return -EINVAL; /* This should never happen. */ } diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index 8944eb1..576516e 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -928,6 +928,7 @@ struct drm_ioctl_desc i915_ioctls[] = { DRM_IOCTL_DEF(DRM_I915_GEM_PREAD, i915_gem_pread_ioctl, 0), DRM_IOCTL_DEF(DRM_I915_GEM_PWRITE, i915_gem_pwrite_ioctl, 0), DRM_IOCTL_DEF(DRM_I915_GEM_MMAP, i915_gem_mmap_ioctl, 0), + DRM_IOCTL_DEF(DRM_I915_GEM_MMAP_GTT, i915_gem_mmap_gtt_ioctl, 0), DRM_IOCTL_DEF(DRM_I915_GEM_SET_DOMAIN, i915_gem_set_domain_ioctl, 0), DRM_IOCTL_DEF(DRM_I915_GEM_SW_FINISH, i915_gem_sw_finish_ioctl, 0), DRM_IOCTL_DEF(DRM_I915_GEM_SET_TILING, i915_gem_set_tiling, 0), diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index ae9ac6b..3147aa1 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -347,6 +347,8 @@ struct drm_i915_gem_object { * This is the same as gtt_space->start */ uint32_t gtt_offset; + uint32_t gtt_alignment; + uint64_t mmap_offset; /** Boolean whether this object has a valid gtt offset. */ int gtt_bound; @@ -462,6 +464,8 @@ int i915_gem_pwrite_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int i915_gem_mmap_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); +int i915_gem_mmap_gtt_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); int i915_gem_set_domain_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int i915_gem_sw_finish_ioctl(struct drm_device *dev, void *data, diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index 7f2126f..fb01db8 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -30,6 +30,7 @@ #include "i915_drm.h" #include "i915_drv.h" #include +#include static int i915_gem_object_set_domain(struct drm_gem_object *obj, @@ -49,6 +50,8 @@ i915_gem_set_domain(struct drm_gem_object *obj, static int i915_gem_object_get_page_list(struct drm_gem_object *obj); static void i915_gem_object_free_page_list(struct drm_gem_object *obj); static int i915_gem_object_wait_rendering(struct drm_gem_object *obj); +static int i915_gem_object_bind_to_gtt(struct drm_gem_object *obj, + unsigned alignment); int i915_gem_init_ioctl(struct drm_device *dev, void *data, @@ -421,6 +424,11 @@ i915_gem_sw_finish_ioctl(struct drm_device *dev, void *data, i915_gem_clflush_object(obj); drm_agp_chipset_flush(dev); } + + /* + * FIXME: If the object was mapped into the GTT we can put it in the + * inactive list too. + */ drm_gem_object_unreference(obj); mutex_unlock(&dev->struct_mutex); return ret; @@ -467,6 +475,121 @@ i915_gem_mmap_ioctl(struct drm_device *dev, void *data, return 0; } +static void i915_gem_vm_open(struct vm_area_struct *vma) +{ +} + +static void i915_gem_vm_close(struct vm_area_struct *vma) +{ +} + +/** + * i915_gem_fault - fault handler for mapped GEM pages + * @vma: VM area struct + * @vmf: fault info + * + * Once mmap has been called on a particular region, its vma should be setup + * to use this fault handler, where depending on the object we may map + * either the backing store physical address (the actual RAM pages) or a GTT + * address (for tiling and WC mappings). + * + * Invalidated mappings of the backing store pages are taken care of by the + * core, but GTT range zapping requires extra work to avoid having a GTT + * address point at the wrong data (either bogus pages or someone else's). + */ +static int i915_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct drm_device *dev = obj->dev; + struct drm_i915_gem_object *obj_priv = obj->driver_private; + unsigned long page_offset; + unsigned long pfn; + int ret = 0; + + /* Get the offset into the object here */ + page_offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >> + PAGE_SHIFT; + + DRM_ERROR("faulting in object %d offset 0x%08lx\n", obj->name, + page_offset); + + /* + * Bind object to GTT so we can get offset (should also fault + * in pages). + */ + if (i915_gem_object_bind_to_gtt(obj, obj_priv->gtt_alignment)) + return VM_FAULT_SIGBUS; + + /* FIXME: should be WC, but need range mapping like i386.c */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Now that the object is bound, get its GTT pfn */ + pfn = (dev->agp->base + obj_priv->gtt_offset + page_offset) >> + PAGE_SHIFT; + + /* Finally, remap it using the new GTT offset */ + ret = vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn); + + return ret == -EAGAIN ? VM_FAULT_OOM : VM_FAULT_NOPAGE; +} + +static int i915_gem_vm_mkwrite(struct vm_area_struct *vma, struct page *page) +{ + return 0; +} + +static int i915_gem_vm_access(struct vm_area_struct *vma, unsigned long addr, + void *buf, int len, int write) +{ + return 0; +} + +static struct vm_operations_struct i915_gem_vm_ops = { + .open = i915_gem_vm_open, + .close = i915_gem_vm_close, + .fault = i915_gem_fault, + .page_mkwrite = i915_gem_vm_mkwrite, + .access = i915_gem_vm_access, +}; + +/** + * i915_gem_mmap_gtt_ioctl - prepare an object for GTT mmap'ing + * @dev: DRM device + * @data: GTT mapping ioctl data + * @file_priv: GEM object info + * + * Allocate a fake mmap offset for the given object and add it + * to the DRM map hash. This allows drm_mmap() to find it so that + * the vm_ops can be overridden with GEM fault & access handlers. + */ +int +i915_gem_mmap_gtt_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_i915_gem_mmap_gtt *args = data; + struct drm_gem_object *obj; + struct drm_i915_gem_object *obj_priv; + + if (!(dev->driver->driver_features & DRIVER_GEM)) + return -ENODEV; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (obj == NULL) + return -EBADF; + + obj_priv = obj->driver_private; + + mutex_lock(&dev->struct_mutex); + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + args->addr_ptr = obj_priv->mmap_offset; + + obj_priv->gtt_alignment = args->alignment; + + return 0; +} + static void i915_gem_object_free_page_list(struct drm_gem_object *obj) { @@ -2128,7 +2251,10 @@ i915_gem_throttle_ioctl(struct drm_device *dev, void *data, int i915_gem_init_object(struct drm_gem_object *obj) { + struct drm_device *dev = obj->dev; struct drm_i915_gem_object *obj_priv; + struct drm_map_list *list; + struct drm_map *map; obj_priv = drm_calloc(1, sizeof(*obj_priv), DRM_MEM_DRIVER); if (obj_priv == NULL) @@ -2146,11 +2272,58 @@ int i915_gem_init_object(struct drm_gem_object *obj) obj->driver_private = obj_priv; obj_priv->obj = obj; INIT_LIST_HEAD(&obj_priv->list); + + /* Set the object up for mmap'ing */ + list = &obj->map_list; + list->map = drm_calloc(1, sizeof(struct drm_map_list), + DRM_MEM_DRIVER); + if (!list->map) + return -ENOMEM; + + map = list->map; + map->type = _DRM_GEM; + map->size = obj->size; + map->handle = obj; + + /* Get a DRM GEM mmap offset allocated... */ + list->file_offset_node = drm_mm_search_free(&dev->offset_manager, + obj->size / PAGE_SIZE, 0, 0); + if (!list->file_offset_node) { + DRM_ERROR("failed to allocate offset for bo %d\n", obj->name); + /* cleanup map_list etc. here */ + return -ENOMEM; + } + + list->file_offset_node = drm_mm_get_block(list->file_offset_node, + obj->size / PAGE_SIZE, 0); + if (!list->file_offset_node) { + /* cleanup */ + return -ENOMEM; + } + + DRM_ERROR("adding object %d with offset 0x%08lx\n", + obj->name, list->file_offset_node->start); + list->hash.key = list->file_offset_node->start; + if (drm_ht_insert_item(&dev->map_hash, &list->hash)) { + DRM_ERROR("failed to add to map hash\n"); + /* cleanup */ + return -ENOMEM; + } + + obj->vm_ops = &i915_gem_vm_ops; + + /* By now we should be all set, any drm_mmap request on the offset + * below will get to our mmap & fault handler */ + obj_priv->mmap_offset = list->file_offset_node->start << PAGE_SHIFT; + return 0; } void i915_gem_free_object(struct drm_gem_object *obj) { + struct drm_device *dev = obj->dev; + struct drm_map_list *list; + struct drm_map *map; struct drm_i915_gem_object *obj_priv = obj->driver_private; while (obj_priv->pin_count > 0) @@ -2158,6 +2331,21 @@ void i915_gem_free_object(struct drm_gem_object *obj) i915_gem_object_unbind(obj); + list = &obj->map_list; + drm_ht_remove_item(&dev->map_hash, &list->hash); + + if (list->file_offset_node) { + drm_mm_put_block(list->file_offset_node); + list->file_offset_node = NULL; + } + + map = list->map; + if (!map) + return; + + drm_free(map, sizeof(*map), DRM_MEM_DRIVER); + list->map = NULL; + drm_free(obj_priv->page_cpu_valid, 1, DRM_MEM_DRIVER); drm_free(obj->driver_private, 1, DRM_MEM_DRIVER); } @@ -2273,7 +2461,15 @@ i915_gem_idle(struct drm_device *dev) * waited for a sequence higher than any pending execbuffer */ BUG_ON(!list_empty(&dev_priv->mm.active_list)); - BUG_ON(!list_empty(&dev_priv->mm.flushing_list)); + if (!list_empty(&dev_priv->mm.flushing_list)) { + struct drm_i915_gem_object *obj_priv; + DRM_ERROR("flushing list not empty, still has:\n"); + list_for_each_entry(obj_priv, &dev_priv->mm.flushing_list, + list) { + DRM_ERROR(" %p: %08x\n", obj_priv, + obj_priv->last_rendering_seqno); + } + } /* Request should now be empty as we've also waited * for the last request in the list @@ -2286,7 +2482,6 @@ i915_gem_idle(struct drm_device *dev) return ret; BUG_ON(!list_empty(&dev_priv->mm.active_list)); - BUG_ON(!list_empty(&dev_priv->mm.flushing_list)); BUG_ON(!list_empty(&dev_priv->mm.inactive_list)); BUG_ON(!list_empty(&dev_priv->mm.request_list)); return 0; diff --git a/include/drm/drm.h b/include/drm/drm.h index dc347ef..8b3c126 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -190,6 +190,7 @@ enum drm_map_type { _DRM_AGP = 3, /**< AGP/GART */ _DRM_SCATTER_GATHER = 4, /**< Scatter/gather memory for PCI DMA */ _DRM_CONSISTENT = 5, /**< Consistent memory for PCI DMA */ + _DRM_GEM = 6, /**< GEM object */ }; /** diff --git a/include/drm/drmP.h b/include/drm/drmP.h index a540db8..04636fa 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -150,6 +150,13 @@ struct drm_device; #define DRM_MAX_CTXBITMAP (PAGE_SIZE * 8) #define DRM_MAP_HASH_OFFSET 0x10000000 +/* + * We make up offsets for buffer objects so we can recognize them at + * mmap time. + */ +#define DRM_FILE_PAGE_OFFSET_START ((0xFFFFFFFFUL >> PAGE_SHIFT) + 1) +#define DRM_FILE_PAGE_OFFSET_SIZE ((0xFFFFFFFFUL >> PAGE_SHIFT) * 16) + /*@}*/ /***********************************************************************/ @@ -523,6 +530,7 @@ struct drm_map_list { struct drm_hash_item hash; struct drm_map *map; /**< mapping */ uint64_t user_token; + struct drm_mm_node *file_offset_node; /**< fake offset */ }; typedef struct drm_map drm_local_map_t; @@ -579,6 +587,12 @@ struct drm_gem_object { /** File representing the shmem storage */ struct file *filp; + /* Mapping info for this object */ + struct drm_map_list map_list; + + /* Driver private ops for this object */ + struct vm_operations_struct *vm_ops; + /** * Size of the object, in bytes. Immutable over the object's * lifetime. @@ -757,6 +771,7 @@ struct drm_device { struct list_head maplist; /**< Linked list of regions */ int map_count; /**< Number of mappable regions */ struct drm_open_hash map_hash; /**< User token hash table for maps */ + struct drm_mm offset_manager; /**< Offset mgmt for buffer objects */ /** \name Context handle management */ /*@{ */ @@ -1185,6 +1200,8 @@ void drm_gem_object_free(struct kref *kref); struct drm_gem_object *drm_gem_object_alloc(struct drm_device *dev, size_t size); void drm_gem_object_handle_free(struct kref *kref); +int drm_gem_mmap(struct vm_area_struct *vma, struct file *filp, + struct drm_map *map); static inline void drm_gem_object_reference(struct drm_gem_object *obj) diff --git a/include/drm/i915_drm.h b/include/drm/i915_drm.h index 59d08fc..dbe22ac 100644 --- a/include/drm/i915_drm.h +++ b/include/drm/i915_drm.h @@ -159,6 +159,7 @@ typedef struct _drm_i915_sarea { #define DRM_I915_GEM_SW_FINISH 0x20 #define DRM_I915_GEM_SET_TILING 0x21 #define DRM_I915_GEM_GET_TILING 0x22 +#define DRM_I915_GEM_MMAP_GTT 0x23 #define DRM_IOCTL_I915_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT, drm_i915_init_t) #define DRM_IOCTL_I915_FLUSH DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLUSH) @@ -186,6 +187,7 @@ typedef struct _drm_i915_sarea { #define DRM_IOCTL_I915_GEM_PREAD DRM_IOW (DRM_COMMAND_BASE + DRM_I915_GEM_PREAD, struct drm_i915_gem_pread) #define DRM_IOCTL_I915_GEM_PWRITE DRM_IOW (DRM_COMMAND_BASE + DRM_I915_GEM_PWRITE, struct drm_i915_gem_pwrite) #define DRM_IOCTL_I915_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GEM_MMAP, struct drm_i915_gem_mmap) +#define DRM_IOCTL_I915_GEM_MMAP_GTT DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GEM_MMAP_GTT, struct drm_i915_gem_mmap_gtt) #define DRM_IOCTL_I915_GEM_SET_DOMAIN DRM_IOW (DRM_COMMAND_BASE + DRM_I915_GEM_SET_DOMAIN, struct drm_i915_gem_set_domain) #define DRM_IOCTL_I915_GEM_SW_FINISH DRM_IOW (DRM_COMMAND_BASE + DRM_I915_GEM_SW_FINISH, struct drm_i915_gem_sw_finish) #define DRM_IOCTL_I915_GEM_SET_TILING DRM_IOWR (DRM_COMMAND_BASE + DRM_I915_GEM_SET_TILING, struct drm_i915_gem_set_tiling) @@ -379,6 +381,24 @@ struct drm_i915_gem_mmap { uint64_t addr_ptr; }; +struct drm_i915_gem_mmap_gtt { + /** Handle for the object being mapped. */ + uint32_t handle; + uint32_t pad; + /** Offset in the object to map. */ + uint64_t offset; + /** + * Length of data to map. + * + * The value will be page-aligned. + */ + uint64_t size; + /** Returned pointer the data was mapped at */ + uint64_t addr_ptr; /* void *, but pointers are not 32/64 compatible */ + uint32_t flags; + uint32_t alignment; +}; + struct drm_i915_gem_set_domain { /** Handle for the object */ uint32_t handle;