Message Queues and DMA Transfers

Message queues are provided for inter-thread communications and for direct-memory-access transfers.

Message Queues

IQueue objects implement non-blocking FIFO queues. IQueue methods add and remove nodes from a queue without locking the queue, allowing the methods to be called from within interrupt handlers. This makes the queue suitable for exchanging data between normal threads and interrupt handlers. A node to be queued on an IQueue object is a block of memory with a QUEUENODE structure as its first field. The QUEUENODE links the block into the queue and also contains information about message routing and message interpretation.

The following interface supports IQueue objects.

IQueue::Get

 

Gets a data node from the front of a queue.

IQueue::GetCondition

 

Gets a condition variable pointer for signaling availability of node in a queue.

IQueue::GetFromInterrupt

 

Gets a data node, when in an interrupt handler, from the front of a queue.

IQueue::Put

 

Puts a data node at the end of a queue.

IQueue::PutFromInterrupt

 

Puts a data node, when in an interrupt handler, at the end of a queue.

QueueCreate

 

Creates a queue object.

QUEUENODE

 

Structure for a queue node.

 

Direct-Memory-Access Transfers

The Talisman device library provides a DMA driver to allow threads to transfer data between host memory and Escalante memory. This driver is a COM library that is loaded and created as described in Modules and COM/DLL Library Support, later in this chapter. A thread that wants to use the driver can obtain a pointer to the driver object's IUnknown interface by binding to the hostdma.cob name in the COB namespace.

The DMA driver implements a single IQueue interface to submit DMA requests. To create a DMA request, a thread fills in a DMA_REQUEST message structure containing a QUEUENODE, source and destination pointers, and a byte count. The thread submits the request to the driver by calling the driver's IQueue::Put or IQueue::PutFromInterrupt method. When the transfer completes, the driver returns the message structure to the queue specified in the DMA_REQUEST structure's QUEUENODE.

This structure facilitates DMA transfers using queues.

DMA_REQUEST

 

Structure containing information for a DMA transfer request.

 

Message Queues and DMA Transfers

The IQueue interface implements nonblocking queues to enable communication between threads, processes, and across processors. The DMA driver also uses this interface to implement direct-memory-access data transfers.

Message Queues

The IQueue interface conceptually implements a FIFO queue of data blocks. The data blocks are placed onto the end of the queue using the IQueue::Put method and retrieved from the front of the queue using the IQueue::Get method. The IQueue interface also provides the ability for a thread to wait until a node is available to be read from a queue using condition variables.

A set of equivalent methods can access the queue from within an interrupt handler: IQueue::PutFromInterrupt and IQueue::GetFromInterrupt. The ability to directly access queues from within interrupt handlers simplifies the design of the handler and also improves system performance. Interrupt handlers can directly receive and send requests to and from other threads without the need for an additional thread to route the requests to and from the handler.

The interface supports multiple concurrent IQueue::Put, IQueue::Get, IQueue::PutFromInterrupt, and IQueue::GetFromInterrupt operations. Multiple threads and interrupt handlers can safely read from and write to the same queue without corrupting it.

Data blocks to be placed on the queue must include a QUEUENODE structure at the beginning of the block. This structure allows the data block to be linked into the queue. The QUEUENODE structure also includes a field specifying a queue to which the data block should be returned when it is done being processed. This allows threads to create an asynchronous procedure-call mechanism between each other by passing data blocks back and forth.

An application can create an IQueue object using the QueueCreate function. This implementation avoids the use of mutexes, and therefore it is lock-free and will not impact thread scheduling.

Applications can also implement their own IQueue interface to provide additional functionality beyond the default implementation, for instance to provide synchronous, queue-based RPC calls.

DMA Transfers Using Queues

The DMA driver is a dynamically-loaded COM object (COB). An application uses the COB namespace to bind to the driver and get its IUnknown interface. Calling the QueryInterface method using IID_IQueue gets the IQueue interface of the DMA driver.

The application can then fill a DMA_REQUEST structure and use the IQueue interface to transfer the data using DMA.

Example: DMA Transfer Using Queues

The following example illustrates the use of queues and the DMA driver. The GetDriverObj function returns a pointer to the IUnknown interface of the DMA driver by binding to the hostdma.cob object in the COB namespace. After calling GetDriverObj, the program queries the resultant IUnknown interface to get a pointer to the IQueue interface of the DMA driver.

The program then creates a return queue for the operation. It fills out a DMA_REQUEST structure for each DMA transfer and uses the IQueue::Put method of the DMA driver IQueue interface to submit each transfer.

The program finally checks for completion of the transfer by looking for returned data and then waiting for the condition variable associated with the transfer.

#include <mmlite.h>
#include <stdio.h>
#include <dma.h>
 
