/** * \file xf86drm.h * OS-independent header for DRM user-level library interface. * * \author Rickard E. (Rik) Faith */ /* * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas. * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. * 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 * PRECISION INSIGHT 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. * */ /* $XFree86: xc/programs/Xserver/hw/xfree86/os-support/xf86drm.h,v 1.26 2003/08/16 19:26:37 dawes Exp $ */ #ifndef _XF86DRM_H_ #define _XF86DRM_H_ #include /* Defaults, if nothing set in xf86config */ #define DRM_DEV_UID 0 #define DRM_DEV_GID 0 /* Default /dev/dri directory permissions 0755 */ #define DRM_DEV_DIRMODE \ (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) #define DRM_DEV_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) #define DRM_DIR_NAME "/dev/dri" #define DRM_DEV_NAME "%s/card%d" #define DRM_PROC_NAME "/proc/dri/" /* For backward Linux compatibility */ #define DRM_ERR_NO_DEVICE (-1001) #define DRM_ERR_NO_ACCESS (-1002) #define DRM_ERR_NOT_ROOT (-1003) #define DRM_ERR_INVALID (-1004) #define DRM_ERR_NO_FD (-1005) #define DRM_AGP_NO_HANDLE 0 typedef unsigned int drmSize, *drmSizePtr; /**< For mapped regions */ typedef void *drmAddress, **drmAddressPtr; /**< For mapped regions */ /** * Driver version information. * * \sa drmGetVersion() and drmSetVersion(). */ typedef struct _drmVersion { int version_major; /**< Major version */ int version_minor; /**< Minor version */ int version_patchlevel; /**< Patch level */ int name_len; /**< Length of name buffer */ char *name; /**< Name of driver */ int date_len; /**< Length of date buffer */ char *date; /**< User-space buffer to hold date */ int desc_len; /**< Length of desc buffer */ char *desc; /**< User-space buffer to hold desc */ } drmVersion, *drmVersionPtr; typedef struct _drmStats { unsigned long count; /**< Number of data */ struct { unsigned long value; /**< Value from kernel */ const char *long_format; /**< Suggested format for long_name */ const char *long_name; /**< Long name for value */ const char *rate_format; /**< Suggested format for rate_name */ const char *rate_name; /**< Short name for value per second */ int isvalue; /**< True if value (vs. counter) */ const char *mult_names; /**< Multiplier names (e.g., "KGM") */ int mult; /**< Multiplier value (e.g., 1024) */ int verbose; /**< Suggest only in verbose output */ } data[15]; } drmStatsT; /* All of these enums *MUST* match with the kernel implementation -- so do *NOT* change them! (The drmlib implementation will just copy the flags instead of translating them.) */ typedef enum { DRM_FRAME_BUFFER = 0, /**< WC, no caching, no core dump */ DRM_REGISTERS = 1, /**< no caching, no core dump */ DRM_SHM = 2, /**< shared, cached */ DRM_AGP = 3, /**< AGP/GART */ DRM_SCATTER_GATHER = 4, /**< PCI scatter/gather */ DRM_CONSISTENT = 5 /**< PCI consistent */ } drmMapType; typedef enum { DRM_RESTRICTED = 0x0001, /**< Cannot be mapped to client-virtual */ DRM_READ_ONLY = 0x0002, /**< Read-only in client-virtual */ DRM_LOCKED = 0x0004, /**< Physical pages locked */ DRM_KERNEL = 0x0008, /**< Kernel requires access */ DRM_WRITE_COMBINING = 0x0010, /**< Use write-combining, if available */ DRM_CONTAINS_LOCK = 0x0020, /**< SHM page that contains lock */ DRM_REMOVABLE = 0x0040 /**< Removable mapping */ } drmMapFlags; /** * \warning These values *MUST* match drm.h */ typedef enum { /** \name Flags for DMA buffer dispatch */ /*@{*/ DRM_DMA_BLOCK = 0x01, /**< * Block until buffer dispatched. * * \note the buffer may not yet have been * processed by the hardware -- getting a * hardware lock with the hardware quiescent * will ensure that the buffer has been * processed. */ DRM_DMA_WHILE_LOCKED = 0x02, /**< Dispatch while lock held */ DRM_DMA_PRIORITY = 0x04, /**< High priority dispatch */ /*@}*/ /** \name Flags for DMA buffer request */ /*@{*/ DRM_DMA_WAIT = 0x10, /**< Wait for free buffers */ DRM_DMA_SMALLER_OK = 0x20, /**< Smaller-than-requested buffers OK */ DRM_DMA_LARGER_OK = 0x40 /**< Larger-than-requested buffers OK */ /*@}*/ } drmDMAFlags; typedef enum { DRM_PAGE_ALIGN = 0x01, DRM_AGP_BUFFER = 0x02, DRM_SG_BUFFER = 0x04, DRM_FB_BUFFER = 0x08 } drmBufDescFlags; typedef enum { DRM_LOCK_READY = 0x01, /**< Wait until hardware is ready for DMA */ DRM_LOCK_QUIESCENT = 0x02, /**< Wait until hardware quiescent */ DRM_LOCK_FLUSH = 0x04, /**< Flush this context's DMA queue first */ DRM_LOCK_FLUSH_ALL = 0x08, /**< Flush all DMA queues first */ /* These *HALT* flags aren't supported yet -- they will be used to support the full-screen DGA-like mode. */ DRM_HALT_ALL_QUEUES = 0x10, /**< Halt all current and future queues */ DRM_HALT_CUR_QUEUES = 0x20 /**< Halt all current queues */ } drmLockFlags; typedef enum { DRM_CONTEXT_PRESERVED = 0x01, /**< This context is preserved and never swapped. */ DRM_CONTEXT_2DONLY = 0x02 /**< This context is for 2D rendering only. */ } drm_context_tFlags, *drm_context_tFlagsPtr; typedef struct _drmBufDesc { int count; /**< Number of buffers of this size */ int size; /**< Size in bytes */ int low_mark; /**< Low water mark */ int high_mark; /**< High water mark */ } drmBufDesc, *drmBufDescPtr; typedef struct _drmBufInfo { int count; /**< Number of buffers described in list */ drmBufDescPtr list; /**< List of buffer descriptions */ } drmBufInfo, *drmBufInfoPtr; typedef struct _drmBuf { int idx; /**< Index into the master buffer list */ int total; /**< Buffer size */ int used; /**< Amount of buffer in use (for DMA) */ drmAddress address; /**< Address */ } drmBuf, *drmBufPtr; /** * Buffer mapping information. * * Used by drmMapBufs() and drmUnmapBufs() to store information about the * mapped buffers. */ typedef struct _drmBufMap { int count; /**< Number of buffers mapped */ drmBufPtr list; /**< Buffers */ } drmBufMap, *drmBufMapPtr; typedef struct _drmLock { volatile unsigned int lock; char padding[60]; /* This is big enough for most current (and future?) architectures: DEC Alpha: 32 bytes Intel Merced: ? Intel P5/PPro/PII/PIII: 32 bytes Intel StrongARM: 32 bytes Intel i386/i486: 16 bytes MIPS: 32 bytes (?) Motorola 68k: 16 bytes Motorola PowerPC: 32 bytes Sun SPARC: 32 bytes */ } drmLock, *drmLockPtr; /** * Indices here refer to the offset into * list in drmBufInfo */ typedef struct _drmDMAReq { drm_context_t context; /**< Context handle */ int send_count; /**< Number of buffers to send */ int *send_list; /**< List of handles to buffers */ int *send_sizes; /**< Lengths of data to send, in bytes */ drmDMAFlags flags; /**< Flags */ int request_count; /**< Number of buffers requested */ int request_size; /**< Desired size of buffers requested */ int *request_list; /**< Buffer information */ int *request_sizes; /**< Minimum acceptable sizes */ int granted_count; /**< Number of buffers granted at this size */ } drmDMAReq, *drmDMAReqPtr; typedef struct _drmRegion { drm_handle_t handle; unsigned int offset; drmSize size; drmAddress map; } drmRegion, *drmRegionPtr; typedef struct _drmTextureRegion { unsigned char next; unsigned char prev; unsigned char in_use; unsigned char padding; /**< Explicitly pad this out */ unsigned int age; } drmTextureRegion, *drmTextureRegionPtr; typedef enum { DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */ DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */ DRM_VBLANK_SIGNAL = 0x40000000 /* Send signal instead of blocking */ } drmVBlankSeqType; typedef struct _drmVBlankReq { drmVBlankSeqType type; unsigned int sequence; unsigned long signal; } drmVBlankReq, *drmVBlankReqPtr; typedef struct _drmVBlankReply { drmVBlankSeqType type; unsigned int sequence; long tval_sec; long tval_usec; } drmVBlankReply, *drmVBlankReplyPtr; typedef union _drmVBlank { drmVBlankReq request; drmVBlankReply reply; } drmVBlank, *drmVBlankPtr; typedef struct _drmSetVersion { int drm_di_major; int drm_di_minor; int drm_dd_major; int drm_dd_minor; } drmSetVersion, *drmSetVersionPtr; #define __drm_dummy_lock(lock) (*(__volatile__ unsigned int *)lock) #define DRM_LOCK_HELD 0x80000000U /**< Hardware lock is held */ #define DRM_LOCK_CONT 0x40000000U /**< Hardware lock is contended */ #if defined(__GNUC__) && (__GNUC__ >= 2) # if defined(__i386) || defined(__AMD64__) /* Reflect changes here to drmP.h */ #define DRM_CAS(lock,old,new,__ret) \ do { \ int __dummy; /* Can't mark eax as clobbered */ \ __asm__ __volatile__( \ "lock ; cmpxchg %4,%1\n\t" \ "setnz %0" \ : "=d" (__ret), \ "=m" (__drm_dummy_lock(lock)), \ "=a" (__dummy) \ : "2" (old), \ "r" (new)); \ } while (0) #elif defined(__alpha__) #define DRM_CAS(lock, old, new, ret) \ do { \ int old32; \ int cur32; \ __asm__ __volatile__( \ " mb\n" \ " zap %4, 0xF0, %0\n" \ " ldl_l %1, %2\n" \ " zap %1, 0xF0, %1\n" \ " cmpeq %0, %1, %1\n" \ " beq %1, 1f\n" \ " bis %5, %5, %1\n" \ " stl_c %1, %2\n" \ "1: xor %1, 1, %1\n" \ " stl %1, %3" \ : "+r" (old32), \ "+&r" (cur32), \ "=m" (__drm_dummy_lock(lock)),\ "=m" (ret) \ : "r" (old), \ "r" (new)); \ } while(0) #elif defined(__sparc__) #define DRM_CAS(lock,old,new,__ret) \ do { register unsigned int __old __asm("o0"); \ register unsigned int __new __asm("o1"); \ register volatile unsigned int *__lock __asm("o2"); \ __old = old; \ __new = new; \ __lock = (volatile unsigned int *)lock; \ __asm__ __volatile__( \ /*"cas [%2], %3, %0"*/ \ ".word 0xd3e29008\n\t" \ /*"membar #StoreStore | #StoreLoad"*/ \ ".word 0x8143e00a" \ : "=&r" (__new) \ : "0" (__new), \ "r" (__lock), \ "r" (__old) \ : "memory"); \ __ret = (__new != __old); \ } while(0) #elif defined(__ia64__) #ifdef __INTEL_COMPILER /* this currently generates bad code (missing stop bits)... */ #include #define DRM_CAS(lock,old,new,__ret) \ do { \ unsigned long __result, __old = (old) & 0xffffffff; \ __mf(); \ __result = _InterlockedCompareExchange_acq(&__drm_dummy_lock(lock), (new), __old);\ __ret = (__result) != (__old); \ /* __ret = (__sync_val_compare_and_swap(&__drm_dummy_lock(lock), \ (old), (new)) \ != (old)); */\ } while (0) #else #define DRM_CAS(lock,old,new,__ret) \ do { \ unsigned int __result, __old = (old); \ __asm__ __volatile__( \ "mf\n" \ "mov ar.ccv=%2\n" \ ";;\n" \ "cmpxchg4.acq %0=%1,%3,ar.ccv" \ : "=r" (__result), "=m" (__drm_dummy_lock(lock)) \ : "r" ((unsigned long)__old), "r" (new) \ : "memory"); \ __ret = (__result) != (__old); \ } while (0) #endif #elif defined(__powerpc__) #define DRM_CAS(lock,old,new,__ret) \ do { \ __asm__ __volatile__( \ "sync;" \ "0: lwarx %0,0,%1;" \ " xor. %0,%3,%0;" \ " bne 1f;" \ " stwcx. %2,0,%1;" \ " bne- 0b;" \ "1: " \ "sync;" \ : "=&r"(__ret) \ : "r"(lock), "r"(new), "r"(old) \ : "cr0", "memory"); \ } while (0) #endif /* architecture */ #endif /* __GNUC__ >= 2 */ #ifndef DRM_CAS #define DRM_CAS(lock,old,new,ret) do { ret=1; } while (0) /* FAST LOCK FAILS */ #endif #if defined(__alpha__) || defined(__powerpc__) #define DRM_CAS_RESULT(_result) int _result #else #define DRM_CAS_RESULT(_result) char _result #endif #define DRM_LIGHT_LOCK(fd,lock,context) \ do { \ DRM_CAS_RESULT(__ret); \ DRM_CAS(lock,context,DRM_LOCK_HELD|context,__ret); \ if (__ret) drmGetLock(fd,context,0); \ } while(0) /* This one counts fast locks -- for benchmarking only. */ #define DRM_LIGHT_LOCK_COUNT(fd,lock,context,count) \ do { \ DRM_CAS_RESULT(__ret); \ DRM_CAS(lock,context,DRM_LOCK_HELD|context,__ret); \ if (__ret) drmGetLock(fd,context,0); \ else ++count; \ } while(0) #define DRM_LOCK(fd,lock,context,flags) \ do { \ if (flags) drmGetLock(fd,context,flags); \ else DRM_LIGHT_LOCK(fd,lock,context); \ } while(0) #define DRM_UNLOCK(fd,lock,context) \ do { \ DRM_CAS_RESULT(__ret); \ DRM_CAS(lock,DRM_LOCK_HELD|context,context,__ret); \ if (__ret) drmUnlock(fd,context); \ } while(0) /* Simple spin locks */ #define DRM_SPINLOCK(spin,val) \ do { \ DRM_CAS_RESULT(__ret); \ do { \ DRM_CAS(spin,0,val,__ret); \ if (__ret) while ((spin)->lock); \ } while (__ret); \ } while(0) #define DRM_SPINLOCK_TAKE(spin,val) \ do { \ DRM_CAS_RESULT(__ret); \ int cur; \ do { \ cur = (*spin).lock; \ DRM_CAS(spin,cur,val,__ret); \ } while (__ret); \ } while(0) #define DRM_SPINLOCK_COUNT(spin,val,count,__ret) \ do { \ int __i; \ __ret = 1; \ for (__i = 0; __ret && __i < count; __i++) { \ DRM_CAS(spin,0,val,__ret); \ if (__ret) for (;__i < count && (spin)->lock; __i++); \ } \ } while(0) #define DRM_SPINUNLOCK(spin,val) \ do { \ DRM_CAS_RESULT(__ret); \ if ((*spin).lock == val) { /* else server stole lock */ \ do { \ DRM_CAS(spin,val,0,__ret); \ } while (__ret); \ } \ } while(0) /* General user-level programmer's API: unprivileged */ extern int drmAvailable(void); extern int drmOpen(const char *name, const char *busid); extern int drmClose(int fd); extern drmVersionPtr drmGetVersion(int fd); extern drmVersionPtr drmGetLibVersion(int fd); extern void drmFreeVersion(drmVersionPtr); extern int drmGetMagic(int fd, drm_magic_t * magic); extern char *drmGetBusid(int fd); extern int drmGetInterruptFromBusID(int fd, int busnum, int devnum, int funcnum); extern int drmGetMap(int fd, int idx, drm_handle_t *offset, drmSize *size, drmMapType *type, drmMapFlags *flags, drm_handle_t *handle, int *mtrr); extern int drmGetClient(int fd, int idx, int *auth, int *pid, int *uid, unsigned long *magic, unsigned long *iocs); extern int drmGetStats(int fd, drmStatsT *stats); extern int drmSetInterfaceVersion(int fd, drmSetVersion *version); extern int drmCommandNone(int fd, unsigned long drmCommandIndex); extern int drmCommandRead(int fd, unsigned long drmCommandIndex, void *data, unsigned long size); extern int drmCommandWrite(int fd, unsigned long drmCommandIndex, void *data, unsigned long size); extern int drmCommandWriteRead(int fd, unsigned long drmCommandIndex, void *data, unsigned long size); /* General user-level programmer's API: X server (root) only */ extern void drmFreeBusid(const char *busid); extern int drmSetBusid(int fd, const char *busid); extern int drmAuthMagic(int fd, drm_magic_t magic); extern int drmAddMap(int fd, drm_handle_t offset, drmSize size, drmMapType type, drmMapFlags flags, drm_handle_t * handle); extern int drmRmMap(int fd, drm_handle_t handle); extern int drmAddContextPrivateMapping(int fd, drm_context_t ctx_id, drm_handle_t handle); extern int drmAddBufs(int fd, int count, int size, drmBufDescFlags flags, int agp_offset); extern int drmMarkBufs(int fd, double low, double high); extern int drmCreateContext(int fd, drm_context_t * handle); extern int drmSetContextFlags(int fd, drm_context_t context, drm_context_tFlags flags); extern int drmGetContextFlags(int fd, drm_context_t context, drm_context_tFlagsPtr flags); extern int drmAddContextTag(int fd, drm_context_t context, void *tag); extern int drmDelContextTag(int fd, drm_context_t context); extern void *drmGetContextTag(int fd, drm_context_t context); extern drm_context_t * drmGetReservedContextList(int fd, int *count); extern void drmFreeReservedContextList(drm_context_t *); extern int drmSwitchToContext(int fd, drm_context_t context); extern int drmDestroyContext(int fd, drm_context_t handle); extern int drmCreateDrawable(int fd, drm_drawable_t * handle); extern int drmDestroyDrawable(int fd, drm_drawable_t handle); extern int drmCtlInstHandler(int fd, int irq); extern int drmCtlUninstHandler(int fd); extern int drmInstallSIGIOHandler(int fd, void (*f)(int fd, void *oldctx, void *newctx)); extern int drmRemoveSIGIOHandler(int fd); /* General user-level programmer's API: authenticated client and/or X */ extern int drmMap(int fd, drm_handle_t handle, drmSize size, drmAddressPtr address); extern int drmUnmap(drmAddress address, drmSize size); extern drmBufInfoPtr drmGetBufInfo(int fd); extern drmBufMapPtr drmMapBufs(int fd); extern int drmUnmapBufs(drmBufMapPtr bufs); extern int drmDMA(int fd, drmDMAReqPtr request); extern int drmFreeBufs(int fd, int count, int *list); extern int drmGetLock(int fd, drm_context_t context, drmLockFlags flags); extern int drmUnlock(int fd, drm_context_t context); extern int drmFinish(int fd, int context, drmLockFlags flags); extern int drmGetContextPrivateMapping(int fd, drm_context_t ctx_id, drm_handle_t * handle); /* AGP/GART support: X server (root) only */ extern int drmAgpAcquire(int fd); extern int drmAgpRelease(int fd); extern int drmAgpEnable(int fd, unsigned long mode); extern int drmAgpAlloc(int fd, unsigned long size, unsigned long type, unsigned long *address, unsigned long *handle); extern int drmAgpFree(int fd, unsigned long handle); extern int drmAgpBind(int fd, unsigned long handle, unsigned long offset); extern int drmAgpUnbind(int fd, unsigned long handle); /* AGP/GART info: authenticated client and/or X */ extern int drmAgpVersionMajor(int fd); extern int drmAgpVersionMinor(int fd); extern unsigned long drmAgpGetMode(int fd); extern unsigned long drmAgpBase(int fd); /* Physical location */ extern unsigned long drmAgpSize(int fd); /* Bytes */ extern unsigned long drmAgpMemoryUsed(int fd); extern unsigned long drmAgpMemoryAvail(int fd); extern unsigned int drmAgpVendorId(int fd); extern unsigned int drmAgpDeviceId(int fd); /* PCI scatter/gather support: X server (root) only */ extern int drmScatterGatherAlloc(int fd, unsigned long size, unsigned long *handle); extern int drmScatterGatherFree(int fd, unsigned long handle); extern int drmWaitVBlank(int fd, drmVBlankPtr vbl); /* Support routines */ extern int drmError(int err, const char *label); extern void *drmMalloc(int size); extern void drmFree(void *pt); /* Hash table routines */ extern void *drmHashCreate(void); extern int drmHashDestroy(void *t); extern int drmHashLookup(void *t, unsigned long key, void **value); extern int drmHashInsert(void *t, unsigned long key, void *value); extern int drmHashDelete(void *t, unsigned long key); extern int drmHashFirst(void *t, unsigned long *key, void **value); extern int drmHashNext(void *t, unsigned long *key, void **value); /* PRNG routines */ extern void *drmRandomCreate(unsigned long seed); extern int drmRandomDestroy(void *state); extern unsigned long drmRandom(void *state); extern double drmRandomDouble(void *state); /* Skip list routines */ extern void *drmSLCreate(void); extern int drmSLDestroy(void *l); extern int drmSLLookup(void *l, unsigned long key, void **value); extern int drmSLInsert(void *l, unsigned long key, void *value); extern int drmSLDelete(void *l, unsigned long key); extern int drmSLNext(void *l, unsigned long *key, void **value); extern int drmSLFirst(void *l, unsigned long *key, void **value); extern void drmSLDump(void *l); extern int drmSLLookupNeighbors(void *l, unsigned long key, unsigned long *prev_key, void **prev_value, unsigned long *next_key, void **next_value); #endif ' href='#n672'>672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
/* via_dmablit.c -- PCI DMA BitBlt support for the VIA Unichrome/Pro
 * 
 * Copyright (C) 2005 Thomas Hellstrom, 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, 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 COPYRIGHT HOLDERS, AUTHORS 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: 
 *    Thomas Hellstrom.
 *    Partially based on code obtained from Digeo Inc.
 */


