2008-07-30 12:06:12 -07:00
/*
* Copyright © 2008 Intel Corporation
*
* 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 :
* Eric Anholt < eric @ anholt . net >
*
*/
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/mm.h>
# include <linux/uaccess.h>
# include <linux/fs.h>
# include <linux/file.h>
# include <linux/module.h>
# include <linux/mman.h>
# include <linux/pagemap.h>
# include "drmP.h"
/** @file drm_gem.c
*
* This file provides some of the base ioctls and library routines for
* the graphics memory manager implemented by each device driver .
*
* Because various devices have different requirements in terms of
* synchronization and migration strategies , implementing that is left up to
* the driver , and all that the general API provides should be generic - -
* allocating objects , reading / writing data with the cpu , freeing objects .
* Even there , platform - dependent optimizations for reading / writing data with
* the CPU mean we ' ll likely hook those out to driver - specific calls . However ,
* the DRI2 implementation wants to have at least allocate / mmap be generic .
*
* The goal was to have swap - backed object allocation managed through
* struct file . However , file descriptors as handles to a struct file have
* two major failings :
* - Process limits prevent more than 1024 or so being used at a time by
* default .
* - Inability to allocate high fds will aggravate the X Server ' s select ( )
* handling , and likely that of many GL client applications as well .
*
* This led to a plan of using our own integer IDs ( called handles , following
* DRM terminology ) to mimic fds , and implement the fd syscalls we need as
* ioctls . The objects themselves will still include the struct file so
* that we can transition to fds if the required kernel infrastructure shows
* up at a later date , and as our interface with shmfs for memory allocation .
*/
2008-11-05 10:31:53 -08:00
/*
* 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)
2008-07-30 12:06:12 -07:00
/**
* Initialize the GEM device fields
*/
int
drm_gem_init ( struct drm_device * dev )
{
2008-11-05 10:31:53 -08:00
struct drm_gem_mm * mm ;
2008-07-30 12:06:12 -07:00
spin_lock_init ( & dev - > object_name_lock ) ;
idr_init ( & dev - > object_name_idr ) ;
atomic_set ( & dev - > object_count , 0 ) ;
atomic_set ( & dev - > object_memory , 0 ) ;
atomic_set ( & dev - > pin_count , 0 ) ;
atomic_set ( & dev - > pin_memory , 0 ) ;
atomic_set ( & dev - > gtt_count , 0 ) ;
atomic_set ( & dev - > gtt_memory , 0 ) ;
2008-11-05 10:31:53 -08:00
2009-03-24 12:23:04 -07:00
mm = kzalloc ( sizeof ( struct drm_gem_mm ) , GFP_KERNEL ) ;
2008-11-05 10:31:53 -08:00
if ( ! mm ) {
DRM_ERROR ( " out of memory \n " ) ;
return - ENOMEM ;
}
dev - > mm_private = mm ;
if ( drm_ht_create ( & mm - > offset_hash , 19 ) ) {
2009-03-24 12:23:04 -07:00
kfree ( mm ) ;
2008-11-05 10:31:53 -08:00
return - ENOMEM ;
}
if ( drm_mm_init ( & mm - > offset_manager , DRM_FILE_PAGE_OFFSET_START ,
DRM_FILE_PAGE_OFFSET_SIZE ) ) {
drm_ht_remove ( & mm - > offset_hash ) ;
2009-03-24 12:23:04 -07:00
kfree ( mm ) ;
2008-11-05 10:31:53 -08:00
return - ENOMEM ;
}
2008-07-30 12:06:12 -07:00
return 0 ;
}
2008-11-05 10:31:53 -08:00
void
drm_gem_destroy ( struct drm_device * dev )
{
struct drm_gem_mm * mm = dev - > mm_private ;
drm_mm_takedown ( & mm - > offset_manager ) ;
drm_ht_remove ( & mm - > offset_hash ) ;
2009-03-24 12:23:04 -07:00
kfree ( mm ) ;
2008-11-05 10:31:53 -08:00
dev - > mm_private = NULL ;
}
2008-07-30 12:06:12 -07:00
/**
* Allocate a GEM object of the specified size with shmfs backing store
*/
struct drm_gem_object *
drm_gem_object_alloc ( struct drm_device * dev , size_t size )
{
struct drm_gem_object * obj ;
BUG_ON ( ( size & ( PAGE_SIZE - 1 ) ) ! = 0 ) ;
2009-06-10 12:43:49 -07:00
obj = kzalloc ( sizeof ( * obj ) , GFP_KERNEL ) ;
2009-07-13 23:20:21 +02:00
if ( ! obj )
goto free ;
2008-07-30 12:06:12 -07:00
obj - > dev = dev ;
2009-01-31 15:08:56 -08:00
obj - > filp = shmem_file_setup ( " drm mm object " , size , VM_NORESERVE ) ;
2009-07-13 23:20:21 +02:00
if ( IS_ERR ( obj - > filp ) )
goto free ;
2008-07-30 12:06:12 -07:00
kref_init ( & obj - > refcount ) ;
kref_init ( & obj - > handlecount ) ;
obj - > size = size ;
if ( dev - > driver - > gem_init_object ! = NULL & &
dev - > driver - > gem_init_object ( obj ) ! = 0 ) {
2009-07-13 23:20:21 +02:00
goto fput ;
2008-07-30 12:06:12 -07:00
}
atomic_inc ( & dev - > object_count ) ;
atomic_add ( obj - > size , & dev - > object_memory ) ;
return obj ;
2009-07-13 23:20:21 +02:00
fput :
fput ( obj - > filp ) ;
free :
kfree ( obj ) ;
return NULL ;
2008-07-30 12:06:12 -07:00
}
EXPORT_SYMBOL ( drm_gem_object_alloc ) ;
/**
* Removes the mapping from handle to filp for this object .
*/
static int
2009-08-23 12:40:55 +03:00
drm_gem_handle_delete ( struct drm_file * filp , u32 handle )
2008-07-30 12:06:12 -07:00
{
struct drm_device * dev ;
struct drm_gem_object * obj ;
/* This is gross. The idr system doesn't let us try a delete and
* return an error code . It just spews if you fail at deleting .
* So , we have to grab a lock around finding the object and then
* doing the delete on it and dropping the refcount , or the user
* could race us to double - decrement the refcount and cause a
* use - after - free later . Given the frequency of our handle lookups ,
* we may want to use ida for number allocation and a hash table
* for the pointers , anyway .
*/
spin_lock ( & filp - > table_lock ) ;
/* Check if we currently have a reference on the object */
obj = idr_find ( & filp - > object_idr , handle ) ;
if ( obj = = NULL ) {
spin_unlock ( & filp - > table_lock ) ;
return - EINVAL ;
}
dev = obj - > dev ;
/* Release reference and decrement refcount. */
idr_remove ( & filp - > object_idr , handle ) ;
spin_unlock ( & filp - > table_lock ) ;
mutex_lock ( & dev - > struct_mutex ) ;
drm_gem_object_handle_unreference ( obj ) ;
mutex_unlock ( & dev - > struct_mutex ) ;
return 0 ;
}
/**
* Create a handle for this object . This adds a handle reference
* to the object , which includes a regular reference count . Callers
* will likely want to dereference the object afterwards .
*/
int
drm_gem_handle_create ( struct drm_file * file_priv ,
struct drm_gem_object * obj ,
2009-08-23 12:40:55 +03:00
u32 * handlep )
2008-07-30 12:06:12 -07:00
{
int ret ;
/*
* Get the user - visible handle using idr .
*/
again :
/* ensure there is space available to allocate a handle */
if ( idr_pre_get ( & file_priv - > object_idr , GFP_KERNEL ) = = 0 )
return - ENOMEM ;
/* do the allocation under our spinlock */
spin_lock ( & file_priv - > table_lock ) ;
2009-08-23 12:40:55 +03:00
ret = idr_get_new_above ( & file_priv - > object_idr , obj , 1 , ( int * ) handlep ) ;
2008-07-30 12:06:12 -07:00
spin_unlock ( & file_priv - > table_lock ) ;
if ( ret = = - EAGAIN )
goto again ;
if ( ret ! = 0 )
return ret ;
drm_gem_object_handle_reference ( obj ) ;
return 0 ;
}
EXPORT_SYMBOL ( drm_gem_handle_create ) ;
/** Returns a reference to the object named by the handle. */
struct drm_gem_object *
drm_gem_object_lookup ( struct drm_device * dev , struct drm_file * filp ,
2009-08-23 12:40:55 +03:00
u32 handle )
2008-07-30 12:06:12 -07:00
{
struct drm_gem_object * obj ;
spin_lock ( & filp - > table_lock ) ;
/* Check if we currently have a reference on the object */
obj = idr_find ( & filp - > object_idr , handle ) ;
if ( obj = = NULL ) {
spin_unlock ( & filp - > table_lock ) ;
return NULL ;
}
drm_gem_object_reference ( obj ) ;
spin_unlock ( & filp - > table_lock ) ;
return obj ;
}
EXPORT_SYMBOL ( drm_gem_object_lookup ) ;
/**
* Releases the handle to an mm object .
*/
int
drm_gem_close_ioctl ( struct drm_device * dev , void * data ,
struct drm_file * file_priv )
{
struct drm_gem_close * args = data ;
int ret ;
if ( ! ( dev - > driver - > driver_features & DRIVER_GEM ) )
return - ENODEV ;
ret = drm_gem_handle_delete ( file_priv , args - > handle ) ;
return ret ;
}
/**
* Create a global name for an object , returning the name .
*
* Note that the name does not hold a reference ; when the object
* is freed , the name goes away .
*/
int
drm_gem_flink_ioctl ( struct drm_device * dev , void * data ,
struct drm_file * file_priv )
{
struct drm_gem_flink * args = data ;
struct drm_gem_object * obj ;
int ret ;
if ( ! ( dev - > driver - > driver_features & DRIVER_GEM ) )
return - ENODEV ;
obj = drm_gem_object_lookup ( dev , file_priv , args - > handle ) ;
if ( obj = = NULL )
2008-09-09 11:40:34 -07:00
return - EBADF ;
2008-07-30 12:06:12 -07:00
again :
2009-02-09 11:31:41 +00:00
if ( idr_pre_get ( & dev - > object_name_idr , GFP_KERNEL ) = = 0 ) {
ret = - ENOMEM ;
goto err ;
}
2008-07-30 12:06:12 -07:00
spin_lock ( & dev - > object_name_lock ) ;
2009-02-11 14:26:28 +00:00
if ( ! obj - > name ) {
ret = idr_get_new_above ( & dev - > object_name_idr , obj , 1 ,
& obj - > name ) ;
args - > name = ( uint64_t ) obj - > name ;
2008-07-30 12:06:12 -07:00
spin_unlock ( & dev - > object_name_lock ) ;
2009-02-11 14:26:28 +00:00
if ( ret = = - EAGAIN )
goto again ;
2008-07-30 12:06:12 -07:00
2009-02-11 14:26:28 +00:00
if ( ret ! = 0 )
goto err ;
2008-07-30 12:06:12 -07:00
2009-02-11 14:26:28 +00:00
/* Allocate a reference for the name table. */
drm_gem_object_reference ( obj ) ;
} else {
args - > name = ( uint64_t ) obj - > name ;
spin_unlock ( & dev - > object_name_lock ) ;
ret = 0 ;
}
2009-02-09 11:31:41 +00:00
err :
mutex_lock ( & dev - > struct_mutex ) ;
drm_gem_object_unreference ( obj ) ;
mutex_unlock ( & dev - > struct_mutex ) ;
return ret ;
2008-07-30 12:06:12 -07:00
}
/**
* Open an object using the global name , returning a handle and the size .
*
* This handle ( of course ) holds a reference to the object , so the object
* will not go away until the handle is deleted .
*/
int
drm_gem_open_ioctl ( struct drm_device * dev , void * data ,
struct drm_file * file_priv )
{
struct drm_gem_open * args = data ;
struct drm_gem_object * obj ;
int ret ;
2009-08-23 12:40:55 +03:00
u32 handle ;
2008-07-30 12:06:12 -07:00
if ( ! ( dev - > driver - > driver_features & DRIVER_GEM ) )
return - ENODEV ;
spin_lock ( & dev - > object_name_lock ) ;
obj = idr_find ( & dev - > object_name_idr , ( int ) args - > name ) ;
if ( obj )
drm_gem_object_reference ( obj ) ;
spin_unlock ( & dev - > object_name_lock ) ;
if ( ! obj )
return - ENOENT ;
ret = drm_gem_handle_create ( file_priv , obj , & handle ) ;
mutex_lock ( & dev - > struct_mutex ) ;
drm_gem_object_unreference ( obj ) ;
mutex_unlock ( & dev - > struct_mutex ) ;
if ( ret )
return ret ;
args - > handle = handle ;
args - > size = obj - > size ;
return 0 ;
}
/**
* Called at device open time , sets up the structure for handling refcounting
* of mm objects .
*/
void
drm_gem_open ( struct drm_device * dev , struct drm_file * file_private )
{
idr_init ( & file_private - > object_idr ) ;
spin_lock_init ( & file_private - > table_lock ) ;
}
/**
* Called at device close to release the file ' s
* handle references on objects .
*/
static int
drm_gem_object_release_handle ( int id , void * ptr , void * data )
{
struct drm_gem_object * obj = ptr ;
drm_gem_object_handle_unreference ( obj ) ;
return 0 ;
}
/**
* Called at close time when the filp is going away .
*
* Releases any remaining references on objects by this filp .
*/
void
drm_gem_release ( struct drm_device * dev , struct drm_file * file_private )
{
mutex_lock ( & dev - > struct_mutex ) ;
idr_for_each ( & file_private - > object_idr ,
& drm_gem_object_release_handle , NULL ) ;
idr_destroy ( & file_private - > object_idr ) ;
mutex_unlock ( & dev - > struct_mutex ) ;
}
/**
* Called after the last reference to the object has been lost .
*
* Frees the object
*/
void
drm_gem_object_free ( struct kref * kref )
{
struct drm_gem_object * obj = ( struct drm_gem_object * ) kref ;
struct drm_device * dev = obj - > dev ;
BUG_ON ( ! mutex_is_locked ( & dev - > struct_mutex ) ) ;
if ( dev - > driver - > gem_free_object ! = NULL )
dev - > driver - > gem_free_object ( obj ) ;
fput ( obj - > filp ) ;
atomic_dec ( & dev - > object_count ) ;
atomic_sub ( obj - > size , & dev - > object_memory ) ;
kfree ( obj ) ;
}
EXPORT_SYMBOL ( drm_gem_object_free ) ;
/**
* Called after the last handle to the object has been closed
*
* Removes any name for the object . Note that this must be
* called before drm_gem_object_free or we ' ll be touching
* freed memory
*/
void
drm_gem_object_handle_free ( struct kref * kref )
{
struct drm_gem_object * obj = container_of ( kref ,
struct drm_gem_object ,
handlecount ) ;
struct drm_device * dev = obj - > dev ;
/* Remove any name for this object */
spin_lock ( & dev - > object_name_lock ) ;
if ( obj - > name ) {
idr_remove ( & dev - > object_name_idr , obj - > name ) ;
2009-02-11 14:26:28 +00:00
obj - > name = 0 ;
2008-07-30 12:06:12 -07:00
spin_unlock ( & dev - > object_name_lock ) ;
/*
* The object name held a reference to this object , drop
* that now .
*/
drm_gem_object_unreference ( obj ) ;
} else
spin_unlock ( & dev - > object_name_lock ) ;
}
EXPORT_SYMBOL ( drm_gem_object_handle_free ) ;
2009-02-11 14:01:46 -08:00
void drm_gem_vm_open ( struct vm_area_struct * vma )
{
struct drm_gem_object * obj = vma - > vm_private_data ;
drm_gem_object_reference ( obj ) ;
}
EXPORT_SYMBOL ( drm_gem_vm_open ) ;
void drm_gem_vm_close ( struct vm_area_struct * vma )
{
struct drm_gem_object * obj = vma - > vm_private_data ;
struct drm_device * dev = obj - > dev ;
mutex_lock ( & dev - > struct_mutex ) ;
drm_gem_object_unreference ( obj ) ;
mutex_unlock ( & dev - > struct_mutex ) ;
}
EXPORT_SYMBOL ( drm_gem_vm_close ) ;
2008-11-05 10:31:53 -08:00
/**
* drm_gem_mmap - memory map routine for GEM objects
* @ filp : DRM file pointer
* @ vma : VMA for the area to be mapped
*
* If a driver supports GEM object mapping , mmap calls on the DRM file
* descriptor will end up here .
*
* If we find the object based on the offset passed in ( vma - > vm_pgoff will
* contain the fake offset we created when the GTT map ioctl was called on
* the object ) , we set up the driver fault handler so that any accesses
* to the object can be trapped , to perform migration , GTT binding , surface
* register allocation , or performance monitoring .
*/
int drm_gem_mmap ( struct file * filp , struct vm_area_struct * vma )
{
struct drm_file * priv = filp - > private_data ;
struct drm_device * dev = priv - > minor - > dev ;
struct drm_gem_mm * mm = dev - > mm_private ;
2009-02-02 16:55:46 +11:00
struct drm_local_map * map = NULL ;
2008-11-05 10:31:53 -08:00
struct drm_gem_object * obj ;
struct drm_hash_item * hash ;
int ret = 0 ;
mutex_lock ( & dev - > struct_mutex ) ;
if ( drm_ht_find_item ( & mm - > offset_hash , vma - > vm_pgoff , & hash ) ) {
mutex_unlock ( & dev - > struct_mutex ) ;
return drm_mmap ( filp , vma ) ;
}
map = drm_hash_entry ( hash , struct drm_map_list , hash ) - > map ;
if ( ! map | |
( ( map - > flags & _DRM_RESTRICTED ) & & ! capable ( CAP_SYS_ADMIN ) ) ) {
ret = - EPERM ;
goto out_unlock ;
}
/* Check for valid size. */
if ( map - > size < vma - > vm_end - vma - > vm_start ) {
ret = - EINVAL ;
goto out_unlock ;
}
obj = map - > handle ;
if ( ! obj - > dev - > driver - > gem_vm_ops ) {
ret = - EINVAL ;
goto out_unlock ;
}
vma - > vm_flags | = VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND ;
vma - > vm_ops = obj - > dev - > driver - > gem_vm_ops ;
vma - > vm_private_data = map - > handle ;
2009-11-17 14:08:54 -08:00
vma - > vm_page_prot = pgprot_writecombine ( vm_get_page_prot ( vma - > vm_flags ) ) ;
2008-11-05 10:31:53 -08:00
2009-02-11 14:01:46 -08:00
/* Take a ref for this mapping of the object, so that the fault
* handler can dereference the mmap offset ' s pointer to the object .
* This reference is cleaned up by the corresponding vm_close
* ( which should happen whether the vma was created by this call , or
* by a vm_open due to mremap or partial unmap or whatever ) .
*/
drm_gem_object_reference ( obj ) ;
2008-11-05 10:31:53 -08:00
vma - > vm_file = filp ; /* Needed for drm_vm_open() */
drm_vm_open_locked ( vma ) ;
out_unlock :
mutex_unlock ( & dev - > struct_mutex ) ;
return ret ;
}
EXPORT_SYMBOL ( drm_gem_mmap ) ;