dma.c 11.4 KB
/*
 * Copyright (C) 1996-1998 by the Board of Trustees
 *    of Leland Stanford Junior University.
 * 
 * This file is part of the SimOS distribution. 
 * See LICENSE file for terms of the license. 
 *
 */

/*****************************************************************
 * dma.c
 * 
 * Author: $Author: blythe $
 * Date: $Date: 2002/05/29 01:09:11 $
 *****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sim.h"
#include "simtypes.h"
#include "cpu_interface.h"
#include "eventcallback.h" 
#include "dma.h"
#include "assert.h"
#include "sim_error.h"
#include "simutil.h"
#include "machine_params.h"
#include "../../caches/memref.h"
#include "arch_specifics.h"


typedef struct DMAChannel { 
   EventCallbackHdr evthdr;   /* So we can make this an event callback */
   bool busy;                 /* DMA channel in use. */
   int  cpuNum;               /* CPU doing DMA */
   int execCpuNum;            /* CPU executing the callbacks and defining 
                               * the timing */
   DMARequest *req;
   void (*doneRoutine)(int);  /* Routine to call when done */
   int doneArg;               /* Argument for done routine */
   SimTime finishTime;
   int  cyclesPerLine;        /* Rate per cache line */   
   SimTime startTime;         /* Time to start next cache line DMA */
   int  currentOffset;        /* Current byte offset into DMA */
   bool inMemorySystem;       /* True if DMA is in memory system */
   int  lineSize;             /* L2-cache line size when defined */
   int transId;
} DMAChannel;

static DMAChannel  *dmaChannel;

static void DMACallBack( int cpuNum,EventCallbackHdr *ptr,void *arg);
static void DMAPrime(DMAChannel *, DMAStatus status);

/* 
 * Initialize all of the DMA channels
 */
void
DMAInit(void)
{
   int i;
   static int initialized = 0;
   ASSERT( !initialized );
   initialized = 1;
   dmaChannel = (DMAChannel *) 
      ZMALLOC(sizeof(DMAChannel)*NUM_DMA_CHANNELS, "DmaChannel");
   for (i = 0; i < NUM_DMA_CHANNELS; i++) {
      dmaChannel[i].busy = FALSE;
   }
}

/*
 * DMAdoTransfer - Perform the specified DMA operation. 
 */
void 
DMAdoTransfer(int cpuNum, DMARequest *req, SimTime finishTime,  
              void (*done)(int), int doneArg, int execCpuNum)
{
   int i;
   SimTime now = (CPUVec.CycleCount ? CPUVec.CycleCount(execCpuNum):0);

   ASSERT(finishTime >= now);

   for (i = 0; i < NUM_DMA_CHANNELS; i++) {
	if (!dmaChannel[i].busy) {
          break;
        }
   }

   if (i >= NUM_DMA_CHANNELS) {
       CPUError("Out of DMA channels\n");
   }

   SIM_DEBUG(('d', "DMA cpu=%i isDMAWrite=%i addr=0x%x+0x%x len=%i\n",
              cpuNum,req->isDMAWrite,*req->pAddrs,
              req->offset, req->remainingLen));

   ASSERT( req->remainingLen > 0  );
   ASSERT( req->handler != 0 );

   dmaChannel[i].transId = DMACHANNEL2TRANSID(i);
   dmaChannel[i].req = req;
   dmaChannel[i].req->dmaLen = 0; /* Set to zero as nothing has been transferred yet */
   dmaChannel[i].busy = TRUE;
   dmaChannel[i].cpuNum = cpuNum;
   dmaChannel[i].execCpuNum = execCpuNum;
   dmaChannel[i].doneRoutine = done;
   dmaChannel[i].doneArg = doneArg;
   dmaChannel[i].cyclesPerLine = (finishTime -now) * 
                                  SCACHE_LINE_SIZE / req->remainingLen;
   dmaChannel[i].finishTime = finishTime;
   dmaChannel[i].currentOffset = 0;
   dmaChannel[i].startTime = now;
   dmaChannel[i].inMemorySystem = FALSE;

   /* 
    * The HP disk model apparently can specify a finish time smaller than
    * the current time.
    */
   if( dmaChannel[i].startTime > dmaChannel[i].finishTime ) {
       dmaChannel[i].finishTime = dmaChannel[i].startTime;
   }

   dmaChannel[i].lineSize = ( SCACHE_LINE_SIZE ? SCACHE_LINE_SIZE : 256 );

   DMAPrime(&dmaChannel[i], DMA_OK); 
}