/*
 * Unmaps the DMA mappings. 
 * FIXME: Is this a NoOp on x86? Also 
 * FIXME: What happens if this one is called and a pending blit has previously done 
 * the same DMA mappings? 
 */

#include "drmP.h"
#include "via_drm.h"
#include "via_drv.h"
#include "via_dmablit.h"

#include <linux/pagemap.h>

#define VIA_PGDN(x)             (((unsigned long)(x)) & PAGE_MASK)
#define VIA_PGOFF(x)            (((unsigned long)(x)) & ~PAGE_MASK)
#define VIA_PFN(x)              ((unsigned long)(x) >> PAGE_SHIFT)

typedef struct _drm_via_descriptor {
	uint32_t mem_addr;
	uint32_t dev_addr;
	uint32_t size;
	uint32_t next;
} drm_via_descriptor_t;


/*
 * Unmap a DMA mapping.
 */



static void
via_unmap_blit_from_device(struct pci_dev *pdev, drm_via_sg_info_t *vsg)
{
	int num_desc = vsg->num_desc;
	unsigned cur_descriptor_page = num_desc / vsg->descriptors_per_page;
	unsigned descriptor_this_page = num_desc % vsg->descriptors_per_page;
	drm_via_descriptor_t *desc_ptr = vsg->desc_pages[cur_descriptor_page] + 
		descriptor_this_page;
	dma_addr_t next = vsg->chain_start;

	while(num_desc--) {
		if (descriptor_this_page-- == 0) {
			cur_descriptor_page--;
			descriptor_this_page = vsg->descriptors_per_page - 1;
			desc_ptr = vsg->desc_pages[cur_descriptor_page] + 
				descriptor_this_page;
		}
		dma_unmap_single(&pdev->dev, next, sizeof(*desc_ptr), DMA_TO_DEVICE);
		dma_unmap_page(&pdev->dev, desc_ptr->mem_addr, desc_ptr->size, vsg->direction);
		next = (dma_addr_t) desc_ptr->next;
		desc_ptr--;
	}
}

