drm: updated DRM map patch for 32/64 bit systems
[linux-2.6.git] / drivers / char / drm / drm_bufs.c
index 4c6191d..d1e0b10 100644 (file)
 #include <linux/vmalloc.h>
 #include "drmP.h"
 
-/**
- * Compute size order.  Returns the exponent of the smaller power of two which
- * is greater or equal to given number.
- * 
- * \param size size.
- * \return order.
- *
- * \todo Can be made faster.
- */
-int drm_order( unsigned long size )
+unsigned long drm_get_resource_start(drm_device_t *dev, unsigned int resource)
 {
-       int order;
-       unsigned long tmp;
+       return pci_resource_start(dev->pdev, resource);
+}
+EXPORT_SYMBOL(drm_get_resource_start);
 
-       for (order = 0, tmp = size >> 1; tmp; tmp >>= 1, order++)
-               ;
+unsigned long drm_get_resource_len(drm_device_t *dev, unsigned int resource)
+{
+       return pci_resource_len(dev->pdev, resource);
+}
+EXPORT_SYMBOL(drm_get_resource_len);
 
-       if (size & (size - 1))
-               ++order;
+static drm_local_map_t *drm_find_matching_map(drm_device_t *dev,
+                                             drm_local_map_t *map)
+{
+       struct list_head *list;
 
-       return order;
+       list_for_each(list, &dev->maplist->head) {
+               drm_map_list_t *entry = list_entry(list, drm_map_list_t, head);
+               if (entry->map && map->type == entry->map->type &&
+                   entry->map->offset == map->offset) {
+                       return entry->map;
+               }
+       }
+
+       return NULL;
 }
-EXPORT_SYMBOL(drm_order);
 
-#ifdef CONFIG_COMPAT
 /*
- * Used to allocate 32-bit handles for _DRM_SHM regions
- * The 0x10000000 value is chosen to be out of the way of
- * FB/register and GART physical addresses.
+ * Used to allocate 32-bit handles for mappings.
  */
-static unsigned int map32_handle = 0x10000000;
+#define START_RANGE 0x10000000
+#define END_RANGE 0x40000000
+
+#ifdef _LP64
+static __inline__ unsigned int HandleID(unsigned long lhandle, drm_device_t *dev) 
+{
+       static unsigned int map32_handle = START_RANGE;
+       unsigned int hash;
+
+       if (lhandle & 0xffffffff00000000) {
+               hash = map32_handle;
+               map32_handle += PAGE_SIZE;
+               if (map32_handle > END_RANGE)
+                       map32_handle = START_RANGE;
+       } else 
+               hash = lhandle;
+
+       while (1) {
+               drm_map_list_t *_entry;
+               list_for_each_entry(_entry, &dev->maplist->head,head) {
+                       if (_entry->user_token == hash)
+                               break;
+               }
+               if (&_entry->head == &dev->maplist->head)
+                       return hash;
+
+               hash += PAGE_SIZE;
+               map32_handle += PAGE_SIZE;
+       }
+}
+#else
+# define HandleID(x,dev) (unsigned int)(x)
 #endif
 
 /**
@@ -82,25 +114,23 @@ static unsigned int map32_handle = 0x10000000;
  * type.  Adds the map to the map list drm_device::maplist. Adds MTRR's where
  * applicable and if supported by the kernel.
  */