static Result 
NullMemsysDMARead(int cpuNum, PA paddr, int transId, int length, byte *data)
{
   char *memAddr = PHYS_TO_MEMADDR(M_FROM_CPU(cpuNum), paddr);

   SIM_DEBUG(('d', "DMA DATA cpu %d READ pAddr=%x len=%i data=%x %x %x %x\n",
              cpuNum, paddr,length, ((uint *)data)[0],
              ((uint *)data)[1],((uint *)data)[2],((uint *)data)[3]));
   
   bcopy(memAddr, data, length);

   DMACmdDone(transId, DMA_OK);
   return SUCCESS;
}

static Result 
NullMemsysDMAWrite(int cpuNum, PA paddr, int transId, int length, byte *data)
{
   char *memAddr = PHYS_TO_MEMADDR(M_FROM_CPU(cpuNum), paddr);
   
   SIM_DEBUG(('d', "DMA DATA cpu %d WRITE pAddr=%x len=%i data=%x %x %x %x\n",
              cpuNum, paddr, length, ((uint *)data)[0],
              ((uint *)data)[1],((uint *)data)[2],((uint *)data)[3]));
   
#ifndef SOLO
   if (CPUVec.CheckFirewall && !CPUVec.CheckFirewall(cpuNum, paddr)) {
      return BUSERROR;
   }
#endif
   bcopy(data, memAddr, length);
   
   ASSERT( data );
   
   DMACmdDone(transId, DMA_OK);
   return SUCCESS;
}


/*****************************************************************
 * Prime DMA to start or continue the current request.
 * execCpuNum is the cpuNum where the callback take place (where this code
 * executes. When using the HP disk model, this is always CPU 0.
 *****************************************************************/