/*
 * If mode = 0, count how many descriptors are needed.
 * If mode = 1, Map the DMA pages for the device, put together and map also the descriptors.
 * Descriptors are run in reverse order by the hardware because we are not allowed to update the
 * 'next' field without syncing calls when the descriptor is already mapped.
 */

static void
via_map_blit_for_device(struct pci_dev *pdev,
		   const drm_via_dmablit_t *xfer,
		   drm_via_sg_info_t *vsg, 
		   int mode)
{
	unsigned cur_descriptor_page = 0;
	unsigned num_descriptors_this_page = 0;
	unsigned char *mem_addr = xfer->mem_addr;
	unsigned char *cur_mem;
	unsigned char *first_addr = (unsigned char *)VIA_PGDN(mem_addr);
	uint32_t fb_addr = xfer->fb_addr;
	uint32_t cur_fb;
	unsigned long line_len;
	unsigned remaining_len;
	int num_desc = 0;
	int cur_line;
	dma_addr_t next = 0 | VIA_DMA_DPR_EC;
	drm_via_descriptor_t *desc_ptr = NULL;

	if (mode == 1) 
		desc_ptr = vsg->desc_pages[cur_descriptor_page];

	for (cur_line = 0; cur_line < xfer->num_lines; ++cur_line) {

		line_len = xfer->line_length;
		cur_fb = fb_addr;
		cur_mem = mem_addr;
		
		while (line_len > 0) {

			remaining_len = min(PAGE_SIZE-VIA_PGOFF(cur_mem), line_len);
			line_len -= remaining_len;

			if (mode == 1) {
				desc_ptr->mem_addr = dma_map_page(&pdev->dev,
					vsg->pages[VIA_PFN(cur_mem) -
					VIA_PFN(first_addr)],
					VIA_PGOFF(cur_mem), remaining_len,
					vsg->direction);
				desc_ptr->dev_addr = cur_fb;
				
				desc_ptr->size = remaining_len;
				desc_ptr->next = (uint32_t) next;
				next = dma_map_single(&pdev->dev, desc_ptr, sizeof(*desc_ptr), 
						      DMA_TO_DEVICE);
				desc_ptr++;
				if (++num_descriptors_this_page >= vsg->descriptors_per_page) {
					num_descriptors_this_page = 0;
					desc_ptr = vsg->desc_pages[++cur_descriptor_page];
				}
			}
			
			num_desc++;
			cur_mem += remaining_len;
			cur_fb += remaining_len;
		}
		
		mem_addr += xfer->mem_stride;
		fb_addr += xfer->fb_stride;
	}

	if (mode == 1) {
		vsg->chain_start = next;
		vsg->state = dr_via_device_mapped;
	}
	vsg->num_desc = num_desc;
}