-int drm_addmap( struct inode *inode, struct file *filp,
-                unsigned int cmd, unsigned long arg )
+int drm_addmap(drm_device_t * dev, unsigned int offset,
+              unsigned int size, drm_map_type_t type,
+              drm_map_flags_t flags, drm_local_map_t ** map_ptr)
 {
-       drm_file_t *priv = filp->private_data;
-       drm_device_t *dev = priv->head->dev;
        drm_map_t *map;
-       drm_map_t __user *argp = (void __user *)arg;
        drm_map_list_t *list;
-
-       if ( !(filp->f_mode & 3) ) return -EACCES; /* Require read/write */
+       drm_dma_handle_t *dmah;
+       drm_local_map_t *found_map;
 
        map = drm_alloc( sizeof(*map), DRM_MEM_MAPS );
        if ( !map )
                return -ENOMEM;
 
-       if ( copy_from_user( map, argp, sizeof(*map) ) ) {
-               drm_free( map, sizeof(*map), DRM_MEM_MAPS );
-               return -EFAULT;
-       }
+       map->offset = offset;
+       map->size = size;
+       map->flags = flags;
+       map->type = type;
 
        /* Only allow shared memory to be removable since we only keep enough
         * book keeping information about shared memory to allow for removal
@@ -132,6 +162,24 @@ int drm_addmap( struct inode *inode, struct file *filp,
 #ifdef __alpha__
                map->offset += dev->hose->mem_space->start;
 #endif
+               /* Some drivers preinitialize some maps, without the X Server
+                * needing to be aware of it.  Therefore, we just return success
+                * when the server tries to create a duplicate map.
+                */
+               found_map = drm_find_matching_map(dev, map);
+               if (found_map != NULL) {
+                       if (found_map->size != map->size) {
+                               DRM_DEBUG("Matching maps of type %d with "
+                                  "mismatched sizes, (%ld vs %ld)\n",
+                                   map->type, map->size, found_map->size);
+                               found_map->size = map->size;
+                       }
+
+                       drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+                       *map_ptr = found_map;
+                       return 0;
+               }
+
                if (drm_core_has_MTRR(dev)) {
                        if ( map->type == _DRM_FRAME_BUFFER ||
                             (map->flags & _DRM_WRITE_COMBINING) ) {
@@ -178,9 +226,22 @@ int drm_addmap( struct inode *inode, struct file *filp,
                        drm_free(map, sizeof(*map), DRM_MEM_MAPS);
                        return -EINVAL;
                }
-               map->offset += dev->sg->handle;
+               map->offset += (unsigned long)dev->sg->virtual;
+               break;
+       case _DRM_CONSISTENT: 
+               /* dma_addr_t is 64bit on i386 with CONFIG_HIGHMEM64G,
+                * As we're limiting the address to 2^32-1 (or less),
+                * casting it down to 32 bits is no problem, but we
+                * need to point to a 64bit variable first. */
+               dmah = drm_pci_alloc(dev, map->size, map->size, 0xffffffffUL);
+               if (!dmah) {
+                       drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+                       return -ENOMEM;
+               }
+               map->handle = dmah->vaddr;
+               map->offset = (unsigned long)dmah->busaddr;
+               kfree(dmah);
                break;
-
        default:
                drm_free( map, sizeof(*map), DRM_MEM_MAPS );
                return -EINVAL;
@@ -196,20 +257,59 @@ int drm_addmap( struct inode *inode, struct file *filp,
 
        down(&dev->struct_sem);
        list_add(&list->head, &dev->maplist->head);
-#ifdef CONFIG_COMPAT
-       /* Assign a 32-bit handle for _DRM_SHM mappings */
+       /* Assign a 32-bit handle */
        /* We do it here so that dev->struct_sem protects the increment */
-       if (map->type == _DRM_SHM)
-               map->offset = map32_handle += PAGE_SIZE;
-#endif
+       list->user_token = HandleID(map->type==_DRM_SHM
+                                   ? (unsigned long)map->handle
+                                   : map->offset, dev);
        up(&dev->struct_sem);
 
-       if ( copy_to_user( argp, map, sizeof(*map) ) )
+       *map_ptr = map;
+       return 0;
+}
+EXPORT_SYMBOL(drm_addmap);
+
+int drm_addmap_ioctl(struct inode *inode, struct file *filp,
+                    unsigned int cmd, unsigned long arg)
+{
+       drm_file_t *priv = filp->private_data;
+       drm_device_t *dev = priv->head->dev;
+       drm_map_t map;
+       drm_map_t *map_ptr;
+       drm_map_t __user *argp = (void __user *)arg;
+       int err;
+       unsigned long handle = 0;
+
+       if (!(filp->f_mode & 3))
+               return -EACCES; /* Require read/write */
+
+       if (copy_from_user(& map, argp, sizeof(map))) {
+               return -EFAULT;
+       }
+
+       err = drm_addmap(dev, map.offset, map.size, map.type, map.flags,
+                        &map_ptr);
+
+       if (err) {
+               return err;
+       }
+
+       {
+               drm_map_list_t *_entry;
+               list_for_each_entry(_entry, &dev->maplist->head, head) {
+                       if (_entry->map == map_ptr)
+                               handle = _entry->user_token;
+               }
+               if (!handle)
+                       return -EFAULT;
+       }
+
+       if (copy_to_user(argp, map_ptr, sizeof(*map_ptr)))
                return -EFAULT;
-       if (copy_to_user(&argp->handle, &map->offset, sizeof(map->offset)))
+       if (put_user(handle, &argp->handle))
                return -EFAULT;
        return 0;
-}
+       }
 
 
 /**
@@ -226,81 +326,138 @@ int drm_addmap( struct inode *inode, struct file *filp,
  * its being used, and free any associate resource (such as MTRR's) if it's not
  * being on use.
  *
- * \sa addmap().
+ * \sa drm_addmap
  */
-int drm_rmmap(struct inode *inode, struct file *filp,
-              unsigned int cmd, unsigned long arg)
+int drm_rmmap_locked(drm_device_t *dev, drm_local_map_t *map)
 {
-       drm_file_t      *priv   = filp->private_data;
-       drm_device_t    *dev    = priv->head->dev;
        struct list_head *list;
        drm_map_list_t *r_list = NULL;
-       drm_vma_entry_t *pt, *prev;
-       drm_map_t *map;
+       drm_dma_handle_t dmah;
+
+       /* Find the list entry for the map and remove it */
+       list_for_each(list, &dev->maplist->head) {
+               r_list = list_entry(list, drm_map_list_t, head);
+
+               if (r_list->map == map) {
+                       list_del(list);
+                       drm_free(list, sizeof(*list), DRM_MEM_MAPS);
+                       break;
+               }
+       }
+
+       /* List has wrapped around to the head pointer, or it's empty and we
+        * didn't find anything.
+        */
+       if (list == (&dev->maplist->head)) {
+               return -EINVAL;
+       }
+
+       switch (map->type) {
+       case _DRM_REGISTERS:
+               drm_ioremapfree(map->handle, map->size, dev);
+               /* FALLTHROUGH */
+       case _DRM_FRAME_BUFFER:
+               if (drm_core_has_MTRR(dev) && map->mtrr >= 0) {
+                       int retcode;
+                       retcode = mtrr_del(map->mtrr, map->offset,
+                                          map->size);
+                       DRM_DEBUG ("mtrr_del=%d\n", retcode);
+               }
+               break;
+       case _DRM_SHM:
+               vfree(map->handle);
+               break;
+       case _DRM_AGP:
+       case _DRM_SCATTER_GATHER:
+               break;
+       case _DRM_CONSISTENT:
+               dmah.vaddr = map->handle;
+               dmah.busaddr = map->offset;
+               dmah.size = map->size;
+               __drm_pci_free(dev, &dmah);
+               break;
+       }
+       drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_rmmap_locked);
+
+int drm_rmmap(drm_device_t *dev, drm_local_map_t *map)
+{
+       int ret;
+
+       down(&dev->struct_sem);
+       ret = drm_rmmap_locked(dev, map);
+       up(&dev->struct_sem);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_rmmap);
+
+/* The rmmap ioctl appears to be unnecessary.  All mappings are torn down on
+ * the last close of the device, and this is necessary for cleanup when things
+ * exit uncleanly.  Therefore, having userland manually remove mappings seems
+ * like a pointless exercise since they're going away anyway.
+ *
+ * One use case might be after addmap is allowed for normal users for SHM and
+ * gets used by drivers that the server doesn't need to care about.  This seems
+ * unlikely.
+ */
+int drm_rmmap_ioctl(struct inode *inode, struct file *filp,
+                   unsigned int cmd, unsigned long arg)
+{
+       drm_file_t *priv = filp->private_data;
+       drm_device_t *dev = priv->head->dev;
        drm_map_t request;
-       int found_maps = 0;
+       drm_local_map_t *map = NULL;
+       struct list_head *list;
+       int ret;
 
-       if (copy_from_user(&request, (drm_map_t __user *)arg,
-                          sizeof(request))) {
+       if (copy_from_user(&request, (drm_map_t __user *)arg, sizeof(request))) {
                return -EFAULT;
        }
 
        down(&dev->struct_sem);
-       list = &dev->maplist->head;
        list_for_each(list, &dev->maplist->head) {
-               r_list = list_entry(list, drm_map_list_t, head);
+               drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);
 
-               if(r_list->map &&
-                  r_list->map->offset == (unsigned long) request.handle &&
-                  r_list->map->flags & _DRM_REMOVABLE) break;
+               if (r_list->map &&
+                   r_list->user_token == (unsigned long) request.handle &&
+                   r_list->map->flags & _DRM_REMOVABLE) {
+                       map = r_list->map;
+                       break;
+               }
        }
 
        /* List has wrapped around to the head pointer, or its empty we didn't
         * find anything.
         */
-       if(list == (&dev->maplist->head)) {
+       if (list == (&dev->maplist->head)) {
                up(&dev->struct_sem);
                return -EINVAL;
        }
-       map = r_list->map;
-       list_del(list);
-       drm_free(list, sizeof(*list), DRM_MEM_MAPS);
 
-       for (pt = dev->vmalist, prev = NULL; pt; prev = pt, pt = pt->next) {
-               if (pt->vma->vm_private_data == map) found_maps++;
-       }
+       if (!map)
+               return -EINVAL;
 
-       if(!found_maps) {
-               switch (map->type) {
-               case _DRM_REGISTERS:
-               case _DRM_FRAME_BUFFER:
-                 if (drm_core_has_MTRR(dev)) {
-                               if (map->mtrr >= 0) {
-                                       int retcode;
-                                       retcode = mtrr_del(map->mtrr,
-                                                          map->offset,
-                                                          map->size);
-                                       DRM_DEBUG("mtrr_del = %d\n", retcode);
-                               }
-                       }
-                       drm_ioremapfree(map->handle, map->size, dev);
-                       break;
-               case _DRM_SHM:
-                       vfree(map->handle);
-                       break;
-               case _DRM_AGP:
-               case _DRM_SCATTER_GATHER:
-                       break;
-               }
-               drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+       /* Register and framebuffer maps are permanent */
+       if ((map->type == _DRM_REGISTERS) || (map->type == _DRM_FRAME_BUFFER)) {
+               up(&dev->struct_sem);
+               return 0;
        }
+
+       ret = drm_rmmap_locked(dev, map);
+
        up(&dev->struct_sem);
-       return 0;
+
+       return ret;
 }
 
 /**
  * Cleanup after an error on one of the addbufs() functions.
  *
+ * \param dev DRM device.
  * \param entry buffer entry where the error occurred.
  *
  * Frees any pages and buffers associated with the given entry.
@@ -344,25 +501,19 @@ static void drm_cleanup_buf_error(drm_device_t *dev, drm_buf_entry_t *entry)
 
 #if __OS_HAS_AGP
 /**
- * Add AGP buffers for DMA transfers (ioctl).
+ * Add AGP buffers for DMA transfers.
  *
- * \param inode device inode.
- * \param filp file pointer.
- * \param cmd command.
- * \param arg pointer to a drm_buf_desc_t request.
+ * \param dev drm_device_t to which the buffers are to be added.
+ * \param request pointer to a drm_buf_desc_t describing the request.
  * \return zero on success or a negative number on failure.
  * 
  * After some sanity checks creates a drm_buf structure for each buffer and
  * reallocates the buffer list of the same size order to accommodate the new
  * buffers.
  */
-static int drm_addbufs_agp( struct inode *inode, struct file *filp,
-                           unsigned int cmd, unsigned long arg )
+int drm_addbufs_agp(drm_device_t *dev, drm_buf_desc_t *request)
 {
-       drm_file_t *priv = filp->private_data;
-       drm_device_t *dev = priv->head->dev;
        drm_device_dma_t *dma = dev->dma;
-       drm_buf_desc_t request;
        drm_buf_entry_t *entry;
        drm_buf_t *buf;
        unsigned long offset;
@@ -376,25 +527,20 @@ static int drm_addbufs_agp( struct inode *inode, struct file *filp,
        int byte_count;
        int i;
        drm_buf_t **temp_buflist;
-       drm_buf_desc_t __user *argp = (void __user *)arg;
 
        if ( !dma ) return -EINVAL;
 
-       if ( copy_from_user( &request, argp,
-                            sizeof(request) ) )
-               return -EFAULT;
-
-       count = request.count;
-       order = drm_order( request.size );
+       count = request->count;
+       order = drm_order(request->size);
        size = 1 << order;
 
-       alignment  = (request.flags & _DRM_PAGE_ALIGN)
+       alignment  = (request->flags & _DRM_PAGE_ALIGN)
                ? PAGE_ALIGN(size) : size;
        page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
        total = PAGE_SIZE << page_order;
 
        byte_count = 0;
-       agp_offset = dev->agp->base + request.agp_start;
+       agp_offset = dev->agp->base + request->agp_start;
 
        DRM_DEBUG( "count:      %d\n",  count );
        DRM_DEBUG( "order:      %d\n",  order );
@@ -508,26 +654,20 @@ static int drm_addbufs_agp( struct inode *inode, struct file *filp,
 
        up( &dev->struct_sem );
 
-       request.count = entry->buf_count;
-       request.size = size;
-
-       if ( copy_to_user( argp, &request, sizeof(request) ) )
-               return -EFAULT;
+       request->count = entry->buf_count;
+       request->size = size;
 
        dma->flags = _DRM_DMA_USE_AGP;
 
        atomic_dec( &dev->buf_alloc );
        return 0;
 }
+EXPORT_SYMBOL(drm_addbufs_agp);
 #endif /* __OS_HAS_AGP */
 
-static int drm_addbufs_pci( struct inode *inode, struct file *filp,
-                           unsigned int cmd, unsigned long arg )
+int drm_addbufs_pci(drm_device_t *dev, drm_buf_desc_t *request)
 {
-       drm_file_t *priv = filp->private_data;
-       drm_device_t *dev = priv->head->dev;
        drm_device_dma_t *dma = dev->dma;
-       drm_buf_desc_t request;
        int count;
        int order;
        int size;
@@ -543,26 +683,22 @@ static int drm_addbufs_pci( struct inode *inode, struct file *filp,
        int page_count;
        unsigned long *temp_pagelist;
        drm_buf_t **temp_buflist;
-       drm_buf_desc_t __user *argp = (void __user *)arg;
 
        if (!drm_core_check_feature(dev, DRIVER_PCI_DMA)) return -EINVAL;
        if ( !dma ) return -EINVAL;
 
-       if ( copy_from_user( &request, argp, sizeof(request) ) )
-               return -EFAULT;
-
-       count = request.count;
-       order = drm_order( request.size );
+       count = request->count;
+       order = drm_order(request->size);
        size = 1 << order;
 
        DRM_DEBUG( "count=%d, size=%d (%d), order=%d, queue_count=%d\n",
-                  request.count, request.size, size,
+                  request->count, request->size, size,
                   order, dev->queue_count );
 
        if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
        if ( dev->queue_count ) return -EBUSY; /* Not while in use */
 
-       alignment = (request.flags & _DRM_PAGE_ALIGN)
+       alignment = (request->flags & _DRM_PAGE_ALIGN)
                ? PAGE_ALIGN(size) : size;
        page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
        total = PAGE_SIZE << page_order;
@@ -740,25 +876,18 @@ static int drm_addbufs_pci( struct inode *inode, struct file *filp,
 
        up( &dev->struct_sem );
 
-       request.count = entry->buf_count;
-       request.size = size;
-
-       if ( copy_to_user( argp, &request, sizeof(request) ) )
-               return -EFAULT;
+       request->count = entry->buf_count;
+       request->size = size;
 
        atomic_dec( &dev->buf_alloc );
        return 0;
 
 }
+EXPORT_SYMBOL(drm_addbufs_pci);
 
-static int drm_addbufs_sg( struct inode *inode, struct file *filp,
-                          unsigned int cmd, unsigned long arg )
+static int drm_addbufs_sg(drm_device_t *dev, drm_buf_desc_t *request)
 {
-       drm_file_t *priv = filp->private_data;
-       drm_device_t *dev = priv->head->dev;
        drm_device_dma_t *dma = dev->dma;
-       drm_buf_desc_t __user *argp = (void __user *)arg;
-       drm_buf_desc_t request;
        drm_buf_entry_t *entry;
        drm_buf_t *buf;
        unsigned long offset;
@@ -777,20 +906,17 @@ static int drm_addbufs_sg( struct inode *inode, struct file *filp,
        
        if ( !dma ) return -EINVAL;
 
-       if ( copy_from_user( &request, argp, sizeof(request) ) )
-               return -EFAULT;
-
-       count = request.count;
-       order = drm_order( request.size );
+       count = request->count;
+       order = drm_order(request->size);
        size = 1 << order;
 
-       alignment  = (request.flags & _DRM_PAGE_ALIGN)
+       alignment  = (request->flags & _DRM_PAGE_ALIGN)
                        ? PAGE_ALIGN(size) : size;
        page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
        total = PAGE_SIZE << page_order;
 
        byte_count = 0;
-       agp_offset = request.agp_start;
+       agp_offset = request->agp_start;
 
        DRM_DEBUG( "count:      %d\n",  count );
        DRM_DEBUG( "order:      %d\n",  order );
@@ -848,7 +974,8 @@ static int drm_addbufs_sg( struct inode *inode, struct file *filp,
 
                buf->offset  = (dma->byte_count + offset);
                buf->bus_address = agp_offset + offset;
-               buf->address = (void *)(agp_offset + offset + dev->sg->handle);
+               buf->address = (void *)(agp_offset + offset 
+                                       + (unsigned long)dev->sg->virtual);
                buf->next    = NULL;
                buf->waiting = 0;
                buf->pending = 0;
@@ -905,11 +1032,8 @@ static int drm_addbufs_sg( struct inode *inode, struct file *filp,
 
        up( &dev->struct_sem );
 
-       request.count = entry->buf_count;
-       request.size = size;
-
-       if ( copy_to_user( argp, &request, sizeof(request) ) )
-               return -EFAULT;
+       request->count = entry->buf_count;
+       request->size = size;
 
        dma->flags = _DRM_DMA_USE_SG;
 
@@ -917,6 +1041,161 @@ static int drm_addbufs_sg( struct inode *inode, struct file *filp,
        return 0;
 }
 
+int drm_addbufs_fb(drm_device_t *dev, drm_buf_desc_t *request)
+{
+       drm_device_dma_t *dma = dev->dma;
+       drm_buf_entry_t *entry;
+       drm_buf_t *buf;
+       unsigned long offset;
+       unsigned long agp_offset;
+       int count;
+       int order;
+       int size;
+       int alignment;
+       int page_order;
+       int total;
+       int byte_count;
+       int i;
+       drm_buf_t **temp_buflist;
+
+       if (!drm_core_check_feature(dev, DRIVER_FB_DMA))
+               return -EINVAL;
+    
+       if (!dma)
+               return -EINVAL;
+
+       count = request->count;
+       order = drm_order(request->size);
+       size = 1 << order;
+
+       alignment = (request->flags & _DRM_PAGE_ALIGN)
+           ? PAGE_ALIGN(size) : size;
+       page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
+       total = PAGE_SIZE << page_order;
+
+       byte_count = 0;
+       agp_offset = request->agp_start;
+
+       DRM_DEBUG("count:      %d\n", count);
+       DRM_DEBUG("order:      %d\n", order);
+       DRM_DEBUG("size:       %d\n", size);
+       DRM_DEBUG("agp_offset: %lu\n", agp_offset);
+       DRM_DEBUG("alignment:  %d\n", alignment);
+       DRM_DEBUG("page_order: %d\n", page_order);
+       DRM_DEBUG("total:      %d\n", total);
+
+       if (order < DRM_MIN_ORDER || order > DRM_MAX_ORDER)
+               return -EINVAL;
+       if (dev->queue_count)
+               return -EBUSY;  /* Not while in use */
+
+       spin_lock(&dev->count_lock);
+       if (dev->buf_use) {
+               spin_unlock(&dev->count_lock);
+               return -EBUSY;
+       }
+       atomic_inc(&dev->buf_alloc);
+       spin_unlock(&dev->count_lock);
+
+       down(&dev->struct_sem);
+       entry = &dma->bufs[order];
+       if (entry->buf_count) {
+               up(&dev->struct_sem);
+               atomic_dec(&dev->buf_alloc);
+               return -ENOMEM; /* May only call once for each order */
+       }
+
+       if (count < 0 || count > 4096) {
+               up(&dev->struct_sem);
+               atomic_dec(&dev->buf_alloc);
+               return -EINVAL;
+       }
+
+       entry->buflist = drm_alloc(count * sizeof(*entry->buflist),
+                                  DRM_MEM_BUFS);
+       if (!entry->buflist) {
+               up(&dev->struct_sem);
+               atomic_dec(&dev->buf_alloc);
+               return -ENOMEM;
+       }
+       memset(entry->buflist, 0, count * sizeof(*entry->buflist));
+
+       entry->buf_size = size;
+       entry->page_order = page_order;
+
+       offset = 0;
+
+       while (entry->buf_count < count) {
+               buf = &entry->buflist[entry->buf_count];
+               buf->idx = dma->buf_count + entry->buf_count;
+               buf->total = alignment;
+               buf->order = order;
+               buf->used = 0;
+
+               buf->offset = (dma->byte_count + offset);
+               buf->bus_address = agp_offset + offset;
+               buf->address = (void *)(agp_offset + offset);
+               buf->next = NULL;
+               buf->waiting = 0;
+               buf->pending = 0;
+               init_waitqueue_head(&buf->dma_wait);
+               buf->filp = NULL;
+
+               buf->dev_priv_size = dev->driver->dev_priv_size;
+               buf->dev_private = drm_alloc(buf->dev_priv_size, DRM_MEM_BUFS);
+               if (!buf->dev_private) {
+                       /* Set count correctly so we free the proper amount. */
+                       entry->buf_count = count;
+                       drm_cleanup_buf_error(dev, entry);
+                       up(&dev->struct_sem);
+                       atomic_dec(&dev->buf_alloc);
+                       return -ENOMEM;
+               }
+               memset(buf->dev_private, 0, buf->dev_priv_size);
+
+               DRM_DEBUG("buffer %d @ %p\n", entry->buf_count, buf->address);
+
+               offset += alignment;
+               entry->buf_count++;
+               byte_count += PAGE_SIZE << page_order;
+       }
+
+       DRM_DEBUG("byte_count: %d\n", byte_count);
+
+       temp_buflist = drm_realloc(dma->buflist,
+                                  dma->buf_count * sizeof(*dma->buflist),
+                                  (dma->buf_count + entry->buf_count)
+                                  * sizeof(*dma->buflist), DRM_MEM_BUFS);
+       if (!temp_buflist) {
+               /* Free the entry because it isn't valid */
+               drm_cleanup_buf_error(dev, entry);
+               up(&dev->struct_sem);
+               atomic_dec(&dev->buf_alloc);
+               return -ENOMEM;
+       }
+       dma->buflist = temp_buflist;
+
+       for (i = 0; i < entry->buf_count; i++) {
+               dma->buflist[i + dma->buf_count] = &entry->buflist[i];
+       }
+
+       dma->buf_count += entry->buf_count;
+       dma->byte_count += byte_count;
+
+       DRM_DEBUG("dma->buf_count : %d\n", dma->buf_count);
+       DRM_DEBUG("entry->buf_count : %d\n", entry->buf_count);
+
+       up(&dev->struct_sem);
+
+       request->count = entry->buf_count;
+       request->size = size;
+
+       dma->flags = _DRM_DMA_USE_FB;
+
+       atomic_dec(&dev->buf_alloc);
+       return 0;
+}
+
 /**
  * Add buffers for DMA transfers (ioctl).
  *
@@ -937,6 +1216,7 @@ int drm_addbufs( struct inode *inode, struct file *filp,
        drm_buf_desc_t request;
        drm_file_t *priv = filp->private_data;
        drm_device_t *dev = priv->head->dev;
+       int ret;
        
        if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
                return -EINVAL;
@@ -947,13 +1227,23 @@ int drm_addbufs( struct inode *inode, struct file *filp,
 
 #if __OS_HAS_AGP
        if ( request.flags & _DRM_AGP_BUFFER )
-               return drm_addbufs_agp( inode, filp, cmd, arg );
+               ret=drm_addbufs_agp(dev, &request);
        else
 #endif
        if ( request.flags & _DRM_SG_BUFFER )
-               return drm_addbufs_sg( inode, filp, cmd, arg );
+               ret=drm_addbufs_sg(dev, &request);
+       else if ( request.flags & _DRM_FB_BUFFER)
+               ret=drm_addbufs_fb(dev, &request);
        else
-               return drm_addbufs_pci( inode, filp, cmd, arg );
+               ret=drm_addbufs_pci(dev, &request);
+
+       if (ret==0) {
+               if (copy_to_user((void __user *)arg, &request,
+                                sizeof(request))) {
+                       ret = -EFAULT;
+               }
+       }
+       return ret;
 }
 
 
@@ -1196,9 +1486,13 @@ int drm_mapbufs( struct inode *inode, struct file *filp,
                return -EFAULT;
 
        if ( request.count >= dma->buf_count ) {
-               if ((drm_core_has_AGP(dev) && (dma->flags & _DRM_DMA_USE_AGP)) ||
-                   (drm_core_check_feature(dev, DRIVER_SG) && (dma->flags & _DRM_DMA_USE_SG)) ) {
+               if ((drm_core_has_AGP(dev) && (dma->flags & _DRM_DMA_USE_AGP))
+                   || (drm_core_check_feature(dev, DRIVER_SG) 
+                       && (dma->flags & _DRM_DMA_USE_SG))
+                   || (drm_core_check_feature(dev, DRIVER_FB_DMA)
+                       && (dma->flags & _DRM_DMA_USE_FB))) {
                        drm_map_t *map = dev->agp_buffer_map;
+                       unsigned long token = dev->agp_buffer_token;
 
                        if ( !map ) {
                                retcode = -EINVAL;
@@ -1213,7 +1507,7 @@ int drm_mapbufs( struct inode *inode, struct file *filp,
                        virtual = do_mmap( filp, 0, map->size,
                                           PROT_READ | PROT_WRITE,
                                           MAP_SHARED,
-                                          (unsigned long)map->offset );
+                                          token );
 #if LINUX_VERSION_CODE <= 0x020402
                        up( &current->mm->mmap_sem );
 #else
@@ -1279,3 +1573,26 @@ int drm_mapbufs( struct inode *inode, struct file *filp,
        return retcode;
 }
 
+/**
+ * Compute size order.  Returns the exponent of the smaller power of two which
+ * is greater or equal to given number.
+ * 
+ * \param size size.
+ * \return order.
+ *
+ * \todo Can be made faster.
+ */
+int drm_order( unsigned long size )
+{
+       int order;
+       unsigned long tmp;
+
+       for (order = 0, tmp = size >> 1; tmp; tmp >>= 1, order++)
+               ;
+
+       if (size & (size - 1))
+               ++order;
+
+       return order;
+}
+EXPORT_SYMBOL(drm_order);