static void
DMAPrime(DMAChannel *dma, DMAStatus status)
{
   SimTime cycleCount;
   Result ret;

   ASSERT(dma->busy);
   cycleCount = (CPUVec.CycleCount ? 
                 CPUVec.CycleCount(dma->execCpuNum) : SIM_MAX_TIME );   

   if (status == DMA_NAK) {
      /* try again */
      dma->req->dmaLen = 0;
   } else if (status != DMA_OK) {
      /* yowsa, memsystem didn't like the last access!  This
       * can happen if the OS tries to DMA to a page that is
       * firewall-protected against it, or if the memory system
       * NAKs the request for some reason.
       *
       * Right thing to do is retry on a NAK and abort transfer
       * on an error.  For now just let the user know that something
       * has gone wrong.
       */
      SIM_DEBUG_DETAIL(('d', "DMA-fail", dma->cpuNum,
                        "DMA %s attempt to %x rejected\n",
                        dma->req->isDMAWrite ? "write" : "read", 
                        (PA)(*(dma->req->pAddrs) + dma->req->offset)));
      CPUWarning("DMA failure -- not handled properly yet by DMAPrime -- see cpu.log\n");
   }
   
   while (1) {
      int lastDMALen = dma->req->dmaLen;
      
      /*
       * Timing: wait for end. 
       */
      if (dma->startTime > cycleCount ) {
         SimTime time = dma->startTime - cycleCount;
         /* Request's time hasn't occurred yet, delay until it occurs. */
         EventDoCallback(dma->execCpuNum, DMACallBack, &dma->evthdr, dma, time);
         return;
      } 
      
      /*
       * figure out next DMA line
       */
      ASSERT(lastDMALen >= 0 && lastDMALen <= dma->lineSize);
      dma->req->offset       += lastDMALen;
      dma->req->remainingLen -= lastDMALen;
      dma->req->amountMoved  += lastDMALen;
      
      /*
       * Go over page boundary. Adjust offset
       */
      if( dma->req->offset > PAGE_SIZE ) { 
         CPUError("dma.c:  %i > %i \n", dma->req->offset, PAGE_SIZE);
      }   
      
      ASSERT( dma->req->offset <= PAGE_SIZE );
      
      if (dma->req->offset == PAGE_SIZE) { 
         dma->req->offset = 0;
         dma->req->pAddrs++;
         ASSERT( !dma->req->remainingLen || *dma->req->pAddrs );
      }    
      
      if (dma->req->remainingLen) {
         int   dmalen;
         PA    pAddr = (PA)(*(dma->req->pAddrs) + dma->req->offset);
         
         if( (dma->req->offset & (dma->lineSize-1)) != 0 ) {
            dmalen = dma->req->offset & (dma->lineSize-1);
            dmalen = dma->lineSize - dmalen;
            
            if (dmalen > dma->req->remainingLen) {
               dmalen = dma->req->remainingLen;
            }               
         } else { 
            if( dma->req->remainingLen > dma->lineSize ) { 
               dmalen = dma->lineSize;
            } else { 
               dmalen = dma->req->remainingLen;
            }
         }
         
         ASSERT(dmalen > 0 && dmalen <= dma->lineSize);
         dma->req->dmaLen        = dmalen;
         
         /* if this is a DMA write request, must update the data
          * buffer right away.
          */
         if ((dma->req->isDMAWrite) && (status != DMA_NAK)) {
            dma->req->handler(dma->req);
            ASSERT(dma->req->data != 0);
         }
         
         /*
          * Call into memory system. Two possible outcomes:
          *  - SUCCESS: transfer has taken place
          *  - STALL: you're stalled, expect a callback to DMACmdDone when
          *    the transfer has finished.
          */
         dma->startTime += dma->cyclesPerLine;           
         dma->inMemorySystem = 1;
         
         

         if (CPUVec.useMemRef) {
            if (dma->req->isDMAWrite) {
               ret = MemRefDMAWrite(dma->cpuNum, pAddr, dma->transId, dmalen, 
                                    dma->req->data);
            } else {
               ret = MemRefDMARead(dma->cpuNum, pAddr, dma->transId, dmalen, 
                                   dma->req->data);
            }

         } else {
            if (dma->req->isDMAWrite) {
               ret = NullMemsysDMAWrite(dma->cpuNum, pAddr, dma->transId,
                                        dmalen, dma->req->data);
            } else {
               ret = NullMemsysDMARead(dma->cpuNum, pAddr, dma->transId,
                                       dmalen, dma->req->data);
            }
         }
         dma->inMemorySystem = 0;
         if (ret != SUCCESS) {
            /* wait for callback to DMACmdDone */
            break; 
         }
      } else {
         /* Finished with DMA or no memory system (embra) 
          * Eventually wait until desired minimal end time. 
          */
         if (CPUVec.CycleCount && cycleCount < dma->finishTime) { 
            dma->req->dmaLen = 0;
            EventDoCallback(dma->execCpuNum,DMACallBack,&dma->evthdr, 
                            dma, dma->finishTime - cycleCount);
            return;
         } else { 
            dma->busy = FALSE;
            dma->doneRoutine(dma->doneArg);
            return;
         }
      }
   }
   
   if (ret == BUSERROR) {
      /* this will only happen with 0-latency mem systems,
       * eg. embra.  Interestingly, we'll call right around
       * to the top of this function to detect the error...
       * let's do it this way for consistency with other
       * memory systems rather than just doing a goto.
       */
      DMACmdDone(dma->transId, DMA_BAD);
      return;
   }

   ASSERT(ret == STALL);
   return;                /* wait for callback to DMACmdDone */
}


/*
 * DMACmdDone - Called when a DMA memory system request finishes.
 */
void 
DMACmdDone(int transId, DMAStatus status)
{
   DMAChannel* dma = &dmaChannel[TRANSID2DMACHANNEL(transId)];
   ASSERT(dma->busy);
   
   /* need to call the data handling function on DMA reads to
    * squirrel away the data and prepare for the next transfer.
    */
   if ((!dma->req->isDMAWrite) && (status != DMA_NAK)) {
      ASSERT(dma->req->dmaLen > 0);
      dma->req->handler(dma->req);
   }
   
   /* prime DMA for rest of transfer (if any) */
   /* Don't call DMAPrime if we're already in the memory system --
    * the DMAPrime while loop will initiate the next request.
    * This only happens on zero-latency operations. */
   if (!dma->inMemorySystem) {
      DMAPrime(dma, status);
   }
}


static void 
DMACallBack(int cpuNum, EventCallbackHdr *ptr,void *arg)
{
   DMAChannel *dma = (DMAChannel*)arg;
   DMAPrime(dma, DMA_OK);
}