/*
 * Function that frees up all resources for a blit. It is usable even if the 
 * blit info has only been partially built as long as the status enum is consistent
 * with the actual status of the used resources.
 */


static void
via_free_sg_info(struct pci_dev *pdev, drm_via_sg_info_t *vsg) 
{
	struct page *page;
	int i;

	switch(vsg->state) {
	case dr_via_device_mapped:
		via_unmap_blit_from_device(pdev, vsg);
	case dr_via_desc_pages_alloc:
		for (i=0; i<vsg->num_desc_pages; ++i) {
			if (vsg->desc_pages[i] != NULL)
			  free_page((unsigned long)vsg->desc_pages[i]);
		}
		kfree(vsg->desc_pages);
	case dr_via_pages_locked:
		for (i=0; i<vsg->num_pages; ++i) {
			if ( NULL != (page = vsg->pages[i])) {
				if (! PageReserved(page) && (DMA_FROM_DEVICE == vsg->direction)) 
					SetPageDirty(page);
				page_cache_release(page);
			}
		}
	case dr_via_pages_alloc:
		vfree(vsg->pages);
	default:
		vsg->state = dr_via_sg_init;
	}
	if (vsg->bounce_buffer) {
		vfree(vsg->bounce_buffer);
		vsg->bounce_buffer = NULL;
	}
	vsg->free_on_sequence = 0;
}		

