aboutsummaryrefslogtreecommitdiffstats
path: root/android/hw-qemud.c
diff options
context:
space:
mode:
Diffstat (limited to 'android/hw-qemud.c')
-rw-r--r--android/hw-qemud.c474
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;
}