aboutsummaryrefslogtreecommitdiffstats
path: root/android/hw-qemud.c
diff options
context:
space:
mode:
authorVladimir Chtchetkine <vchtchetkine@google.com>2011-08-04 12:19:04 -0700
committerVladimir Chtchetkine <vchtchetkine@google.com>2011-08-04 12:19:04 -0700
commit77e61ceb3ecd53d6c4de9631c9450b6183d9244b (patch)
treec3f2187231919786a80300ed4ac764816f9b8448 /android/hw-qemud.c
parentf4ea240ad33aec9ad9b57c336edc24e9365d78d1 (diff)
downloadexternal_qemu-77e61ceb3ecd53d6c4de9631c9450b6183d9244b.zip
external_qemu-77e61ceb3ecd53d6c4de9631c9450b6183d9244b.tar.gz
external_qemu-77e61ceb3ecd53d6c4de9631c9450b6183d9244b.tar.bz2
Enable QEMUD over pipe.
In addition to the currently implemented QEMUD over serial port, this CL adds capability to connect with guest clients via pipe device. The connection is established in request from the guest, that opens a "qemud:service-name" on /dev/qemu-pipe, which in turn invikes a callback in the emulator requesting to create a client for a service "service-name". Note that serial port version of QEMUD is fully preserved here. Change-Id: I6621f141cf9b4eb93bfa7cb1a689d80154922c87
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;
}