/*
 * Fire a blit engine.
 */

static void
via_fire_dmablit(drm_device_t *dev, drm_via_sg_info_t *vsg, int engine)
{
	drm_via_private_t *dev_priv = (drm_via_private_t *)dev->dev_private;

	VIA_WRITE(VIA_PCI_DMA_MAR0 + engine*0x10, 0);
	VIA_WRITE(VIA_PCI_DMA_DAR0 + engine*0x10, 0);
	VIA_WRITE(VIA_PCI_DMA_CSR0 + engine*0x04, VIA_DMA_CSR_DD | VIA_DMA_CSR_TD | 
		  VIA_DMA_CSR_DE);
	VIA_WRITE(VIA_PCI_DMA_MR0  + engine*0x04, VIA_DMA_MR_CM | VIA_DMA_MR_TDIE);
	VIA_WRITE(VIA_PCI_DMA_BCR0 + engine*0x10, 0);
	VIA_WRITE(VIA_PCI_DMA_DPR0 + engine*0x10, vsg->chain_start);
	VIA_WRITE(VIA_PCI_DMA_CSR0 + engine*0x04, VIA_DMA_CSR_DE | VIA_DMA_CSR_TS);
}

/*
 * Obtain a page pointer array and lock all pages into system memory. A segmentation violation will
 * occur here if the calling user does not have access to the submitted address.
 */

static int
via_lock_all_dma_pages(drm_via_sg_info_t *vsg,  drm_via_dmablit_t *xfer)
{
	int ret;
	unsigned long first_pfn = VIA_PFN(xfer->mem_addr);
	vsg->num_pages = VIA_PFN(xfer->mem_addr + (xfer->num_lines * xfer->mem_stride -1)) - 
		first_pfn + 1;
	
	if (NULL == (vsg->pages = vmalloc(sizeof(struct page *) * vsg->num_pages)))
		return DRM_ERR(ENOMEM);
	memset(vsg->pages, 0, sizeof(struct page *) * vsg->num_pages);
	down_read(&current->mm->mmap_sem);
	ret = get_user_pages(current, current->mm, (unsigned long) xfer->mem_addr,
			     vsg->num_pages, (vsg->direction == DMA_FROM_DEVICE), 
			     0, vsg->pages, NULL);

	up_read(&current->mm->mmap_sem);
	if (ret != vsg->num_pages) {
		if (ret < 0) 
			return ret;
		vsg->state = dr_via_pages_locked;
		return DRM_ERR(EINVAL);
	}
	vsg->state = dr_via_pages_locked;
	DRM_DEBUG("DMA pages locked\n");
	return 0;
}

/*
 * Allocate DMA capable memory for the blit descriptor chain, and an array that keeps track of the
 * pages we allocate. We don't want to use kmalloc for the descriptor chain because it may be
 * quite large for some blits, and pages don't need to be contingous.
 */

