diff options
author | Kevin E Martin <kem@kem.org> | 2000-02-22 15:43:59 +0000 |
---|---|---|
committer | Kevin E Martin <kem@kem.org> | 2000-02-22 15:43:59 +0000 |
commit | 7a9b291ab55dd9dcfeb35217f6105ad57c94f433 (patch) | |
tree | 8941caba3a974912d8abc9f2f7b307c82ef5a54a /linux/mga_dma.c | |
parent | 9a1197da5cd84624f5b0741e0a20fee60eb8b4f1 (diff) |
Import of XFree86 3.9.18X_3_9_18
Diffstat (limited to 'linux/mga_dma.c')
-rw-r--r-- | linux/mga_dma.c | 978 |
1 files changed, 978 insertions, 0 deletions
diff --git a/linux/mga_dma.c b/linux/mga_dma.c new file mode 100644 index 00000000..2e24e5b4 --- /dev/null +++ b/linux/mga_dma.c @@ -0,0 +1,978 @@ +/* mga_dma.c -- DMA support for mga g200/g400 -*- linux-c -*- + * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com + * + * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. + * 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. + * + * Authors: Rickard E. (Rik) Faith <faith@precisioninsight.com> + * Jeff Hartmann <jhartmann@precisioninsight.com> + * + * $XFree86: xc/programs/Xserver/hw/xfree86/os-support/linux/drm/kernel/mga_dma.c,v 1.1 2000/02/11 17:26:07 dawes Exp $ + * + */ + +#define __NO_VERSION__ +#include "drmP.h" +#include "mga_drv.h" +#include "mgareg_flags.h" +#include "mga_dma.h" +#include "mga_state.h" + +#include <linux/interrupt.h> /* For task queue support */ + +#define MGA_REG(reg) 2 +#define MGA_BASE(reg) ((unsigned long) \ + ((drm_device_t *)dev)->maplist[MGA_REG(reg)]->handle) +#define MGA_ADDR(reg) (MGA_BASE(reg) + reg) +#define MGA_DEREF(reg) *(__volatile__ int *)MGA_ADDR(reg) +#define MGA_READ(reg) MGA_DEREF(reg) +#define MGA_WRITE(reg,val) do { MGA_DEREF(reg) = val; } while (0) + +#define PDEA_pagpxfer_enable 0x2 +#define MGA_SYNC_TAG 0x423f4200 + +typedef enum { + TT_GENERAL, + TT_BLIT, + TT_VECTOR, + TT_VERTEX +} transferType_t; + + +static void mga_delay(void) +{ + return; +} + +int mga_dma_cleanup(drm_device_t *dev) +{ + if(dev->dev_private) { + drm_mga_private_t *dev_priv = + (drm_mga_private_t *) dev->dev_private; + + if(dev_priv->ioremap) { + int temp = (dev_priv->warp_ucode_size + + dev_priv->primary_size + + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; + + drm_ioremapfree((void *) dev_priv->ioremap, temp); + } + + drm_free(dev->dev_private, sizeof(drm_mga_private_t), + DRM_MEM_DRIVER); + dev->dev_private = NULL; + } + + return 0; +} + +static int mga_alloc_kernel_queue(drm_device_t *dev) +{ + drm_queue_t *queue = NULL; + /* Allocate a new queue */ + down(&dev->struct_sem); + + if(dev->queue_count != 0) { + /* Reseting the kernel context here is not + * a race, since it can only happen when that + * queue is empty. + */ + queue = dev->queuelist[DRM_KERNEL_CONTEXT]; + printk("Kernel queue already allocated\n"); + } else { + queue = drm_alloc(sizeof(*queue), DRM_MEM_QUEUES); + if(!queue) { + up(&dev->struct_sem); + printk("out of memory\n"); + return -ENOMEM; + } + ++dev->queue_count; + dev->queuelist = drm_alloc(sizeof(*dev->queuelist), + DRM_MEM_QUEUES); + if(!dev->queuelist) { + up(&dev->struct_sem); + drm_free(queue, sizeof(*queue), DRM_MEM_QUEUES); + printk("out of memory\n"); + return -ENOMEM; + } + } + + memset(queue, 0, sizeof(*queue)); + atomic_set(&queue->use_count, 1); + atomic_set(&queue->finalization, 0); + atomic_set(&queue->block_count, 0); + atomic_set(&queue->block_read, 0); + atomic_set(&queue->block_write, 0); + atomic_set(&queue->total_queued, 0); + atomic_set(&queue->total_flushed, 0); + atomic_set(&queue->total_locks, 0); + + init_waitqueue_head(&queue->write_queue); + init_waitqueue_head(&queue->read_queue); + init_waitqueue_head(&queue->flush_queue); + + queue->flags = 0; + + drm_waitlist_create(&queue->waitlist, dev->dma->buf_count); + + dev->queue_slots = 1; + dev->queuelist[DRM_KERNEL_CONTEXT] = queue; + dev->queue_count--; + + up(&dev->struct_sem); + printk("%d (new)\n", dev->queue_count - 1); + return DRM_KERNEL_CONTEXT; +} + +static int mga_dma_initialize(drm_device_t *dev, drm_mga_init_t *init) { + drm_mga_private_t *dev_priv; + drm_map_t *prim_map = NULL; + drm_map_t *sarea_map = NULL; + int temp; + + + dev_priv = drm_alloc(sizeof(drm_mga_private_t), DRM_MEM_DRIVER); + if(dev_priv == NULL) return -ENOMEM; + dev->dev_private = (void *) dev_priv; + + printk("dev_private\n"); + + memset(dev_priv, 0, sizeof(drm_mga_private_t)); + atomic_set(&dev_priv->pending_bufs, 0); + + if((init->reserved_map_idx >= dev->map_count) || + (init->buffer_map_idx >= dev->map_count)) { + mga_dma_cleanup(dev); + printk("reserved_map or buffer_map are invalid\n"); + return -EINVAL; + } + + if(mga_alloc_kernel_queue(dev) != DRM_KERNEL_CONTEXT) { + mga_dma_cleanup(dev); + DRM_ERROR("Kernel context queue not present\n"); + } + + dev_priv->reserved_map_idx = init->reserved_map_idx; + dev_priv->buffer_map_idx = init->buffer_map_idx; + sarea_map = dev->maplist[0]; + dev_priv->sarea_priv = (drm_mga_sarea_t *) + ((u8 *)sarea_map->handle + + init->sarea_priv_offset); + printk("sarea_priv\n"); + + /* Scale primary size to the next page */ + dev_priv->primary_size = ((init->primary_size + PAGE_SIZE - 1) / + PAGE_SIZE) * PAGE_SIZE; + dev_priv->warp_ucode_size = init->warp_ucode_size; + dev_priv->chipset = init->chipset; + dev_priv->fbOffset = init->fbOffset; + dev_priv->backOffset = init->backOffset; + dev_priv->depthOffset = init->depthOffset; + dev_priv->textureOffset = init->textureOffset; + dev_priv->textureSize = init->textureSize; + dev_priv->cpp = init->cpp; + dev_priv->sgram = init->sgram; + dev_priv->stride = init->stride; + + dev_priv->frontOrg = init->frontOrg; + dev_priv->backOrg = init->backOrg; + dev_priv->depthOrg = init->depthOrg; + dev_priv->mAccess = init->mAccess; + + + printk("memcpy\n"); + memcpy(&dev_priv->WarpIndex, &init->WarpIndex, + sizeof(mgaWarpIndex) * MGA_MAX_WARP_PIPES); + printk("memcpy done\n"); + prim_map = dev->maplist[init->reserved_map_idx]; + dev_priv->prim_phys_head = dev->agp->base + init->reserved_map_agpstart; + temp = init->warp_ucode_size + dev_priv->primary_size; + temp = ((temp + PAGE_SIZE - 1) / + PAGE_SIZE) * PAGE_SIZE; + printk("temp : %x\n", temp); + printk("dev->agp->base: %lx\n", dev->agp->base); + printk("init->reserved_map_agpstart: %x\n", init->reserved_map_agpstart); + + + dev_priv->ioremap = drm_ioremap(dev->agp->base + init->reserved_map_agpstart, + temp); + if(dev_priv->ioremap == NULL) { + printk("Ioremap failed\n"); + mga_dma_cleanup(dev); + return -ENOMEM; + } + + + + dev_priv->prim_head = (u32 *)dev_priv->ioremap; + printk("dev_priv->prim_head : %p\n", dev_priv->prim_head); + dev_priv->current_dma_ptr = dev_priv->prim_head; + dev_priv->prim_num_dwords = 0; + dev_priv->prim_max_dwords = dev_priv->primary_size / 4; + + printk("dma initialization\n"); + + /* Private is now filled in, initialize the hardware */ + { + PRIMLOCALS; + PRIMRESET( dev_priv ); + PRIMGETPTR( dev_priv ); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DWGSYNC, 0); + PRIMOUTREG(MGAREG_SOFTRAP, 0); + PRIMADVANCE( dev_priv ); + + /* Poll for the first buffer to insure that + * the status register will be correct + */ + printk("phys_head : %lx\n", phys_head); + + MGA_WRITE(MGAREG_DWGSYNC, MGA_SYNC_TAG); + + while(MGA_READ(MGAREG_DWGSYNC) != MGA_SYNC_TAG) { + int i; + for(i = 0 ; i < 4096; i++) mga_delay(); + } + + MGA_WRITE(MGAREG_PRIMADDRESS, phys_head | TT_GENERAL); + + MGA_WRITE(MGAREG_PRIMEND, ((phys_head + num_dwords * 4) | + PDEA_pagpxfer_enable)); + + while(MGA_READ(MGAREG_DWGSYNC) == MGA_SYNC_TAG) { + int i; + for(i = 0; i < 4096; i++) mga_delay(); + } + + } + + printk("dma init was successful\n"); + return 0; +} + +int mga_dma_init(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + drm_file_t *priv = filp->private_data; + drm_device_t *dev = priv->dev; + drm_mga_init_t init; + + copy_from_user_ret(&init, (drm_mga_init_t *)arg, sizeof(init), -EFAULT); + + switch(init.func) { + case MGA_INIT_DMA: + return mga_dma_initialize(dev, &init); + case MGA_CLEANUP_DMA: + return mga_dma_cleanup(dev); + } + + return -EINVAL; +} + +#define MGA_ILOAD_CMD (DC_opcod_iload | DC_atype_rpl | \ + DC_linear_linear | DC_bltmod_bfcol | \ + (0xC << DC_bop_SHIFT) | DC_sgnzero_enable | \ + DC_shftzero_enable | DC_clipdis_enable) + +static void __mga_iload_small(drm_device_t *dev, + drm_buf_t *buf, + int use_agp) +{ + drm_mga_private_t *dev_priv = dev->dev_private; + drm_mga_buf_priv_t *buf_priv = buf->dev_private; + drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv; + unsigned long address = (unsigned long)buf->bus_address; + int length = buf->used; + int y1 = buf_priv->boxes[0].y1; + int x1 = buf_priv->boxes[0].x1; + int y2 = buf_priv->boxes[0].y2; + int x2 = buf_priv->boxes[0].x2; + int dstorg = buf_priv->ContextState[MGA_CTXREG_DSTORG]; + int maccess = buf_priv->ContextState[MGA_CTXREG_MACCESS]; + PRIMLOCALS; + + PRIMRESET(dev_priv); + PRIMGETPTR(dev_priv); + + PRIMOUTREG(MGAREG_DSTORG, dstorg | use_agp); + PRIMOUTREG(MGAREG_MACCESS, maccess); + PRIMOUTREG(MGAREG_PITCH, (1 << 15)); + PRIMOUTREG(MGAREG_YDST, y1 * (x2 - x1)); + PRIMOUTREG(MGAREG_LEN, 1); + PRIMOUTREG(MGAREG_FXBNDRY, ((x2 - x1) * (y2 - y1) - 1) << 16); + PRIMOUTREG(MGAREG_AR0, (x2 - x1) * (y2 - y1) - 1); + PRIMOUTREG(MGAREG_AR3, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DWGCTL+MGAREG_MGA_EXEC, MGA_ILOAD_CMD); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_SECADDRESS, address | TT_BLIT); + PRIMOUTREG(MGAREG_SECEND, (address + length) | use_agp); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DWGSYNC, 0); + PRIMOUTREG(MGAREG_SOFTRAP, 0); + PRIMADVANCE(dev_priv); +#if 0 + /* For now we need to set this in the ioctl */ + sarea_priv->dirty |= MGASAREA_NEW_CONTEXT; +#endif + MGA_WRITE(MGAREG_DWGSYNC, MGA_SYNC_TAG); + while(MGA_READ(MGAREG_DWGSYNC) != MGA_SYNC_TAG) ; + + MGA_WRITE(MGAREG_PRIMADDRESS, dev_priv->prim_phys_head | TT_GENERAL); + MGA_WRITE(MGAREG_PRIMEND, (phys_head + num_dwords * 4) | use_agp); +} + +static void __mga_iload_xy(drm_device_t *dev, + drm_buf_t *buf, + int use_agp) +{ + drm_mga_private_t *dev_priv = dev->dev_private; + drm_mga_buf_priv_t *buf_priv = buf->dev_private; + drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv; + unsigned long address = (unsigned long)buf->bus_address; + int length = buf->used; + int y1 = buf_priv->boxes[0].y1; + int x1 = buf_priv->boxes[0].x1; + int y2 = buf_priv->boxes[0].y2; + int x2 = buf_priv->boxes[0].x2; + int dstorg = buf_priv->ContextState[MGA_CTXREG_DSTORG]; + int maccess = buf_priv->ContextState[MGA_CTXREG_MACCESS]; + int pitch = buf_priv->ServerState[MGA_2DREG_PITCH]; + int width, height; + int texperdword = 0; + PRIMLOCALS; + + width = (x2 - x1); + height = (y2 - y1); + switch((maccess & 0x00000003)) { + case 0: + texperdword = 4; + break; + case 1: + texperdword = 2; + break; + case 2: + texperdword = 1; + break; + default: + DRM_ERROR("Invalid maccess value passed to __mga_iload_xy\n"); + return; + } + + x2 = x1 + width; + x2 = (x2 + (texperdword - 1)) & ~(texperdword - 1); + x1 = (x1 + (texperdword - 1)) & ~(texperdword - 1); + width = x2 - x1; + + PRIMRESET(dev_priv); + PRIMGETPTR(dev_priv); + PRIMOUTREG(MGAREG_DSTORG, dstorg | use_agp); + PRIMOUTREG(MGAREG_MACCESS, maccess); + PRIMOUTREG(MGAREG_PITCH, pitch); + PRIMOUTREG(MGAREG_YDSTLEN, (y1 << 16) | height); + + PRIMOUTREG(MGAREG_FXBNDRY, ((x1+width-1) << 16) | x1); + PRIMOUTREG(MGAREG_AR0, width * height - 1); + PRIMOUTREG(MGAREG_AR3, 0 ); + PRIMOUTREG(MGAREG_DWGCTL+MGAREG_MGA_EXEC, MGA_ILOAD_CMD); + + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_SECADDRESS, address | TT_BLIT); + PRIMOUTREG(MGAREG_SECEND, (address + length) | use_agp); + + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DMAPAD, 0); + PRIMOUTREG(MGAREG_DWGSYNC, 0); + PRIMOUTREG(MGAREG_SOFTRAP, 0); + PRIMADVANCE(dev_priv); +#if 0 + /* For now we need to set this in the ioctl */ + sarea_priv->dirty |= MGASAREA_NEW_CONTEXT; +#endif + MGA_WRITE(MGAREG_DWGSYNC, MGA_SYNC_TAG); + while(MGA_READ(MGAREG_DWGSYNC) != MGA_SYNC_TAG) ; + + MGA_WRITE(MGAREG_PRIMADDRESS, dev_priv->prim_phys_head | TT_GENERAL); + MGA_WRITE(MGAREG_PRIMEND, (phys_head + num_dwords * 4) | use_agp); +} + +static void mga_dma_dispatch_iload(drm_device_t *dev, drm_buf_t *buf) +{ + drm_mga_buf_priv_t *buf_priv = buf->dev_private; + + int use_agp = PDEA_pagpxfer_enable; + int x1 = buf_priv->boxes[0].x1; + int x2 = buf_priv->boxes[0].x2; + + if((x2 - x1) < 32) { + printk("using iload small\n"); + __mga_iload_small(dev, buf, use_agp); + } else { + printk("using iload xy\n"); + __mga_iload_xy(dev, buf, use_agp); + } +} + +static void mga_dma_dispatch_vertex(drm_device_t *dev, drm_buf_t *buf) +{ + drm_mga_private_t *dev_priv = dev->dev_private; + drm_mga_buf_priv_t *buf_priv = buf->dev_private; + unsigned long address = (unsigned long)buf->bus_address; + int length = buf->used; + int use_agp = PDEA_pagpxfer_enable; + int i, count; + PRIMLOCALS; + + PRIMRESET(dev_priv); + + count = buf_priv->nbox; + if (count == 0) + count = 1; + + mgaEmitState( dev_priv, buf_priv ); + + for (i = 0 ; i < count ; i++) { + if (i < buf_priv->nbox) + mgaEmitClipRect( dev_priv, &buf_priv->boxes[i] ); + + PRIMGETPTR(dev_priv); + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_SECADDRESS, address | TT_VERTEX); + PRIMOUTREG( MGAREG_SECEND, (address + length) | use_agp); + + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_DWGSYNC, 0); + PRIMOUTREG( MGAREG_SOFTRAP, 0); + PRIMADVANCE(dev_priv); + } + + PRIMGETPTR( dev_priv ); + + MGA_WRITE(MGAREG_DWGSYNC, MGA_SYNC_TAG); + while(MGA_READ(MGAREG_DWGSYNC) != MGA_SYNC_TAG) ; + + MGA_WRITE(MGAREG_PRIMADDRESS, dev_priv->prim_phys_head | TT_GENERAL); + MGA_WRITE(MGAREG_PRIMEND, (phys_head + num_dwords * 4) | use_agp); +} + + +/* Used internally for the small buffers generated from client state + * information. + */ +static void mga_dma_dispatch_general(drm_device_t *dev, drm_buf_t *buf) +{ + drm_mga_private_t *dev_priv = dev->dev_private; + unsigned long address = (unsigned long)buf->bus_address; + int length = buf->used; + int use_agp = PDEA_pagpxfer_enable; + PRIMLOCALS; + + PRIMRESET(dev_priv); + PRIMGETPTR(dev_priv); + + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_SECADDRESS, address | TT_GENERAL); + PRIMOUTREG( MGAREG_SECEND, (address + length) | use_agp); + + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_DMAPAD, 0); + PRIMOUTREG( MGAREG_DWGSYNC, 0); + PRIMOUTREG( MGAREG_SOFTRAP, 0); + PRIMADVANCE(dev_priv); + + MGA_WRITE(MGAREG_DWGSYNC, MGA_SYNC_TAG); + while(MGA_READ(MGAREG_DWGSYNC) != MGA_SYNC_TAG) ; + + MGA_WRITE(MGAREG_PRIMADDRESS, dev_priv->prim_phys_head | TT_GENERAL); + MGA_WRITE(MGAREG_PRIMEND, (phys_head + num_dwords * 4) | use_agp); +} + +/* Frees dispatch lock */ +static inline void mga_dma_quiescent(drm_device_t *dev) +{ + drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private; + + while(1) { + atomic_inc(&dev_priv->dispatch_lock); + if(atomic_read(&dev_priv->dispatch_lock) == 1) { + break; + } else { + atomic_dec(&dev_priv->dispatch_lock); + } + } + while((MGA_READ(MGAREG_STATUS) & 0x00020001) != 0x00020000) ; +#if 0 + MGA_WRITE(MGAREG_DWGSYNC, MGA_SYNC_TAG); +#endif + while(MGA_READ(MGAREG_DWGSYNC) == MGA_SYNC_TAG) ; + MGA_WRITE(MGAREG_DWGSYNC, MGA_SYNC_TAG); + while(MGA_READ(MGAREG_DWGSYNC) != MGA_SYNC_TAG) ; + atomic_dec(&dev_priv->dispatch_lock); +} + +/* Keeps dispatch lock held */ + +static inline int mga_dma_is_ready(drm_device_t *dev) +{ + drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private; + + atomic_inc(&dev_priv->dispatch_lock); + if(atomic_read(&dev_priv->dispatch_lock) == 1) { + /* We got the lock */ + return 1; + } else { + atomic_dec(&dev_priv->dispatch_lock); + return 0; + } +} + +static inline int mga_dma_is_ready_no_hold(drm_device_t *dev) +{ + drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private; + + atomic_inc(&dev_priv->dispatch_lock); + if(atomic_read(&dev_priv->dispatch_lock) == 1) { + /* We got the lock, but free it */ + atomic_dec(&dev_priv->dispatch_lock); + return 1; + } else { + atomic_dec(&dev_priv->dispatch_lock); + return 0; + } +} + +static void mga_dma_service(int irq, void *device, struct pt_regs *regs) +{ + drm_device_t *dev = (drm_device_t *)device; + drm_device_dma_t *dma = dev->dma; + drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private; + + atomic_dec(&dev_priv->dispatch_lock); + atomic_inc(&dev->total_irq); + MGA_WRITE(MGAREG_ICLEAR, 0xfa7); + + /* Free previous buffer */ + if (test_and_set_bit(0, &dev->dma_flag)) { + atomic_inc(&dma->total_missed_free); + return; + } + if (dma->this_buffer) { + drm_free_buffer(dev, dma->this_buffer); + dma->this_buffer = NULL; + } + clear_bit(0, &dev->dma_flag); + + /* Dispatch new buffer */ + queue_task(&dev->tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); + +} + +/* Only called by mga_dma_schedule. */ +static int mga_do_dma(drm_device_t *dev, int locked) +{ + drm_buf_t *buf; + int retcode = 0; + drm_device_dma_t *dma = dev->dma; + drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private; + drm_mga_buf_priv_t *buf_priv; + + printk("mga_do_dma\n"); + if (test_and_set_bit(0, &dev->dma_flag)) { + atomic_inc(&dma->total_missed_dma); + return -EBUSY; + } + + if (!dma->next_buffer) { + DRM_ERROR("No next_buffer\n"); + clear_bit(0, &dev->dma_flag); + return -EINVAL; + } + + buf = dma->next_buffer; + + printk("context %d, buffer %d\n", buf->context, buf->idx); + + if (buf->list == DRM_LIST_RECLAIM) { + drm_clear_next_buffer(dev); + drm_free_buffer(dev, buf); + clear_bit(0, &dev->dma_flag); + return -EINVAL; + } + + if (!buf->used) { + DRM_ERROR("0 length buffer\n"); + drm_clear_next_buffer(dev); + drm_free_buffer(dev, buf); + clear_bit(0, &dev->dma_flag); + return 0; + } + + if (mga_dma_is_ready(dev) == 0) { + clear_bit(0, &dev->dma_flag); + return -EBUSY; + } + + /* Always hold the hardware lock while dispatching. + */ + if (!locked && !drm_lock_take(&dev->lock.hw_lock->lock, + DRM_KERNEL_CONTEXT)) { + atomic_inc(&dma->total_missed_lock); + clear_bit(0, &dev->dma_flag); + atomic_dec(&dev_priv->dispatch_lock); + return -EBUSY; + } + + dma->next_queue = dev->queuelist[DRM_KERNEL_CONTEXT]; + drm_clear_next_buffer(dev); + buf->pending = 1; + buf->waiting = 0; + buf->list = DRM_LIST_PEND; + + buf_priv = buf->dev_private; + + printk("dispatch!\n"); + switch (buf_priv->dma_type) { + case MGA_DMA_GENERAL: + mga_dma_dispatch_general(dev, buf); + break; + case MGA_DMA_VERTEX: + mga_dma_dispatch_vertex(dev, buf); + break; +/* case MGA_DMA_SETUP: */ +/* mga_dma_dispatch_setup(dev, address, length); */ +/* break; */ + case MGA_DMA_ILOAD: + mga_dma_dispatch_iload(dev, buf); + break; + default: + printk("bad buffer type %x in dispatch\n", buf_priv->dma_type); + break; + } + atomic_dec(&dev_priv->pending_bufs); + + drm_free_buffer(dev, dma->this_buffer); + dma->this_buffer = buf; + + atomic_add(buf->used, &dma->total_bytes); + atomic_inc(&dma->total_dmas); + + if (!locked) { + if (drm_lock_free(dev, &dev->lock.hw_lock->lock, + DRM_KERNEL_CONTEXT)) { + DRM_ERROR("\n"); + } + } + + clear_bit(0, &dev->dma_flag); + + if(!atomic_read(&dev_priv->pending_bufs)) { + wake_up_interruptible(&dev->queuelist[DRM_KERNEL_CONTEXT]->flush_queue); + } + +#if 0 + wake_up_interruptible(&dev->lock.lock_queue); +#endif + + /* We hold the dispatch lock until the interrupt handler + * frees it + */ + return retcode; +} + +static void mga_dma_schedule_timer_wrapper(unsigned long dev) +{ + mga_dma_schedule((drm_device_t *)dev, 0); +} + +static void mga_dma_schedule_tq_wrapper(void *dev) +{ + mga_dma_schedule(dev, 0); +} + +int mga_dma_schedule(drm_device_t *dev, int locked) +{ + drm_queue_t *q; + drm_buf_t *buf; + int retcode = 0; + int processed = 0; + int missed; + int expire = 20; + drm_device_dma_t *dma = dev->dma; + + printk("mga_dma_schedule\n"); + + if (test_and_set_bit(0, &dev->interrupt_flag)) { + /* Not reentrant */ + atomic_inc(&dma->total_missed_sched); + return -EBUSY; + } + missed = atomic_read(&dma->total_missed_sched); + +again: + /* There is only one queue: + */ + if (!dma->next_buffer && DRM_WAITCOUNT(dev, DRM_KERNEL_CONTEXT)) { + q = dev->queuelist[DRM_KERNEL_CONTEXT]; + buf = drm_waitlist_get(&q->waitlist); + dma->next_buffer = buf; + dma->next_queue = q; + if (buf && buf->list == DRM_LIST_RECLAIM) { + drm_clear_next_buffer(dev); + drm_free_buffer(dev, buf); + } + } + + if (dma->next_buffer) { + if (!(retcode = mga_do_dma(dev, locked))) + ++processed; + } + + /* Try again if we succesfully dispatched a buffer, or if someone + * tried to schedule while we were working. + */ + if (--expire) { + if (missed != atomic_read(&dma->total_missed_sched)) { + atomic_inc(&dma->total_lost); + if (mga_dma_is_ready_no_hold(dev)) + goto again; + } + + if (processed && mga_dma_is_ready_no_hold(dev)) { + atomic_inc(&dma->total_lost); + processed = 0; + goto again; + } + } + + clear_bit(0, &dev->interrupt_flag); + + return retcode; +} + +int mga_irq_install(drm_device_t *dev, int irq) +{ + int retcode; + + if (!irq) return -EINVAL; + + down(&dev->struct_sem); + if (dev->irq) { + up(&dev->struct_sem); + return -EBUSY; + } + dev->irq = irq; + up(&dev->struct_sem); + + printk("install irq handler %d\n", irq); + + dev->context_flag = 0; + dev->interrupt_flag = 0; + dev->dma_flag = 0; + dev->dma->next_buffer = NULL; + dev->dma->next_queue = NULL; + dev->dma->this_buffer = NULL; + dev->tq.next = NULL; + dev->tq.sync = 0; + dev->tq.routine = mga_dma_schedule_tq_wrapper; + dev->tq.data = dev; + + /* Before installing handler */ + MGA_WRITE(MGAREG_ICLEAR, 0xfa7); + MGA_WRITE(MGAREG_IEN, 0); + + /* Install handler */ + if ((retcode = request_irq(dev->irq, + mga_dma_service, + 0, + dev->devname, + dev))) { + down(&dev->struct_sem); + dev->irq = 0; + up(&dev->struct_sem); + return retcode; + } + + /* After installing handler */ + MGA_WRITE(MGAREG_ICLEAR, 0xfa7); + MGA_WRITE(MGAREG_IEN, 0x00000001); + + return 0; +} + +int mga_irq_uninstall(drm_device_t *dev) +{ + int irq; + + down(&dev->struct_sem); + irq = dev->irq; + dev->irq = 0; + up(&dev->struct_sem); + + if (!irq) return -EINVAL; + + printk("remove irq handler %d\n", irq); + + MGA_WRITE(MGAREG_ICLEAR, 0xfa7); + MGA_WRITE(MGAREG_IEN, 0); + MGA_WRITE(MGAREG_ICLEAR, 0xfa7); + + free_irq(irq, dev); + + return 0; +} + + +int mga_control(struct inode *inode, struct file *filp, unsigned int cmd, + unsigned long arg) +{ + drm_file_t *priv = filp->private_data; + drm_device_t *dev = priv->dev; + drm_control_t ctl; + + copy_from_user_ret(&ctl, (drm_control_t *)arg, sizeof(ctl), -EFAULT); + + switch (ctl.func) { + case DRM_INST_HANDLER: + return mga_irq_install(dev, ctl.irq); + case DRM_UNINST_HANDLER: + return mga_irq_uninstall(dev); + default: + return -EINVAL; + } +} + +int mga_flush_queue(drm_device_t *dev) +{ + DECLARE_WAITQUEUE(entry, current); + drm_queue_t *q = dev->queuelist[DRM_KERNEL_CONTEXT]; + drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private; + int ret = 0; + + printk("mga_flush_queue\n"); + if(atomic_read(&dev_priv->pending_bufs) != 0) { + current->state = TASK_INTERRUPTIBLE; + add_wait_queue(&q->flush_queue, &entry); + for (;;) { + if (!atomic_read(&dev_priv->pending_bufs)) break; + printk("Calling schedule from flush_queue : %d\n", + atomic_read(&dev_priv->pending_bufs)); + mga_dma_schedule(dev, 1); + schedule(); + if (signal_pending(current)) { + ret = -EINTR; /* Can't restart */ + break; + } + } + printk("Exited out of schedule from flush_queue\n"); + current->state = TASK_RUNNING; + remove_wait_queue(&q->flush_queue, &entry); + } + + return ret; +} + +int mga_lock(struct inode *inode, struct file *filp, unsigned int cmd, + unsigned long arg) +{ + drm_file_t *priv = filp->private_data; + drm_device_t *dev = priv->dev; + DECLARE_WAITQUEUE(entry, current); + int ret = 0; + drm_lock_t lock; + + copy_from_user_ret(&lock, (drm_lock_t *)arg, sizeof(lock), -EFAULT); + + if (lock.context == DRM_KERNEL_CONTEXT) { + DRM_ERROR("Process %d using kernel context %d\n", + current->pid, lock.context); + return -EINVAL; + } + + printk("%d (pid %d) requests lock (0x%08x), flags = 0x%08x\n", + lock.context, current->pid, dev->lock.hw_lock->lock, + lock.flags); + + + if (lock.context < 0) { + return -EINVAL; + } + + /* Only one queue: + */ + + if (!ret) { + add_wait_queue(&dev->lock.lock_queue, &entry); + for (;;) { + if (!dev->lock.hw_lock) { + /* Device has been unregistered */ + ret = -EINTR; + break; + } + if (drm_lock_take(&dev->lock.hw_lock->lock, + lock.context)) { + dev->lock.pid = current->pid; + dev->lock.lock_time = jiffies; + atomic_inc(&dev->total_locks); + break; /* Got lock */ + } + + /* Contention */ + atomic_inc(&dev->total_sleeps); + current->state = TASK_INTERRUPTIBLE; + current->policy |= SCHED_YIELD; + printk("Calling lock schedule\n"); + schedule(); + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + } + current->state = TASK_RUNNING; + remove_wait_queue(&dev->lock.lock_queue, &entry); + } + + if (!ret) { + if (lock.flags & _DRM_LOCK_QUIESCENT) { + printk("_DRM_LOCK_QUIESCENT\n"); + ret = mga_flush_queue(dev); + if(ret != 0) { + drm_lock_free(dev, &dev->lock.hw_lock->lock, + lock.context); + } else { + mga_dma_quiescent(dev); + } + } + } + printk("%d %s\n", lock.context, ret ? "interrupted" : "has lock"); + return ret; +} + |