mesa-drm/linux/mga_dma.c
Rik Faith 39a659e877 Audit calls to schedule() Remove tags from files shared with Linux kernel
tree Remove debugging statements to make debugging more useful Other
    minor cleanups in preparation for sync with Linux 2.4.0-test9-pre7
2000-09-29 01:47:11 +00:00

1051 lines
29 KiB
C

/* 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.
* 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.
*
* Authors: Rickard E. (Rik) Faith <faith@valinux.com>
* Jeff Hartmann <jhartmann@valinux.com>
* Keith Whitwell <keithw@valinux.com>
*
*/
#define __NO_VERSION__
#include "drmP.h"
#include "mga_drv.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
static int mga_flush_queue(drm_device_t *dev);
static unsigned long mga_alloc_page(drm_device_t *dev)
{
unsigned long address;
address = __get_free_page(GFP_KERNEL);
if(address == 0UL) {
return 0;
}
atomic_inc(&virt_to_page(address)->count);
set_bit(PG_reserved, &virt_to_page(address)->flags);
return address;
}
static void mga_free_page(drm_device_t *dev, unsigned long page)
{
if(!page) return;
atomic_dec(&virt_to_page(page)->count);
clear_bit(PG_reserved, &virt_to_page(page)->flags);
free_page(page);
return;
}
static void mga_delay(void)
{
return;
}
/* These are two age tags that will never be sent to
* the hardware */
#define MGA_BUF_USED 0xffffffff
#define MGA_BUF_FREE 0
static int mga_freelist_init(drm_device_t *dev)
{
drm_device_dma_t *dma = dev->dma;
drm_buf_t *buf;
drm_mga_buf_priv_t *buf_priv;
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
drm_mga_freelist_t *item;
int i;
dev_priv->head = drm_alloc(sizeof(drm_mga_freelist_t), DRM_MEM_DRIVER);
if(dev_priv->head == NULL) return -ENOMEM;
memset(dev_priv->head, 0, sizeof(drm_mga_freelist_t));
dev_priv->head->age = MGA_BUF_USED;
for (i = 0; i < dma->buf_count; i++) {
buf = dma->buflist[ i ];
buf_priv = buf->dev_private;
item = drm_alloc(sizeof(drm_mga_freelist_t),
DRM_MEM_DRIVER);
if(item == NULL) return -ENOMEM;
memset(item, 0, sizeof(drm_mga_freelist_t));
item->age = MGA_BUF_FREE;
item->prev = dev_priv->head;
item->next = dev_priv->head->next;
if(dev_priv->head->next != NULL)
dev_priv->head->next->prev = item;
if(item->next == NULL) dev_priv->tail = item;
item->buf = buf;
buf_priv->my_freelist = item;
buf_priv->discard = 0;
buf_priv->dispatched = 0;
dev_priv->head->next = item;
}
return 0;
}
static void mga_freelist_cleanup(drm_device_t *dev)
{
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
drm_mga_freelist_t *item;
drm_mga_freelist_t *prev;
item = dev_priv->head;
while(item) {
prev = item;
item = item->next;
drm_free(prev, sizeof(drm_mga_freelist_t), DRM_MEM_DRIVER);
}
dev_priv->head = dev_priv->tail = NULL;
}
/* Frees dispatch lock */
static inline void mga_dma_quiescent(drm_device_t *dev)
{
drm_device_dma_t *dma = dev->dma;
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
unsigned long end;
int i;
DRM_DEBUG("dispatch_status = 0x%02x\n", dev_priv->dispatch_status);
end = jiffies + (HZ*3);
while(1) {
if(!test_and_set_bit(MGA_IN_DISPATCH,
&dev_priv->dispatch_status)) {
break;
}
if((signed)(end - jiffies) <= 0) {
DRM_ERROR("irqs: %d wanted %d\n",
atomic_read(&dev->total_irq),
atomic_read(&dma->total_lost));
DRM_ERROR("lockup: dispatch_status = 0x%02x,"
" jiffies = %lu, end = %lu\n",
dev_priv->dispatch_status, jiffies, end);
return;
}
for (i = 0 ; i < 2000 ; i++) mga_delay();
}
end = jiffies + (HZ*3);
DRM_DEBUG("quiescent status : %x\n", MGA_READ(MGAREG_STATUS));
while((MGA_READ(MGAREG_STATUS) & 0x00030001) != 0x00020000) {
if((signed)(end - jiffies) <= 0) {
DRM_ERROR("irqs: %d wanted %d\n",
atomic_read(&dev->total_irq),
atomic_read(&dma->total_lost));
DRM_ERROR("lockup\n");
clear_bit(MGA_IN_DISPATCH, &dev_priv->dispatch_status);
return;
}
for (i = 0 ; i < 2000 ; i++) mga_delay();
}
sarea_priv->dirty |= MGA_DMA_FLUSH;
clear_bit(MGA_IN_DISPATCH, &dev_priv->dispatch_status);
DRM_DEBUG("exit, dispatch_status = 0x%02x\n",
dev_priv->dispatch_status);
}
static void mga_reset_freelist(drm_device_t *dev)
{
drm_device_dma_t *dma = dev->dma;
drm_buf_t *buf;
drm_mga_buf_priv_t *buf_priv;
int i;
for (i = 0; i < dma->buf_count; i++) {
buf = dma->buflist[ i ];
buf_priv = buf->dev_private;
buf_priv->my_freelist->age = MGA_BUF_FREE;
}
}
/* Least recently used :
* These operations are not atomic b/c they are protected by the
* hardware lock */
drm_buf_t *mga_freelist_get(drm_device_t *dev)
{
DECLARE_WAITQUEUE(entry, current);
drm_mga_private_t *dev_priv =
(drm_mga_private_t *) dev->dev_private;
drm_mga_freelist_t *prev;
drm_mga_freelist_t *next;
static int failed = 0;
int return_null = 0;
if(failed >= 1000 && dev_priv->tail->age >= dev_priv->last_prim_age) {
DRM_DEBUG("Waiting on freelist,"
" tail->age = %d, last_prim_age= %d\n",
dev_priv->tail->age,
dev_priv->last_prim_age);
add_wait_queue(&dev_priv->buf_queue, &entry);
set_bit(MGA_IN_GETBUF, &dev_priv->dispatch_status);
for (;;) {
current->state = TASK_INTERRUPTIBLE;
mga_dma_schedule(dev, 0);
if(dev_priv->tail->age < dev_priv->last_prim_age)
break;
atomic_inc(&dev->total_sleeps);
schedule();
if (signal_pending(current)) {
++return_null;
break;
}
}
clear_bit(MGA_IN_GETBUF, &dev_priv->dispatch_status);
current->state = TASK_RUNNING;
remove_wait_queue(&dev_priv->buf_queue, &entry);
if (return_null) return NULL;
}
if(dev_priv->tail->age < dev_priv->last_prim_age) {
prev = dev_priv->tail->prev;
next = dev_priv->tail;
prev->next = NULL;
next->prev = next->next = NULL;
dev_priv->tail = prev;
next->age = MGA_BUF_USED;
failed = 0;
return next->buf;
}
failed++;
return NULL;
}
int mga_freelist_put(drm_device_t *dev, drm_buf_t *buf)
{
drm_mga_private_t *dev_priv =
(drm_mga_private_t *) dev->dev_private;
drm_mga_buf_priv_t *buf_priv = buf->dev_private;
drm_mga_freelist_t *prev;
drm_mga_freelist_t *head;
drm_mga_freelist_t *next;
if(buf_priv->my_freelist->age == MGA_BUF_USED) {
/* Discarded buffer, put it on the tail */
next = buf_priv->my_freelist;
next->age = MGA_BUF_FREE;
prev = dev_priv->tail;
prev->next = next;
next->prev = prev;
next->next = NULL;
dev_priv->tail = next;
} else {
/* Normally aged buffer, put it on the head + 1,
* as the real head is a sentinal element
*/
next = buf_priv->my_freelist;
head = dev_priv->head;
prev = head->next;
head->next = next;
prev->prev = next;
next->prev = head;
next->next = prev;
}
return 0;
}
static int mga_init_primary_bufs(drm_device_t *dev, drm_mga_init_t *init)
{
drm_mga_private_t *dev_priv = dev->dev_private;
drm_mga_prim_buf_t *prim_buffer;
int i, temp, size_of_buf;
int offset = init->reserved_map_agpstart;
dev_priv->primary_size = ((init->primary_size + PAGE_SIZE - 1) /
PAGE_SIZE) * PAGE_SIZE;
size_of_buf = dev_priv->primary_size / MGA_NUM_PRIM_BUFS;
dev_priv->warp_ucode_size = init->warp_ucode_size;
dev_priv->prim_bufs = drm_alloc(sizeof(drm_mga_prim_buf_t *) *
(MGA_NUM_PRIM_BUFS + 1),
DRM_MEM_DRIVER);
if(dev_priv->prim_bufs == NULL) {
DRM_ERROR("Unable to allocate memory for prim_buf\n");
return -ENOMEM;
}
memset(dev_priv->prim_bufs,
0, sizeof(drm_mga_prim_buf_t *) * (MGA_NUM_PRIM_BUFS + 1));
temp = init->warp_ucode_size + dev_priv->primary_size;
temp = ((temp + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE;
dev_priv->ioremap = drm_ioremap(dev->agp->base + offset,
temp);
if(dev_priv->ioremap == NULL) {
DRM_ERROR("Ioremap failed\n");
return -ENOMEM;
}
init_waitqueue_head(&dev_priv->wait_queue);
for(i = 0; i < MGA_NUM_PRIM_BUFS; i++) {
prim_buffer = drm_alloc(sizeof(drm_mga_prim_buf_t),
DRM_MEM_DRIVER);
if(prim_buffer == NULL) return -ENOMEM;
memset(prim_buffer, 0, sizeof(drm_mga_prim_buf_t));
prim_buffer->phys_head = offset + dev->agp->base;
prim_buffer->current_dma_ptr =
prim_buffer->head =
(u32 *) (dev_priv->ioremap +
offset -
init->reserved_map_agpstart);
prim_buffer->num_dwords = 0;
prim_buffer->max_dwords = size_of_buf / sizeof(u32);
prim_buffer->max_dwords -= 5; /* Leave room for the softrap */
prim_buffer->sec_used = 0;
prim_buffer->idx = i;
prim_buffer->prim_age = i + 1;
offset = offset + size_of_buf;
dev_priv->prim_bufs[i] = prim_buffer;
}
dev_priv->current_prim_idx = 0;
dev_priv->next_prim =
dev_priv->last_prim =
dev_priv->current_prim =
dev_priv->prim_bufs[0];
dev_priv->next_prim_age = 2;
dev_priv->last_prim_age = 1;
set_bit(MGA_BUF_IN_USE, &dev_priv->current_prim->buffer_status);
return 0;
}
void mga_fire_primary(drm_device_t *dev, drm_mga_prim_buf_t *prim)
{
drm_mga_private_t *dev_priv = dev->dev_private;
drm_device_dma_t *dma = dev->dma;
drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
int use_agp = PDEA_pagpxfer_enable;
unsigned long end;
int i;
int next_idx;
PRIMLOCALS;
dev_priv->last_prim = prim;
/* We never check for overflow, b/c there is always room */
PRIMPTR(prim);
if(num_dwords <= 0) {
DRM_ERROR("num_dwords == 0 when dispatched\n");
goto out_prim_wait;
}
PRIMOUTREG( MGAREG_DMAPAD, 0);
PRIMOUTREG( MGAREG_DMAPAD, 0);
PRIMOUTREG( MGAREG_DMAPAD, 0);
PRIMOUTREG( MGAREG_SOFTRAP, 0);
PRIMFINISH(prim);
end = jiffies + (HZ*3);
if(sarea_priv->dirty & MGA_DMA_FLUSH) {
while((MGA_READ(MGAREG_STATUS) & 0x00030001) != 0x00020000) {
if((signed)(end - jiffies) <= 0) {
DRM_ERROR("irqs: %d wanted %d\n",
atomic_read(&dev->total_irq),
atomic_read(&dma->total_lost));
DRM_ERROR("lockup (flush)\n");
goto out_prim_wait;
}
for (i = 0 ; i < 4096 ; i++) mga_delay();
}
sarea_priv->dirty &= ~(MGA_DMA_FLUSH);
} else {
while((MGA_READ(MGAREG_STATUS) & 0x00020001) != 0x00020000) {
if((signed)(end - jiffies) <= 0) {
DRM_ERROR("irqs: %d wanted %d\n",
atomic_read(&dev->total_irq),
atomic_read(&dma->total_lost));
DRM_ERROR("lockup (wait)\n");
goto out_prim_wait;
}
for (i = 0 ; i < 4096 ; i++) mga_delay();
}
}
mga_flush_write_combine();
atomic_inc(&dev_priv->pending_bufs);
MGA_WRITE(MGAREG_PRIMADDRESS, phys_head | TT_GENERAL);
MGA_WRITE(MGAREG_PRIMEND, (phys_head + num_dwords * 4) | use_agp);
prim->num_dwords = 0;
sarea_priv->last_enqueue = prim->prim_age;
next_idx = prim->idx + 1;
if(next_idx >= MGA_NUM_PRIM_BUFS)
next_idx = 0;
dev_priv->next_prim = dev_priv->prim_bufs[next_idx];
return;
out_prim_wait:
prim->num_dwords = 0;
prim->sec_used = 0;
clear_bit(MGA_BUF_IN_USE, &prim->buffer_status);
wake_up_interruptible(&dev_priv->wait_queue);
clear_bit(MGA_BUF_SWAP_PENDING, &prim->buffer_status);
clear_bit(MGA_IN_DISPATCH, &dev_priv->dispatch_status);
}
int mga_advance_primary(drm_device_t *dev)
{
DECLARE_WAITQUEUE(entry, current);
drm_mga_private_t *dev_priv = dev->dev_private;
drm_mga_prim_buf_t *prim_buffer;
drm_device_dma_t *dma = dev->dma;
int next_prim_idx;
int ret = 0;
/* This needs to reset the primary buffer if available,
* we should collect stats on how many times it bites
* it's tail */
next_prim_idx = dev_priv->current_prim_idx + 1;
if(next_prim_idx >= MGA_NUM_PRIM_BUFS)
next_prim_idx = 0;
prim_buffer = dev_priv->prim_bufs[next_prim_idx];
set_bit(MGA_IN_WAIT, &dev_priv->dispatch_status);
/* In use is cleared in interrupt handler */
if(test_and_set_bit(MGA_BUF_IN_USE, &prim_buffer->buffer_status)) {
add_wait_queue(&dev_priv->wait_queue, &entry);
for (;;) {
current->state = TASK_INTERRUPTIBLE;
mga_dma_schedule(dev, 0);
if(!test_and_set_bit(MGA_BUF_IN_USE,
&prim_buffer->buffer_status))
break;
atomic_inc(&dev->total_sleeps);
atomic_inc(&dma->total_missed_sched);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
}
current->state = TASK_RUNNING;
remove_wait_queue(&dev_priv->wait_queue, &entry);
if(ret) return ret;
}
clear_bit(MGA_IN_WAIT, &dev_priv->dispatch_status);
/* This primary buffer is now free to use */
prim_buffer->current_dma_ptr = prim_buffer->head;
prim_buffer->num_dwords = 0;
prim_buffer->sec_used = 0;
prim_buffer->prim_age = dev_priv->next_prim_age++;
if(prim_buffer->prim_age == 0 || prim_buffer->prim_age == 0xffffffff) {
mga_flush_queue(dev);
mga_dma_quiescent(dev);
mga_reset_freelist(dev);
prim_buffer->prim_age = (dev_priv->next_prim_age += 2);
}
/* Reset all buffer status stuff */
clear_bit(MGA_BUF_NEEDS_OVERFLOW, &prim_buffer->buffer_status);
clear_bit(MGA_BUF_FORCE_FIRE, &prim_buffer->buffer_status);
clear_bit(MGA_BUF_SWAP_PENDING, &prim_buffer->buffer_status);
dev_priv->current_prim = prim_buffer;
dev_priv->current_prim_idx = next_prim_idx;
return 0;
}
/* More dynamic performance decisions */
static inline int mga_decide_to_fire(drm_device_t *dev)
{
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
if(test_bit(MGA_BUF_FORCE_FIRE, &dev_priv->next_prim->buffer_status)) {
return 1;
}
if (test_bit(MGA_IN_GETBUF, &dev_priv->dispatch_status) &&
dev_priv->next_prim->num_dwords) {
return 1;
}
if (test_bit(MGA_IN_FLUSH, &dev_priv->dispatch_status) &&
dev_priv->next_prim->num_dwords) {
return 1;
}
if(atomic_read(&dev_priv->pending_bufs) <= MGA_NUM_PRIM_BUFS - 1) {
if(test_bit(MGA_BUF_SWAP_PENDING,
&dev_priv->next_prim->buffer_status)) {
return 1;
}
}
if(atomic_read(&dev_priv->pending_bufs) <= MGA_NUM_PRIM_BUFS / 2) {
if(dev_priv->next_prim->sec_used >= MGA_DMA_BUF_NR / 8) {
return 1;
}
}
if(atomic_read(&dev_priv->pending_bufs) >= MGA_NUM_PRIM_BUFS / 2) {
if(dev_priv->next_prim->sec_used >= MGA_DMA_BUF_NR / 4) {
return 1;
}
}
return 0;
}
int mga_dma_schedule(drm_device_t *dev, int locked)
{
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
int retval = 0;
if (!dev_priv) return -EBUSY;
if (test_and_set_bit(0, &dev->dma_flag)) {
retval = -EBUSY;
goto sch_out_wakeup;
}
if(test_bit(MGA_IN_FLUSH, &dev_priv->dispatch_status) ||
test_bit(MGA_IN_WAIT, &dev_priv->dispatch_status) ||
test_bit(MGA_IN_GETBUF, &dev_priv->dispatch_status)) {
locked = 1;
}
if (!locked &&
!drm_lock_take(&dev->lock.hw_lock->lock, DRM_KERNEL_CONTEXT)) {
clear_bit(0, &dev->dma_flag);
retval = -EBUSY;
goto sch_out_wakeup;
}
if(!test_and_set_bit(MGA_IN_DISPATCH, &dev_priv->dispatch_status)) {
/* Fire dma buffer */
if(mga_decide_to_fire(dev)) {
clear_bit(MGA_BUF_FORCE_FIRE,
&dev_priv->next_prim->buffer_status);
if(dev_priv->current_prim == dev_priv->next_prim) {
/* Schedule overflow for a later time */
set_bit(MGA_BUF_NEEDS_OVERFLOW,
&dev_priv->next_prim->buffer_status);
}
mga_fire_primary(dev, dev_priv->next_prim);
} else {
clear_bit(MGA_IN_DISPATCH, &dev_priv->dispatch_status);
}
}
if (!locked) {
if (drm_lock_free(dev, &dev->lock.hw_lock->lock,
DRM_KERNEL_CONTEXT)) {
DRM_ERROR("\n");
}
}
clear_bit(0, &dev->dma_flag);
sch_out_wakeup:
if(test_bit(MGA_IN_FLUSH, &dev_priv->dispatch_status) &&
atomic_read(&dev_priv->pending_bufs) == 0) {
/* Everything has been processed by the hardware */
clear_bit(MGA_IN_FLUSH, &dev_priv->dispatch_status);
wake_up_interruptible(&dev_priv->flush_queue);
}
if(test_bit(MGA_IN_GETBUF, &dev_priv->dispatch_status)
&& dev_priv->tail->age < dev_priv->last_prim_age)
wake_up_interruptible(&dev_priv->buf_queue);
return retval;
}
static void mga_dma_service(int irq, void *device, struct pt_regs *regs)
{
drm_device_t *dev = (drm_device_t *)device;
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
drm_mga_prim_buf_t *last_prim_buffer;
atomic_inc(&dev->total_irq);
if((MGA_READ(MGAREG_STATUS) & 0x00000001) != 0x00000001) return;
MGA_WRITE(MGAREG_ICLEAR, 0x00000001);
last_prim_buffer = dev_priv->last_prim;
last_prim_buffer->num_dwords = 0;
last_prim_buffer->sec_used = 0;
dev_priv->sarea_priv->last_dispatch =
dev_priv->last_prim_age = last_prim_buffer->prim_age;
clear_bit(MGA_BUF_IN_USE, &last_prim_buffer->buffer_status);
clear_bit(MGA_BUF_SWAP_PENDING, &last_prim_buffer->buffer_status);
clear_bit(MGA_IN_DISPATCH, &dev_priv->dispatch_status);
atomic_dec(&dev_priv->pending_bufs);
queue_task(&dev->tq, &tq_immediate);
mark_bh(IMMEDIATE_BH);
wake_up_interruptible(&dev_priv->wait_queue);
}
static void mga_dma_task_queue(void *device)
{
mga_dma_schedule((drm_device_t *)device, 0);
}
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->irq) mga_flush_queue(dev);
mga_dma_quiescent(dev);
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);
}
if(dev_priv->status_page != NULL) {
iounmap(dev_priv->status_page);
}
if(dev_priv->real_status_page != 0UL) {
mga_free_page(dev, dev_priv->real_status_page);
}
if(dev_priv->prim_bufs != NULL) {
int i;
for(i = 0; i < MGA_NUM_PRIM_BUFS; i++) {
if(dev_priv->prim_bufs[i] != NULL) {
drm_free(dev_priv->prim_bufs[i],
sizeof(drm_mga_prim_buf_t),
DRM_MEM_DRIVER);
}
}
drm_free(dev_priv->prim_bufs, sizeof(void *) *
(MGA_NUM_PRIM_BUFS + 1),
DRM_MEM_DRIVER);
}
if(dev_priv->head != NULL) {
mga_freelist_cleanup(dev);
}
drm_free(dev->dev_private, sizeof(drm_mga_private_t),
DRM_MEM_DRIVER);
dev->dev_private = NULL;
}
return 0;
}
static int mga_dma_initialize(drm_device_t *dev, drm_mga_init_t *init) {
drm_mga_private_t *dev_priv;
drm_map_t *sarea_map = NULL;
dev_priv = drm_alloc(sizeof(drm_mga_private_t), DRM_MEM_DRIVER);
if(dev_priv == NULL) return -ENOMEM;
dev->dev_private = (void *) dev_priv;
memset(dev_priv, 0, sizeof(drm_mga_private_t));
if((init->reserved_map_idx >= dev->map_count) ||
(init->buffer_map_idx >= dev->map_count)) {
mga_dma_cleanup(dev);
return -EINVAL;
}
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);
/* Scale primary size to the next page */
dev_priv->chipset = init->chipset;
dev_priv->frontOffset = init->frontOffset;
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->mAccess = init->mAccess;
init_waitqueue_head(&dev_priv->flush_queue);
init_waitqueue_head(&dev_priv->buf_queue);
dev_priv->WarpPipe = 0xff000000;
dev_priv->vertexsize = 0;
DRM_DEBUG("chipset=%d ucode_size=%d backOffset=%x depthOffset=%x\n",
dev_priv->chipset, dev_priv->warp_ucode_size,
dev_priv->backOffset, dev_priv->depthOffset);
DRM_DEBUG("cpp: %d sgram: %d stride: %d maccess: %x\n",
dev_priv->cpp, dev_priv->sgram, dev_priv->stride,
dev_priv->mAccess);
memcpy(&dev_priv->WarpIndex, &init->WarpIndex,
sizeof(drm_mga_warp_index_t) * MGA_MAX_WARP_PIPES);
if(mga_init_primary_bufs(dev, init) != 0) {
DRM_ERROR("Can not initialize primary buffers\n");
mga_dma_cleanup(dev);
return -ENOMEM;
}
dev_priv->real_status_page = mga_alloc_page(dev);
if(dev_priv->real_status_page == 0UL) {
mga_dma_cleanup(dev);
DRM_ERROR("Can not allocate status page\n");
return -ENOMEM;
}
dev_priv->status_page =
ioremap_nocache(virt_to_bus((void *)dev_priv->real_status_page),
PAGE_SIZE);
if(dev_priv->status_page == NULL) {
mga_dma_cleanup(dev);
DRM_ERROR("Can not remap status page\n");
return -ENOMEM;
}
/* Write status page when secend or softrap occurs */
MGA_WRITE(MGAREG_PRIMPTR,
virt_to_bus((void *)dev_priv->real_status_page) | 0x00000003);
/* Private is now filled in, initialize the hardware */
{
PRIMLOCALS;
PRIMGETPTR( dev_priv );
PRIMOUTREG(MGAREG_DMAPAD, 0);
PRIMOUTREG(MGAREG_DMAPAD, 0);
PRIMOUTREG(MGAREG_DWGSYNC, 0x0100);
PRIMOUTREG(MGAREG_SOFTRAP, 0);
/* Poll for the first buffer to insure that
* the status register will be correct
*/
mga_flush_write_combine();
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) != 0x0100) ;
}
if(mga_freelist_init(dev) != 0) {
DRM_ERROR("Could not initialize freelist\n");
mga_dma_cleanup(dev);
return -ENOMEM;
}
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;
if (copy_from_user(&init, (drm_mga_init_t *)arg, sizeof(init)))
return -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;
}
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);
DRM_DEBUG("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_task_queue;
dev->tq.data = dev;
/* Before installing handler */
MGA_WRITE(MGAREG_IEN, 0);
/* Install handler */
if ((retcode = request_irq(dev->irq,
mga_dma_service,
SA_SHIRQ,
dev->devname,
dev))) {
down(&dev->struct_sem);
dev->irq = 0;
up(&dev->struct_sem);
return retcode;
}
/* After installing handler */
MGA_WRITE(MGAREG_ICLEAR, 0x00000001);
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;
DRM_DEBUG("remove irq handler %d\n", irq);
MGA_WRITE(MGAREG_ICLEAR, 0x00000001);
MGA_WRITE(MGAREG_IEN, 0);
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;
if (copy_from_user(&ctl, (drm_control_t *)arg, sizeof(ctl)))
return -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;
}
}
static int mga_flush_queue(drm_device_t *dev)
{
DECLARE_WAITQUEUE(entry, current);
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
int ret = 0;
if(!dev_priv) return 0;
if(dev_priv->next_prim->num_dwords != 0) {
add_wait_queue(&dev_priv->flush_queue, &entry);
if (test_bit(MGA_IN_FLUSH, &dev_priv->dispatch_status))
DRM_ERROR("Incorrect mga_flush_queue logic\n");
set_bit(MGA_IN_FLUSH, &dev_priv->dispatch_status);
mga_dma_schedule(dev, 0);
for (;;) {
current->state = TASK_INTERRUPTIBLE;
if (!test_bit(MGA_IN_FLUSH,
&dev_priv->dispatch_status))
break;
atomic_inc(&dev->total_sleeps);
schedule();
if (signal_pending(current)) {
ret = -EINTR; /* Can't restart */
clear_bit(MGA_IN_FLUSH,
&dev_priv->dispatch_status);
break;
}
}
current->state = TASK_RUNNING;
remove_wait_queue(&dev_priv->flush_queue, &entry);
}
return ret;
}
/* Must be called with the lock held */
void mga_reclaim_buffers(drm_device_t *dev, pid_t pid)
{
drm_device_dma_t *dma = dev->dma;
int i;
if (!dma) return;
if(dev->dev_private == NULL) return;
if(dma->buflist == NULL) return;
DRM_DEBUG("buf_count=%d\n", dma->buf_count);
mga_flush_queue(dev);
for (i = 0; i < dma->buf_count; i++) {
drm_buf_t *buf = dma->buflist[ i ];
drm_mga_buf_priv_t *buf_priv = buf->dev_private;
/* Only buffers that need to get reclaimed ever
* get set to free
*/
if (buf->pid == pid && buf_priv) {
if(buf_priv->my_freelist->age == MGA_BUF_USED)
buf_priv->my_freelist->age = MGA_BUF_FREE;
}
}
}
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;
if (copy_from_user(&lock, (drm_lock_t *)arg, sizeof(lock)))
return -EFAULT;
if (lock.context == DRM_KERNEL_CONTEXT) {
DRM_ERROR("Process %d using kernel context %d\n",
current->pid, lock.context);
return -EINVAL;
}
if (lock.context < 0) return -EINVAL;
/* Only one queue:
*/
if (!ret) {
add_wait_queue(&dev->lock.lock_queue, &entry);
for (;;) {
current->state = TASK_INTERRUPTIBLE;
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);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
}
current->state = TASK_RUNNING;
remove_wait_queue(&dev->lock.lock_queue, &entry);
}
if (!ret) {
sigemptyset(&dev->sigmask);
sigaddset(&dev->sigmask, SIGSTOP);
sigaddset(&dev->sigmask, SIGTSTP);
sigaddset(&dev->sigmask, SIGTTIN);
sigaddset(&dev->sigmask, SIGTTOU);
dev->sigdata.context = lock.context;
dev->sigdata.lock = dev->lock.hw_lock;
block_all_signals(drm_notifier, &dev->sigdata, &dev->sigmask);
if (lock.flags & _DRM_LOCK_QUIESCENT) {
DRM_DEBUG("_DRM_LOCK_QUIESCENT\n");
mga_flush_queue(dev);
mga_dma_quiescent(dev);
}
}
if (ret) DRM_DEBUG("%d %s\n", lock.context,
ret ? "interrupted" : "has lock");
return ret;
}
int mga_flush_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->dev;
drm_lock_t lock;
drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
if (copy_from_user(&lock, (drm_lock_t *)arg, sizeof(lock)))
return -EFAULT;
if(!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) {
DRM_ERROR("lock not held\n");
return -EINVAL;
}
if(lock.flags & _DRM_LOCK_FLUSH || lock.flags & _DRM_LOCK_FLUSH_ALL) {
drm_mga_prim_buf_t *temp_buf;
temp_buf = dev_priv->current_prim;
if(temp_buf && temp_buf->num_dwords) {
set_bit(MGA_BUF_FORCE_FIRE, &temp_buf->buffer_status);
mga_advance_primary(dev);
}
mga_dma_schedule(dev, 1);
}
if(lock.flags & _DRM_LOCK_QUIESCENT) {
mga_flush_queue(dev);
mga_dma_quiescent(dev);
}
return 0;
}