static int 
via_alloc_desc_pages(drm_via_sg_info_t *vsg)
{
	int i;
	
	vsg->descriptors_per_page = PAGE_SIZE / sizeof( drm_via_descriptor_t);
	vsg->num_desc_pages = (vsg->num_desc + vsg->descriptors_per_page - 1) / 
		vsg->descriptors_per_page;

	if (NULL ==  (vsg->desc_pages = kmalloc(sizeof(void *) * vsg->num_desc_pages, GFP_KERNEL))) 
		return DRM_ERR(ENOMEM);
	
	memset(vsg->desc_pages, 0, sizeof(void *) * vsg->num_desc_pages);
	vsg->state = dr_via_desc_pages_alloc;
	for (i=0; i<vsg->num_desc_pages; ++i) {
		if (NULL == (vsg->desc_pages[i] = 
			     (drm_via_descriptor_t *) __get_free_page(GFP_KERNEL)))
			return DRM_ERR(ENOMEM);
	}
	DRM_DEBUG("Allocated %d pages for %d descriptors.\n", vsg->num_desc_pages,
		  vsg->num_desc);
	return 0;
}
			
static void
via_abort_dmablit(drm_device_t *dev, int engine)
{
	drm_via_private_t *dev_priv = (drm_via_private_t *)dev->dev_private;

	VIA_WRITE(VIA_PCI_DMA_CSR0 + engine*0x04, VIA_DMA_CSR_TA);
}

static void
via_dmablit_engine_off(drm_device_t *dev, int engine)
{
	drm_via_private_t *dev_priv = (drm_via_private_t *)dev->dev_private;

	VIA_WRITE(VIA_PCI_DMA_CSR0 + engine*0x04, VIA_DMA_CSR_TD | VIA_DMA_CSR_DD); 
}



/*
 * The dmablit part of the IRQ handler. Trying to do only reasonably fast things here.
 * The rest, like unmapping and freeing memory for done blits is done in a separate workqueue
 * task. Basically the task of the interrupt handler is to submit a new blit to the engine, while
 * the workqueue task takes care of processing associated with the old blit.
 */
		
void
via_dmablit_handler(drm_device_t *dev, int engine, int from_irq)
{
	drm_via_private_t *dev_priv = (drm_via_private_t *)dev->dev_private;
	drm_via_blitq_t *blitq = dev_priv->blit_queues + engine;
	int cur;
	int done_transfer;
	unsigned long irqsave=0;
	uint32_t status = 0;

	DRM_DEBUG("DMA blit handler called. engine = %d, from_irq = %d, blitq = 0x%lx\n",
		  engine, from_irq, (unsigned long) blitq);

	if (from_irq) {
		spin_lock(&blitq->blit_lock);
	} else {
		spin_lock_irqsave(&blitq->blit_lock, irqsave);
	}

	done_transfer = blitq->is_active && 
	  (( status = VIA_READ(VIA_PCI_DMA_CSR0 + engine*0x04)) & VIA_DMA_CSR_TD);
	done_transfer = done_transfer || ( blitq->aborting && !(status & VIA_DMA_CSR_DE)); 

	cur = blitq->cur;
	if (done_transfer) {

		blitq->blits[cur]->aborted = blitq->aborting;
		blitq->done_blit_handle++;
		DRM_WAKEUP(blitq->blit_queue + cur);		

		cur++;
		if (cur >= VIA_NUM_BLIT_SLOTS) 
			cur = 0;
		blitq->cur = cur;

		/*
		 * Clear transfer done flag.
		 */

		VIA_WRITE(VIA_PCI_DMA_CSR0 + engine*0x04,  VIA_DMA_CSR_TD);

		blitq->is_active = 0;
		blitq->aborting = 0;
		schedule_work(&blitq->wq);	

	} else if (blitq->is_active && time_after_eq(jiffies, blitq->end)) {

		/*
		 * Abort transfer after one second.
		 */

		via_abort_dmablit(dev, engine);
		blitq->aborting = 1;
		blitq->end = jiffies + DRM_HZ;
	}
	  		
	if (!blitq->is_active) {
		if (blitq->num_outstanding) {
			via_fire_dmablit(dev, blitq->blits[cur], engine);
			blitq->is_active = 1;
			blitq->cur = cur;
			blitq->num_outstanding--;
			blitq->end = jiffies + DRM_HZ;
			if (!timer_pending(&blitq->poll_timer)) {
				blitq->poll_timer.expires = jiffies+1;
				add_timer(&blitq->poll_timer);
			}
		} else {
			if (timer_pending(&blitq->poll_timer)) {
				del_timer(&blitq->poll_timer);
			}
			via_dmablit_engine_off(dev, engine);
		}
	}		

	if (from_irq) {
		spin_unlock(&blitq->blit_lock);
	} else {
		spin_unlock_irqrestore(&blitq->blit_lock, irqsave);
	}
} 



/*
 * Check whether this blit is still active, performing necessary locking.
 */

static int
via_dmablit_active(drm_via_blitq_t *blitq, int engine, uint32_t handle, wait_queue_head_t **queue)
{
	unsigned long irqsave;
	uint32_t slot;
	int active;

	spin_lock_irqsave(&blitq->blit_lock, irqsave);

	/*
	 * Allow for handle wraparounds.
	 */

	active = ((blitq->done_blit_handle - handle) > (1 << 23)) &&
		((blitq->cur_blit_handle - handle) <= (1 << 23));

	if (queue && active) {
		slot = handle - blitq->done_blit_handle + blitq->cur -1;
		if (slot >= VIA_NUM_BLIT_SLOTS) {
			slot -= VIA_NUM_BLIT_SLOTS;
		}
		*queue = blitq->blit_queue + slot;
	}

	spin_unlock_irqrestore(&blitq->blit_lock, irqsave);

	return active;
}
	
/*
 * Sync. Wait for at least three seconds for the blit to be performed.
 */

static int
via_dmablit_sync(drm_device_t *dev, uint32_t handle, int engine) 
{

	drm_via_private_t *dev_priv = (drm_via_private_t *)dev->dev_private;
	drm_via_blitq_t *blitq = dev_priv->blit_queues + engine;
	wait_queue_head_t *queue;
	int ret = 0;

	if (via_dmablit_active(blitq, engine, handle, &queue)) {
		DRM_WAIT_ON(ret, *queue, 3 * DRM_HZ, 
			    !via_dmablit_active(blitq, engine, handle, NULL));
	}
	DRM_DEBUG("DMA blit sync handle 0x%x engine %d returned %d\n",
		  handle, engine, ret);
	
	return ret;
}


