/*
* Copyright (c) 2010 NVIDIA Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NVIDIA Corporation nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
/** @file
* @brief NVIDIA Driver Development Kit:
* Transport API
*
* @b Description: This is the implementation of Transport API, which
* implements a simple means to pass messages across a port name regardless of
* port exist in what processor (on same processor or other processor).
*/
#include "nvrm_transport.h"
#include "nvrm_xpc.h"
#include "nvrm_interrupt.h"
#include "nvrm_message.h"
#include "nvutil.h"
#include "nvassert.h"
#include "nvcommon.h"
#include "avp.h"
#include
#define LOOPBACK_PROFILE 0
// indices where to save data for the loopback test
#define LOOP_CPU_SEND_INDEX 0
#define LOOP_AVP_ISR_INDEX 1
#define LOOP_AVP_RECV_INDEX 2
#define LOOP_AVP_SEND_INDEX 3
#define LOOP_CPU_ISR_INDEX 4
#define LOOP_CPU_RECV_INDEX 5
#define SEMAPHORE_BASED_MUTUAL_EXCLUSION 0
enum {MAX_INT_FOR_TRANSPORT = 2};
// Interrupt bit index in the interrupt controller relocation table.
enum {CPU_TRANSPORT_INT_OBE = 1};
enum {CPU_TRANSPORT_INT_IBF = 0};
enum {AVP_TRANSPORT_INT_OBE = 0};
enum {AVP_TRANSPORT_INT_IBF = 1};
// Some constraints parameter to develop the transport APIs.
// Maximum port name length
enum {MAX_PORT_NAME_LENGTH = 16};
// Maximum possible message length between the ports
#define MAX_COMMAND_SIZE 16
// Message header size MessageCommand + port Name + message Length (24 Bytes)
enum {MESSAGE_HEADER_SIZE = 0x20};
// Maximum receive message queue depth
enum {MAX_MESSAGE_DEPTH = 30};
// Maximum time to wait for the response when open the port.
enum {MAX_OPEN_TIMEOUT_MS = 200};
// Try to resend the message after this time.
enum {MESSAGE_RETRY_AFTER_MS = 500 };
// Connection message transfer and response wait timeout.
enum {MAX_CONNECTION_TIMEOUT_MS = 500 };
// Transport Commands which uses to do the handshaking and message transfer
// between the processor. This commands are send to the remote processor
// when any type if transaction happens.
typedef enum
{
TransportCmd_None = 0x0,
// The first transport command from the cpu->avp will inform the
// avp of size of the buffer.
TransportCmd_SetBufferInfo,
// Transport command for staring the connection process.
TransportCmd_Connect,
// Transport command for disconnecting the port and deleting the port entry.
TransportCmd_Disconnect,
// Transport command which used for normal message transfer to the port.
TransportCmd_Message,
// When a command requires a response, the value in the command field will
// be changed by the called processor here to indicate that the response is ready.
TransportCmd_Response,
TransportCmd_Force32 = 0x7FFFFFFF
} TransportCmd;
// Ports (endpoint) state.
typedef enum
{
// Port is opened only.
PortState_Open = 0x1,
// Port is waiting for connection.
PortState_Waiting,
// Port is connected.
PortState_Connected,
// Port has been disconnected from other side. You can pop out messages
// but you can't send anymore
PortState_Disconnected,
// Set to destroy when there is someone waiting for a connection, but
// and a different thread calls to kill close the port.
PortState_Destroy,
PortState_Force32 = 0x7FFFFFFF
} PortState;
// Message list which will be queued in the port receive message queue.
typedef struct RmReceiveMessageRec
{
// Length of message.
NvU32 MessageLength;
// Fixed size message buffer where the receiving message will be store.
NvU8 MessageBuffer[MAX_MESSAGE_LENGTH];
} RmReceiveMessage;
// Combines the information for keeping the received messages to the
// corresponding ports.
typedef struct MessageQueueRec
{
// Receive message Q details to receive the message. We make the queue 1 extra bigger than the
// requested size, and then we can do lockless updates because only the Recv function modifies
// ReadIndex, and only the ISR modifies the WriteIndex
RmReceiveMessage *pReceiveMsg;
volatile NvU16 ReadIndex;
volatile NvU16 WriteIndex;
NvU16 QueueSize;
} MessageQueue;
// Combines all required information for the transport port.
// The port information contains the state, recv message q, message depth and
// message length.
typedef struct NvRmTransportRec
{
// Name of the port, 1 exra byte for NULL termination
char PortName[MAX_PORT_NAME_LENGTH+1];
// The state of port whether this is open or connected or waiting for
// connection.
PortState State;
// Receive message Box which contains the receive messages for this port.
MessageQueue RecvMessageQueue;
// Semaphore which is signal after getting the message for that port.
// This is the client passed semaphore.
NvOsSemaphoreHandle hOnPushMsgSem;
// Pointer to the partner port. If the connect is to a remote partner,
// then this pointer is NULL
NvRmTransportHandle hConnectedPort;
// If this is a remote connection, this holds the remote ports "name"
NvU32 RemotePort;
// save a copy of the rm handle.
NvRmDeviceHandle hRmDevice;
struct NvRmTransportRec *pNext;
// unlikely to be used members at the end
// to be signalled when someone waits for a connector.
NvOsSemaphoreHandle hOnConnectSem;
#if LOOPBACK_PROFILE
NvBool bLoopTest;
#endif
} NvRmTransport;
// Combines the common information for keeping the transport information and
// sending and receiving the messages.
typedef struct NvRmPrivPortsRec
{
// Device handle.
NvRmDeviceHandle hDevice;
// List of port names of the open ports in the system.
NvRmTransport *pPortHead;
// Mutex for transport
NvOsMutexHandle mutex;
NvRmMemHandle hMessageMem;
void *pTransmitMem;
void *pReceiveMem;
NvU32 MessageMemPhysAddr;
NvRmPrivXpcMessageHandle hXpc;
// if a message comes in, but the receiver's queue is full,
// then we don't clear the inbound message to allow another message
// and set this flag. We use 2 variables here, so we don't need a lock.
volatile NvU8 ReceiveBackPressureOn;
NvU8 ReceiveBackPressureOff;
#if LOOPBACK_PROFILE
volatile NvU32 *pTimer;
#endif
} NvRmPrivPorts;
// !!! Fixme, this should be part of the rm handle.
static NvRmPrivPorts s_TransportInfo;
extern NvU32 NvRmAvpPrivGetUncachedAddress(NvU32 addr);
#define MESSAGE_QUEUE_SIZE_IN_BYTES ( sizeof(RmReceiveMessage) * (MAX_MESSAGE_DEPTH+1) )
static NvU32 s_RpcAvpQueue[ (MESSAGE_QUEUE_SIZE_IN_BYTES + 3) / 4 ];
static NvU32 s_RpcCpuQueue[ (MESSAGE_QUEUE_SIZE_IN_BYTES + 3) / 4 ];
static struct NvRmTransportRec s_RpcAvpPortStruct;
static struct NvRmTransportRec s_RpcCpuPortStruct;
static NvOsInterruptHandle s_TransportInterruptHandle = NULL;
static NvRmTransportHandle
FindPort(NvRmDeviceHandle hDevice, char *pPortName);
static NvError NvRmPrivTransportSendMessage(NvRmDeviceHandle hDevice,
NvU32 *messagehdr, NvU32 MessageHdrLength,
NvU32 *Message, NvU32 MessageLength);
static void HandleAVPResetMessage(NvRmDeviceHandle hDevice);
// expect caller to handle mutex
static char *NvRmPrivTransportUniqueName(void)
{
static char UniqueName[] = "aaaaaaaa+";
NvU32 len = 8;
NvU32 i;
// this will roll a new name until we hit zzzz:zzzz
// it's not unbounded, but it is a lot of names...
// Unique names end in a '+' which won't be allowed in supplied names, to avoid
// collision.
for (i=0; i < len; ++i)
{
++UniqueName[i];
if (UniqueName[i] != 'z')
{
break;
}
UniqueName[i] = 'a';
}
return UniqueName;
}
/* Returns NV_TRUE if the message was inserted ok
* Returns NV_FALSE if message was not inserted because the queue is already full
*/static NvBool
InsertMessage(NvRmTransportHandle hPort, const NvU8 *message, const NvU32 MessageSize)
{
NvU32 index;
NvU32 NextIndex;
index = (NvU32)hPort->RecvMessageQueue.WriteIndex;
NextIndex = index + 1;
if (NextIndex == hPort->RecvMessageQueue.QueueSize)
NextIndex = 0;
// check for full condition
if (NextIndex == hPort->RecvMessageQueue.ReadIndex)
return NV_FALSE;
// copy in the message
NvOsMemcpy(hPort->RecvMessageQueue.pReceiveMsg[index].MessageBuffer,
message,
MessageSize);
hPort->RecvMessageQueue.pReceiveMsg[index].MessageLength = MessageSize;
hPort->RecvMessageQueue.WriteIndex = (NvU16)NextIndex;
return NV_TRUE;
}
static void
ExtractMessage(NvRmTransportHandle hPort, NvU8 *message, NvU32 *pMessageSize, NvU32 MaxSize)
{
NvU32 NextIndex;
NvU32 index = (NvU32)hPort->RecvMessageQueue.ReadIndex;
NvU32 size = hPort->RecvMessageQueue.pReceiveMsg[index].MessageLength;
NextIndex = index + 1;
if (NextIndex == hPort->RecvMessageQueue.QueueSize)
NextIndex = 0;
NV_ASSERT(index != hPort->RecvMessageQueue.WriteIndex); // assert on empty condition
NV_ASSERT(size <= MaxSize);
*pMessageSize = size;
// only do the copy and update if there is sufficient room, otherwise
// the caller will propogate an error up.
if (size > MaxSize)
{
return;
}
NvOsMemcpy(message,
hPort->RecvMessageQueue.pReceiveMsg[index].MessageBuffer,
size);
hPort->RecvMessageQueue.ReadIndex = (NvU16)NextIndex;
}
static void *s_TmpIsrMsgBuffer;
/**
* Connect message
* [ Transport Command ]
* [ Remote Handle ]
* [ Port Name ]
*
* Response:
* [ Remote Handle ] <- [ Local Handle ]
*/
static void
HandleConnectMessage(NvRmDeviceHandle hDevice, volatile NvU32 *pMessage)
{
char PortName[MAX_PORT_NAME_LENGTH+1];
NvU32 RemotePort;
NvRmTransportHandle hPort;
RemotePort = pMessage[1];
NvOsMemcpy(PortName, (void*)&pMessage[2], MAX_PORT_NAME_LENGTH);
PortName[MAX_PORT_NAME_LENGTH] = 0;
// See if there is a local port with that name
hPort = FindPort(hDevice, PortName);
if (hPort && hPort->State == PortState_Waiting)
{
NvOsAtomicCompareExchange32((NvS32 *)&hPort->State, PortState_Waiting, PortState_Connected);
if (hPort->State == PortState_Connected)
{
hPort->RemotePort = RemotePort;
NvOsSemaphoreSignal(hPort->hOnConnectSem);
pMessage[1] = (NvU32)hPort;
}
else
{
pMessage[1] = 0;
}
}
else
{
pMessage[1] = 0;
}
pMessage[0] = TransportCmd_Response;
}
/**
* Disconnect message
* [ Transport Command ]
* [ Local Handle ]
*
* Response:
* [ Local Handle ] <- 0
*/
static void
HandleDisconnectMessage(NvRmDeviceHandle hDevice, volatile NvU32 *pMessage)
{
NvRmTransportHandle hPort;
hPort = (NvRmTransportHandle)pMessage[1];
// !!! For sanity we should walk the list of open ports to make sure this is a valid port!
if (hPort && hPort->State == PortState_Connected)
{
hPort->State = PortState_Disconnected;
hPort->RemotePort = 0;
}
pMessage[1] = 0;
pMessage[0] = TransportCmd_None;
}
/**
* Disconnect message
* [ Transport Command ]
* [ Local Handle ]
* [ Message Length ]
* [ Message ]
*
* Response:
* [ Message Length ] <- NvSuccess
* [ Transport Command ] <- When we can accept a new message
*/
static void
HandlePortMessage(NvRmDeviceHandle hDevice, volatile NvU32 *pMessage)
{
NvRmTransportHandle hPort;
NvU32 MessageLength;
NvBool bSuccess;
hPort = (NvRmTransportHandle)pMessage[1];
MessageLength = pMessage[2];
#if LOOPBACK_PROFILE
if (hPort && hPort->bLoopTest)
{
# if NV_IS_AVP
pMessage[LOOP_AVP_ISR_INDEX + 3] = *s_TransportInfo.pTimer;
# else
pMessage[LOOP_CPU_ISR_INDEX + 3] = *s_TransportInfo.pTimer;
# endif
}
#endif
// !!! For sanity we should walk the list of open ports to make sure this is a valid port!
// Queue the message even if in the open state as presumably this should only have happened if
// due to a race condition with the transport connected messages.
if (hPort && (hPort->State == PortState_Connected || hPort->State == PortState_Open))
{
bSuccess = InsertMessage(hPort, (NvU8*)&pMessage[3], MessageLength);
if (bSuccess)
{
if (hPort->hOnPushMsgSem)
NvOsSemaphoreSignal(hPort->hOnPushMsgSem);
pMessage[0] = TransportCmd_None;
}
else
{
++s_TransportInfo.ReceiveBackPressureOn;
}
}
}
static void
HandleAVPResetMessage(NvRmDeviceHandle hDevice)
{
NvRmTransportHandle hPort;
hPort = FindPort(hDevice,(char*)"RPC_CPU_PORT");
if (hPort && (hPort->State == PortState_Connected || hPort->State == PortState_Open))
{
NvU32 message;
message = NvRmMsg_AVP_Reset;
InsertMessage(hPort, (NvU8*)&message, sizeof(NvU32));
if (hPort->hOnPushMsgSem)
NvOsSemaphoreSignal(hPort->hOnPushMsgSem);
else
NV_ASSERT(0);
}
else
NV_ASSERT(0);
}
/**
* Handle the Inbox full interrupt.
*/
static void
InboxFullIsr(void *args)
{
NvRmDeviceHandle hDevice = (NvRmDeviceHandle)args;
NvU32 MessageData;
NvU32 MessageCommand;
volatile NvU32 *pMessage;
MessageData = NvRmPrivXpcGetMessage(s_TransportInfo.hXpc);
if(MessageData == AVP_WDT_RESET)
{
HandleAVPResetMessage(hDevice);
NvRmInterruptDone(s_TransportInterruptHandle);
return;
}
// if we're on the AVP, the first message we get will configure the message info
if (s_TransportInfo.MessageMemPhysAddr == 0)
{
#if NV_IS_AVP
MessageData = NvRmAvpPrivGetUncachedAddress(MessageData);
#else
MessageData = MessageData;
#endif
s_TransportInfo.MessageMemPhysAddr = MessageData;
s_TransportInfo.pReceiveMem = (void*)MessageData;
s_TransportInfo.pTransmitMem = (void *) (MessageData + MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE);
// ack the message and return.
*(NvU32*)s_TransportInfo.pReceiveMem = TransportCmd_None;
return;
}
// otherwise decode and dispatch the message.
if (s_TransportInfo.pReceiveMem == NULL)
{
/* QT/EMUTRANS takes this path. */
NvRmMemRead(s_TransportInfo.hMessageMem, MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE, s_TmpIsrMsgBuffer, MAX_MESSAGE_LENGTH);
pMessage = s_TmpIsrMsgBuffer;
NvRmMemWrite(s_TransportInfo.hMessageMem, MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE, s_TmpIsrMsgBuffer, 2*sizeof(NvU32));
}
else
{
pMessage = (NvU32*)s_TransportInfo.pReceiveMem;
}
MessageCommand = pMessage[0];
switch (MessageCommand)
{
case TransportCmd_Connect:
HandleConnectMessage(hDevice, pMessage);
break;
case TransportCmd_Disconnect:
HandleDisconnectMessage(hDevice, pMessage);
break;
case TransportCmd_Message:
HandlePortMessage(hDevice, pMessage);
break;
default:
NV_ASSERT(0);
}
NvRmInterruptDone(s_TransportInterruptHandle);
}
/**
* Handle the outbox empty interrupt.
*/
static void
OutboxEmptyIsr(void *args)
{
// !!! This is not currently used... ignore for now. Might be required if we find that we
// need to spin for long periods of time waiting for other end of the connection to consume
// messages.
//
NvRmInterruptDone(s_TransportInterruptHandle);
}
static void
NvRmPrivProcIdGetProcessorInfo(
NvRmDeviceHandle hDevice,
NvRmModuleID *pProcModuleId)
{
#if NV_IS_AVP
*pProcModuleId = NvRmModuleID_Avp;
#else
*pProcModuleId = NvRmModuleID_Cpu;
#endif
}
/**
* Register for the transport interrupts.
*/
static NvError
RegisterTransportInterrupt(NvRmDeviceHandle hDevice)
{
NvOsInterruptHandler DmaIntHandlers[MAX_INT_FOR_TRANSPORT];
NvU32 IrqList[MAX_INT_FOR_TRANSPORT];
NvRmModuleID ProcModuleId;
if (s_TransportInterruptHandle)
{
return NvSuccess;
}
NvRmPrivProcIdGetProcessorInfo(hDevice, &ProcModuleId);
if (ProcModuleId == NvRmModuleID_Cpu)
{
IrqList[0] = NvRmGetIrqForLogicalInterrupt(
hDevice, NvRmModuleID_ResourceSema, CPU_TRANSPORT_INT_IBF);
IrqList[1] = NvRmGetIrqForLogicalInterrupt(
hDevice, NvRmModuleID_ResourceSema, CPU_TRANSPORT_INT_OBE);
}
else
{
IrqList[0] = NvRmGetIrqForLogicalInterrupt(
hDevice, NvRmModuleID_ResourceSema, AVP_TRANSPORT_INT_IBF);
IrqList[1] = NvRmGetIrqForLogicalInterrupt(
hDevice, NvRmModuleID_ResourceSema, AVP_TRANSPORT_INT_OBE);
}
/* There is no need for registering the OutboxEmptyIsr, so we only register
* one interrupt i.e. InboxFullIsr */
DmaIntHandlers[0] = InboxFullIsr;
DmaIntHandlers[1] = OutboxEmptyIsr;
return NvRmInterruptRegister(hDevice, 1, IrqList, DmaIntHandlers,
hDevice, &s_TransportInterruptHandle, NV_TRUE);
}
// allocate buffers to be used for sending/receiving messages.
static void
NvRmPrivTransportAllocBuffers(NvRmDeviceHandle hRmDevice)
{
#if !NV_IS_AVP
// These buffers are always allocated on the CPU side. We'll pass the address over the AVP
//
NvError Error = NvSuccess;
NvRmMemHandle hNewMemHandle = NULL;
// Create memory handle
Error = NvRmMemHandleCreate(hRmDevice, &hNewMemHandle, (MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE)*2);
if (Error)
goto fail;
// Allocates the memory from the Heap
Error = NvRmMemAlloc(hNewMemHandle, NULL, 0,
XPC_MESSAGE_ALIGNMENT_SIZE, NvOsMemAttribute_Uncached);
if (Error)
goto fail;
s_TransportInfo.MessageMemPhysAddr = NvRmMemPin(hNewMemHandle);
// If it is success to create the memory handle.
// We have to be able to get a mapping to this, because it is used at interrupt time!
s_TransportInfo.hMessageMem = hNewMemHandle;
Error = NvRmMemMap(hNewMemHandle, 0,
(MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE)*2,
NVOS_MEM_READ_WRITE,
&s_TransportInfo.pTransmitMem);
if (Error)
{
s_TransportInfo.pTransmitMem = NULL;
s_TransportInfo.pReceiveMem = NULL;
}
else
{
s_TransportInfo.pReceiveMem = (void *) (((NvUPtr)s_TransportInfo.pTransmitMem) +
MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE);
}
s_TransportInfo.hMessageMem = hNewMemHandle;
NvRmMemWr32(hNewMemHandle, 0, 0xdeadf00d); // set this non-zero to throttle messages to the avp till avp is ready.
NvRmMemWr32(hNewMemHandle, MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE, 0);
NvRmPrivXpcSendMessage(s_TransportInfo.hXpc, s_TransportInfo.MessageMemPhysAddr);
return;
fail:
NvRmMemHandleFree(hNewMemHandle);
s_TransportInfo.hMessageMem = NULL;
return;
#else
return;
#endif
}
static void
NvRmPrivTransportFreeBuffers(NvRmDeviceHandle hRmDevice)
{
#if !NV_IS_AVP
NvRmMemHandleFree(s_TransportInfo.hMessageMem);
#endif
}
static volatile NvBool s_Transport_Inited = NV_FALSE;
/**
* Initialize the transport structures, this is callled once
* at NvRmOpen time.
*/
NvError NvRmTransportInit(NvRmDeviceHandle hRmDevice)
{
NvError err;
NvOsMemset(&s_TransportInfo, 0, sizeof(s_TransportInfo));
s_TransportInfo.hDevice = hRmDevice;
err = NvOsMutexCreate(&s_TransportInfo.mutex);
if (err)
goto fail;
#if !NVOS_IS_WINDOWS || NVOS_IS_WINDOWS_CE
err = NvRmPrivXpcCreate(hRmDevice, &s_TransportInfo.hXpc);
if (err)
goto fail;
NvRmPrivTransportAllocBuffers(hRmDevice);
#endif
if (1) // Used in EMUTRANS mode where the buffers cannot be mapped.
{
s_TmpIsrMsgBuffer = NvOsAlloc(MAX_MESSAGE_LENGTH);
if (!s_TmpIsrMsgBuffer)
goto fail;
}
#if LOOPBACK_PROFILE
{
NvU32 TimerAddr;
NvU32 TimerSize;
NvRmModuleGetBaseAddress(hRmDevice, NvRmModuleID_TimerUs, &TimerAddr, &TimerSize);
// map the us counter
err = NvRmPhysicalMemMap(TimerAddr, TimerSize, NVOS_MEM_READ_WRITE,
NvOsMemAttribute_Uncached, (void*)&s_TransportInfo.pTimer);
if (err)
goto fail;
}
#endif
#if !NVOS_IS_WINDOWS || NVOS_IS_WINDOWS_CE
err = RegisterTransportInterrupt(hRmDevice);
if (err)
goto fail;
#endif
s_Transport_Inited = NV_TRUE;
return NvSuccess;
fail:
#if !NVOS_IS_WINDOWS || NVOS_IS_WINDOWS_CE
NvRmPrivXpcDestroy(s_TransportInfo.hXpc);
NvRmPrivTransportFreeBuffers(hRmDevice);
#endif
NvOsFree(s_TmpIsrMsgBuffer);
NvOsMutexDestroy(s_TransportInfo.mutex);
return err;
}
/**
* DeInitialize the transport structures.
*/
void NvRmTransportDeInit(NvRmDeviceHandle hRmDevice)
{
// Unregister the interrupts.
#if !NVOS_IS_WINDOWS || NVOS_IS_WINDOWS_CE
NvRmPrivXpcDestroy(s_TransportInfo.hXpc);
NvRmPrivTransportFreeBuffers(hRmDevice);
NvRmInterruptUnregister(hRmDevice, s_TransportInterruptHandle);
s_TransportInterruptHandle = NULL;
#endif
NvOsFree(s_TmpIsrMsgBuffer);
NvOsMutexDestroy(s_TransportInfo.mutex);
}
static void
InsertPort(NvRmDeviceHandle hDevice, NvRmTransportHandle hPort)
{
hPort->pNext = s_TransportInfo.pPortHead;
s_TransportInfo.pPortHead = hPort;
}
static NvRmTransportHandle
FindPort(NvRmDeviceHandle hDevice, char *pPortName)
{
NvRmTransportHandle hPort = NULL;
NvRmTransportHandle hIter = NULL;
hIter = s_TransportInfo.pPortHead;
while (hIter)
{
if ( NvOsStrcmp(pPortName, hIter->PortName) == 0)
{
hPort = hIter;
break;
}
hIter = hIter->pNext;
}
return hPort;
}
// Remove the given hPort from the list of ports
static void
DeletePort(NvRmDeviceHandle hRmDevice, const NvRmTransportHandle hPort)
{
// Pointer to the pointer alleviates all special cases in linked list walking.
// I wish I was clever enough to have figured this out myself.
NvRmTransportHandle *hIter;
hIter = &s_TransportInfo.pPortHead;
while (*hIter)
{
if ( *hIter == hPort )
{
*hIter = (*hIter)->pNext;
break;
}
hIter = &(*hIter)->pNext;
}
}
/**
* Open the port handle with a given port name. With the same name, only two
* port can be open.
* Thread Safety: It is done inside the function.
*/
NvError
NvRmTransportOpen(
NvRmDeviceHandle hRmDevice,
char *pPortName,
NvOsSemaphoreHandle RecvMessageSemaphore,
NvRmTransportHandle *phTransport)
{
NvU32 PortNameLen;
NvRmTransportHandle hPartner = NULL;
NvRmTransportHandle hPort = NULL;
NvError err = NvError_InsufficientMemory;
char TmpName[MAX_PORT_NAME_LENGTH+1];
while (!s_Transport_Inited) {
// This can happen, if this API is called before avp init.
NvOsSleepMS(500);
}
// Look and see if this port exists anywhere.
if (pPortName == NULL)
{
NvOsMutexLock(s_TransportInfo.mutex);
pPortName = NvRmPrivTransportUniqueName();
PortNameLen = NvOsStrlen(pPortName);
NvOsStrncpy(TmpName, pPortName, sizeof(TmpName) );
pPortName = TmpName;
NvOsMutexUnlock(s_TransportInfo.mutex);
}
else
{
PortNameLen = NvOsStrlen(pPortName);
NV_ASSERT(PortNameLen <= MAX_PORT_NAME_LENGTH);
}
NvOsMutexLock(s_TransportInfo.mutex);
hPartner = FindPort(hRmDevice, pPortName);
if (hPartner && hPartner->hConnectedPort != NULL)
{
NvOsMutexUnlock(s_TransportInfo.mutex);
return NvError_TransportPortAlreadyExist;
}
// check if this is one of the special RPC ports used by the rm
if ( NvOsStrcmp(pPortName, "RPC_AVP_PORT") == 0)
{
//If someone else wants to open this port
//just return the one already created.
if (hPartner)
{
hPort = hPartner;
goto success;
}
else
{
hPort = &s_RpcAvpPortStruct;
hPort->RecvMessageQueue.pReceiveMsg = (void *)&s_RpcAvpQueue[0];
}
}
else if (NvOsStrcmp(pPortName, "RPC_CPU_PORT") == 0)
{
hPort = &s_RpcCpuPortStruct;
hPort->RecvMessageQueue.pReceiveMsg = (void *)&s_RpcCpuQueue[0];
}
else
{
// Create a new TransportPort
hPort = NvOsAlloc( sizeof(*hPort) );
if (!hPort)
goto fail;
NvOsMemset(hPort, 0, sizeof(*hPort) );
// Allocate the receive queue
hPort->RecvMessageQueue.pReceiveMsg = NvOsAlloc( sizeof(RmReceiveMessage) * (MAX_MESSAGE_DEPTH+1));
if (!hPort->RecvMessageQueue.pReceiveMsg)
goto fail;
}
NvOsStrncpy(hPort->PortName, pPortName, PortNameLen);
hPort->State = PortState_Open;
hPort->hConnectedPort = hPartner;
if (RecvMessageSemaphore)
{
err = NvOsSemaphoreClone(RecvMessageSemaphore, &hPort->hOnPushMsgSem);
if (err)
goto fail;
}
hPort->RecvMessageQueue.QueueSize = MAX_MESSAGE_DEPTH+1;
hPort->hRmDevice = hRmDevice;
if (hPort->hConnectedPort != NULL)
{
hPort->hConnectedPort->hConnectedPort = hPort;
}
InsertPort(hRmDevice, hPort);
// !!! loopback info
#if LOOPBACK_PROFILE
if (NvOsStrcmp(hPort->PortName, "LOOPTEST") == 0)
hPort->bLoopTest = 1;
#endif
success:
NvOsMutexUnlock(s_TransportInfo.mutex);
*phTransport = hPort;
return NvSuccess;
fail:
if (hPort)
{
NvOsFree(hPort->RecvMessageQueue.pReceiveMsg);
NvOsSemaphoreDestroy(hPort->hOnPushMsgSem);
NvOsFree(hPort);
hPort = NULL;
}
NvOsMutexUnlock(s_TransportInfo.mutex);
return err;
}
/**
* Close the transport handle
* Thread Safety: It is done inside the function.
*/
void NvRmTransportClose(NvRmTransportHandle hPort)
{
NvU32 RemoteMessage[4];
if (!hPort)
return;
// Look and see if this port exists anywhere.
NV_ASSERT(hPort);
NvOsMutexLock(s_TransportInfo.mutex);
DeletePort(hPort->hRmDevice, hPort); // unlink this port
// Check if there is already a port waiting to connect, and if there is
// switch the port state to _Destroy, and signal the waiters semaphore.
// The "State" member is not protected by the mutex because it can be
// updated by the ISR.
while (hPort->State == PortState_Waiting)
{
NvOsAtomicCompareExchange32((NvS32*)&hPort->State, PortState_Waiting, PortState_Destroy);
if (hPort->State == PortState_Destroy)
{
NvOsSemaphoreSignal(hPort->hOnConnectSem);
// in this case, we can't complete the destroy, the signalled thread will
// have to complete. We just return now
NvOsMutexUnlock(s_TransportInfo.mutex);
return;
}
}
if (hPort->hConnectedPort)
{
// unlink this port from the other side of the connection.
hPort->hConnectedPort->hConnectedPort = NULL;
}
if (hPort->RemotePort)
{
RemoteMessage[0] = TransportCmd_Disconnect;
RemoteMessage[1] = hPort->RemotePort;
NvRmPrivTransportSendMessage(hPort->hRmDevice, RemoteMessage,
2*sizeof(NvU32), NULL, 0);
}
NvOsSemaphoreDestroy(hPort->hOnPushMsgSem);
if (hPort == &s_RpcAvpPortStruct ||
hPort == &s_RpcCpuPortStruct)
{
// don't free these..
NvOsMemset(hPort, 0, sizeof(*hPort));
}
else
{
NvOsFree(hPort->RecvMessageQueue.pReceiveMsg);
NvOsFree(hPort);
}
NvOsMutexUnlock(s_TransportInfo.mutex);
}
/**
* Wait for the connection to the other end.
* Thread Safety: It is done inside the function.
*/
NvError
NvRmTransportWaitForConnect(
NvRmTransportHandle hPort,
NvU32 TimeoutMS)
{
NvOsSemaphoreHandle hSem = NULL;
NvError err = NvSuccess;
NvOsMutexLock(s_TransportInfo.mutex);
if (hPort->State != PortState_Open)
{
NvOsMutexUnlock(s_TransportInfo.mutex);
err = NvError_TransportPortAlreadyExist;
goto exit_gracefully;
}
err = NvOsSemaphoreCreate(&hSem, 0);
if (err)
{
NvOsMutexUnlock(s_TransportInfo.mutex);
goto exit_gracefully;
}
hPort->hOnConnectSem = hSem;
hPort->State = PortState_Waiting;
NvOsMutexUnlock(s_TransportInfo.mutex);
err = NvOsSemaphoreWaitTimeout(hSem, TimeoutMS);
if (err)
{
// we have to be careful here, the ISR _might_ happen just after the semaphore
// times out.
NvOsAtomicCompareExchange32((NvS32 *)&hPort->State, PortState_Waiting, PortState_Open);
NV_ASSERT(hPort->State == PortState_Open || hPort->State == PortState_Connected);
if (hPort->State == PortState_Connected)
{
err = NvSuccess;
}
}
NvOsMutexLock(s_TransportInfo.mutex);
hPort->hOnConnectSem = NULL;
NvOsMutexUnlock(s_TransportInfo.mutex);
if (hPort->State == PortState_Destroy)
{
// finish the destroy process
NvRmTransportClose(hPort);
err = NvError_TransportConnectionFailed;
}
exit_gracefully:
NvOsSemaphoreDestroy(hSem);
return err;
}
static NvError
NvRmPrivTransportWaitResponse(NvRmDeviceHandle hDevice, NvU32 *response, NvU32 ResponseLength, NvU32 TimeoutMS)
{
NvU32 CurrentTime;
NvU32 StartTime;
NvU32 Response;
NvBool GotResponse = NV_TRUE;
NvError err = NvError_Timeout;
volatile NvU32 *pXpcMessage = (volatile NvU32*)s_TransportInfo.pTransmitMem;
if (pXpcMessage == NULL)
{
if (!NV_IS_AVP)
{
Response = NvRmMemRd32(s_TransportInfo.hMessageMem, 0);
} else
{
NV_ASSERT(0);
return NvSuccess;
}
}
else
{
Response = pXpcMessage[0];
}
if (Response != TransportCmd_Response)
{
GotResponse = NV_FALSE;
// response is not back yet, so spin till its here.
StartTime = NvOsGetTimeMS();
CurrentTime = StartTime;
while ( (CurrentTime - StartTime) < TimeoutMS )
{
if ( pXpcMessage && (pXpcMessage[0] == TransportCmd_Response) )
{
GotResponse = NV_TRUE;
break;
}
else if ( !pXpcMessage )
{
NV_ASSERT(!"Invalid pXpcMessage pointer is accessed");
}
CurrentTime = NvOsGetTimeMS();
}
}
if ( pXpcMessage && GotResponse )
{
err = NvSuccess;
NvOsMemcpy(response, (void *)pXpcMessage, ResponseLength);
}
return err;
}
static NvError NvRmPrivTransportSendMessage(NvRmDeviceHandle hDevice,
NvU32 *MessageHdr, NvU32 MessageHdrLength,
NvU32 *Message, NvU32 MessageLength)
{
NvU32 ReadData;
if (s_TransportInfo.pTransmitMem == NULL)
{
/* QT/EMUTRANS takes this code path */
if (!NV_IS_AVP)
{
ReadData = NvRmMemRd32(s_TransportInfo.hMessageMem, 0);
} else
{
NV_ASSERT(0);
return NvSuccess;
}
}
else
{
ReadData = ((volatile NvU32*)s_TransportInfo.pTransmitMem)[0];
}
// Check for clear to send
if ( ReadData != 0)
return NvError_TransportMessageBoxFull; // someone else is sending a message
if (s_TransportInfo.pTransmitMem == NULL)
{
/* QT/EMUTRANS takes this code path */
NvRmMemWrite(s_TransportInfo.hMessageMem, 0, MessageHdr, MessageHdrLength);
if (Message && MessageLength)
{
NvRmMemWrite(s_TransportInfo.hMessageMem, MessageHdrLength,
Message, MessageLength);
}
}
else
{
NvOsMemcpy(s_TransportInfo.pTransmitMem, MessageHdr, MessageHdrLength);
if (Message && MessageLength)
{
NvOsMemcpy(s_TransportInfo.pTransmitMem + MessageHdrLength,
Message, MessageLength);
}
NvOsFlushWriteCombineBuffer();
}
NvRmPrivXpcSendMessage(s_TransportInfo.hXpc, s_TransportInfo.MessageMemPhysAddr);
return NvSuccess;
}
NvError NvRmTransportSendMsgInLP0(NvRmTransportHandle hPort,
void *pMessageBuffer, NvU32 MessageSize)
{
NvU32 ReadData;
NvU32 MessageHdr[3];
NV_ASSERT(pMessageBuffer);
MessageHdr[0] = TransportCmd_Message;
MessageHdr[1] = hPort->RemotePort;
MessageHdr[2] = MessageSize;
ReadData = ((volatile NvU32*)s_TransportInfo.pTransmitMem)[0];
// Check for clear to send
if ( ReadData != 0)
return NvError_TransportMessageBoxFull; // someone else is sending a message
NvOsMemcpy(s_TransportInfo.pTransmitMem, MessageHdr, sizeof(MessageHdr));
if (MessageSize) {
NvOsMemcpy(s_TransportInfo.pTransmitMem + sizeof(MessageHdr),
pMessageBuffer, MessageSize);
}
NvOsFlushWriteCombineBuffer();
NvRmPrivXpcSendMessage(s_TransportInfo.hXpc, s_TransportInfo.MessageMemPhysAddr);
return NvSuccess;
}
static void
NvRmPrivTransportClearSend(NvRmDeviceHandle hDevice)
{
if (s_TransportInfo.pTransmitMem == NULL)
{
/* QT/EMUTRANS take this path */
if (!NV_IS_AVP)
{
NvRmMemWr32(s_TransportInfo.hMessageMem, 0, TransportCmd_None);
} else
{
NV_ASSERT(0);
}
}
else
{
((NvU32*)s_TransportInfo.pTransmitMem)[0] = TransportCmd_None;
}
}
/**
* Make the connection to the other end.
* Thread Safety: It is done inside the function.
*/
NvError NvRmTransportConnect(NvRmTransportHandle hPort, NvU32 TimeoutMS)
{
NvRmTransportHandle hPartnerPort;
NvU32 StartTime;
NvU32 CurrentTime;
NvU32 ConnectMessage[ MAX_PORT_NAME_LENGTH/4 + 3];
NvError err;
// Look and see if there is a local port with the same name that is currently waiting, if there is
// mark both ports as connected.
NV_ASSERT(hPort);
NV_ASSERT(hPort->hRmDevice);
NV_ASSERT(hPort->State == PortState_Open);
StartTime = NvOsGetTimeMS();
for (;;)
{
// Someone is waiting for a connection here locally.
NvOsMutexLock(s_TransportInfo.mutex);
hPartnerPort = hPort->hConnectedPort;
if (hPartnerPort)
{
// Found a local connection
if (hPartnerPort->State == PortState_Waiting)
{
hPartnerPort->State = PortState_Connected;
hPartnerPort->hConnectedPort = hPort;
hPort->State = PortState_Connected;
NvOsSemaphoreSignal(hPartnerPort->hOnConnectSem);
break;
}
}
else if (s_TransportInfo.hMessageMem || s_TransportInfo.pReceiveMem) // if no shared buffer, then we can't create a remote connection.
{
ConnectMessage[0] = TransportCmd_Connect;
ConnectMessage[1] = (NvU32)hPort;
NvOsMemcpy(&ConnectMessage[2], hPort->PortName, MAX_PORT_NAME_LENGTH);
err = NvRmPrivTransportSendMessage(hPort->hRmDevice,
ConnectMessage, sizeof(ConnectMessage), NULL, 0);
if (!err)
{
// should send back 2 words of data. Give remote side 1000ms to respond, which should be about 100x more
// than it needs.
NvU32 WaitTime = NV_MAX(1000, TimeoutMS);
if (TimeoutMS == NV_WAIT_INFINITE)
TimeoutMS = NV_WAIT_INFINITE;
// !!! Note, we can do this without holding the mutex...
err = NvRmPrivTransportWaitResponse(hPort->hRmDevice, ConnectMessage, 2*sizeof(NvU32), WaitTime);
NvRmPrivTransportClearSend(hPort->hRmDevice);
if (err)
{
// the other side is not responding to messages, doh!
NvOsMutexUnlock(s_TransportInfo.mutex);
return NvError_TransportConnectionFailed;
}
// check the response
hPort->RemotePort = ConnectMessage[1];
if (hPort->RemotePort != 0)
{
hPort->State = PortState_Connected;
break;
}
}
}
NvOsMutexUnlock(s_TransportInfo.mutex);
NV_ASSERT(hPort->State == PortState_Open); // it better still be open
// Didn't find a connection, wait a few ms and then try again
CurrentTime = NvOsGetTimeMS();
if ( (CurrentTime - StartTime) > TimeoutMS )
return NvError_Timeout;
NvOsSleepMS(10);
}
NvOsMutexUnlock(s_TransportInfo.mutex);
return NvSuccess;
}
/**
* Set the queue depth and message size of the transport handle.
* Thread Safety: It is done inside the function.
*/
NvError NvRmTransportSetQueueDepth(
NvRmTransportHandle hPort,
NvU32 MaxQueueDepth,
NvU32 MaxMessageSize)
{
RmReceiveMessage *pNewReceiveMsg = NULL;
NV_ASSERT(hPort != NULL);
NV_ASSERT(MaxQueueDepth != 0);
NV_ASSERT(MaxMessageSize != 0);
// You cannot change the queue after a connection has been opened
NV_ASSERT(hPort->State == PortState_Open);
// !!! FIXME
// Xpc does not allow changing the base message size, so we can't change the message size here (yet!)
// Once we have per port message buffers we can set this.
NV_ASSERT(MaxMessageSize <= MAX_MESSAGE_LENGTH);
// These are statically allocated ports, they cannot be modified!
// !!! FIXME: this is just a sanity check. Remove this and make it so that
// cpu/avp rpc doesn't call this function and just knows that the
// transport will give it a port with a large enough queue to support
// rpc, since rpc ports and queue are statically allocated this has to be true.
if (hPort == &s_RpcAvpPortStruct ||
hPort == &s_RpcCpuPortStruct)
{
if (MaxMessageSize <= MAX_MESSAGE_LENGTH &&
MaxQueueDepth <= MAX_MESSAGE_DEPTH)
{
return NvSuccess;
}
NV_ASSERT(!" Illegal meesage length or queue depth. ");
}
// Freeing default allocated message queue.
NvOsFree(hPort->RecvMessageQueue.pReceiveMsg);
hPort->RecvMessageQueue.pReceiveMsg = NULL;
// create a new message queue struct, one longer than requested on purpose.
pNewReceiveMsg = NvOsAlloc( sizeof(RmReceiveMessage) * (MaxQueueDepth+1));
if (pNewReceiveMsg == NULL)
return NvError_InsufficientMemory;
hPort->RecvMessageQueue.pReceiveMsg = pNewReceiveMsg;
hPort->RecvMessageQueue.QueueSize = (NvU16)(MaxQueueDepth+1);
return NvSuccess;
}
static NvError
NvRmPrivTransportSendRemoteMsg(
NvRmTransportHandle hPort,
void* pMessageBuffer,
NvU32 MessageSize,
NvU32 TimeoutMS)
{
NvError err;
NvU32 StartTime;
NvU32 CurrentTime;
NvU32 MessageHdr[3];
NvU32 JiffyTime = jiffies_to_msecs(1);
NV_ASSERT((MAX_MESSAGE_LENGTH) >= MessageSize);
StartTime = NvOsGetTimeMS();
MessageHdr[0] = TransportCmd_Message;
MessageHdr[1] = hPort->RemotePort;
MessageHdr[2] = MessageSize;
for (;;)
{
NvOsMutexLock(s_TransportInfo.mutex);
err = NvRmPrivTransportSendMessage(hPort->hRmDevice,
MessageHdr, sizeof(MessageHdr),
pMessageBuffer, MessageSize);
NvOsMutexUnlock(s_TransportInfo.mutex);
if (err == NvSuccess)
{
return NvSuccess;
}
// Sleep and then try again in a few ms to send again
CurrentTime = NvOsGetTimeMS();
if ( TimeoutMS != NV_WAIT_INFINITE && (CurrentTime - StartTime) > TimeoutMS )
return NvError_Timeout;
/* Sleeping for 1msec may not sleep exactly for 1msec. It depends
* on OS jiffy(tick) time. If jiffy time is much bigger,then this 1msec
* sleep would cause performance issues. At the same time, if complete
* polling is used, it can potentially block other threads from running.
* To reduce the impact of sleep in either ways, poll for one jiffy time
* and if operation is not complete then start sleeping.
*/
if ( (CurrentTime - StartTime) > JiffyTime )
NvOsSleepMS(1); // try again later...
}
}
static NvError
NvRmPrivTransportSendLocalMsg(
NvRmTransportHandle hPort,
void* pMessageBuffer,
NvU32 MessageSize,
NvU32 TimeoutMS)
{
NvU32 CurrentTime;
NvU32 StartTime;
NvError err = NvSuccess;
NvU32 JiffyTime = jiffies_to_msecs(1);
NvRmTransportHandle hRemotePort;
NvOsMutexLock(s_TransportInfo.mutex);
hRemotePort = hPort->hConnectedPort;
StartTime = NvOsGetTimeMS();
CurrentTime = StartTime;
for (;;)
{
// try to insert into the message into the receivers queue.
NvBool bSuccess = InsertMessage(hRemotePort, (NvU8*)pMessageBuffer, MessageSize);
if (bSuccess)
{
if (hRemotePort->hOnPushMsgSem)
NvOsSemaphoreSignal(hRemotePort->hOnPushMsgSem);
break;
}
// The destination port is full.
if (TimeoutMS == 0)
{
err = NvError_TransportMessageBoxFull;
break;
}
// The user wants a timeout, so we just sleep a short time so the
// other thread can pop a message. It would be better to use another semaphore
// to indicate that the box is not full, but that just seems overkill since this
// should rarely happen anyhow.
// unlock the mutex, and wait a small amount of time.
NvOsMutexUnlock(s_TransportInfo.mutex);
/* Sleeping for 1msec may not sleep exactly for 1msec. It depends
* on OS jiffy(tick) time. If jiffy time is much bigger,then this 1msec
* sleep would cause performance issues. At the same time, if complete
* polling is used, it can potentially block other threads from running.
* To reduce the impact of sleep in either ways, poll for one jiffy time
* and if operation is not complete then start sleeping.
*/
if ( (CurrentTime - StartTime) > JiffyTime )
NvOsSleepMS(1);
NvOsMutexLock(s_TransportInfo.mutex);
if (TimeoutMS != NV_WAIT_INFINITE)
{
// check for a timeout condition.
CurrentTime = NvOsGetTimeMS();
if ( (CurrentTime - StartTime) >= TimeoutMS)
{
err = NvError_Timeout;
break;
}
}
}
NvOsMutexUnlock(s_TransportInfo.mutex);
return err;
}
/**
* Send the message to the other end port.
* Thread Safety: It is done inside the function.
*/
NvError
NvRmTransportSendMsg(
NvRmTransportHandle hPort,
void* pMessageBuffer,
NvU32 MessageSize,
NvU32 TimeoutMS)
{
NvError err;
NV_ASSERT(hPort);
NV_ASSERT(hPort->State == PortState_Connected);
NV_ASSERT(pMessageBuffer);
#if LOOPBACK_PROFILE
if (hPort->bLoopTest)
{
# if NV_IS_AVP
((NvU32*)pMessageBuffer)[LOOP_AVP_SEND_INDEX] = *s_TransportInfo.pTimer;
# else
((NvU32*)pMessageBuffer)[LOOP_CPU_SEND_INDEX] = *s_TransportInfo.pTimer;
# endif
}
#endif
if (hPort->hConnectedPort)
{
err = NvRmPrivTransportSendLocalMsg(hPort, pMessageBuffer, MessageSize, TimeoutMS);
}
else if (hPort->State == PortState_Connected)
{
err = NvRmPrivTransportSendRemoteMsg(hPort, pMessageBuffer, MessageSize, TimeoutMS);
}
else
{
NV_ASSERT(0); // someone did something naughty
err = NvError_TransportNotConnected;
}
return err;
}
/**
* Receive the message from the other end port.
* Thread Safety: It is done inside the function.
*/
NvError
NvRmTransportRecvMsg(
NvRmTransportHandle hPort,
void* pMessageBuffer,
NvU32 MaxSize,
NvU32 *pMessageSize)
{
NvU8 TmpMessage[MAX_MESSAGE_LENGTH];
NV_ASSERT(hPort);
NV_ASSERT( (hPort->State == PortState_Connected) || (hPort->State == PortState_Disconnected) );
NV_ASSERT(pMessageBuffer);
NV_ASSERT(pMessageSize);
*pMessageSize = 0;
NvOsMutexLock(s_TransportInfo.mutex);
if (hPort->RecvMessageQueue.ReadIndex == hPort->RecvMessageQueue.WriteIndex)
{
NvOsMutexUnlock(s_TransportInfo.mutex);
return NvError_TransportMessageBoxEmpty;
}
ExtractMessage(hPort, (NvU8*)pMessageBuffer, pMessageSize, MaxSize);
if (*pMessageSize > MaxSize)
{
// not enough room to copy the message
NvOsMutexUnlock(s_TransportInfo.mutex);
NV_ASSERT(!" RM Transport: Illegal message size. ");
return NvError_InvalidSize;
}
// if there was backpressure asserted, try to handle the currently posted message, and re-enable messages
if (s_TransportInfo.ReceiveBackPressureOn != s_TransportInfo.ReceiveBackPressureOff)
{
NV_ASSERT( ((NvU8)s_TransportInfo.ReceiveBackPressureOn) == ((NvU8)(s_TransportInfo.ReceiveBackPressureOff+1)) );
++s_TransportInfo.ReceiveBackPressureOff;
if (s_TransportInfo.pReceiveMem == NULL)
{
/* QT/EMUTRANS takes this path. */
NvRmMemRead(s_TransportInfo.hMessageMem,
MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE,
TmpMessage,
MAX_MESSAGE_LENGTH);
HandlePortMessage(hPort->hRmDevice, (volatile void *)TmpMessage);
NvRmMemWrite(s_TransportInfo.hMessageMem,
MAX_MESSAGE_LENGTH + MAX_COMMAND_SIZE,
TmpMessage,
2*sizeof(NvU32) );
}
else
{
HandlePortMessage(hPort->hRmDevice, (NvU32*)s_TransportInfo.pReceiveMem);
}
}
#if LOOPBACK_PROFILE
if (hPort->bLoopTest)
{
# if NV_IS_AVP
((NvU32*)pMessageBuffer)[LOOP_AVP_RECV_INDEX] = *s_TransportInfo.pTimer;
# else
((NvU32*)pMessageBuffer)[LOOP_CPU_RECV_INDEX] = *s_TransportInfo.pTimer;
# endif
}
#endif
NvOsMutexUnlock(s_TransportInfo.mutex);
return NvSuccess;
}
void
NvRmTransportGetPortName(
NvRmTransportHandle hPort,
NvU8 *PortName,
NvU32 PortNameSize )
{
NvU32 len;
NV_ASSERT(hPort);
NV_ASSERT(PortName);
len = NvOsStrlen(hPort->PortName);
if (len >= PortNameSize)
{
NV_ASSERT(!" RM Transport: Port Name too long. ");
}
NvOsStrncpy((char *)PortName, hPort->PortName, PortNameSize);
}