From d4d688e926097faba7fd3717d1c0d3f296bdb526 Mon Sep 17 00:00:00 2001 From: David 'Digit' Turner Date: Tue, 26 Apr 2011 18:09:17 +0200 Subject: New goldfish_pipe virtual device. This adds a new virtual hardware device named "goldfish_pipe" used to implement a very fast communication channel between the guest system and the emulator. IMPORTANT: This depends on a special kernel driver, see: https://review.source.android.com/#change,22496 Usage from the guest is simply the following: fd = open("/dev/qemu_pipe", O_RDWR); const char* pipename = "pipe:"; ret = write(fd, pipename, strlen(pipename)+1); if (ret < 0) { /* could not connect to service named */ } /* now you can read()/write()/close() as a normal * file descriptor to exchange data with the service. */ In addition, this implements the following pipe services in the emulator: tcp: tcp:: unix: opengles The 'tcp:' and 'unix:' services simply redirect to a TCP or Unix socket on the host with minimal The 'opengles' service simply connects to tcp:locahost:22468 for now. We may change this to be more configurable in the future, but that's the port number used by the current experimental OpenGL ES hardware emulation host libraries / programs. Benchmarking with a simple ping-pong program shows that the guest <-> emulator can achieve a roundtrip bandwidth of 192 MB/s (on a 2.7 Ghz Xeon PC). Using the tcp: service to talk to a ping-pong server listening on localhost reaches 102 MB/s on the same machine, using a Unix socket reaches 140 MB/s. By contrast, using standard sockets in the guest reaches only 3.8 MB/s on the same machine (and requires special privileges from the application anyway). --- hw/android_arm.c | 4 +- hw/goldfish_pipe.c | 1388 +++++++++++++++++++++++++++++++-------------------- hw/goldfish_pipe.h | 203 +++++--- hw/goldfish_trace.c | 15 - 4 files changed, 996 insertions(+), 614 deletions(-) (limited to 'hw') diff --git a/hw/android_arm.c b/hw/android_arm.c index 806d9bd..3b9dc6d 100644 --- a/hw/android_arm.c +++ b/hw/android_arm.c @@ -27,7 +27,6 @@ #endif // CONFIG_MEMCHECK #include "android/utils/debug.h" -#include "android/hw-qemud-pipe.h" #define D(...) VERBOSE_PRINT(init,__VA_ARGS__) @@ -148,7 +147,6 @@ 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(); } @@ -159,7 +157,7 @@ static void android_arm_init_(ram_addr_t ram_size, } #endif - init_qemud_pipes(); + pipe_dev_init(); #if TEST_SWITCH { diff --git a/hw/goldfish_pipe.c b/hw/goldfish_pipe.c index 6c24208..998ba49 100644 --- a/hw/goldfish_pipe.c +++ b/hw/goldfish_pipe.c @@ -9,14 +9,16 @@ ** 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" +#include "hw/goldfish_device.h" +#include "qemu-timer.h" -#define DEBUG 0 +#define DEBUG 1 + +/* Set to 1 to debug i/o register reads/writes */ +#define DEBUG_REGS 0 #if DEBUG >= 1 # define D(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n") @@ -30,711 +32,1035 @@ # define DD(...) (void)0 #endif +#if DEBUG_REGS >= 1 +# define DR(...) D(__VA_ARGS__) +#else +# define DR(...) (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); - -/********************************************************************** - ********************************************************************** +/* Set to 1 to enable the 'zero' pipe type, useful for debugging */ +#define DEBUG_ZERO_PIPE 1 + +/* Set to 1 to enable the 'pingpong' pipe type, useful for debugging */ +#define DEBUG_PINGPONG_PIPE 1 + +/* Set to 1 to enable the 'throttle' pipe type, useful for debugging */ +#define DEBUG_THROTTLE_PIPE 1 + +/*********************************************************************** + *********************************************************************** ***** - ***** REGISTRY OF SUPPORTED PIPE SERVICES + ***** P I P E S E R V I C E R E G I S T R A T I O N ***** *****/ +#define MAX_PIPE_SERVICES 8 typedef struct { - const char* pipeName; - void* pipeOpaque; - const QemudPipeHandlerFuncs* pipeFuncs; -} PipeType; + const char* name; + void* opaque; + GoldfishPipeFuncs funcs; +} PipeService; -#define MAX_PIPE_TYPES 4 +typedef struct { + int count; + PipeService services[MAX_PIPE_SERVICES]; +} PipeServices; -static PipeType sPipeTypes[MAX_PIPE_TYPES]; -static int sPipeTypeCount; +static PipeServices _pipeServices[1]; void -goldfish_pipe_add_type( const char* pipeName, - void* pipeOpaque, - const QemudPipeHandlerFuncs* pipeFuncs ) +goldfish_pipe_add_type(const char* pipeName, + void* pipeOpaque, + const GoldfishPipeFuncs* pipeFuncs ) { - int count = sPipeTypeCount; + PipeServices* list = _pipeServices; + int count = list->count; - if (count >= MAX_PIPE_TYPES) { - APANIC("%s: Too many qemud pipe types!", __FUNCTION__); + if (count >= MAX_PIPE_SERVICES) { + APANIC("Too many goldfish pipe services (%d)", count); } - sPipeTypes[count].pipeName = pipeName; - sPipeTypes[count].pipeOpaque = pipeOpaque; - sPipeTypes[count].pipeFuncs = pipeFuncs; - sPipeTypeCount = ++count; + list->services[count].name = pipeName; + list->services[count].opaque = pipeOpaque; + list->services[count].funcs = pipeFuncs[0]; + + list->count++; } -static const PipeType* -goldfish_pipe_find_type( const char* pipeName ) +static const PipeService* +goldfish_pipe_find_type(const char* pipeName) { - const PipeType* ptype = sPipeTypes; - const PipeType* limit = ptype + sPipeTypeCount; + PipeServices* list = _pipeServices; + int count = list->count; + int nn; - for ( ; ptype < limit; ptype++ ) { - if (!strcmp(pipeName, ptype->pipeName)) { - return ptype; + for (nn = 0; nn < count; nn++) { + if (!strcmp(list->services[nn].name, pipeName)) { + return &list->services[nn]; } } return NULL; } -/********************************************************************** - ********************************************************************** +/*********************************************************************** + *********************************************************************** ***** - ***** THREAD-SPECIFIC STATE + ***** P I P E C O N N E C T I O N S ***** *****/ -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; +typedef struct PipeDevice PipeDevice; -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); +typedef struct Pipe { + struct Pipe* next; + struct Pipe* next_waked; + PipeDevice* device; + uint32_t channel; + void* opaque; + const GoldfishPipeFuncs* funcs; + unsigned char wanted; + char closed; +} Pipe; - AFREE(ts); -} +/* Forward */ +static void* pipeConnector_new(Pipe* pipe); -static ThreadState* -threadState_new( void ) +Pipe* +pipe_new(uint32_t channel, PipeDevice* dev) { - ThreadState* ts; - ANEW0(ts); - ts->pipes = aintMap_new(); - return ts; + Pipe* pipe; + ANEW0(pipe); + pipe->channel = channel; + pipe->device = dev; + pipe->opaque = pipeConnector_new(pipe); + return pipe; } -static int -threadState_addPipe( ThreadState* ts, int localId, PipeClient* pcl ) +static Pipe** +pipe_list_findp_channel( Pipe** list, uint32_t channel ) { - /* We shouldn't already have a pipe for this localId */ - if (aintMap_get(ts->pipes, localId) != NULL) { - errno = EBADF; - return -1; + Pipe** pnode = list; + for (;;) { + Pipe* node = *pnode; + if (node == NULL || node->channel == channel) { + break; + } + pnode = &node->next; } - aintMap_set(ts->pipes, localId, pcl); - return 0; + return pnode; } -static void -threadState_delPipe( ThreadState* ts, int localId ) +#if 0 +static Pipe** +pipe_list_findp_opaque( Pipe** list, void* opaque ) { - aintMap_del(ts->pipes, localId); + Pipe** pnode = list; + for (;;) { + Pipe* node = *pnode; + if (node == NULL || node->opaque == opaque) { + break; + } + pnode = &node->next; + } + return pnode; } +#endif -static void -threadState_write( ThreadState* ts, int offset, uint32_t value ) +static Pipe** +pipe_list_findp_waked( Pipe** list, Pipe* pipe ) { - 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; + Pipe** pnode = list; + for (;;) { + Pipe* node = *pnode; + if (node == NULL || node == pipe) { break; - case SLOT_SIZE: - ts->size = value; - break; - case SLOT_CHANNEL: - ts->localId = value; - break; - default: - /* XXX: PRINT ERROR? */ - ; + } + pnode = &node->next_waked; } + return pnode; } -static uint32_t -threadState_read( ThreadState* ts, int offset ) + +static void +pipe_list_remove_waked( Pipe** list, Pipe* pipe ) { - 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** lookup = pipe_list_findp_waked(list, pipe); + Pipe* node = *lookup; + + if (node != NULL) { + (*lookup) = node->next_waked; + node->next_waked = NULL; } } -/********************************************************************** - ********************************************************************** +/*********************************************************************** + *********************************************************************** ***** - ***** PIPE CLIENT STATE + ***** P I P E C O N N E C T O R S ***** *****/ -/* Each client object points to a PipeState after it has received the - * initial request from the guest, which shall look like: - * :: +/* These are used to handle the initial connection attempt, where the + * client is going to write the name of the pipe service it wants to + * connect to, followed by a terminating zero. */ -struct PipeClient { - char* pipeName; - QemudClient* client; - PipeService* pipeSvc; - - int tid; - uint32_t localId; - void* handler; - const QemudPipeHandlerFuncs* handlerFuncs; -}; +typedef struct { + Pipe* pipe; + char buffer[128]; + int buffpos; +} PipeConnector; -static int pipeService_addPipe(PipeService* pipeSvc, int tid, int localId, PipeClient* pcl); -static void pipeService_removePipe(PipeService* pipeSvc, int tid, int localId); +static const GoldfishPipeFuncs pipeConnector_funcs; // forward -static void -pipeClient_closeFromThread( PipeClient* pcl ) +void* +pipeConnector_new(Pipe* pipe) { - qemud_client_close(pcl->client); + PipeConnector* pcon; + + ANEW0(pcon); + pcon->pipe = pipe; + pipe->funcs = &pipeConnector_funcs; + return pcon; } -/* This function should only be invoked through qemud_client_close(). - * Never call it explicitely. */ static void -pipeClient_close( void* opaque ) +pipeConnector_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); + PipeConnector* pcon = opaque; + AFREE(pcon); } -static void -pipeClient_recv( void* opaque, uint8_t* msg, int msgLen, QemudClient* client ) +static int +pipeConnector_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) { - PipeClient* pcl = opaque; - const PipeType* ptype; + PipeConnector* pcon = opaque; + const GoldfishPipeBuffer* buffers_limit = buffers + numBuffers; + int ret = 0; - const char* p; - int failure = 1; - char answer[64]; + DD("%s: channel=0x%x numBuffers=%d", __FUNCTION__, + pcon->pipe->channel, + numBuffers); - 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; - } + while (buffers < buffers_limit) { + int avail; - /* The message format is :: */ - 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; - } + DD("%s: buffer data (%3d bytes): '%.*s'", __FUNCTION__, + buffers[0].size, buffers[0].size, buffers[0].data); - pcl->pipeName = ASTRDUP(p); + if (buffers[0].size == 0) { + buffers++; + continue; + } - ptype = goldfish_pipe_find_type(pcl->pipeName); - if (ptype == NULL) { - goto UNKNOWN_PIPE_TYPE; - } + avail = sizeof(pcon->buffer) - pcon->buffpos; + if (avail > buffers[0].size) + avail = buffers[0].size; - pcl->handlerFuncs = ptype->pipeFuncs; - pcl->handler = ptype->pipeFuncs->init( client, ptype->pipeOpaque ); - if (pcl->handler == NULL) { - goto BAD_INIT; + if (avail > 0) { + memcpy(pcon->buffer + pcon->buffpos, buffers[0].data, avail); + pcon->buffpos += avail; + ret += avail; + } + buffers++; } - if (pipeService_addPipe(pcl->pipeSvc, pcl->tid, pcl->localId, pcl) < 0) { - goto DUPLICATE_REQUEST; - } + /* Now check that our buffer contains a zero-terminated string */ + if (memchr(pcon->buffer, '\0', pcon->buffpos) != NULL) { + /* Acceptable formats for the connection string are: + * + * pipe: + * pipe:: + */ + char* pipeName; + char* pipeArgs; - D("qemud:pipe: Added new client: %s", msg); - failure = 0; - snprintf(answer, sizeof answer, "OK"); - goto SEND_ANSWER; + D("%s: connector: '%s'", __FUNCTION__, pcon->buffer); -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; + if (memcmp(pcon->buffer, "pipe:", 5) != 0) { + /* Nope, we don't handle these for now. */ + D("%s: Unknown pipe connection: '%s'", __FUNCTION__, pcon->buffer); + return PIPE_ERROR_INVAL; + } + + pipeName = pcon->buffer + 5; + pipeArgs = strchr(pipeName, ':'); -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; + if (pipeArgs != NULL) { + *pipeArgs++ = '\0'; + } -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; + Pipe* pipe = pcon->pipe; + const PipeService* svc = goldfish_pipe_find_type(pipeName); + if (svc == NULL) { + D("%s: Unknown server!", __FUNCTION__); + return PIPE_ERROR_INVAL; + } -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; + void* peer = svc->funcs.init(pipe, svc->opaque, pipeArgs); + if (peer == NULL) { + D("%s: Initialization failed!", __FUNCTION__); + return PIPE_ERROR_INVAL; + } -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); + /* Do the evil switch now */ + pipe->opaque = peer; + pipe->funcs = &svc->funcs; + AFREE(pcon); } + + return ret; } -/* 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 ) +pipeConnector_recvBuffers( void* opaque, GoldfishPipeBuffer* buffers, int numBuffers ) { - CPUState* env = cpu_single_env; - int count = 0; + return PIPE_ERROR_IO; +} - while (size > 0) { - uint32_t vstart = address & TARGET_PAGE_MASK; - uint32_t vend = vstart + TARGET_PAGE_SIZE; - uint32_t next = address + size; +static unsigned +pipeConnector_poll( void* opaque ) +{ + return PIPE_WAKE_WRITE; +} - DD("%s: trying to map (0x%x - 0x%0x, %d bytes) -> page=0x%x - 0x%x", - __FUNCTION__, address, address+size, size, vstart, vend); +static void +pipeConnector_wakeOn( void* opaque, int flags ) +{ + /* nothing, really should never happen */ +} - if (next > vend) { - next = vend; - } +static const GoldfishPipeFuncs pipeConnector_funcs = { + NULL, /* init */ + pipeConnector_close, /* should rarely happen */ + pipeConnector_sendBuffers, /* the interesting stuff */ + pipeConnector_recvBuffers, /* should not happen */ + pipeConnector_poll, /* should not happen */ + pipeConnector_wakeOn, /* should not happen */ +}; - /* Check that the address is valid */ - if (cpu_get_phys_page_debug(env, vstart) == -1) { - DD("%s: bad guest address!", __FUNCTION__); - return -1; - } +/*********************************************************************** + *********************************************************************** + ***** + ***** Z E R O P I P E S + ***** + *****/ + +/* A simple pipe service that mimics /dev/zero, you can write anything to + * it, and you can always read any number of zeros from it. Useful for debugging + * the kernel driver. + */ +#if DEBUG_ZERO_PIPE + +typedef struct { + void* hwpipe; +} ZeroPipe; + +static void* +zeroPipe_init( void* hwpipe, void* svcOpaque, const char* args ) +{ + ZeroPipe* zpipe; + + D("%s: hwpipe=%p", __FUNCTION__, hwpipe); + ANEW0(zpipe); + zpipe->hwpipe = hwpipe; + return zpipe; +} - count++; +static void +zeroPipe_close( void* opaque ) +{ + ZeroPipe* zpipe = opaque; - size -= (next - address); - address = next; + D("%s: hwpipe=%p", __FUNCTION__, zpipe->hwpipe); + AFREE(zpipe); +} + +static int +zeroPipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) +{ + int ret = 0; + while (numBuffers > 0) { + ret += buffers[0].size; + buffers++; + numBuffers--; } - return count; + return ret; +} + +static int +zeroPipe_recvBuffers( void* opaque, GoldfishPipeBuffer* buffers, int numBuffers ) +{ + int ret = 0; + while (numBuffers > 0) { + ret += buffers[0].size; + memset(buffers[0].data, 0, buffers[0].size); + buffers++; + numBuffers--; + } + return ret; +} + +static unsigned +zeroPipe_poll( void* opaque ) +{ + return PIPE_WAKE_READ | PIPE_WAKE_WRITE; } -/* 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 ) +zeroPipe_wakeOn( void* opaque, int flags ) { - CPUState* env = cpu_single_env; + /* nothing to do here */ +} - while (size > 0) { - uint32_t vstart = address & TARGET_PAGE_MASK; - uint32_t vend = vstart + TARGET_PAGE_SIZE; - uint32_t next = address + size; +static const GoldfishPipeFuncs zeroPipe_funcs = { + zeroPipe_init, + zeroPipe_close, + zeroPipe_sendBuffers, + zeroPipe_recvBuffers, + zeroPipe_poll, + zeroPipe_wakeOn, +}; - if (next > vend) { - next = vend; - } +#endif /* DEBUG_ZERO */ - /* Translate virtual address into physical one, into emulator - * memory. */ - target_phys_addr_t phys = cpu_get_phys_page_debug(env, vstart); +/*********************************************************************** + *********************************************************************** + ***** + ***** P I N G P O N G P I P E S + ***** + *****/ - buffers[0].data = qemu_get_ram_ptr(phys) + (address - vstart); - buffers[0].size = next - address; - buffers++; +/* Similar debug service that sends back anything it receives */ +/* All data is kept in a circular dynamic buffer */ - size -= (next - address); - address = next; - } +#if DEBUG_PINGPONG_PIPE + +/* Initial buffer size */ +#define PINGPONG_SIZE 1024 + +typedef struct { + void* hwpipe; + uint8_t* buffer; + size_t size; + size_t pos; + size_t count; + unsigned flags; +} PingPongPipe; + +static void +pingPongPipe_init0( PingPongPipe* pipe, void* hwpipe, void* svcOpaque ) +{ + pipe->hwpipe = hwpipe; + pipe->size = PINGPONG_SIZE; + pipe->buffer = malloc(pipe->size); + pipe->pos = 0; + pipe->count = 0; } -static uint32_t -pipeClient_doCommand( PipeClient* pcl, - uint32_t command, - uint32_t address, - uint32_t* pSize ) +static void* +pingPongPipe_init( void* hwpipe, void* svcOpaque, const char* args ) { - uint32_t size = *pSize; + PingPongPipe* ppipe; - D("%s: TID=%4d CHANNEL=%08x COMMAND=%d ADDRESS=%08x SIZE=%d\n", - __FUNCTION__, pcl->tid, pcl->localId, command, address, size); + D("%s: hwpipe=%p", __FUNCTION__, hwpipe); + ANEW0(ppipe); + pingPongPipe_init0(ppipe, hwpipe, svcOpaque); + return ppipe; +} - /* 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; - } +static void +pingPongPipe_close( void* opaque ) +{ + PingPongPipe* ppipe = opaque; - 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; + D("%s: hwpipe=%p (pos=%d count=%d size=%d)", __FUNCTION__, + ppipe->hwpipe, ppipe->pos, ppipe->count, ppipe->size); + free(ppipe->buffer); + AFREE(ppipe); +} + +static int +pingPongPipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) +{ + PingPongPipe* pipe = opaque; + int ret = 0; + int count; + const GoldfishPipeBuffer* buff = buffers; + const GoldfishPipeBuffer* buffEnd = buff + numBuffers; + + count = 0; + for ( ; buff < buffEnd; buff++ ) + count += buff->size; + + /* Do we need to grow the pingpong buffer? */ + while (count > pipe->size - pipe->count) { + size_t newsize = pipe->size*2; + uint8_t* newbuff = realloc(pipe->buffer, newsize); + int wpos = pipe->pos + pipe->count; + if (newbuff == NULL) { + break; } + if (wpos > pipe->size) { + wpos -= pipe->size; + memcpy(newbuff + pipe->size, newbuff, wpos); + } + pipe->buffer = newbuff; + pipe->size = newsize; + D("pingpong buffer is now %d bytes", newsize); + } - case QEMUD_PIPE_CMD_WAKE_ON_SEND: { - pcl->handlerFuncs->wakeOn(pcl->handler, QEMUD_PIPE_WAKE_ON_SEND); - return 0; + for ( buff = buffers; buff < buffEnd; buff++ ) { + int avail = pipe->size - pipe->count; + if (avail <= 0) { + if (ret == 0) + ret = PIPE_ERROR_AGAIN; + break; + } + if (avail > buff->size) { + avail = buff->size; } - case QEMUD_PIPE_CMD_WAKE_ON_RECV: { - pcl->handlerFuncs->wakeOn(pcl->handler, QEMUD_PIPE_WAKE_ON_RECV); - return 0; + int wpos = pipe->pos + pipe->count; + if (wpos >= pipe->size) { + wpos -= pipe->size; + } + if (wpos + avail <= pipe->size) { + memcpy(pipe->buffer + wpos, buff->data, avail); + } else { + int avail2 = pipe->size - wpos; + memcpy(pipe->buffer + wpos, buff->data, avail2); + memcpy(pipe->buffer, buff->data + avail2, avail - avail2); } + pipe->count += avail; + ret += avail; + } - default: - return QEMUD_PIPE_ERROR_CONNRESET; + /* Wake up any waiting readers if we wrote something */ + if (pipe->count > 0 && (pipe->flags & PIPE_WAKE_READ)) { + goldfish_pipe_wake(pipe->hwpipe, PIPE_WAKE_READ); } + + return ret; } +static int +pingPongPipe_recvBuffers( void* opaque, GoldfishPipeBuffer* buffers, int numBuffers ) +{ + PingPongPipe* pipe = opaque; + int ret = 0; + + while (numBuffers > 0) { + int avail = pipe->count; + if (avail <= 0) { + if (ret == 0) + ret = PIPE_ERROR_AGAIN; + break; + } + if (avail > buffers[0].size) { + avail = buffers[0].size; + } + + int rpos = pipe->pos; + + if (rpos + avail <= pipe->size) { + memcpy(buffers[0].data, pipe->buffer + rpos, avail); + } else { + int avail2 = pipe->size - rpos; + memcpy(buffers[0].data, pipe->buffer + rpos, avail2); + memcpy(buffers[0].data + avail2, pipe->buffer, avail - avail2); + } + pipe->count -= avail; + pipe->pos += avail; + if (pipe->pos >= pipe->size) { + pipe->pos -= pipe->size; + } + ret += avail; + numBuffers--; + buffers++; + } + + /* Wake up any waiting readers if we wrote something */ + if (pipe->count < PINGPONG_SIZE && (pipe->flags & PIPE_WAKE_WRITE)) { + goldfish_pipe_wake(pipe->hwpipe, PIPE_WAKE_WRITE); + } + return ret; +} -QemudClient* -pipeClient_connect( void* opaque, QemudService* svc, int channel ) +static unsigned +pingPongPipe_poll( void* opaque ) { - PipeClient* pcl; + PingPongPipe* pipe = opaque; + unsigned ret = 0; + + if (pipe->count < pipe->size) + ret |= PIPE_WAKE_WRITE; - 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 */ + if (pipe->count > 0) + ret |= PIPE_WAKE_READ; - /* Only for the initial connection message */ - qemud_client_set_framing(pcl->client, 1); - return pcl->client; + return ret; } -/********************************************************************** - ********************************************************************** +static void +pingPongPipe_wakeOn( void* opaque, int flags ) +{ + PingPongPipe* pipe = opaque; + pipe->flags |= (unsigned)flags; +} + +static const GoldfishPipeFuncs pingPongPipe_funcs = { + pingPongPipe_init, + pingPongPipe_close, + pingPongPipe_sendBuffers, + pingPongPipe_recvBuffers, + pingPongPipe_poll, + pingPongPipe_wakeOn, +}; + +#endif /* DEBUG_PINGPONG_PIPE */ + +/*********************************************************************** + *********************************************************************** ***** - ***** GLOBAL PIPE STATE + ***** T H R O T T L E P I P E S ***** *****/ -struct PipeService { - AIntMap* threadMap; /* maps tid to ThreadState */ -}; +/* Similar to PingPongPipe, but will throttle the bandwidth to test + * blocking I/O. + */ -#if 0 -static void -pipeService_done(PipeService* pipeSvc) +#ifdef DEBUG_THROTTLE_PIPE + +typedef struct { + PingPongPipe pingpong; + double sendRate; + int64_t sendExpiration; + double recvRate; + int64_t recvExpiration; + QEMUTimer* timer; +} ThrottlePipe; + +/* forward declaration */ +static void throttlePipe_timerFunc( void* opaque ); + +static void* +throttlePipe_init( void* hwpipe, void* svcOpaque, const char* args ) { - /* Get rid of the tid -> ThreadState map */ - AINTMAP_FOREACH_VALUE(pipeSvc->threadMap, ts, threadState_free(ts)); - aintMap_free(pipeSvc->threadMap); - pipeSvc->threadMap = NULL; + ThrottlePipe* pipe; + + ANEW0(pipe); + pingPongPipe_init0(&pipe->pingpong, hwpipe, svcOpaque); + pipe->timer = qemu_new_timer(vm_clock, throttlePipe_timerFunc, pipe); + /* For now, limit to 500 KB/s in both directions */ + pipe->sendRate = 1e9 / (500*1024*8); + pipe->recvRate = pipe->sendRate; + return pipe; } -#endif static void -pipeService_init(PipeService* pipeSvc) +throttlePipe_close( void* opaque ) { - pipeSvc->threadMap = aintMap_new(); + ThrottlePipe* pipe = opaque; - qemud_service_register( "fast-pipe", 0, pipeSvc, - pipeClient_connect, - NULL, /* TODO: NO SNAPSHOT SAVE SUPPORT */ - NULL /* TODO: NO SNAPSHOT LOAD SUPPORT */ ); + qemu_del_timer(pipe->timer); + qemu_free_timer(pipe->timer); + pingPongPipe_close(&pipe->pingpong); } -static ThreadState* -pipeService_getState(PipeService* pipeSvc, int tid) +static void +throttlePipe_rearm( ThrottlePipe* pipe ) { - if (pipeSvc->threadMap == NULL) - pipeSvc->threadMap = aintMap_new(); + int64_t minExpiration = 0; + + DD("%s: sendExpiration=%lld recvExpiration=%lld\n", __FUNCTION__, pipe->sendExpiration, pipe->recvExpiration); + + if (pipe->sendExpiration) { + if (minExpiration == 0 || pipe->sendExpiration < minExpiration) + minExpiration = pipe->sendExpiration; + } - return (ThreadState*) aintMap_get(pipeSvc->threadMap, tid); + if (pipe->recvExpiration) { + if (minExpiration == 0 || pipe->recvExpiration < minExpiration) + minExpiration = pipe->recvExpiration; + } + + if (minExpiration != 0) { + DD("%s: Arming for %lld\n", __FUNCTION__, minExpiration); + qemu_mod_timer(pipe->timer, minExpiration); + } } static void -pipeService_removeState(PipeService* pipeSvc, int tid) +throttlePipe_timerFunc( void* opaque ) { - ThreadState* ts = pipeService_getState(pipeSvc, tid); + ThrottlePipe* pipe = opaque; + int64_t now = qemu_get_clock_ns(vm_clock); - if (ts == NULL) - return; + DD("%s: TICK! now=%lld sendExpiration=%lld recvExpiration=%lld\n", + __FUNCTION__, now, pipe->sendExpiration, pipe->recvExpiration); + + /* Timer has expired, signal wake up if needed */ + int flags = 0; + + if (pipe->sendExpiration && now > pipe->sendExpiration) { + flags |= PIPE_WAKE_WRITE; + pipe->sendExpiration = 0; + } + if (pipe->recvExpiration && now > pipe->recvExpiration) { + flags |= PIPE_WAKE_READ; + pipe->recvExpiration = 0; + } + flags &= pipe->pingpong.flags; + if (flags != 0) { + DD("%s: WAKE %d\n", __FUNCTION__, flags); + goldfish_pipe_wake(pipe->pingpong.hwpipe, flags); + } - aintMap_del(pipeSvc->threadMap,tid); - threadState_free(ts); + throttlePipe_rearm(pipe); } static int -pipeService_addPipe(PipeService* pipeSvc, int tid, int localId, PipeClient* pcl) +throttlePipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) { - ThreadState* ts = pipeService_getState(pipeSvc, tid); + ThrottlePipe* pipe = opaque; + int ret; - if (ts == NULL) { - ts = threadState_new(); - aintMap_set(pipeSvc->threadMap, tid, ts); + if (pipe->sendExpiration > 0) { + return PIPE_ERROR_AGAIN; } - return threadState_addPipe(ts, localId, pcl); + ret = pingPongPipe_sendBuffers(&pipe->pingpong, buffers, numBuffers); + if (ret > 0) { + /* Compute next send expiration time */ + pipe->sendExpiration = qemu_get_clock_ns(vm_clock) + ret*pipe->sendRate; + throttlePipe_rearm(pipe); + } + return ret; } -static void -pipeService_removePipe(PipeService* pipeSvc, int tid, int localId) +static int +throttlePipe_recvBuffers( void* opaque, GoldfishPipeBuffer* buffers, int numBuffers ) { - ThreadState* ts = pipeService_getState(pipeSvc, tid); + ThrottlePipe* pipe = opaque; + int ret; - if (ts == NULL) - return; + if (pipe->recvExpiration > 0) { + return PIPE_ERROR_AGAIN; + } - threadState_delPipe(ts, localId); + ret = pingPongPipe_recvBuffers(&pipe->pingpong, buffers, numBuffers); + if (ret > 0) { + pipe->recvExpiration = qemu_get_clock_ns(vm_clock) + ret*pipe->recvRate; + throttlePipe_rearm(pipe); + } + return ret; } -/********************************************************************** - ********************************************************************** +static unsigned +throttlePipe_poll( void* opaque ) +{ + ThrottlePipe* pipe = opaque; + unsigned ret = pingPongPipe_poll(&pipe->pingpong); + + if (pipe->sendExpiration > 0) + ret &= ~PIPE_WAKE_WRITE; + + if (pipe->recvExpiration > 0) + ret &= ~PIPE_WAKE_READ; + + return ret; +} + +static void +throttlePipe_wakeOn( void* opaque, int flags ) +{ + ThrottlePipe* pipe = opaque; + pingPongPipe_wakeOn(&pipe->pingpong, flags); +} + +static const GoldfishPipeFuncs throttlePipe_funcs = { + throttlePipe_init, + throttlePipe_close, + throttlePipe_sendBuffers, + throttlePipe_recvBuffers, + throttlePipe_poll, + throttlePipe_wakeOn, +}; + +#endif /* DEBUG_THROTTLE_PIPE */ + +/*********************************************************************** + *********************************************************************** ***** - ***** HARDWARE API - AS SEEN FROM hw/goldfish_trace.c + ***** G O L D F I S H P I P E D E V I C E ***** *****/ -static PipeService _globalState[1]; +struct PipeDevice { + struct goldfish_device dev; + + /* the list of all pipes */ + Pipe* pipes; -void init_qemud_pipes(void) + /* the list of signalled pipes */ + Pipe* signaled_pipes; + + /* i/o registers */ + uint32_t address; + uint32_t size; + uint32_t status; + uint32_t channel; + uint32_t wakes; +}; + + +static void +pipeDevice_doCommand( PipeDevice* dev, uint32_t command ) { - pipeService_init(_globalState); + Pipe** lookup = pipe_list_findp_channel(&dev->pipes, dev->channel); + Pipe* pipe = *lookup; + CPUState* env = cpu_single_env; + + /* Check that we're referring a known pipe channel */ + if (command != PIPE_CMD_OPEN && pipe == NULL) { + dev->status = PIPE_ERROR_INVAL; + return; + } + + /* If the pipe is closed by the host, return an error */ + if (pipe != NULL && pipe->closed && command != PIPE_CMD_CLOSE) { + dev->status = PIPE_ERROR_IO; + return; + } + + switch (command) { + case PIPE_CMD_OPEN: + DD("%s: CMD_OPEN channel=0x%x", __FUNCTION__, dev->channel); + if (pipe != NULL) { + dev->status = PIPE_ERROR_INVAL; + break; + } + pipe = pipe_new(dev->channel, dev); + pipe->next = dev->pipes; + dev->pipes = pipe; + dev->status = 0; + break; + + case PIPE_CMD_CLOSE: + DD("%s: CMD_CLOSE channel=0x%x", __FUNCTION__, dev->channel); + /* Remove from device's lists */ + *lookup = pipe->next; + pipe->next = NULL; + pipe_list_remove_waked(&dev->signaled_pipes, pipe); + /* Call close callback */ + if (pipe->funcs->close) { + pipe->funcs->close(pipe->opaque); + } + /* Free stuff */ + AFREE(pipe); + break; + + case PIPE_CMD_POLL: + dev->status = pipe->funcs->poll(pipe->opaque); + DD("%s: CMD_POLL > status=%d", __FUNCTION__, dev->status); + break; + + case PIPE_CMD_READ_BUFFER: { + /* Translate virtual address into physical one, into emulator memory. */ + GoldfishPipeBuffer buffer; + uint32_t address = dev->address; + uint32_t page = address & TARGET_PAGE_MASK; + target_phys_addr_t phys = cpu_get_phys_page_debug(env, page); + buffer.data = qemu_get_ram_ptr(phys) + (address - page); + buffer.size = dev->size; + dev->status = pipe->funcs->recvBuffers(pipe->opaque, &buffer, 1); + DD("%s: CMD_READ_BUFFER channel=0x%x address=0x%08x size=%d > status=%d", + __FUNCTION__, dev->channel, dev->address, dev->size, dev->status); + break; + } + + case PIPE_CMD_WRITE_BUFFER: { + /* Translate virtual address into physical one, into emulator memory. */ + GoldfishPipeBuffer buffer; + uint32_t address = dev->address; + uint32_t page = address & TARGET_PAGE_MASK; + target_phys_addr_t phys = cpu_get_phys_page_debug(env, page); + buffer.data = qemu_get_ram_ptr(phys) + (address - page); + buffer.size = dev->size; + dev->status = pipe->funcs->sendBuffers(pipe->opaque, &buffer, 1); + DD("%s: CMD_WRITE_BUFFER channel=0x%x address=0x%08x size=%d > status=%d", + __FUNCTION__, dev->channel, dev->address, dev->size, dev->status); + break; + } + + case PIPE_CMD_WAKE_ON_READ: + DD("%s: CMD_WAKE_ON_READ channel=0x%x", __FUNCTION__, dev->channel); + if ((pipe->wanted & PIPE_WAKE_READ) == 0) { + pipe->wanted |= PIPE_WAKE_READ; + pipe->funcs->wakeOn(pipe->opaque, pipe->wanted); + } + dev->status = 0; + break; + + case PIPE_CMD_WAKE_ON_WRITE: + DD("%s: CMD_WAKE_ON_WRITE channel=0x%x", __FUNCTION__, dev->channel); + if ((pipe->wanted & PIPE_WAKE_WRITE) == 0) { + pipe->wanted |= PIPE_WAKE_WRITE; + pipe->funcs->wakeOn(pipe->opaque, pipe->wanted); + } + dev->status = 0; + break; + + default: + D("%s: command=%d (0x%x)\n", __FUNCTION__, command, command); + } } -void -goldfish_pipe_thread_death(int tid) +static void pipe_dev_write(void *opaque, target_phys_addr_t offset, uint32_t value) { - PipeService* pipeSvc = _globalState; - pipeService_removeState(pipeSvc, tid); + PipeDevice *s = (PipeDevice *)opaque; + + switch (offset) { + case PIPE_REG_COMMAND: + DR("%s: command=%d (0x%x)", __FUNCTION__, value, value); + pipeDevice_doCommand(s, value); + break; + + case PIPE_REG_SIZE: + DR("%s: size=%d (0x%x)", __FUNCTION__, value, value); + s->size = value; + break; + + case PIPE_REG_ADDRESS: + DR("%s: address=%d (0x%x)", __FUNCTION__, value, value); + s->address = value; + break; + + case PIPE_REG_CHANNEL: + DR("%s: channel=%d (0x%x)", __FUNCTION__, value, value); + s->channel = value; + break; + + default: + D("%s: offset=%d (0x%x) value=%d (0x%x)\n", __FUNCTION__, offset, + offset, value, value); + break; + } } -void -goldfish_pipe_write(int tid, int offset, uint32_t value) +/* I/O read */ +static uint32_t pipe_dev_read(void *opaque, target_phys_addr_t offset) { - PipeService* pipeSvc = _globalState; - DD("%s: tid=%d offset=%d value=%d (0x%x)", __FUNCTION__, tid, - offset, value, value); + PipeDevice *dev = (PipeDevice *)opaque; - ThreadState* ts = pipeService_getState(pipeSvc, tid); + switch (offset) { + case PIPE_REG_STATUS: + DR("%s: REG_STATUS status=%d (0x%x)", __FUNCTION__, dev->status, dev->status); + return dev->status; + + case PIPE_REG_CHANNEL: + if (dev->signaled_pipes != NULL) { + Pipe* pipe = dev->signaled_pipes; + DR("%s: channel=0x%x wanted=%d", __FUNCTION__, + pipe->channel, pipe->wanted); + dev->wakes = pipe->wanted; + pipe->wanted = 0; + dev->signaled_pipes = pipe->next_waked; + pipe->next_waked = NULL; + if (dev->signaled_pipes == NULL) { + goldfish_device_set_irq(&dev->dev, 0, 0); + DD("%s: lowering IRQ", __FUNCTION__); + } + return pipe->channel; + } + DR("%s: no signaled channels", __FUNCTION__); + return 0; - if (ts == NULL) { - D("%s: no thread state for tid=%d", __FUNCTION__, tid); - return; + case PIPE_REG_WAKES: + DR("%s: wakes %d", __FUNCTION__, dev->wakes); + return dev->wakes; + + default: + D("%s: offset=%d (0x%x)\n", __FUNCTION__, offset, offset); } - threadState_write(ts, offset, value); + return 0; } -uint32_t -goldfish_pipe_read(int tid, int offset) +static CPUReadMemoryFunc *pipe_dev_readfn[] = { + pipe_dev_read, + pipe_dev_read, + pipe_dev_read +}; + +static CPUWriteMemoryFunc *pipe_dev_writefn[] = { + pipe_dev_write, + pipe_dev_write, + pipe_dev_write +}; + +/* initialize the trace device */ +void pipe_dev_init() { - PipeService* pipeSvc = _globalState; - uint32_t ret; + PipeDevice *s; - DD("%s: tid=%d offset=%d", __FUNCTION__, tid, offset); + s = (PipeDevice *) qemu_mallocz(sizeof(*s)); - ThreadState* ts = pipeService_getState(pipeSvc, tid); + s->dev.name = "qemu_pipe"; + s->dev.id = -1; + s->dev.base = 0; // will be allocated dynamically + s->dev.size = 0x2000; + s->dev.irq = 0; + s->dev.irq_count = 1; - if (ts == NULL) { - D("%s: no thread state for tid=%d", __FUNCTION__, tid); - return 0; + goldfish_device_add(&s->dev, pipe_dev_readfn, pipe_dev_writefn, s); + +#if DEBUG_ZERO_PIPE + goldfish_pipe_add_type("zero", NULL, &zeroPipe_funcs); +#endif +#if DEBUG_PINGPONG_PIPE + goldfish_pipe_add_type("pingpong", NULL, &pingPongPipe_funcs); +#endif +#if DEBUG_THROTTLE_PIPE + goldfish_pipe_add_type("throttle", NULL, &throttlePipe_funcs); +#endif +} + +void +goldfish_pipe_wake( void* hwpipe, unsigned flags ) +{ + Pipe* pipe = hwpipe; + Pipe** lookup; + PipeDevice* dev = pipe->device; + + DD("%s: channel=0x%x flags=%d", __FUNCTION__, pipe->channel, flags); + + /* If not already there, add to the list of signaled pipes */ + lookup = pipe_list_findp_waked(&dev->signaled_pipes, pipe); + if (!*lookup) { + pipe->next_waked = dev->signaled_pipes; + dev->signaled_pipes = pipe; + } + pipe->wanted |= (unsigned)flags; + + /* Raise IRQ to indicate there are items on our list ! */ + goldfish_device_set_irq(&dev->dev, 0, 1); + DD("%s: raising IRQ", __FUNCTION__); +} + +void +goldfish_pipe_close( void* hwpipe ) +{ + Pipe* pipe = hwpipe; + + D("%s: channel=0x%x (closed=%d)", __FUNCTION__, pipe->channel, pipe->closed); + + if (!pipe->closed) { + pipe->closed = 1; + goldfish_pipe_wake( hwpipe, PIPE_WAKE_CLOSED ); } - 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 index 03bc443..be5c449 100644 --- a/hw/goldfish_pipe.h +++ b/hw/goldfish_pipe.h @@ -13,66 +13,45 @@ #define _HW_GOLDFISH_PIPE_H #include -#include "android/hw-qemud.h" +#include "hw/hw.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. +/* TECHNICAL NOTE: + * + * A goldfish pipe is a very fast communication channel between the guest + * system and the emulator program. + * + * To open a new pipe to the emulator, a guest client will do the following: + * + * fd = open("/dev/qemu_pipe", O_RDWR); + * char invite[64]; + * snprintf(invite, sizeof invite, "%s", pipeName); + * ret = write(fd, invite, strlen(invite)); + * + * if (ret < 0) { + * // something bad happened, see errno + * } + * + * now read()/write() to communicate with service in the + * emulator. + * + * This header provides the interface used by pipe services in the emulator + * to receive new client connection and deal with them. + * + * + * 1/ Call goldfish_pipe_add_type() to register a new pipe service by name. + * This must provide a pointer to a series of functions that will be called + * during normal pipe operations. + * + * 2/ When a client connects to the service, the 'init' callback will be called + * to create a new service-specific client identifier (which must returned + * by the function). + * + * 3/ Call goldfish_pipe_close() to force the closure of a given pipe. + * + * 4/ Call goldfish_pipe_signal() to signal a change of state to the pipe. + * */ -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; @@ -81,18 +60,112 @@ typedef struct 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 ); + /* Create new client connection, 'hwpipe' must be passed to other + * goldfish_pipe_xxx functions, while the returned value will be passed + * to other callbacks (e.g. close). 'pipeOpaque' is the value passed + * to goldfish_pipe_add_type() when registering a given pipe service. + */ + void* (*init)( void* hwpipe, void* pipeOpaque, const char* args ); + + /* Called when the guest kernel has finally closed a pipe connection. + * This is the only place where you can release/free the client connection. + * You should never invoke this callback directly. Call goldfish_pipe_close() + * instead. + */ + void (*close)( void* pipe ); + + /* Called when the guest is write()-ing to the pipe. Should return the + * number of bytes transfered, 0 for EOF status, or a negative error + * value otherwise, including PIPE_ERROR_AGAIN to indicate that the + * emulator is not ready to receive data yet. + */ + int (*sendBuffers)( void* pipe, const GoldfishPipeBuffer* buffers, int numBuffers ); + + /* Same as sendBuffers when the guest is read()-ing from the pipe. */ + int (*recvBuffers)( void* pipe, GoldfishPipeBuffer* buffers, int numBuffers ); + + /* Called when guest wants to poll the read/write status for the pipe. + * Should return a combination of PIPE_WAKE_XXX flags. + */ + unsigned (*poll)( void* pipe ); + + /* Called to signal that the guest wants to be woken when the set of + * PIPE_WAKE_XXX bit-flags in 'flags' occur. When the condition occurs, + * then the pipe implementation shall call goldfish_pipe_wake(). + */ void (*wakeOn)( void* opaque, int flags ); -} QemudPipeHandlerFuncs; +} GoldfishPipeFuncs; /* 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 ); +extern void goldfish_pipe_add_type(const char* pipeName, + void* pipeOpaque, + const GoldfishPipeFuncs* pipeFuncs ); + +/* This tells the guest system that we want to close the pipe and that + * further attempts to read or write to it will fail. This will not + * necessarily call the 'close' callback immediately though. + * + * This will also wake-up any blocked guest threads waiting for i/o. + */ +extern void goldfish_pipe_close( void* hwpipe ); + +/* Signal that the pipe can be woken up. 'flags' must be a combination of + * PIPE_WAKE_READ and PIPE_WAKE_WRITE. + */ +extern void goldfish_pipe_wake( void* hwpipe, unsigned flags ); + +/* The following definitions must match those under: + * + * $KERNEL/drivers/misc/qemupipe/qemu_pipe.c + * + * Where $KERNEL points to the android-goldfish-2.6.xx branch on: + * + * android.git.kernel.org/kernel/qemu.git. + */ + +/* pipe device registers */ +#define PIPE_REG_COMMAND 0x00 /* write: value = command */ +#define PIPE_REG_STATUS 0x04 /* read */ +#define PIPE_REG_CHANNEL 0x08 /* read/write: channel id */ +#define PIPE_REG_SIZE 0x0c /* read/write: buffer size */ +#define PIPE_REG_ADDRESS 0x10 /* write: physical address */ +#define PIPE_REG_WAKES 0x14 /* read: wake flags */ + +/* list of commands for PIPE_REG_COMMAND */ +#define PIPE_CMD_OPEN 1 /* open new channel */ +#define PIPE_CMD_CLOSE 2 /* close channel (from guest) */ +#define PIPE_CMD_POLL 3 /* poll read/write status */ + +/* List of bitflags returned in status of CMD_POLL command */ +#define PIPE_POLL_IN (1 << 0) +#define PIPE_POLL_OUT (1 << 1) +#define PIPE_POLL_HUP (1 << 2) + +/* The following commands are related to write operations */ +#define PIPE_CMD_WRITE_BUFFER 4 /* send a user buffer to the emulator */ +#define PIPE_CMD_WAKE_ON_WRITE 5 /* tell the emulator to wake us when writing is possible */ + +/* The following commands are related to read operations, they must be + * listed in the same order than the corresponding write ones, since we + * will use (CMD_READ_BUFFER - CMD_WRITE_BUFFER) as a special offset + * in qemu_pipe_read_write() below. + */ +#define PIPE_CMD_READ_BUFFER 6 /* receive a page-contained buffer from the emulator */ +#define PIPE_CMD_WAKE_ON_READ 7 /* tell the emulator to wake us when reading is possible */ + +/* Possible status values used to signal errors - see qemu_pipe_error_convert */ +#define PIPE_ERROR_INVAL -1 +#define PIPE_ERROR_AGAIN -2 +#define PIPE_ERROR_NOMEM -3 +#define PIPE_ERROR_IO -4 + +/* Bit-flags used to signal events from the emulator */ +#define PIPE_WAKE_CLOSED (1 << 0) /* emulator closed pipe */ +#define PIPE_WAKE_READ (1 << 1) /* pipe can now be read from */ +#define PIPE_WAKE_WRITE (1 << 2) /* pipe can now be written to */ + +void pipe_dev_init(void); #endif /* _HW_GOLDFISH_PIPE_H */ diff --git a/hw/goldfish_trace.c b/hw/goldfish_trace.c index ee51a8b..02f9b8d 100644 --- a/hw/goldfish_trace.c +++ b/hw/goldfish_trace.c @@ -15,7 +15,6 @@ */ #include "qemu_file.h" #include "goldfish_trace.h" -#include "goldfish_pipe.h" #include "sysemu.h" #include "trace.h" #ifdef CONFIG_MEMCHECK @@ -177,7 +176,6 @@ static void trace_dev_write(void *opaque, target_phys_addr_t offset, uint32_t va 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); @@ -350,13 +348,6 @@ 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); @@ -379,12 +370,6 @@ static uint32_t trace_dev_read(void *opaque, target_phys_addr_t offset) 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); -- cgit v1.1