/*
 * A timer that regularly polls the blit engine in cases where we don't have interrupts:
 * a) Broken hardware (typically those that don't have any video capture facility).
 * b) Blit abort. The hardware doesn't send an interrupt when a blit is aborted.
 * The timer and hardware IRQ's can and do work in parallel. If the hardware has
 * irqs, it will shorten the latency somewhat.
 */



static void
via_dmablit_timer(unsigned long data)
{
	drm_via_blitq_t *blitq = (drm_via_blitq_t *) data;
	drm_device_t *dev = blitq->dev;
	int engine = (int)
		(blitq - ((drm_via_private_t *)dev->dev_private)->blit_queues);
		
	DRM_DEBUG("Polling timer called for engine %d, jiffies %lu\n", engine, 
		  (unsigned long) jiffies);

	via_dmablit_handler(dev, engine, 0);
	
	if (!timer_pending(&blitq->poll_timer)) {
		blitq->poll_timer.expires = jiffies+1;
		add_timer(&blitq->poll_timer);

		/*
		 * Rerun handler to delete timer if engines are off, and
		 * to shorten abort latency. This is a little nasty.
		 */

		via_dmablit_handler(dev, engine, 0);
	}
}




/*
 * Workqueue task that frees data and mappings associated with a blit.
 * Also wakes up waiting processes. Each of these tasks handles one
 * blit engine only and may not be called on each interrupt.
 */


static void 
via_dmablit_workqueue(void *data)
{
	drm_via_blitq_t *blitq = (drm_via_blitq_t *) data;
	drm_device_t *dev = blitq->dev;
	unsigned long irqsave;
	drm_via_sg_info_t *cur_sg;
	int cur_released;
	
	
	DRM_DEBUG("Workqueue task called for blit engine %ld\n",(unsigned long) 
		  (blitq - ((drm_via_private_t *)dev->dev_private)->blit_queues));

	spin_lock_irqsave(&blitq->blit_lock, irqsave);
	
	while(blitq->serviced != blitq->cur) {

		cur_released = blitq->serviced++;

		DRM_DEBUG("Releasing blit slot %d\n", cur_released);

		if (blitq->serviced >= VIA_NUM_BLIT_SLOTS) 
			blitq->serviced = 0;
		
		cur_sg = blitq->blits[cur_released];
		blitq->num_free++;
				
		spin_unlock_irqrestore(&blitq->blit_lock, irqsave);
		
		DRM_WAKEUP(&blitq->busy_queue);
		
		via_free_sg_info(dev->pdev, cur_sg);
		kfree(cur_sg);
		
		spin_lock_irqsave(&blitq->blit_lock, irqsave);
	}

	spin_unlock_irqrestore(&blitq->blit_lock, irqsave);
}
	

/*
 * Init all blit engines. Currently we use two, but some hardware have 4.
 */


void
via_init_dmablit(drm_device_t *dev)
{
	int i,j;
	drm_via_private_t *dev_priv = (drm_via_private_t *)dev->dev_private;
	drm_via_blitq_t *blitq;

	pci_set_master(dev->pdev);	
	
	for (i=0; i< VIA_NUM_BLIT_ENGINES; ++i) {
		blitq = dev_priv->blit_queues + i;
		blitq->dev = dev;
		blitq->cur_blit_handle = 0;
		blitq->done_blit_handle = 0;
		blitq->head = 0;
		blitq->cur = 0;
		blitq->serviced = 0;
		blitq->num_free = VIA_NUM_BLIT_SLOTS;
		blitq->num_outstanding = 0;
		blitq->is_active = 0;
		blitq->aborting = 0;
		blitq->blit_lock = SPIN_LOCK_UNLOCKED;
		for (j=0; j<VIA_NUM_BLIT_SLOTS; ++j) {
			DRM_INIT_WAITQUEUE(blitq->blit_queue + j);
		}
		DRM_INIT_WAITQUEUE(&blitq->busy_queue);
		INIT_WORK(&blitq->wq, via_dmablit_workqueue, blitq);
		init_timer(&blitq->poll_timer);
		blitq->poll_timer.function = &via_dmablit_timer;
		blitq->poll_timer.data = (unsigned long) blitq;
	}	
}

/*
 * Build all info and do all mappings required for a blit.
 */
		