IUnknown *GetDriverObj(char *DriverName)
{
    INameSpace *pSysNs, *pCobNs;
    IUnknown *pIUnk;
    UINT RefCnt;
    SCODE sc;
 
    /* Get pointer to system namespace. */
    pSysNs = CurrentNameSpace();
 
    /* Get pointer to COB namespace */
    sc = pSysNs->v->Bind(pSysNs, "COB", 0, (IUnknown **)&pCobNs);
    if (sc != S_OK)
        return 0;
 
    sc = pCobNs->v->Bind(pCobNs, DriverName, 0, (IUnknown **)&pIUnk);
    if (sc != S_OK)
        return 0;
 
    sc = pIUnk->v->QueryInterface(pIUnk,
                    &IID_INameSpace, (void **)&pCobNs);
    pIUnk->v->Release(pIUnk);
    if(sc != S_OK)
        return 0;
 
    RefCnt = pCobNs->v->Release(pCobNs);
 
    return pIUnk;
}
 
#define NUMJOBS 4
#define JOBSIZE 10
char Source[NUMJOBS][JOBSIZE] = {"A man","A plan","A canal","Panama"};
char Dest[NUMJOBS][JOBSIZE];
DMA_REQUEST DmaRequest[NUMJOBS];
 
int main(void)
{
    IUnknown *pIUnk;
    UINT RefCnt;
    SCODE sc;
    IQueue *pIHostDma;
    IQueue *DmaReturnQueue;
    PCONDITION pDmaReturnCondition;
    int i;
    DMA_REQUEST *pReturnedRequest;
    TIME TimeWait;
 
    pIUnk = GetDriverObj("hostdma.cob");
    pIUnk->v->QueryInterface(pIUnk, &IID_IQueue, (void **)&pIHostDma);
        QueueCreate(&pDmaReturnCondition, &DmaReturnQueue);
 
    /* Submit 4 jobs to the DMA engine */
    for (i=0; i<NUMJOBS; i++) {
        DmaRequest[i].Node.Request = REQUEST_DMA_TRANSFER;
        DmaRequest[i].Node.Result  = 0;
        DmaRequest[i].Node.ReturnQueue = DmaReturnQueue;
        DmaRequest[i].pSource      = Source[i];
        DmaRequest[i].pDest        = Dest[i];
        DmaRequest[i].BytesTotal   = JOBSIZE;
        printf(_TEXT("HostDmaTest: About to submit job %d (0x%x)
               to DMA queue, Source=%s\n"), i, &DmaRequest[i], Source[i]);
        pIHostDma->v->Put(pIHostDma, &DmaRequest[i].Node);
    }
 
    Int32ToInt64(TimeWait, TIME_RELATIVE(TIME_MILLIS(1)));
    for (i=0; i<NUMJOBS; i++) {
        while ((pReturnedRequest =
               (DMA_REQUEST *)DmaReturnQueue->v->Get(DmaReturnQueue))
               ==  NULL) {
            printf(_TEXT("HostDmaTest: Nothing on queue!?\n"));
            sc = Condition_TimedWait(pDmaReturnCondition, &TimeWait);
            if (sc == E_TIMEOUT)
                printf(_TEXT("HostDmaTest: Condition_TimedWait
                              timed out on buffer %d!?\n", i));
        }
 
        printf(_TEXT("HostDmaTest: Job 0x%x moved %d bytes, pDest = %s\n"),
                pReturnedRequest, pReturnedRequest->Node.Result,
                pReturnedRequest->pDest);
        if (pReturnedRequest->Node.Result
            != pReturnedRequest->BytesTotal)
            printf(_TEXT("HostDmaTest: DMA driver didn't move all data,
                    Requested=%d, Actual=%d\n"),
                pReturnedRequest->BytesTotal, pReturnedRequest->Node.Result);
        if (pReturnedRequest != &DmaRequest[i])
            printf("HostDmaTest: Requests returned out of order!?\n");
    }
 
        DmaReturnQueue->v->Release(DmaReturnQueue);
    RefCnt = pIHostDma->v->Release(pIHostDma);
    RefCnt = pIUnk->v->Release(pIUnk);
 
    return 0;
}

Interrupt Services

The Base provides services to install and remove interrupt service routines (ISRs) to allow threads to handle interrupts from external peripherals.

ISRs run under extreme restrictions, to preclude them from interfering with the real-time scheduling. The ideal ISR simply clears the interrupt, and invokes Condition_InterruptSignal to wake up a thread to perform any other processing (similar to Windows NT's deferred procedure calls).

Aside from these restrictions, MMLite does not define a model for device drivers. Most drivers are simply COM objects that are registered in the COB Name Space.

BaseRtl provides the following interrupt services.

AddDevice

 

Adds an interrupt service routine for a device driver to the system.

RemoveDevice

 

Removes an interrupt service routine for a device driver from the system.