diff options
author | Vladimir Chtchetkine <vchtchetkine@google.com> | 2011-08-05 07:58:08 -0700 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2011-08-05 07:58:08 -0700 |
commit | eddbb5040e396080a89828bfeb55b1c7f849f992 (patch) | |
tree | c3f2187231919786a80300ed4ac764816f9b8448 /android/hw-qemud.c | |
parent | 4b84380f8d2915ff248ac691bcd55a6290c88f19 (diff) | |
parent | 77e61ceb3ecd53d6c4de9631c9450b6183d9244b (diff) | |
download | external_qemu-eddbb5040e396080a89828bfeb55b1c7f849f992.zip external_qemu-eddbb5040e396080a89828bfeb55b1c7f849f992.tar.gz external_qemu-eddbb5040e396080a89828bfeb55b1c7f849f992.tar.bz2 |
Merge "Enable QEMUD over pipe."
Diffstat (limited to 'android/hw-qemud.c')
-rw-r--r-- | android/hw-qemud.c | 474 |
1 files changed, 432 insertions, 42 deletions
diff --git a/android/hw-qemud.c b/android/hw-qemud.c index e91ec78..16deae8 100644 --- a/android/hw-qemud.c +++ b/android/hw-qemud.c @@ -14,12 +14,15 @@ #include "android/utils/misc.h" #include "android/utils/system.h" #include "android/utils/bufprint.h" +#include "android/looper.h" #include "hw/hw.h" +#include "hw/goldfish_pipe.h" #include "qemu-char.h" #include "charpipe.h" #include "cbuffer.h" -#define D(...) VERBOSE_PRINT(qemud,__VA_ARGS__) +//#define D(...) VERBOSE_PRINT(qemud,__VA_ARGS__) +#define D(...) printf(__VA_ARGS__), printf("\n") #define D_ACTIVE VERBOSE_CHECK(qemud) /* the T(...) macro is used to dump traffic */ @@ -575,18 +578,54 @@ qemud_serial_send( QemudSerial* s, /** CLIENTS **/ +/* Descriptor for a data buffer pending to be sent to a qemud pipe client. + * + * When a service decides to send data to the client, there could be cases when + * client is not ready to read them. In this case there is no GoldfishPipeBuffer + * available to write service's data to, So, we need to cache that data into the + * client descriptor, and "send" them over to the client in _qemudPipe_recvBuffers + * callback. Pending service data is stored in the client descriptor as a list + * of QemudPipeMessage instances. + */ +typedef struct QemudPipeMessage QemudPipeMessage; +struct QemudPipeMessage { + /* Message to send. */ + uint8_t* message; + /* Message size. */ + size_t size; + /* Offset in the message buffer of the chunk, that has not been sent + * to the pipe yet. */ + size_t offset; + /* Links next message in the client. */ + QemudPipeMessage* next; +}; + + /* A QemudClient models a single client as seen by the emulator. - * Each client has its own channel id, and belongs to a given - * QemudService (see below). + * Each client has its own channel id (for the serial qemud), or pipe descriptor + * (for the pipe based qemud), and belongs to a given QemudService (see below). * - * There is a global list of clients used to multiplex incoming - * messages from the channel id (see qemud_multiplexer_serial_recv()). + * There is a global list of serial clients used to multiplex incoming + * messages from the channel id (see qemud_multiplexer_serial_recv()). Pipe + * clients don't need multiplexing, because they are communicated via qemud pipes + * that are unique for each client. * */ +/* Defines type of the client: pipe, or serial. + */ +typedef enum QemudProtocol { + /* Client is communicating via pipe. */ + QEMUD_PROTOCOL_PIPE, + /* Client is communicating via serial port. */ + QEMUD_PROTOCOL_SERIAL +} QemudProtocol; + struct QemudClient { - int channel; - QemudSerial* serial; + /* Defines protocol, used by the client. */ + QemudProtocol protocol; + + /* Fields that are common for all protocols. */ void* clie_opaque; QemudClientRecv clie_recv; QemudClientClose clie_close; @@ -604,8 +643,28 @@ struct QemudClient { QemudSink header[1]; uint8_t header0[FRAME_HEADER_SIZE]; QemudSink payload[1]; + + /* Fields that are protocol-specific. */ + union { + /* Serial-specific fields. */ + struct { + int channel; + QemudSerial* serial; + } Serial; + /* Pipe-specific fields. */ + struct { + void* hwpipe; + QemudPipeMessage* messages; + } Pipe; + } ProtocolSelector; }; +static ABool +_is_pipe_client(QemudClient* client) +{ + return (client-> protocol == QEMUD_PROTOCOL_PIPE) ? true : false; +} + static void qemud_service_remove_client( QemudService* service, QemudClient* client ); @@ -716,6 +775,11 @@ qemud_client_recv( void* opaque, uint8_t* msg, int msglen ) } } +/* Sends data to a pipe-based client. + */ +static void +_qemud_pipe_send(QemudClient* client, const uint8_t* msg, int msglen); + /* disconnect a client. this automatically frees the QemudClient. * note that this also removes the client from the global list * and from its service's list, if any. @@ -734,10 +798,15 @@ qemud_client_disconnect( void* opaque ) qemud_client_remove(c); /* send a disconnect command to the daemon */ - if (c->channel > 0) { + if (_is_pipe_client(c)) { char tmp[128], *p=tmp, *end=p+sizeof(tmp); - p = bufprint(tmp, end, "disconnect:%02x", c->channel); - qemud_serial_send(c->serial, 0, 0, (uint8_t*)tmp, p-tmp); + p = bufprint(tmp, end, "disconnect:00"); + _qemud_pipe_send(c, (uint8_t*)tmp, p-tmp); + } else if (c->ProtocolSelector.Serial.channel > 0) { + char tmp[128], *p=tmp, *end=p+sizeof(tmp); + p = bufprint(tmp, end, "disconnect:%02x", + c->ProtocolSelector.Serial.channel); + qemud_serial_send(c->ProtocolSelector.Serial.serial, 0, 0, (uint8_t*)tmp, p-tmp); } /* call the client close callback */ @@ -756,7 +825,10 @@ qemud_client_disconnect( void* opaque ) AFREE(c); } -/* allocate a new QemudClient object */ +/* allocate a new QemudClient object + * NOTE: channel_id valie is used as a selector between serial and pipe clients. + * Since channel_id < 0 is an invalid value for a serial client, it would + * indicate that creating client is a pipe client. */ static QemudClient* qemud_client_alloc( int channel_id, void* clie_opaque, @@ -771,14 +843,25 @@ qemud_client_alloc( int channel_id, ANEW0(c); - c->serial = serial; - c->channel = channel_id; + if (channel_id < 0) { + /* Allocating a pipe client. */ + c->protocol = QEMUD_PROTOCOL_PIPE; + c->ProtocolSelector.Pipe.messages = NULL; + c->ProtocolSelector.Pipe.hwpipe = NULL; + } else { + /* Allocating a serial client. */ + c->protocol = QEMUD_PROTOCOL_SERIAL; + c->ProtocolSelector.Serial.serial = serial; + c->ProtocolSelector.Serial.channel = channel_id; + } c->clie_opaque = clie_opaque; c->clie_recv = clie_recv; c->clie_close = clie_close; c->clie_save = clie_save; c->clie_load = clie_load; - + c->service = NULL; + c->next_serv = NULL; + c->next = NULL; c->framing = 0; c->need_header = 1; qemud_sink_reset(c->header, FRAME_HEADER_SIZE, c->header0); @@ -794,7 +877,7 @@ static char* qemud_service_load_name( QEMUFile* f ); static QemudService* qemud_service_find( QemudService* service_list, const char* service_name ); static QemudClient* qemud_service_connect_client( QemudService *sv, - int channel_id ); + int channel_id); /* Saves the client state needed to re-establish connections on load. */ @@ -803,7 +886,10 @@ qemud_client_save(QEMUFile* f, QemudClient* c) { /* save generic information */ qemud_service_save_name(f, c->service); - qemu_put_be32(f, c->channel); + qemu_put_be32(f, c->protocol); + if (_is_pipe_client(c)) { + qemu_put_be32(f, c->ProtocolSelector.Serial.channel); + } /* save client-specific state */ if (c->clie_save) @@ -840,8 +926,13 @@ qemud_client_load(QEMUFile* f, QemudService* current_services ) return -EIO; } + /* get protocol. */ + QemudProtocol protocol = qemu_get_be32(f); /* get channel id */ - int channel = qemu_get_be32(f); + int channel = -1; + if (protocol == QEMUD_PROTOCOL_SERIAL) { + qemu_get_be32(f); + } if (channel == 0) { D("%s: illegal snapshot: client for control channel must no be saved\n", __FUNCTION__); @@ -849,6 +940,7 @@ qemud_client_load(QEMUFile* f, QemudService* current_services ) } /* re-connect client */ + // TODO: Save / load is_pipe here! QemudClient* c = qemud_service_connect_client(sv, channel); if(c == NULL) return -EIO; @@ -976,8 +1068,8 @@ qemud_service_remove_client( QemudService* s, QemudClient* c ) for (;;) { node = *pnode; if (node == NULL) { - D("%s: could not find client %d for service '%s'", - __FUNCTION__, c->channel, s->name); + D("%s: could not find client for service '%s'", + __FUNCTION__, s->name); return; } if (node == c) @@ -1004,7 +1096,6 @@ qemud_service_connect_client(QemudService *sv, int channel_id) __FUNCTION__, sv->name); return NULL; } - D("%s: registered client channel %d for '%s' service", __FUNCTION__, channel_id, sv->name); return client; @@ -1153,7 +1244,7 @@ qemud_multiplexer_serial_recv( void* opaque, * QemudClient that is setup in qemud_multiplexer_init() */ for ( ; c != NULL; c = c->next ) { - if (c->channel == channel) { + if (!_is_pipe_client(c) && c->ProtocolSelector.Serial.channel == channel) { qemud_client_recv(c, msg, msglen); return; } @@ -1203,13 +1294,13 @@ qemud_multiplexer_disconnect( QemudMultiplexer* m, /* find the client by its channel id, then disconnect it */ for (c = m->clients; c; c = c->next) { - if (c->channel == channel) { + if (!_is_pipe_client(c) && c->ProtocolSelector.Serial.channel == channel) { D("%s: disconnecting client %d", __FUNCTION__, channel); /* note thatt this removes the client from * m->clients automatically. */ - c->channel = -1; /* no need to send disconnect:<id> */ + c->ProtocolSelector.Serial.channel = -1; /* no need to send disconnect:<id> */ qemud_client_disconnect(c); return; } @@ -1233,12 +1324,13 @@ qemud_multiplexer_disconnect_noncontrol( QemudMultiplexer* m ) c = next; next = c->next; /* disconnect frees c, remember next in advance */ - if (c->channel > 0) { /* skip control channel */ + if (!_is_pipe_client(c) && c->ProtocolSelector.Serial.channel > 0) { + /* skip control channel */ D("%s: disconnecting client %d", - __FUNCTION__, c->channel); + __FUNCTION__, c->ProtocolSelector.Serial.channel); D("%s: disconnecting client %d\n", - __FUNCTION__, c->channel); - c->channel = -1; /* do not send disconnect:<id> */ + __FUNCTION__, c->ProtocolSelector.Serial.channel); + c->ProtocolSelector.Serial.channel = -1; /* do not send disconnect:<id> */ qemud_client_disconnect(c); } } @@ -1442,13 +1534,91 @@ qemud_client_new( QemudService* service, return c; } +/* Caches a service message into the client's descriptor. + * + * See comments on QemudPipeMessage structure for more info. + */ +static void +_qemud_pipe_cache_buffer(QemudClient* client, const uint8_t* msg, int msglen) +{ + QemudPipeMessage* buf; + QemudPipeMessage** ins_at = &client->ProtocolSelector.Pipe.messages; + + /* Allocate descriptor big enough to contain message as well. */ + buf = (QemudPipeMessage*)malloc(msglen + sizeof(QemudPipeMessage)); + if (buf != NULL) { + /* Message starts right after the descriptor. */ + buf->message = (uint8_t*)buf + sizeof(QemudPipeMessage); + buf->size = msglen; + memcpy(buf->message, msg, msglen); + buf->offset = 0; + buf->next = NULL; + while (*ins_at != NULL) { + ins_at = &(*ins_at)->next; + } + *ins_at = buf; + /* Notify the pipe that there is data to read. */ + goldfish_pipe_wake(client->ProtocolSelector.Pipe.hwpipe, PIPE_WAKE_READ); + } +} + +/* Sends service message to the client. + */ +static void +_qemud_pipe_send(QemudClient* client, const uint8_t* msg, int msglen) +{ + uint8_t frame[FRAME_HEADER_SIZE]; + int avail, len = msglen; + int framing = client->framing; + + if (msglen <= 0) + return; + + D("%s: len=%3d '%s'", + __FUNCTION__, msglen, quote_bytes((const void*)msg, msglen)); + + if (framing) { + len += FRAME_HEADER_SIZE; + } + + /* packetize the payload for the serial MTU */ + while (len > 0) + { + avail = len; + if (avail > MAX_SERIAL_PAYLOAD) + avail = MAX_SERIAL_PAYLOAD; + + /* insert frame header when needed */ + if (framing) { + int2hex(frame, FRAME_HEADER_SIZE, msglen); + T("%s: '%.*s'", __FUNCTION__, FRAME_HEADER_SIZE, frame); + _qemud_pipe_cache_buffer(client, frame, FRAME_HEADER_SIZE); + avail -= FRAME_HEADER_SIZE; + len -= FRAME_HEADER_SIZE; + framing = 0; + } + + /* write message content */ + T("%s: '%.*s'", __FUNCTION__, avail, msg); + _qemud_pipe_cache_buffer(client, msg, avail); + msg += avail; + len -= avail; + } +} + /* this can be used by a service implementation to send an answer * or message to a specific client. */ void qemud_client_send ( QemudClient* client, const uint8_t* msg, int msglen ) { - qemud_serial_send(client->serial, client->channel, client->framing != 0, msg, msglen); + if (_is_pipe_client(client)) { + _qemud_pipe_send(client, msg, msglen); + } else { + qemud_serial_send(client->ProtocolSelector.Serial.serial, + client->ProtocolSelector.Serial.channel, + client->framing != 0, msg, msglen); + } } /* enable framing for this client. When TRUE, this will @@ -1488,7 +1658,8 @@ qemud_client_save_count(QEMUFile* f, QemudClient* c) { unsigned int client_count = 0; for( ; c; c = c->next) // walk over linked list - if (c->channel > 0) // skip control channel, which is not saved + // skip control channel, which is not saved + if (_is_pipe_client(c) || c->ProtocolSelector.Serial.channel > 0) client_count++; qemu_put_be32(f, client_count); @@ -1530,7 +1701,8 @@ qemud_save(QEMUFile* f, void* opaque) qemud_client_save_count(f, m->clients); QemudClient *c; for (c = m->clients; c; c = c->next) { - if (c->channel > 0) { /* skip control channel client */ + /* skip control channel client */ + if (_is_pipe_client(c) || c->ProtocolSelector.Serial.channel > 0) { qemud_client_save(f, c); } } @@ -1604,6 +1776,211 @@ qemud_load(QEMUFile *f, void* opaque, int version) return 0; } +/*------------------------------------------------------------------------------ + * + * QEMUD PIPE service callbacks + * + * ----------------------------------------------------------------------------*/ + +/* Descriptor for a QEMUD pipe connection. + * + * Every time a client connects to the QEMUD via pipe, an instance of this + * structure is created to represent a connection used by new pipe client. + */ +typedef struct QemudPipe { + /* Pipe descriptor. */ + void* hwpipe; + /* Looper used for I/O */ + void* looper; + /* Service for this pipe. */ + QemudService* service; + /* Client for this pipe. */ + QemudClient* client; +} QemudPipe; + +/* This is a callback that gets invoked when guest is connecting to the service. + * + * Here we will create a new client as well as pipe descriptor representing new + * connection. + */ +static void* +_qemudPipe_init(void* hwpipe, void* _looper, const char* args) +{ + QemudMultiplexer *m = _multiplexer; + QemudService* sv = m->services; + QemudClient* client; + QemudPipe* pipe = NULL; + + /* 'args' passed in this callback represents name of the service the guest is + * connecting to. It can't be NULL. */ + if (args == NULL) { + D("%s: Missing address!", __FUNCTION__); + return NULL; + } + + /* Lookup registered service by its name. */ + while (sv != NULL && strcmp(sv->name, args)) { + sv = sv->next; + } + if (sv == NULL) { + D("%s: Service '%s' has not been registered!", __FUNCTION__, args); + return NULL; + } + + /* Create a client for this connection. -1 as a channel ID signals that this + * is a pipe client. */ + client = qemud_service_connect_client(sv, -1); + if (client != NULL) { + ANEW0(pipe); + pipe->hwpipe = hwpipe; + pipe->looper = _looper; + pipe->service = sv; + pipe->client = client; + client->ProtocolSelector.Pipe.hwpipe = hwpipe; + } + + return pipe; +} + +/* Called when the guest wants to close the channel. +*/ +static void +_qemudPipe_closeFromGuest( void* opaque ) +{ + QemudPipe* pipe = opaque; + QemudClient* client = pipe->client; + D("%s", __FUNCTION__); + qemud_client_disconnect(client); +} + +/* Called when the guest has sent some data to the client. + */ +static int +_qemudPipe_sendBuffers(void* opaque, + const GoldfishPipeBuffer* buffers, + int numBuffers) +{ + QemudPipe* pipe = opaque; + QemudClient* client = pipe->client; + size_t transferred = 0; + + if (numBuffers == 1) { + /* Simple case: all data are in one buffer. */ + D("%s: %s", __FUNCTION__, quote_bytes((char*)buffers->data, buffers->size)); + qemud_client_recv(client, buffers->data, buffers->size); + transferred = buffers->size; + } else { + /* If there are multiple buffers involved, collect all data in one buffer + * before calling the high level client. */ + uint8_t* msg, *wrk; + int n; + for (n = 0; n < numBuffers; n++) { + transferred += buffers[n].size; + } + msg = malloc(transferred); + wrk = msg; + for (n = 0; n < numBuffers; n++) { + memcpy(wrk, buffers[n].data, buffers[n].size); + wrk += buffers[n].size; + } + D("%s: %s", __FUNCTION__, quote_bytes((char*)msg, transferred)); + qemud_client_recv(client, msg, transferred); + free(msg); + } + + return transferred; +} + +#define min(a, b) (((a) < (b)) ? (a) : (b)) + +/* Called when the guest is reading data from the client. + */ +static int +_qemudPipe_recvBuffers(void* opaque, GoldfishPipeBuffer* buffers, int numBuffers) +{ + QemudPipe* pipe = opaque; + QemudClient* client = pipe->client; + QemudPipeMessage** msg_list = &client->ProtocolSelector.Pipe.messages; + GoldfishPipeBuffer* buff = buffers; + GoldfishPipeBuffer* endbuff = buffers + numBuffers; + size_t sent_bytes = 0; + size_t off_in_buff = 0; + + if (*msg_list == NULL) { + /* No data to send. Let it block until we wake it up with + * PIPE_WAKE_READ when service sends data to the client. */ + return PIPE_ERROR_AGAIN; + } + + /* Fill in goldfish buffers while they are still available, and there are + * messages in the client's message list. */ + while (buff != endbuff && *msg_list != NULL) { + QemudPipeMessage* msg = *msg_list; + /* Message data fiting the current pipe's buffer. */ + size_t to_copy = min(msg->size - msg->offset, buff->size); + memcpy(buff->data + off_in_buff, msg->message + msg->offset, to_copy); + /* Update offsets. */ + off_in_buff += to_copy; + msg->offset += to_copy; + sent_bytes += to_copy; + if (msg->size == msg->offset) { + /* We're done with the current message. Go to the next one. */ + *msg_list = msg->next; + free(msg); + } + if (off_in_buff == buff->size) { + /* Current pipe buffer is full. Continue with the next one. */ + buff++; + } + } + + D("%s: -> %u (of %u)", __FUNCTION__, sent_bytes, buffers->size); + + return sent_bytes; +} + +static unsigned +_qemudPipe_poll(void* opaque) +{ + QemudPipe* pipe = opaque; + QemudClient* client = pipe->client; + unsigned ret = PIPE_WAKE_WRITE; + if (client->ProtocolSelector.Pipe.messages != NULL) { + ret |= PIPE_WAKE_WRITE; + } + + return ret; +} + +static void +_qemudPipe_wakeOn(void* opaque, int flags) +{ + D("%s: -> %X", __FUNCTION__, flags); +} + +/* QEMUD pipe functions. + */ +static const GoldfishPipeFuncs _qemudPipe_funcs = { + _qemudPipe_init, + _qemudPipe_closeFromGuest, + _qemudPipe_sendBuffers, + _qemudPipe_recvBuffers, + _qemudPipe_poll, + _qemudPipe_wakeOn, +}; + +/* Initializes QEMUD pipe interface. + */ +static void +_android_qemud_pipe_init(void) +{ + static ABool _qemud_pipe_initialized = false; + + if (!_qemud_pipe_initialized) { + goldfish_pipe_add_type( "qemud", looper_newCore(), &_qemudPipe_funcs ); + _qemud_pipe_initialized = true; + } +} /* this is the end of the serial charpipe that must be passed * to the emulated tty implementation. The other end of the @@ -1611,8 +1988,10 @@ qemud_load(QEMUFile *f, void* opaque, int version) */ static CharDriverState* android_qemud_cs; -extern void -android_qemud_init( void ) +/* Initializes QEMUD serial interface. + */ +static void +_android_qemud_serial_init(void) { CharDriverState* cs; @@ -1631,6 +2010,18 @@ android_qemud_init( void ) qemud_save, qemud_load, _multiplexer); } +extern void +android_qemud_init( void ) +{ + D("%s", __FUNCTION__); + /* We don't know in advance whether the guest system supports qemud pipes, + * so we will initialize both qemud machineries, the legacy (over serial + * port), and the new one (over qemu pipe). Then we let the guest to connect + * via one, or the other. */ + _android_qemud_serial_init(); + _android_qemud_pipe_init(); +} + /* return the serial charpipe endpoint that must be used * by the emulated tty implementation. */ @@ -1663,20 +2054,19 @@ qemud_service_register( const char* service_name, QemudServiceSave serv_save, QemudServiceLoad serv_load ) { - QemudMultiplexer* m = _multiplexer; QemudService* sv; + QemudMultiplexer* m = _multiplexer; - if (android_qemud_cs == NULL) - android_qemud_init(); + android_qemud_init(); sv = qemud_service_new(service_name, - max_clients, - serv_opaque, - serv_connect, - serv_save, - serv_load, - &m->services); - + max_clients, + serv_opaque, + serv_connect, + serv_save, + serv_load, + &m->services); + D("Registered QEMUD service %s", service_name); return sv; } |