static int
via_build_sg_info(drm_device_t *dev, drm_via_sg_info_t *vsg, drm_via_dmablit_t *xfer)
{
	int draw = xfer->to_fb;
	int ret = 0;
	
	vsg->direction = (draw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
	vsg->bounce_buffer = NULL;

	vsg->state = dr_via_sg_init;

	if (xfer->num_lines <= 0 || xfer->line_length <= 0) {
		DRM_ERROR("Zero size bitblt.\n");
		return DRM_ERR(EINVAL);
	}

	/*
	 * Below check is a driver limitation, not a hardware one. We
	 * don't want to lock unused pages, and don't want to incoporate the
	 * extra logic of avoiding them. Make sure there are no. 
	 * (Not a big limitation anyway.)
	 */

	if ((xfer->mem_stride - xfer->line_length) >= PAGE_SIZE) {
		DRM_ERROR("Too large system memory stride. Stride: %d, "
			  "Length: %d\n", xfer->mem_stride, xfer->line_length);
		return DRM_ERR(EINVAL);
	}

	if ((xfer->mem_stride == xfer->line_length) &&
	    (xfer->fb_stride == xfer->line_length)) {
		xfer->mem_stride *= xfer->num_lines;
		xfer->line_length = xfer->mem_stride;
		xfer->fb_stride = xfer->mem_stride;
		xfer->num_lines = 1;
	}

	/*
	 * Don't lock an arbitrary large number of pages, since that causes a
	 * DOS security hole.
	 */

	if (xfer->num_lines > 2048 || (xfer->num_lines*xfer->mem_stride > (2048*2048*4))) {
		DRM_ERROR("Too large PCI DMA bitblt.\n");
		return DRM_ERR(EINVAL);
	}		

	/* 
	 * we allow a negative fb stride to allow flipping of images in
	 * transfer. 
	 */

	if (xfer->mem_stride < xfer->line_length ||
	    abs(xfer->fb_stride) < xfer->line_length) {
		DRM_ERROR("Invalid frame-buffer / memory stride.\n");
		return DRM_ERR(EINVAL);
	}

	/*
	 * A hardware bug seems to be worked around if system memory addresses start on
	 * 16 byte boundaries. This seems a bit restrictive however. VIA is contacted
	 * about this. Meanwhile, impose the following restrictions:
	 */

#ifdef VIA_BUGFREE
	if ((((unsigned long)xfer->mem_addr & 3) != ((unsigned long)xfer->fb_addr & 3)) ||
	    ((xfer->num_lines > 1) && ((xfer->mem_stride & 3) != (xfer->fb_stride & 3)))) {
		DRM_ERROR("Invalid DRM bitblt alignment.\n");
		return DRM_ERR(EINVAL);
	}
#else
	if ((((unsigned long)xfer->mem_addr & 15) || ((unsigned long)xfer->fb_addr & 3)) ||
	    ((xfer->num_lines > 1) && ((xfer->mem_stride & 15) || (xfer->fb_stride & 3)))) {
		DRM_ERROR("Invalid DRM bitblt alignment.\n");
		return DRM_ERR(EINVAL);
	}	
#endif

	if (0 != (ret = via_lock_all_dma_pages(vsg, xfer))) {
		DRM_ERROR("Could not lock DMA pages.\n");
		via_free_sg_info(dev->pdev, vsg);
		return ret;
	}

	via_map_blit_for_device(dev->pdev, xfer, vsg, 0);
	if (0 != (ret = via_alloc_desc_pages(vsg))) {
		DRM_ERROR("Could not allocate DMA descriptor pages.\n");
		via_free_sg_info(dev->pdev, vsg);
		return ret;
	}
	via_map_blit_for_device(dev->pdev, xfer, vsg, 1);
	
	return 0;
}
	

/*
 * Reserve one free slot in the blit queue. Will wait for one second for one
 * to become available. Otherwise -EBUSY is returned.
 */

static int 
via_dmablit_grab_slot(drm_via_blitq_t *blitq, int engine)
{
	int ret=0;
	unsigned long irqsave;

	DRM_DEBUG("Num free is %d\n", blitq->num_free);
	spin_lock_irqsave(&blitq->blit_lock, irqsave);
	while(blitq->num_free == 0) {
		spin_unlock_irqrestore(&blitq->blit_lock, irqsave);

		DRM_WAIT_ON(ret, blitq->busy_queue, DRM_HZ, blitq->num_free > 0);
		if (ret) {
			return (DRM_ERR(EINTR) == ret) ? DRM_ERR(EAGAIN) : ret;
		}
		
		spin_lock_irqsave(&blitq->blit_lock, irqsave);
	}
	
	blitq->num_free--;
	spin_unlock_irqrestore(&blitq->blit_lock, irqsave);

	return 0;
}

/*
 * Hand back a free slot if we changed our mind.
 */

static void 
via_dmablit_release_slot(drm_via_blitq_t *blitq)
{
	unsigned long irqsave;

	spin_lock_irqsave(&blitq->blit_lock, irqsave);
	blitq->num_free++;
	spin_unlock_irqrestore(&blitq->blit_lock, irqsave);
	DRM_WAKEUP( &blitq->busy_queue );
}

/*
 * Grab a free slot. Build blit info and queue a blit.
 */


static int 
via_dmablit(drm_device_t *dev, drm_via_dmablit_t *xfer)	 
{
	drm_via_private_t *dev_priv = (drm_via_private_t *)dev->dev_private;
	drm_via_sg_info_t *vsg;
	drm_via_blitq_t *blitq;
	int ret;
	int engine;
	unsigned long irqsave;

	if (dev_priv == NULL) {
		DRM_ERROR("Called without initialization.\n");
		return DRM_ERR(EINVAL);
	}

	engine = (xfer->to_fb) ? 0 : 1;
	blitq = dev_priv->blit_queues + engine;
	if (0 != (ret = via_dmablit_grab_slot(blitq, engine))) {
		return ret;
	}
	if (NULL == (vsg = kmalloc(sizeof(*vsg), GFP_KERNEL))) {
		via_dmablit_release_slot(blitq);
		return DRM_ERR(ENOMEM);
	}
	if (0 != (ret = via_build_sg_info(dev, vsg, xfer))) {
		via_dmablit_release_slot(blitq);
		kfree(vsg);
		return ret;
	}
	spin_lock_irqsave(&blitq->blit_lock, irqsave);

	blitq->blits[blitq->head++] = vsg;
	if (blitq->head >= VIA_NUM_BLIT_SLOTS) 
		blitq->head = 0;
	blitq->num_outstanding++;
	xfer->sync.sync_handle = ++blitq->cur_blit_handle; 

	spin_unlock_irqrestore(&blitq->blit_lock, irqsave);
	xfer->sync.engine = engine;

       	via_dmablit_handler(dev, engine, 0);

	return 0;
}

/*
 * Sync on a previously submitted blit. Note that the X server use signals extensively, and
 * that there is a very big probability that this IOCTL will be interrupted by a signal. In that
 * case it returns with -EAGAIN for the signal to be delivered. 
 * The caller should then reissue the IOCTL. This is similar to what is being done for drmGetLock().
 */

int
via_dma_blit_sync( DRM_IOCTL_ARGS )
{
	drm_via_blitsync_t sync;
	int err;
	DRM_DEVICE;

	DRM_COPY_FROM_USER_IOCTL(sync, (drm_via_blitsync_t *)data, sizeof(sync));
	
	if (sync.engine >= VIA_NUM_BLIT_ENGINES) 
		return DRM_ERR(EINVAL);

	err = via_dmablit_sync(dev, sync.sync_handle, sync.engine);

	if (DRM_ERR(EINTR) == err)
		err = DRM_ERR(EAGAIN);

	return err;
}
	

/*
 * Queue a blit and hand back a handle to be used for sync. This IOCTL may be interrupted by a signal
 * while waiting for a free slot in the blit queue. In that case it returns with -EAGAIN and should 
 * be reissued. See the above IOCTL code.
 */

int 
via_dma_blit( DRM_IOCTL_ARGS )
{
	drm_via_dmablit_t xfer;
	int err;
	DRM_DEVICE;

	DRM_COPY_FROM_USER_IOCTL(xfer, (drm_via_dmablit_t __user *)data, sizeof(xfer));

	err = via_dmablit(dev, &xfer);

	DRM_COPY_TO_USER_IOCTL((void __user *)data, xfer, sizeof(xfer));

	return err;
}