diff options
Diffstat (limited to 'hw')
-rw-r--r-- | hw/android_arm.c | 5 | ||||
-rw-r--r-- | hw/goldfish_pipe.c | 740 | ||||
-rw-r--r-- | hw/goldfish_pipe.h | 98 | ||||
-rw-r--r-- | hw/goldfish_trace.c | 98 | ||||
-rw-r--r-- | hw/goldfish_trace.h | 39 |
5 files changed, 944 insertions, 36 deletions
diff --git a/hw/android_arm.c b/hw/android_arm.c index 6062981..806d9bd 100644 --- a/hw/android_arm.c +++ b/hw/android_arm.c @@ -21,11 +21,13 @@ #include "arm-misc.h" #include "console.h" #include "blockdev.h" +#include "goldfish_pipe.h" #ifdef CONFIG_MEMCHECK #include "memcheck/memcheck_api.h" #endif // CONFIG_MEMCHECK #include "android/utils/debug.h" +#include "android/hw-qemud-pipe.h" #define D(...) VERBOSE_PRINT(init,__VA_ARGS__) @@ -146,6 +148,7 @@ static void android_arm_init_(ram_addr_t ram_size, #ifdef CONFIG_MEMCHECK || memcheck_enabled #endif // CONFIG_MEMCHECK + || 1 /* XXX: ALWAYS AVAILABLE FOR QEMUD PIPES */ ) { trace_dev_init(); } @@ -156,6 +159,8 @@ static void android_arm_init_(ram_addr_t ram_size, } #endif + init_qemud_pipes(); + #if TEST_SWITCH { void *sw; diff --git a/hw/goldfish_pipe.c b/hw/goldfish_pipe.c new file mode 100644 index 0000000..6c24208 --- /dev/null +++ b/hw/goldfish_pipe.c @@ -0,0 +1,740 @@ +/* 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; +} diff --git a/hw/goldfish_pipe.h b/hw/goldfish_pipe.h new file mode 100644 index 0000000..03bc443 --- /dev/null +++ b/hw/goldfish_pipe.h @@ -0,0 +1,98 @@ +/* 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. +*/ +#ifndef _HW_GOLDFISH_PIPE_H +#define _HW_GOLDFISH_PIPE_H + +#include <stdint.h> +#include "android/hw-qemud.h" + +/* The following functions should called from hw/goldfish_trace.c and are + * used to implement QEMUD 'fast-pipes' in the Android emulator. + */ +extern void goldfish_pipe_thread_death(int tid); +extern void goldfish_pipe_write(int tid, int offset, uint32_t value); +extern uint32_t goldfish_pipe_read(int tid, int offset); + +/* The following definitions are used to define a "pipe handler" type. + * Each pipe handler manages a given, named pipe type, and must provide + * a few callbacks that will be used at appropriate times: + * + * - init :: + * is called when a guest client has connected to a given + * pipe. The function should return an opaque pointer that + * will be passed as the first parameter to other callbacks. + * + * Note: pipeOpaque is the value that was passed to + * goldfish_pipe_add_type() to register the pipe handler. + * + * - close :: + * is called when the pipe is closed.(either from the guest, or + * when the handler itself calls qemud_client_close() on the + * corresponding QemudClient object passed to init()). + * + * - sendBuffers :: + * is called when the guest is sending data through the pipe. This + * callback receives a list of buffer descriptors that indicate + * where the data is located in memory. + * + * Must return 0 on success, or -1 on failure, with errno of: + * + * ENOMEM -> indicates that the message is too large + * EAGAIN -> indicates that the handler is not ready + * to accept the message yet. + * + * - recvBuffers :: + * Is called when the guest wants to receive data from the pipe. + * The caller provides a list of memory buffers to put the data into. + * + * Must return the size of the incoming data on success, or -1 + * on error, with errno of: + * + * ENOMEM -> buffer too small to receive the message + * EAGAIN -> no incoming data yet + * + * - wakeOn :: + * is called to indicate that the guest wants to be waked when the + * pipe becomes able to either receive data from the guest, or send it + * new incoming data. It is the responsability of the pipe handler to + * signal the corresponding events by sending a single byte containing + * QEMUD_PIPE_WAKE_XXX bit flags through qemud_client_send() to do so. + */ + +enum { + QEMUD_PIPE_WAKE_ON_SEND = (1 << 0), + QEMUD_PIPE_WAKE_ON_RECV = (1 << 1), +}; + +/* Buffer descriptor for sendBuffers() and recvBuffers() callbacks */ +typedef struct GoldfishPipeBuffer { + uint8_t* data; + size_t size; +} GoldfishPipeBuffer; + +/* Pipe handler funcs */ +typedef struct { + void* (*init)( QemudClient* client, void* pipeOpaque ); + void (*close)( void* opaque ); + int (*sendBuffers)( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ); + int (*recvBuffers)( void* opaque, GoldfishPipeBuffer* buffers, int numBuffers ); + void (*wakeOn)( void* opaque, int flags ); +} QemudPipeHandlerFuncs; + +/* Register a new pipe handler type. 'pipeOpaque' is passed directly + * to 'init() when a new pipe is connected to. + */ +extern void goldfish_pipe_add_type(const char* pipeName, + void* pipeOpaque, + const QemudPipeHandlerFuncs* pipeFuncs ); + +#endif /* _HW_GOLDFISH_PIPE_H */ diff --git a/hw/goldfish_trace.c b/hw/goldfish_trace.c index fc338c8..ee51a8b 100644 --- a/hw/goldfish_trace.c +++ b/hw/goldfish_trace.c @@ -15,6 +15,7 @@ */ #include "qemu_file.h" #include "goldfish_trace.h" +#include "goldfish_pipe.h" #include "sysemu.h" #include "trace.h" #ifdef CONFIG_MEMCHECK @@ -22,7 +23,23 @@ #include "memcheck/memcheck_util.h" #endif // CONFIG_MEMCHECK -//#define DEBUG 1 +/* Set to 1 to debug tracing */ +#define DEBUG 0 + +#if DEBUG +# define D(...) printf(__VA_ARGS__), fflush(stdout) +#else +# define D(...) ((void)0) +#endif + +/* Set to 1 to debug PID tracking */ +#define DEBUG_PID 0 + +#if DEBUG_PID +# define DPID(...) printf(__VA_ARGS__), fflush(stdout) +#else +# define DPID(...) ((void)0) +#endif extern void cpu_loop_exit(void); @@ -38,6 +55,7 @@ static unsigned long eoff; // offset in EXE file static unsigned cmdlen; // cmdline length static unsigned pid; // PID (really thread id) static unsigned tgid; // thread group id (really process id) +static unsigned tid; // current thread id (same as pid, most of the time) static unsigned long dsaddr; // dynamic symbol address static unsigned long unmap_start; // start address to unmap @@ -53,32 +71,30 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va switch (offset >> 2) { case TRACE_DEV_REG_SWITCH: // context switch, switch to pid + DPID("QEMU.trace: context switch tid=%u\n", value); if (trace_filename != NULL) { trace_switch(value); -#ifdef DEBUG - printf("QEMU.trace: kernel, context switch %u\n", value); -#endif + D("QEMU.trace: kernel, context switch %u\n", value); } #ifdef CONFIG_MEMCHECK if (memcheck_enabled) { memcheck_switch(value); } #endif // CONFIG_MEMCHECK + tid = (unsigned) value; break; case TRACE_DEV_REG_TGID: // save the tgid for the following fork/clone + DPID("QEMU.trace: tgid=%u\n", value); tgid = value; -#ifdef DEBUG if (trace_filename != NULL) { - printf("QEMU.trace: kernel, tgid %u\n", value); + D("QEMU.trace: kernel, tgid %u\n", value); } -#endif break; case TRACE_DEV_REG_FORK: // fork, fork new pid + DPID("QEMU.trace: fork (pid=%d tgid=%d value=%d)\n", pid, tgid, value); if (trace_filename != NULL) { trace_fork(tgid, value); -#ifdef DEBUG - printf("QEMU.trace: kernel, fork %u\n", value); -#endif + D("QEMU.trace: kernel, fork %u\n", value); } #ifdef CONFIG_MEMCHECK if (memcheck_enabled) { @@ -87,11 +103,10 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va #endif // CONFIG_MEMCHECK break; case TRACE_DEV_REG_CLONE: // fork, clone new pid (i.e. thread) + DPID("QEMU.trace: clone (pid=%d tgid=%d value=%d)\n", pid, tgid, value); if (trace_filename != NULL) { trace_clone(tgid, value); -#ifdef DEBUG - printf("QEMU.trace: kernel, clone %u\n", value); -#endif + D("QEMU.trace: kernel, clone %u\n", value); } #ifdef CONFIG_MEMCHECK if (memcheck_enabled) { @@ -112,10 +127,8 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va vstrcpy(value, exec_path, CLIENT_PAGE_SIZE); if (trace_filename != NULL) { trace_init_exec(vstart, vend, eoff, exec_path); -#ifdef DEBUG - printf("QEMU.trace: kernel, init exec [%lx,%lx]@%lx [%s]\n", - vstart, vend, eoff, exec_path); -#endif + D("QEMU.trace: kernel, init exec [%lx,%lx]@%lx [%s]\n", + vstart, vend, eoff, exec_path); } #ifdef CONFIG_MEMCHECK if (memcheck_enabled) { @@ -142,7 +155,7 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va memcheck_set_cmd_line(exec_arg, cmdlen); } #endif // CONFIG_MEMCHECK -#ifdef DEBUG +#if DEBUG || DEBUG_PID if (trace_filename != NULL) { int i; for (i = 0; i < cmdlen; i ++) @@ -154,20 +167,21 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va #endif break; case TRACE_DEV_REG_EXIT: // exit, exit current process with exit code + DPID("QEMU.trace: exit tid=%u\n", value); if (trace_filename != NULL) { trace_exit(value); -#ifdef DEBUG - printf("QEMU.trace: kernel, exit %x\n", value); -#endif + D("QEMU.trace: kernel, exit %x\n", value); } #ifdef CONFIG_MEMCHECK if (memcheck_enabled) { memcheck_exit(value); } #endif // CONFIG_MEMCHECK + goldfish_pipe_thread_death((int)tid); break; case TRACE_DEV_REG_NAME: // record thread name vstrcpy(value, exec_path, CLIENT_PAGE_SIZE); + DPID("QEMU.trace: thread name=%s\n", exec_path); // Remove the trailing newline if it exists int len = strlen(exec_path); @@ -176,18 +190,15 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va } if (trace_filename != NULL) { trace_name(exec_path); -#ifdef DEBUG - printf("QEMU.trace: kernel, name %s\n", exec_path); -#endif + D("QEMU.trace: kernel, name %s\n", exec_path); } break; case TRACE_DEV_REG_MMAP_EXEPATH: // mmap, path of EXE, the others are same as execve vstrcpy(value, exec_path, CLIENT_PAGE_SIZE); + DPID("QEMU.trace: mmap exe=%s\n", exec_path); if (trace_filename != NULL) { trace_mmap(vstart, vend, eoff, exec_path); -#ifdef DEBUG - printf("QEMU.trace: kernel, mmap [%lx,%lx]@%lx [%s]\n", vstart, vend, eoff, exec_path); -#endif + D("QEMU.trace: kernel, mmap [%lx,%lx]@%lx [%s]\n", vstart, vend, eoff, exec_path); } #ifdef CONFIG_MEMCHECK if (memcheck_enabled) { @@ -203,6 +214,7 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va break; case TRACE_DEV_REG_INIT_PID: // init, name the pid that starts before device registered pid = value; + DPID("QEMU.trace: pid=%d\n", value); #ifdef CONFIG_MEMCHECK if (memcheck_enabled) { memcheck_init_pid(value); @@ -211,11 +223,10 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va break; case TRACE_DEV_REG_INIT_NAME: // init, the comm of the init pid vstrcpy(value, exec_path, CLIENT_PAGE_SIZE); + DPID("QEMU.trace: tgid=%d pid=%d name=%s\n", tgid, pid, exec_path); if (trace_filename != NULL) { trace_init_name(tgid, pid, exec_path); -#ifdef DEBUG - printf("QEMU.trace: kernel, init name %u [%s]\n", pid, exec_path); -#endif + D("QEMU.trace: kernel, init name %u [%s]\n", pid, exec_path); } exec_path[0] = 0; break; @@ -227,18 +238,14 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va vstrcpy(value, exec_arg, CLIENT_PAGE_SIZE); if (trace_filename != NULL) { trace_dynamic_symbol_add(dsaddr, exec_arg); -#ifdef DEBUG - printf("QEMU.trace: dynamic symbol %lx:%s\n", dsaddr, exec_arg); -#endif + D("QEMU.trace: dynamic symbol %lx:%s\n", dsaddr, exec_arg); } exec_arg[0] = 0; break; case TRACE_DEV_REG_REMOVE_ADDR: // remove dynamic symbol addr if (trace_filename != NULL) { trace_dynamic_symbol_remove(value); -#ifdef DEBUG - printf("QEMU.trace: dynamic symbol remove %lx\n", dsaddr); -#endif + D("QEMU.trace: dynamic symbol remove %lx\n", dsaddr); } break; @@ -343,9 +350,19 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va break; #endif // CONFIG_MEMCHECK + case TRACE_DEV_PIPE_COMMAND: + case TRACE_DEV_PIPE_ADDRESS: + case TRACE_DEV_PIPE_SIZE: + case TRACE_DEV_PIPE_CHANNEL: + goldfish_pipe_write(tid, ((offset >> 2) - TRACE_DEV_PIPE_BASE), value); + break; + default: if (offset < 4096) { cpu_abort(cpu_single_env, "trace_dev_write: Bad offset %x\n", offset); + } else { + D("%s: offset=%d (0x%x) value=%d (0x%x)\n", __FUNCTION__, offset, + offset, value, value); } break; } @@ -361,9 +378,18 @@ static uint32_t trace_dev_read(void *opaque, target_phys_addr_t offset) switch (offset >> 2) { case TRACE_DEV_REG_ENABLE: // tracing enable return tracing; + + case TRACE_DEV_PIPE_COMMAND: + case TRACE_DEV_PIPE_ADDRESS: + case TRACE_DEV_PIPE_SIZE: + case TRACE_DEV_PIPE_CHANNEL: + return goldfish_pipe_read(tid, (offset >> 2) - TRACE_DEV_PIPE_BASE); + default: if (offset < 4096) { cpu_abort(cpu_single_env, "trace_dev_read: Bad offset %x\n", offset); + } else { + D("%s: offset=%d (0x%x)\n", __FUNCTION__, offset, offset); } return 0; } diff --git a/hw/goldfish_trace.h b/hw/goldfish_trace.h index 76b61a8..61d3f4f 100644 --- a/hw/goldfish_trace.h +++ b/hw/goldfish_trace.h @@ -17,6 +17,11 @@ #define CLIENT_PAGE_SIZE 4096 /* trace device registers */ + +/* The indices below all corresponds to slots that can only be accessed + * by the guest kernel. See below for indices reachable from the guest + * user-land. + */ #define TRACE_DEV_REG_SWITCH 0 #define TRACE_DEV_REG_FORK 1 #define TRACE_DEV_REG_EXECVE_PID 2 @@ -43,6 +48,22 @@ #define TRACE_DEV_REG_PRINT_NUM_HEX 62 #define TRACE_DEV_REG_STOP_EMU 90 #define TRACE_DEV_REG_ENABLE 100 + +/* NOTE: The device's second physical page is mapped to /dev/qemu_trace + * This means that if you do the following: + * + * magicPage = my_mmap("/dev/qemu_trace", ...); + * *(uint32_t*)magicPage[index] = value; + * + * The write at address magicPage+index*4 here will be seen + * by the device as a write to the i/o offset 4096 + index*4, + * i.e. (1024 + index)*4. + * + * As a consequence, any index defined below corresponds to + * location (index-1024)*4 in the mmapped page in the guest. + */ + +/* The first 64 entries are reserved for VM instrumentation */ #define TRACE_DEV_REG_METHOD_ENTRY 1024 #define TRACE_DEV_REG_METHOD_EXIT 1025 #define TRACE_DEV_REG_METHOD_EXCEPTION 1026 @@ -50,6 +71,24 @@ #define TRACE_DEV_REG_NATIVE_EXIT 1029 #define TRACE_DEV_REG_NATIVE_EXCEPTION 1030 +/* Next, QEMUD fast pipes */ +#define TRACE_DEV_PIPE_BASE 1280 /* 1024 + (64*4) */ +#define TRACE_DEV_PIPE_COMMAND (TRACE_DEV_PIPE_BASE + 0) +#define TRACE_DEV_PIPE_STATUS (TRACE_DEV_PIPE_BASE + 0) +#define TRACE_DEV_PIPE_ADDRESS (TRACE_DEV_PIPE_BASE + 1) +#define TRACE_DEV_PIPE_SIZE (TRACE_DEV_PIPE_BASE + 2) +#define TRACE_DEV_PIPE_CHANNEL (TRACE_DEV_PIPE_BASE + 3) + +/* These entries are reserved for libc instrumentation, i.e. memcheck */ +#if 0 /* see memcheck_common.h */ +#define TRACE_DEV_REG_MEMCHECK 1536 /* 1024 + (128*4) */ +#define TRACE_DEV_REG_LIBC_INIT (TRACE_DEV_REG_MEMCHECK + MEMCHECK_EVENT_LIBC_INIT) +#define TRACE_DEV_REG_MALLOC (TRACE_DEV_REG_MEMCHECK + MEMCHECK_EVENT_MALLOC) +#define TRACE_DEV_REG_FREE_PTR (TRACE_DEV_REG_MEMCHECK + MEMCHECK_EVENT_FREE_PTR) +#define TRACE_DEV_REG_QUERY_MALLOC (TRACE_DEV_REG_MEMCHECK + MEMCHECK_EVENT_QUERY_MALLOC) +#define TRACE_DEV_REG_PRINT_USER_STR (TRACE_DEV_REG_MEMCHECK + MEMCHECK_EVENT_PRINT_USER_STR) +#endif + /* the virtual trace device state */ typedef struct { struct goldfish_device dev; |