diff options
author | David 'Digit' Turner <digit@android.com> | 2011-04-05 12:48:01 +0200 |
---|---|---|
committer | David 'Digit' Turner <digit@android.com> | 2011-04-11 18:19:16 +0200 |
commit | 9b4029807c91ea42ff5e84e3616c90e2ce42d6f8 (patch) | |
tree | 3108c57f8ebd03b8dfa571c88761ca3c48052532 /android | |
parent | 335d2c1342bb887ac67f1f60cff795f0c06beaca (diff) | |
download | external_qemu-9b4029807c91ea42ff5e84e3616c90e2ce42d6f8.zip external_qemu-9b4029807c91ea42ff5e84e3616c90e2ce42d6f8.tar.gz external_qemu-9b4029807c91ea42ff5e84e3616c90e2ce42d6f8.tar.bz2 |
Add OpenGLES pipe implementation.
Change-Id: I3ad498380de8438dbf430316af2d4391876057cc
Diffstat (limited to 'android')
-rw-r--r-- | android/hw-qemud-pipe-net.c | 788 |
1 files changed, 788 insertions, 0 deletions
diff --git a/android/hw-qemud-pipe-net.c b/android/hw-qemud-pipe-net.c new file mode 100644 index 0000000..874bc69 --- /dev/null +++ b/android/hw-qemud-pipe-net.c @@ -0,0 +1,788 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sockets.h" +#include "android/utils/assert.h" +#include "android/utils/panic.h" +#include "android/utils/system.h" +#include "android/async-utils.h" +#include "android/looper.h" +#include "android/hw-qemud-pipe.h" +#include "hw/goldfish_pipe.h" + +/* Implement the OpenGL fast-pipe */ + +/* Set to 1 or 2 for debug traces */ +#define DEBUG 0 + +#if DEBUG >= 1 +# define D(...) printf(__VA_ARGS__), printf("\n") +#else +# define D(...) ((void)0) +#endif + +#if DEBUG >= 2 +# define DD(...) printf(__VA_ARGS__), printf("\n") +# define DDASSERT(cond) _ANDROID_ASSERT(cond, "Assertion failure: ", #cond) +# define DDASSERT_INT_OP(cond,val,op) _ANDROID_ASSERT_INT_OP(cond,val,op) +#else +# define DD(...) ((void)0) +# define DDASSERT(cond) ((void)0) +# define DDASSERT_INT_OP(cond,val,op) ((void)0) +#endif + +#define DDASSERT_INT_LT(cond,val) DDASSERT_INT_OP(cond,val,<) +#define DDASSERT_INT_LTE(cond,val) DDASSERT_INT_OP(cond,val,<=) +#define DDASSERT_INT_GT(cond,val) DDASSERT_INT_OP(cond,val,>) +#define DDASSERT_INT_GTE(cond,val) DDASSERT_INT_OP(cond,val,>=) +#define DDASSERT_INT_EQ(cond,val) DDASSERT_INT_OP(cond,val,==) +#define DDASSERT_INT_NEQ(cond,val) DDASSERT_INT_OP(cond,val,!=) + + +/* Forward declarations */ + +typedef struct NetPipeInit { + Looper* looper; + SockAddress serverAddress[1]; +} NetPipeInit; + +/********************************************************************** + ********************************************************************** + ***** + ***** P I P E M E S S A G E S + ***** + *****/ + +typedef struct PipeMsg { + struct PipeMsg* next; + size_t size; + uint8_t data[1]; +} PipeMsg; + +static PipeMsg* +pipeMsg_alloc( size_t size ) +{ + PipeMsg* msg = android_alloc(sizeof(*msg) + size); + + msg->next = NULL; + msg->size = size; + return msg; +} + +static void +pipeMsg_free( PipeMsg* msg ) +{ + AFREE(msg); +} + +/********************************************************************** + ********************************************************************** + ***** + ***** M E S S A G E L I S T + ***** + *****/ + +typedef struct { + PipeMsg* firstMsg; /* first message in list */ + PipeMsg* lastMsg; /* last message in list */ + size_t firstBytes; /* bytes in firstMsg that were already sent */ + size_t lastBytes; /* bytes in lastMsg that were already received */ + size_t totalBytes; /* total bytes in list */ +} MsgList; + +/* Default receiver buffer size to accept incoming data */ +#define DEFAULT_RECEIVER_SIZE 8180 + +/* Initialize a message list - appropriate for sending them out */ +static void +msgList_initSend( MsgList* list ) +{ + list->firstMsg = NULL; + list->lastMsg = NULL; + list->firstBytes = 0; + list->lastBytes = 0; + list->totalBytes = 0; +} + +/* Initialize a message list for receiving data */ +static void +msgList_initReceive( MsgList* list ) +{ + msgList_initSend(list); + list->firstMsg = list->lastMsg = pipeMsg_alloc( DEFAULT_RECEIVER_SIZE ); +} + +/* Finalize a message list */ +static void +msgList_done( MsgList* list ) +{ + PipeMsg* msg; + + while ((msg = list->firstMsg) != NULL) { + list->firstMsg = msg->next; + pipeMsg_free(msg); + } + list->lastMsg = NULL; + + list->firstBytes = 0; + list->lastBytes = 0; + list->totalBytes = 0; +} + +static int +msgList_hasData( MsgList* list ) +{ + return list->totalBytes > 0; +} + +/* Append a list of buffers to a message list. + * + * This is a very simple implementation that simply mallocs a single + * new message containing all of the buffer's data, and append it to + * our link list. This also makes the implementation of msgList_send() + * quite simple, since there is no need to deal with the 'lastBytes' + * pointer (it is always assumed to be 'lastMsg->size'). + */ +static int +msgList_sendBuffers( MsgList* list, + const GoldfishPipeBuffer* buffers, + int numBuffers ) +{ + const GoldfishPipeBuffer* buff = buffers; + const GoldfishPipeBuffer* buffEnd = buff + numBuffers; + PipeMsg* msg; + size_t msgSize = 0; + size_t pos; + + /* Count the total number of bytes */ + for ( ; buff < buffEnd; buff++ ) { + msgSize += buff[0].size; + } + + /* Allocate a new message */ + msg = pipeMsg_alloc(msgSize); + if (msg == NULL) { + errno = ENOMEM; + return -1; + } + + /* Copy data from buffers to message */ + for ( pos = 0, buff = buffers; buff < buffEnd; buff++ ) { + memcpy(msg->data + pos, buff->data, buff->size); + pos += buff->size; + } + + /* Append message to current list */ + if (list->lastMsg != NULL) { + list->lastMsg->next = msg; + } else { + list->firstMsg = msg; + list->firstBytes = 0; + } + list->lastMsg = msg; + + list->totalBytes += msgSize; + + /* We are done */ + return 0; +} + +/* Try to send outgoing messages in the list through non-blocking socket 'fd'. + * Return 0 on success, and -1 on failure, where errno will be: + * + * ECONNRESET - connection reset by peer + * + * Note that 0 will be returned if socket_send() returns EAGAIN/EWOULDBLOCK. + */ +static int +msgList_send( MsgList* list, int fd ) +{ + int ret = 0; + + for (;;) { + PipeMsg* msg = list->firstMsg; + size_t sentBytes = list->firstBytes; + size_t availBytes; + + if (msg == NULL) { + /* We sent everything */ + return 0; + } + DDASSERT(sentBytes < msg->size); + availBytes = msg->size - sentBytes; + + ret = socket_send(fd, msg->data + sentBytes, availBytes); + if (ret <= 0) { + goto ERROR; + } + + list->totalBytes -= ret; + list->firstBytes += ret; + + if (list->firstBytes < msg->size) { + continue; + } + + /* We sent the full first packet - remove it from the head */ + list->firstBytes = 0; + list->firstMsg = msg->next; + if (list->firstMsg == NULL) { + list->lastMsg = NULL; + } + pipeMsg_free(msg); + } + +ERROR: + if (ret < 0) { /* EAGAIN/EWOULDBLOCK or disconnection */ + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ret = 0; /* clear error - this is normal */ + } else { + DD("%s: socket_send() returned %d: %s\n", __FUNCTION__, errno, errno_str); + errno = ECONNRESET; + } + } else { +#if DEBUG >= 2 + int err = socket_get_error(fd); + DD("%s: socket_send() returned 0 (error=%d: %s)", __FUNCTION__, + err, strerror(err)); +#endif + errno = ECONNRESET; + ret = -1; + } + return ret; +} + + +/* Try to receive data into caller-provided buffers, and return the total + * size of bytes that were read. Returns -1 on error, with errno: + * + * ECONNRESET: Connection reset by peer + * EAGAIN: No incoming data, wait for it to arrive. + */ +static int +msgList_recvBuffers( MsgList* list, + GoldfishPipeBuffer* buffers, + int numBuffers ) +{ + GoldfishPipeBuffer* buff = buffers; + GoldfishPipeBuffer* buffEnd = buff + numBuffers; + size_t buffStart = 0; + PipeMsg* msg = list->firstMsg; + size_t msgStart = list->firstBytes; + size_t totalSize = 0; + + DDASSERT(msg != NULL); + + D("%s: ENTER list.firstBytes=%d list.lastBytes=%d list.totalBytes=%d list.firstSize=%d list.lastSize=%d list.firstEqualLast=%d", + __FUNCTION__, list->firstBytes, list->lastBytes, list->totalBytes, + list->firstMsg->size, list->lastMsg->size, list->firstMsg == list->lastMsg); + + /* If there is no incoming data, return EAGAIN */ + if (list->totalBytes == 0) { + errno = EAGAIN; + return -1; + } + + /* Now try to transfer as much from the list of incoming messages + * into the buffers. + */ + while (msg != NULL && buff < buffEnd) { + DDASSERT(msgStart < msg->size); + DDASSERT(buffStart < buff->size); + + /* Copy data from current message into next buffer. + * For a given message, first determine the start and end + * of available data. Then try to see how much of these + * we can copy to the current buffer. + */ + size_t msgEnd = msg->size; + + if (msg == list->lastMsg) { + msgEnd = list->lastBytes; + } + + size_t msgAvail = msgEnd - msgStart; + size_t buffAvail = buff->size - buffStart; + + if (msgAvail > buffAvail) { + msgAvail = buffAvail; + } + + DDASSERT(msgAvail > 0); + + D("%s: transfer %d bytes (msgStart=%d msgSize=%d buffStart=%d buffSize=%d)", + __FUNCTION__, msgAvail, msgStart, msg->size, buffStart, buff->size); + memcpy(buff->data + buffStart, msg->data + msgStart, msgAvail); + + /* Advance cursors */ + msgStart += msgAvail; + buffStart += msgAvail; + totalSize += msgAvail; + + /* Did we fill up the current buffer? */ + if (buffStart >= buff->size) { + buffStart = 0; + buff++; + } + + /* Did we empty the current message? */ + if (msgStart >= msgEnd) { + msgStart = 0; + /* If this is the last message, reset the 'first' and 'last' + * pointers to reuse it for the next recv(). */ + if (msg == list->lastMsg) { + list->lastBytes = 0; + msg = NULL; + } else { + /* Otherwise, delete the message, and jump to the next one */ + list->firstMsg = msg->next; + pipeMsg_free(msg); + msg = list->firstMsg; + } + } + + } + list->firstBytes = msgStart; + list->totalBytes -= totalSize; + + D("%s: EXIT list.firstBytes=%d list.lastBytes=%d list.totalBytes=%d list.firstSize=%d list.lastSize=%d list.firstEqualLast=%d", + __FUNCTION__, list->firstBytes, list->lastBytes, list->totalBytes, + list->firstMsg->size, list->lastMsg->size, list->firstMsg == list->lastMsg); + + return (int)totalSize; +} + + +/* Try to receive data from non-blocking socket 'fd'. + * Return 0 on success, or -1 on error, where errno can be: + * + * ECONNRESET - connection reset by peer + * ENOMEM - full message list, no room to receive more data + */ +static int +msgList_recv( MsgList* list, int fd ) +{ + int ret = 0; + + D("%s: ENTER list.firstBytes=%d list.lastBytes=%d list.totalBytes=%d list.firstSize=%d list.lastSize=%d list.firstEqualLast=%d", + __FUNCTION__, list->firstBytes, list->lastBytes, list->totalBytes, + list->firstMsg->size, list->lastMsg->size, list->firstMsg == list->lastMsg); + + for (;;) { + PipeMsg* last = list->lastMsg; + size_t lastBytes = list->lastBytes; + size_t availBytes; + + /* Compute how many bytes we can receive in the last buffer*/ + DDASSERT(last != NULL); + DDASSERT(last->size > 0); + DDASSERT(lastBytes < last->size); + + availBytes = last->size - lastBytes; + + /* Try to receive the data, act on errors */ + ret = socket_recv(fd, last->data + lastBytes, availBytes); + if (ret <= 0) { + goto ERROR; + } + + /* Acknowledge received data */ + list->lastBytes += ret; + list->totalBytes += ret; + + if (list->lastBytes < last->size) { + continue; + } + + /* We filled-up the last message buffer, allocate a new one */ + last = pipeMsg_alloc( DEFAULT_RECEIVER_SIZE ); + list->lastMsg->next = last; + list->lastMsg = last; + list->lastBytes = 0; + } + +ERROR: + if (ret < 0) { /* EAGAIN/EWOULDBLOCK or disconnection */ + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ret = 0; /* clear error - this is normal */ + } else { + DD("%s: socket_send() returned %d: %s\n", __FUNCTION__, errno, errno_str); + errno = ECONNRESET; + } + } else /* ret == 0 */ { +#if DEBUG >= 2 + int err = socket_get_error(fd); + DD("%s: socket_send() returned 0 (error=%d: %s)", __FUNCTION__, + err, strerror(err)); +#endif + errno = ECONNRESET; + ret = -1; + } + D("%s: EXIT list.firstBytes=%d list.lastBytes=%d list.totalBytes=%d list.firstSize=%d list.lastSize=%d list.firstEqualLast=%d", + __FUNCTION__, list->firstBytes, list->lastBytes, list->totalBytes, + list->firstMsg->size, list->lastMsg->size, list->firstMsg == list->lastMsg); + + return ret; +} + + + +/********************************************************************** + ********************************************************************** + ***** + ***** P I P E H A N D L E R S + ***** + *****/ + +/* Technical Note: + * + * Each NetPipe object is connected to the following: + * + * - a remote rendering process through a normal TCP socket. + * - a Goldfish pipe (see hw/goldfish_pipe.h) to exchange messages with the guest. + * - a Qemud client (see android/hw-qemud.h) to signal state changes to the guest. + * + * REMOTE <---socket---> PIPE <------> GOLDFISH PIPE + * PROCESS <--+ + * | + * +---> QEMUD CHANNEL (android/hw-qemud.h) + * + */ +enum { + STATE_INIT, + STATE_CONNECTING, + STATE_CONNECTED, + STATE_CLOSING_GUEST, + STATE_CLOSING_SOCKET +}; + +#define DEFAULT_INCOMING_SIZE 4000 + +#define MAX_IN_BUFFERS 4 + +typedef struct { + QemudClient* client; + int state; + int wakeWanted; + + MsgList outList[1]; + MsgList inList[1]; + + LoopIo io[1]; + AsyncConnector connector[1]; + + GoldfishPipeBuffer outBuffer[1]; + GoldfishPipeBuffer inBuffers[MAX_IN_BUFFERS]; + int inBufferCount; + +} NetPipe; + +static void +netPipe_free( NetPipe* pipe ) +{ + int fd; + + /* Removing any pending incoming packet */ + msgList_done(pipe->outList); + msgList_done(pipe->inList); + + /* Close the socket */ + fd = pipe->io->fd; + loopIo_done(pipe->io); + socket_close(fd); + + /* Release the pipe object */ + AFREE(pipe); +} + + +static void +netPipe_resetState( NetPipe* pipe ) +{ + /* If there is a pending outgoing packet, open the valve */ + if (msgList_hasData(pipe->outList)) { + loopIo_wantWrite(pipe->io); + } else { + loopIo_dontWantWrite(pipe->io); + } + + /* Accept incoming data if we are not closing, and our input list isn't full */ + if (pipe->state == STATE_CONNECTED) { + loopIo_wantRead(pipe->io); + } else { + loopIo_dontWantRead(pipe->io); + } +} + + +/* This function is only called when the socket is disconnected. + * See netPipe_closeFromGuest() for the case when the guest requires + * the disconnection. */ +static void +netPipe_closeFromSocket( void* opaque ) +{ + NetPipe* pipe = opaque; + + /* If the guest already ordered the pipe to be closed, delete immediately */ + if (pipe->state == STATE_CLOSING_GUEST) { + netPipe_free(pipe); + return; + } + + /* Force the closure of the QEMUD channel - if a guest is blocked + * waiting for a wake signal, it will receive an error. */ + if (pipe->client != NULL) { + qemud_client_close(pipe->client); + pipe->client = NULL; + } + + /* Remove any outgoing packets - they won't go anywhere */ + msgList_done(pipe->outList); + + pipe->state = STATE_CLOSING_SOCKET; + netPipe_resetState(pipe); +} + + +/* This is the function that gets called each time there is an asynchronous + * event on the network pipe. + */ +static void +netPipe_io_func( void* opaque, int fd, unsigned events ) +{ + NetPipe* pipe = opaque; + int wakeFlags = 0; + + /* Run the connector if we are in the CONNECTING state */ + /* TODO: Add some sort of time-out, to deal with the case */ + /* where the renderer process is wedged. */ + if (pipe->state == STATE_CONNECTING) { + AsyncStatus status = asyncConnector_run(pipe->connector); + if (status == ASYNC_NEED_MORE) { + return; + } + else if (status == ASYNC_ERROR) { + /* Could not connect, tell our client by closing the channel. */ + + netPipe_closeFromSocket(pipe); + return; + } + pipe->state = STATE_CONNECTED; + netPipe_resetState(pipe); + return; + } + + /* Otherwise, accept incoming data */ + if ((events & LOOP_IO_READ) != 0) { + int ret; + + if ((pipe->wakeWanted & QEMUD_PIPE_WAKE_ON_RECV) != 0) { + wakeFlags |= QEMUD_PIPE_WAKE_ON_RECV; + } + + ret = msgList_recv(pipe->inList, fd); + if (ret < 0) { + wakeFlags &= ~QEMUD_PIPE_WAKE_ON_RECV; + + if (errno == ENOMEM) { /* shouldn't happen */ + DD("%s: msgList_recv() return ENOMEM!?\n", __FUNCTION__); + } else { + /* errno == ECONNRESET */ + DD("%s: msgList_recv() error, closing pipe\n", __FUNCTION__); + netPipe_closeFromSocket(pipe); + return; + } + } + } + + if ((events & LOOP_IO_WRITE) != 0) { + int ret; + + DDASSERT(msgList_hasData(pipe->outList)); + + ret = msgList_send(pipe->outList, fd); + if (ret < 0) { + DD("%s: msgList_send() error, closing pipe\n", __FUNCTION__); + netPipe_closeFromSocket(pipe); + return; + } + + if ((pipe->wakeWanted & QEMUD_PIPE_WAKE_ON_SEND) != 0) { + wakeFlags |= QEMUD_PIPE_WAKE_ON_SEND; + } + } + + /* Send wake signal to the guest if needed */ + if (wakeFlags != 0) { + uint8_t byte = (uint8_t) wakeFlags; + DD("%s: Sending wake flags %d (wanted=%d)", __FUNCTION__, byte, pipe->wakeWanted); + qemud_client_send(pipe->client, &byte, 1); + pipe->wakeWanted &= ~wakeFlags; + } + + /* Reset state */ + netPipe_resetState(pipe); +} + + +void* +netPipe_init( QemudClient* qcl, void* pipeOpaque ) +{ + NetPipe* pipe; + NetPipeInit* pipeSvc = pipeOpaque; + + ANEW0(pipe); + + pipe->client = qcl; + pipe->state = STATE_INIT; + + msgList_initSend(pipe->outList); + msgList_initReceive(pipe->inList); + +#define DEFAULT_OPENGLES_PORT 22468 + + { + AsyncStatus status; + + int fd = socket_create_inet( SOCKET_STREAM ); + if (fd < 0) { + netPipe_free(pipe); + return NULL; + } + + loopIo_init(pipe->io, pipeSvc->looper, fd, netPipe_io_func, pipe); + asyncConnector_init(pipe->connector, pipeSvc->serverAddress, pipe->io); + pipe->state = STATE_CONNECTING; + + status = asyncConnector_run(pipe->connector); + if (status == ASYNC_ERROR) { + D("%s: Could not create to renderer process: %s", + __FUNCTION__, errno_str); + netPipe_free(pipe); + return NULL; + } + if (status == ASYNC_COMPLETE) { + pipe->state = STATE_CONNECTED; + netPipe_resetState(pipe); + } + } + + return pipe; +} + +/* Called when the guest wants to close the channel. This is different + * from netPipe_closeFromSocket() which is called when the socket is + * disconnected. */ +static void +netPipe_closeFromGuest( void* opaque ) +{ + NetPipe* pipe = opaque; + + /* The qemud client is gone when we reach this code */ + pipe->client = NULL; + + /* Remove input messages */ + msgList_done(pipe->inList); + + /* If the socket is already closed, or if there are no + * outgoing messages, delete immediately */ + if (pipe->state == STATE_CLOSING_SOCKET || + !msgList_hasData(pipe->outList)) { + netPipe_free(pipe); + return; + } + + /* Otherwise, mark our pipe as closing, and wait until everything is + * sent before deleting the object. */ + pipe->state = STATE_CLOSING_GUEST; + netPipe_resetState(pipe); +} + + +static int +netPipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) +{ + NetPipe* pipe = opaque; + int ret; + + ret = msgList_sendBuffers(pipe->outList, buffers, numBuffers); + netPipe_resetState(pipe); + return ret; +} + +static int +netPipe_recvBuffers( void* opaque, GoldfishPipeBuffer* buffers, int numBuffers ) +{ + NetPipe* pipe = opaque; + int ret; + + ret = msgList_recvBuffers(pipe->inList, buffers, numBuffers); + netPipe_resetState(pipe); + return ret; +} + +static void +netPipe_wakeOn( void* opaque, int flags ) +{ + NetPipe* pipe = opaque; + + pipe->wakeWanted |= flags; +} + +/********************************************************************** + ********************************************************************** + ***** + ***** N E T W O R K P I P E M E S S A G E S + ***** + *****/ + +static const QemudPipeHandlerFuncs net_pipe_handler_funcs = { + netPipe_init, + netPipe_closeFromGuest, + netPipe_sendBuffers, + netPipe_recvBuffers, + netPipe_wakeOn, +}; + + +static NetPipeInit _netPipeService[1]; + +/********************************************************************** + ********************************************************************** + ***** + ***** O P E N G L E S P I P E S E R V I C E + ***** + *****/ + +void +android_hw_opengles_init(void) +{ + NetPipeInit* svc = _netPipeService; + int ret; + + DD("%s: Registering service\n", __FUNCTION__); + + svc->looper = looper_newCore(); + + ret = sock_address_init_resolve(svc->serverAddress, + "127.0.0.1", + DEFAULT_OPENGLES_PORT, + 0); + if (ret < 0) { + APANIC("Could not resolve renderer process address!"); + } + + goldfish_pipe_add_type( "opengles", svc, &net_pipe_handler_funcs ); +} |