blob: 6c24208a59c75035c3be853ebd52c14ff50ddefa [file] [log] [blame]
/* Copyright (C) 2011 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "android/utils/intmap.h"
#include "android/utils/panic.h"
#include "android/utils/reflist.h"
#include "android/utils/system.h"
#include "android/hw-qemud.h"
#include "hw/goldfish_pipe.h"
#define DEBUG 0
#if DEBUG >= 1
# define D(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n")
#else
# define D(...) (void)0
#endif
#if DEBUG >= 2
# define DD(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n")
#else
# define DD(...) (void)0
#endif
#define E(...) fprintf(stderr, "ERROR:" __VA_ARGS__), fprintf(stderr, "\n")
/* Must match hw/goldfish_trace.h */
#define SLOT_COMMAND 0
#define SLOT_STATUS 0
#define SLOT_ADDRESS 1
#define SLOT_SIZE 2
#define SLOT_CHANNEL 3
#define QEMUD_PIPE_CMD_CLOSE 1
#define QEMUD_PIPE_CMD_SEND 2
#define QEMUD_PIPE_CMD_RECV 3
#define QEMUD_PIPE_CMD_WAKE_ON_SEND 4
#define QEMUD_PIPE_CMD_WAKE_ON_RECV 5
#define QEMUD_PIPE_ERROR_INVAL 22 /* EINVAL */
#define QEMUD_PIPE_ERROR_AGAIN 11 /* EAGAIN */
#define QEMUD_PIPE_ERROR_CONNRESET 104 /* ECONNRESET */
#define QEMUD_PIPE_ERROR_NOMEM 12 /* ENOMEM */
/**********************************************************************
**********************************************************************
**
** TECHNICAL NOTE ON THE FOLLOWING IMPLEMENTATION:
**
** PipeService ::
** The global state for the QEMUD fast pipes service.
** Holds a tid -> ThreadState map. Registers as a Qemud
** service named "fast-pipes" which creates PipeClient
** objects on connection.
**
** PipeClient ::
** The state of each QEMUD pipe. This handles the initial
** connection, message exchanges and signalling.
**
** ThreadState ::
** Hold the thread-specific state corresponding to each guest
** thread that has created a least one qemud pipe. Stores the
** state of our 4 I/O Registers, and a map of localId -> PipeState
**
**
** The following graphics is an example corresponding to the following
** situation:
**
** - two guest threads have opened pipe connections
** - the first thread has opened two different pipes
** - the second thread has opened only one pipe
**
**
** QEMUD-SERVICE
** | |
** | |________________________________________
** | | | |
** | v v v
** | QEMUD-CLIENT#1 QEMUD-CLIENT#2 QEMUD-CLIENT#3
** | ^ ^ ^
** | | | |
** | | | |
** | v v v
** | PIPE-CLIENT#1 PIPE-CLIENT#2 PIPE-CLIENT#3
** | ^ ^ ^
** | | | |
** | |________________| |
** | | +
** | | |
** | THREAD-STATE#1 THREAD-STATE#2
** | ^ ^
** | ____|___________________________________|
** | |
** | |
** PIPE-SERVICE
**
** Note that the QemudService and QemudClient objects are created by
** hw-qemud.c and not defined here.
**
**/
typedef struct PipeService PipeService;
typedef struct PipeClient PipeClient;
static void pipeService_removeState(PipeService* pipeSvc, int tid);
/**********************************************************************
**********************************************************************
*****
***** REGISTRY OF SUPPORTED PIPE SERVICES
*****
*****/
typedef struct {
const char* pipeName;
void* pipeOpaque;
const QemudPipeHandlerFuncs* pipeFuncs;
} PipeType;
#define MAX_PIPE_TYPES 4
static PipeType sPipeTypes[MAX_PIPE_TYPES];
static int sPipeTypeCount;
void
goldfish_pipe_add_type( const char* pipeName,
void* pipeOpaque,
const QemudPipeHandlerFuncs* pipeFuncs )
{
int count = sPipeTypeCount;
if (count >= MAX_PIPE_TYPES) {
APANIC("%s: Too many qemud pipe types!", __FUNCTION__);
}
sPipeTypes[count].pipeName = pipeName;
sPipeTypes[count].pipeOpaque = pipeOpaque;
sPipeTypes[count].pipeFuncs = pipeFuncs;
sPipeTypeCount = ++count;
}
static const PipeType*
goldfish_pipe_find_type( const char* pipeName )
{
const PipeType* ptype = sPipeTypes;
const PipeType* limit = ptype + sPipeTypeCount;
for ( ; ptype < limit; ptype++ ) {
if (!strcmp(pipeName, ptype->pipeName)) {
return ptype;
}
}
return NULL;
}
/**********************************************************************
**********************************************************************
*****
***** THREAD-SPECIFIC STATE
*****
*****/
static void
pipeClient_closeFromThread( PipeClient* pcl );
static uint32_t
pipeClient_doCommand( PipeClient* pcl,
uint32_t command,
uint32_t address,
uint32_t* pSize );
/* For each guest thread, we will store the following state:
*
* - The current state of the 'address', 'size', 'localId' and 'status'
* I/O slots provided through the magic page by hw/goldfish_trace.c
*
* - A list of PipeClient objects, corresponding to all the pipes in
* this thread, identified by localId.
*/
typedef struct {
uint32_t address;
uint32_t size;
uint32_t localId;
uint32_t status;
AIntMap* pipes;
} ThreadState;
static void
threadState_free( ThreadState* ts )
{
/* Get rid of the localId -> PipeClient map */
AINTMAP_FOREACH_VALUE(ts->pipes, pcl, pipeClient_closeFromThread(pcl));
aintMap_free(ts->pipes);
AFREE(ts);
}
static ThreadState*
threadState_new( void )
{
ThreadState* ts;
ANEW0(ts);
ts->pipes = aintMap_new();
return ts;
}
static int
threadState_addPipe( ThreadState* ts, int localId, PipeClient* pcl )
{
/* We shouldn't already have a pipe for this localId */
if (aintMap_get(ts->pipes, localId) != NULL) {
errno = EBADF;
return -1;
}
aintMap_set(ts->pipes, localId, pcl);
return 0;
}
static void
threadState_delPipe( ThreadState* ts, int localId )
{
aintMap_del(ts->pipes, localId);
}
static void
threadState_write( ThreadState* ts, int offset, uint32_t value )
{
PipeClient* pcl;
switch (offset) {
case SLOT_COMMAND:
pcl = aintMap_get(ts->pipes, (int)ts->localId);
if (pcl == NULL) {
D("%s: Invalid localId (%d)",
__FUNCTION__, ts->localId);
ts->status = QEMUD_PIPE_ERROR_INVAL;
} else {
ts->status = pipeClient_doCommand(pcl,
value,
ts->address,
&ts->size);
}
break;
case SLOT_ADDRESS:
ts->address = value;
break;
case SLOT_SIZE:
ts->size = value;
break;
case SLOT_CHANNEL:
ts->localId = value;
break;
default:
/* XXX: PRINT ERROR? */
;
}
}
static uint32_t
threadState_read( ThreadState* ts, int offset )
{
switch (offset) {
case SLOT_STATUS: return ts->status;
case SLOT_ADDRESS: return ts->address;
case SLOT_SIZE: return ts->size;
case SLOT_CHANNEL: return ts->localId;
default: return 0;
}
}
/**********************************************************************
**********************************************************************
*****
***** PIPE CLIENT STATE
*****
*****/
/* Each client object points to a PipeState after it has received the
* initial request from the guest, which shall look like:
* <tid>:<localId>:<name>
*/
struct PipeClient {
char* pipeName;
QemudClient* client;
PipeService* pipeSvc;
int tid;
uint32_t localId;
void* handler;
const QemudPipeHandlerFuncs* handlerFuncs;
};
static int pipeService_addPipe(PipeService* pipeSvc, int tid, int localId, PipeClient* pcl);
static void pipeService_removePipe(PipeService* pipeSvc, int tid, int localId);
static void
pipeClient_closeFromThread( PipeClient* pcl )
{
qemud_client_close(pcl->client);
}
/* This function should only be invoked through qemud_client_close().
* Never call it explicitely. */
static void
pipeClient_close( void* opaque )
{
PipeClient* pcl = opaque;
if (pcl->handler && pcl->handlerFuncs && pcl->handlerFuncs->close) {
pcl->handlerFuncs->close(pcl->handler);
}
D("qemud:pipe: closing client (%d,%x)", pcl->tid, pcl->localId);
qemud_client_close(pcl->client);
pcl->client = NULL;
/* The guest is closing the connection, so remove it from our state */
pipeService_removePipe(pcl->pipeSvc, pcl->tid, pcl->localId);
AFREE(pcl->pipeName);
}
static void
pipeClient_recv( void* opaque, uint8_t* msg, int msgLen, QemudClient* client )
{
PipeClient* pcl = opaque;
const PipeType* ptype;
const char* p;
int failure = 1;
char answer[64];
if (pcl->pipeName != NULL) {
/* Should never happen, the only time we'll receive something
* is when the connection is created! Simply ignore any incoming
* message.
*/
return;
}
/* The message format is <tid>:<localIdHex>:<name> */
if (sscanf((char*)msg, "%d:%x:", &pcl->tid, &pcl->localId) != 2) {
goto BAD_FORMAT;
}
p = strchr((const char*)msg, ':');
if (p != NULL) {
p = strchr(p+1, ':');
if (p != NULL)
p += 1;
}
if (p == NULL || *p == '\0') {
goto BAD_FORMAT;
}
pcl->pipeName = ASTRDUP(p);
ptype = goldfish_pipe_find_type(pcl->pipeName);
if (ptype == NULL) {
goto UNKNOWN_PIPE_TYPE;
}
pcl->handlerFuncs = ptype->pipeFuncs;
pcl->handler = ptype->pipeFuncs->init( client, ptype->pipeOpaque );
if (pcl->handler == NULL) {
goto BAD_INIT;
}
if (pipeService_addPipe(pcl->pipeSvc, pcl->tid, pcl->localId, pcl) < 0) {
goto DUPLICATE_REQUEST;
}
D("qemud:pipe: Added new client: %s", msg);
failure = 0;
snprintf(answer, sizeof answer, "OK");
goto SEND_ANSWER;
BAD_INIT:
/* Initialization failed for some reason! */
E("qemud:pipe: Could not initialize pipe: '%s'", pcl->pipeName);
snprintf(answer, sizeof answer, "KO:%d:Could not initialize pipe",
QEMUD_PIPE_ERROR_INVAL);
goto SEND_ANSWER;
UNKNOWN_PIPE_TYPE:
E("qemud:pipe: Unknown pipe type: '%s'", p);
snprintf(answer, sizeof answer, "KO:%d:Unknown pipe type name",
QEMUD_PIPE_ERROR_INVAL);
goto SEND_ANSWER;
BAD_FORMAT:
E("qemud:pipe: Invalid connection request: '%s'", msg);
snprintf(answer, sizeof answer, "KO:%d:Invalid connection request",
QEMUD_PIPE_ERROR_INVAL);
goto SEND_ANSWER;
DUPLICATE_REQUEST:
E("qemud:pipe: Duplicate connection request: '%s'", msg);
snprintf(answer, sizeof answer, "KO:%d:Duplicate connection request",
QEMUD_PIPE_ERROR_INVAL);
goto SEND_ANSWER;
SEND_ANSWER:
qemud_client_send(client, (uint8_t*)answer, strlen(answer));
if (failure) {
qemud_client_close(client);
} else {
/* Disable framing for the rest of signalling */
qemud_client_set_framing(client, 0);
}
}
/* Count the number of GoldfishPipeBuffers we will need to transfer
* memory from/to [address...address+size)
*/
static int
_countPipeBuffers( uint32_t address, uint32_t size )
{
CPUState* env = cpu_single_env;
int count = 0;
while (size > 0) {
uint32_t vstart = address & TARGET_PAGE_MASK;
uint32_t vend = vstart + TARGET_PAGE_SIZE;
uint32_t next = address + size;
DD("%s: trying to map (0x%x - 0x%0x, %d bytes) -> page=0x%x - 0x%x",
__FUNCTION__, address, address+size, size, vstart, vend);
if (next > vend) {
next = vend;
}
/* Check that the address is valid */
if (cpu_get_phys_page_debug(env, vstart) == -1) {
DD("%s: bad guest address!", __FUNCTION__);
return -1;
}
count++;
size -= (next - address);
address = next;
}
return count;
}
/* Fill the pipe buffers to prepare memory transfer from/to
* [address...address+size). This assumes 'buffers' points to an array
* which size corresponds to _countPipeBuffers(address, size)
*/
static void
_fillPipeBuffers( uint32_t address, uint32_t size, GoldfishPipeBuffer* buffers )
{
CPUState* env = cpu_single_env;
while (size > 0) {
uint32_t vstart = address & TARGET_PAGE_MASK;
uint32_t vend = vstart + TARGET_PAGE_SIZE;
uint32_t next = address + size;
if (next > vend) {
next = vend;
}
/* Translate virtual address into physical one, into emulator
* memory. */
target_phys_addr_t phys = cpu_get_phys_page_debug(env, vstart);
buffers[0].data = qemu_get_ram_ptr(phys) + (address - vstart);
buffers[0].size = next - address;
buffers++;
size -= (next - address);
address = next;
}
}
static uint32_t
pipeClient_doCommand( PipeClient* pcl,
uint32_t command,
uint32_t address,
uint32_t* pSize )
{
uint32_t size = *pSize;
D("%s: TID=%4d CHANNEL=%08x COMMAND=%d ADDRESS=%08x SIZE=%d\n",
__FUNCTION__, pcl->tid, pcl->localId, command, address, size);
/* XXX: TODO */
switch (command) {
case QEMUD_PIPE_CMD_CLOSE:
/* The client is asking us to close the connection, so
* just do that. */
qemud_client_close(pcl->client);
return 0;
case QEMUD_PIPE_CMD_SEND: {
/* First, try to allocate a buffer from the handler */
void* opaque = pcl->handler;
GoldfishPipeBuffer* buffers;
int numBuffers = 0;
/* Count the number of buffers we need, allocate them,
* then fill them. */
numBuffers = _countPipeBuffers(address, size);
if (numBuffers < 0) {
D("%s: Invalid guest address range 0x%x - 0x%x (%d bytes)",
__FUNCTION__, address, address+size, size);
return QEMUD_PIPE_ERROR_NOMEM;
}
buffers = alloca( sizeof(*buffers) * numBuffers );
_fillPipeBuffers(address, size, buffers);
D("%s: Sending %d bytes using %d buffers", __FUNCTION__,
size, numBuffers);
/* Send the data */
if (pcl->handlerFuncs->sendBuffers(opaque, buffers, numBuffers) < 0) {
/* When .sendBuffers() returns -1, it usually means
* that the handler isn't ready to accept a new message. There
* is however once exception: when the message is too large
* and it wasn't possible to allocate the buffer.
*
* Differentiate between these two cases by looking at errno.
*/
if (errno == ENOMEM)
return QEMUD_PIPE_ERROR_NOMEM;
else
return QEMUD_PIPE_ERROR_AGAIN;
}
return 0;
}
case QEMUD_PIPE_CMD_RECV: {
void* opaque = pcl->handler;
GoldfishPipeBuffer* buffers;
int numBuffers, ret;
/* Count the number of buffers we have */
numBuffers = _countPipeBuffers(address, size);
if (numBuffers < 0) {
D("%s: Invalid guest address range 0x%x - 0x%x (%d bytes)",
__FUNCTION__, address, address+size, size);
return QEMUD_PIPE_ERROR_NOMEM;
}
buffers = alloca(sizeof(*buffers)*numBuffers);
_fillPipeBuffers(address, size, buffers);
/* Receive data */
ret = pcl->handlerFuncs->recvBuffers(opaque, buffers, numBuffers);
if (ret < 0) {
if (errno == ENOMEM) {
// XXXX: TODO *pSize = msgSize;
return QEMUD_PIPE_ERROR_NOMEM;
} else {
return QEMUD_PIPE_ERROR_AGAIN;
}
}
*pSize = ret;
return 0;
}
case QEMUD_PIPE_CMD_WAKE_ON_SEND: {
pcl->handlerFuncs->wakeOn(pcl->handler, QEMUD_PIPE_WAKE_ON_SEND);
return 0;
}
case QEMUD_PIPE_CMD_WAKE_ON_RECV: {
pcl->handlerFuncs->wakeOn(pcl->handler, QEMUD_PIPE_WAKE_ON_RECV);
return 0;
}
default:
return QEMUD_PIPE_ERROR_CONNRESET;
}
}
QemudClient*
pipeClient_connect( void* opaque, QemudService* svc, int channel )
{
PipeClient* pcl;
ANEW0(pcl);
pcl->pipeSvc = opaque;
pcl->client = qemud_client_new( svc, channel, pcl,
pipeClient_recv,
pipeClient_close,
NULL, /* TODO: NO SNAPSHOT SAVE */
NULL ); /* TODO: NO SNAPHOT LOAD */
/* Only for the initial connection message */
qemud_client_set_framing(pcl->client, 1);
return pcl->client;
}
/**********************************************************************
**********************************************************************
*****
***** GLOBAL PIPE STATE
*****
*****/
struct PipeService {
AIntMap* threadMap; /* maps tid to ThreadState */
};
#if 0
static void
pipeService_done(PipeService* pipeSvc)
{
/* Get rid of the tid -> ThreadState map */
AINTMAP_FOREACH_VALUE(pipeSvc->threadMap, ts, threadState_free(ts));
aintMap_free(pipeSvc->threadMap);
pipeSvc->threadMap = NULL;
}
#endif
static void
pipeService_init(PipeService* pipeSvc)
{
pipeSvc->threadMap = aintMap_new();
qemud_service_register( "fast-pipe", 0, pipeSvc,
pipeClient_connect,
NULL, /* TODO: NO SNAPSHOT SAVE SUPPORT */
NULL /* TODO: NO SNAPSHOT LOAD SUPPORT */ );
}
static ThreadState*
pipeService_getState(PipeService* pipeSvc, int tid)
{
if (pipeSvc->threadMap == NULL)
pipeSvc->threadMap = aintMap_new();
return (ThreadState*) aintMap_get(pipeSvc->threadMap, tid);
}
static void
pipeService_removeState(PipeService* pipeSvc, int tid)
{
ThreadState* ts = pipeService_getState(pipeSvc, tid);
if (ts == NULL)
return;
aintMap_del(pipeSvc->threadMap,tid);
threadState_free(ts);
}
static int
pipeService_addPipe(PipeService* pipeSvc, int tid, int localId, PipeClient* pcl)
{
ThreadState* ts = pipeService_getState(pipeSvc, tid);
if (ts == NULL) {
ts = threadState_new();
aintMap_set(pipeSvc->threadMap, tid, ts);
}
return threadState_addPipe(ts, localId, pcl);
}
static void
pipeService_removePipe(PipeService* pipeSvc, int tid, int localId)
{
ThreadState* ts = pipeService_getState(pipeSvc, tid);
if (ts == NULL)
return;
threadState_delPipe(ts, localId);
}
/**********************************************************************
**********************************************************************
*****
***** HARDWARE API - AS SEEN FROM hw/goldfish_trace.c
*****
*****/
static PipeService _globalState[1];
void init_qemud_pipes(void)
{
pipeService_init(_globalState);
}
void
goldfish_pipe_thread_death(int tid)
{
PipeService* pipeSvc = _globalState;
pipeService_removeState(pipeSvc, tid);
}
void
goldfish_pipe_write(int tid, int offset, uint32_t value)
{
PipeService* pipeSvc = _globalState;
DD("%s: tid=%d offset=%d value=%d (0x%x)", __FUNCTION__, tid,
offset, value, value);
ThreadState* ts = pipeService_getState(pipeSvc, tid);
if (ts == NULL) {
D("%s: no thread state for tid=%d", __FUNCTION__, tid);
return;
}
threadState_write(ts, offset, value);
}
uint32_t
goldfish_pipe_read(int tid, int offset)
{
PipeService* pipeSvc = _globalState;
uint32_t ret;
DD("%s: tid=%d offset=%d", __FUNCTION__, tid, offset);
ThreadState* ts = pipeService_getState(pipeSvc, tid);
if (ts == NULL) {
D("%s: no thread state for tid=%d", __FUNCTION__, tid);
return 0;
}
ret = threadState_read(ts, offset);
DD("%s: result=%d (0x%d)", __FUNCTION__, ret, ret);
return ret;
}