auto import from //branches/cupcake_rel/...@140373
diff --git a/emulator/qemud/qemud.c b/emulator/qemud/qemud.c
index ae6797e..bd49e52 100644
--- a/emulator/qemud/qemud.c
+++ b/emulator/qemud/qemud.c
@@ -10,96 +10,79 @@
#include <cutils/sockets.h>
/*
- * the qemud program is only used within the Android emulator as a bridge
+ * the qemud daemon program is only used within Android as a bridge
* between the emulator program and the emulated system. it really works as
* a simple stream multiplexer that works as follows:
*
+ * - qemud is started by init following instructions in
+ * /system/etc/init.goldfish.rc (i.e. it is never started on real devices)
+ *
* - qemud communicates with the emulator program through a single serial
* port, whose name is passed through a kernel boot parameter
* (e.g. android.qemud=ttyS1)
*
- * - qemud setups one or more unix local stream sockets in the
- * emulated system each one of these represent a different communication
- * 'channel' between the emulator program and the emulated system.
+ * - qemud binds one unix local stream socket (/dev/socket/qemud, created
+ * by init through /system/etc/init.goldfish.rc).
*
- * as an example, one channel is used for the emulated GSM modem
- * (AT command channel), another channel is used for the emulated GPS,
- * etc...
*
- * - the protocol used on the serial connection is pretty simple:
+ * emulator <==serial==> qemud <---> /dev/socket/qemud <-+--> client1
+ * |
+ * +--> client2
*
- * offset size description
- * 0 4 4-char hex string giving the payload size
- * 4 2 2-char hex string giving the destination or
- * source channel
- * 6 n the message payload
+ * - the special channel index 0 is used by the emulator and qemud only.
+ * other channel numbers correspond to clients. More specifically,
+ * connection are created like this:
*
- * for emulator->system messages, the 'channel' index indicates
- * to which channel the payload must be sent
+ * * the client connects to /dev/socket/qemud
*
- * for system->emulator messages, the 'channel' index indicates from
- * which channel the payload comes from.
+ * * the client sends the service name through the socket, as
+ * <service-name>
*
- * - a special channel index (0) is used to communicate with the qemud
- * program directly from the emulator. this is used for the following
- * commands: (content of the payload):
+ * * qemud creates a "Client" object internally, assigns it an
+ * internal unique channel number > 0, then sends a connection
+ * initiation request to the emulator (i.e. through channel 0):
*
- * request: connect:<name>
- * answer: ok:connect:<name>:XX // succesful name lookup
- * answer: ko:connect:bad name // failed lookup
+ * connect:<hxid>:<name>
*
- * the emulator queries the index of a given channel given
- * its human-readable name. the answer contains a 2-char hex
- * string for the channel index.
+ * where <name> is the service name, and <hxid> is a 4-hexchar
+ * number corresponding to the channel number.
*
- * not all emulated systems may need the same communication
- * channels, so this function may fail.
+ * * in case of success, the emulator responds through channel 0
+ * with:
*
- * any invalid request will get an answer of:
+ * ok:connect:<hxid>
+ *
+ * after this, all messages between the client and the emulator
+ * are passed in pass-through mode.
+ *
+ * * if the emulator refuses the service connection, it will
+ * send the following through channel 0:
+ *
+ * ko:connect:<hxid>:reason-for-failure
+ *
+ * * If the client closes the connection, qemud sends the following
+ * to the emulator:
+ *
+ * disconnect:<hxid>
+ *
+ * The same message is the opposite direction if the emulator
+ * chooses to close the connection.
+ *
+ * * any command sent through channel 0 to the emulator that is
+ * not properly recognized will be answered by:
*
* ko:unknown command
*
*
- * here's a diagram of how things work:
- *
- *
- * _________
- * _____________ creates | |
- * ________ | |==========>| Channel |--*--
- * | |---->| Multiplexer | |_________|
- * --*--| Serial | |_____________| || creates
- * |________| | _____v___
- * A +--------------->| |
- * | | Client |--*--
- * +---------------------------------|_________|
- *
- * which really means that:
- *
- * - the multiplexer creates one Channel object per control socket qemud
- * handles (e.g. /dev/socket/qemud_gsm, /dev/socket/qemud_gps)
- *
- * - each Channel object has a numerical index that is >= 1, and waits
- * for client connection. it will create a Client object when this
- * happens
- *
- * - the Serial object receives packets from the serial port and sends them
- * to the multiplexer
- *
- * - the multiplexer tries to find a channel the packet is addressed to,
- * and will send the packet to all clients that correspond to it
- *
- * - when a Client receives data, it sends it directly to the Serial object
- *
- * - there are two kinds of Channel objects:
- *
- * CHANNEL_BROADCAST :: used for emulator -> clients broadcasts only
- *
- * CHANNEL_DUPLEX :: used for bidirectional communication with the
- * emulator, with only *one* client allowed per
- * duplex channel
+ * Internally, the daemon maintains a "Client" object for each client
+ * connection (i.e. accepting socket connection).
*/
-#define DEBUG 0
+/* name of the single control socket used by the daemon */
+#define CONTROL_SOCKET_NAME "qemud"
+
+#define DEBUG 1
+#define T_ACTIVE 0 /* set to 1 to dump traffic */
#if DEBUG
# define LOG_TAG "qemud"
@@ -107,6 +90,13 @@
# define D(...) LOGD(__VA_ARGS__)
#else
# define D(...) ((void)0)
+# define T(...) ((void)0)
+#endif
+
+#if T_ACTIVE
+# define T(...) D(__VA_ARGS__)
+#else
+# define T(...) ((void)0)
#endif
/** UTILITIES
@@ -262,30 +252,84 @@
}
}
+
+static int
+fd_accept(int fd)
+{
+ struct sockaddr from;
+ socklen_t fromlen = sizeof(from);
+ int ret;
+
+ do {
+ ret = accept(fd, &from, &fromlen);
+ } while (ret < 0 && errno == EINTR);
+
+ return ret;
+}
+
/** FD EVENT LOOP
**/
+/* A Looper object is used to monitor activity on one or more
+ * file descriptors (e.g sockets).
+ *
+ * - call looper_add() to register a function that will be
+ * called when events happen on the file descriptor.
+ *
+ * - call looper_enable() or looper_disable() to enable/disable
+ * the set of monitored events for a given file descriptor.
+ *
+ * - call looper_del() to unregister a file descriptor.
+ * this does *not* close the file descriptor.
+ *
+ * Note that you can only provide a single function to handle
+ * all events related to a given file descriptor.
+
+ * You can call looper_enable/_disable/_del within a function
+ * callback.
+ */
+
+/* the current implementation uses Linux's epoll facility
+ * the event mask we use are simply combinations of EPOLLIN
+ * EPOLLOUT, EPOLLHUP and EPOLLERR
+ */
#include <sys/epoll.h>
#define MAX_CHANNELS 16
#define MAX_EVENTS (MAX_CHANNELS+1) /* each channel + the serial fd */
+/* the event handler function type, 'user' is a user-specific
+ * opaque pointer passed to looper_add().
+ */
typedef void (*EventFunc)( void* user, int events );
+/* bit flags for the LoopHook structure.
+ *
+ * HOOK_PENDING means that an event happened on the
+ * corresponding file descriptor.
+ *
+ * HOOK_CLOSING is used to delay-close monitored
+ * file descriptors.
+ */
enum {
HOOK_PENDING = (1 << 0),
HOOK_CLOSING = (1 << 1),
};
+/* A LoopHook structure is used to monitor a given
+ * file descriptor and record its event handler.
+ */
typedef struct {
int fd;
- int wanted;
- int events;
- int state;
- void* ev_user;
- EventFunc ev_func;
+ int wanted; /* events we are monitoring */
+ int events; /* events that occured */
+ int state; /* see HOOK_XXX constants */
+ void* ev_user; /* user-provided handler parameter */
+ EventFunc ev_func; /* event handler callback */
} LoopHook;
+/* Looper is the main object modeling a looper object
+ */
typedef struct {
int epoll_fd;
int num_fds;
@@ -294,6 +338,7 @@
LoopHook* hooks;
} Looper;
+/* initialize a looper object */
static void
looper_init( Looper* l )
{
@@ -304,6 +349,7 @@
l->hooks = NULL;
}
+/* finalize a looper object */
static void
looper_done( Looper* l )
{
@@ -316,6 +362,9 @@
l->epoll_fd = -1;
}
+/* return the LoopHook corresponding to a given
+ * monitored file descriptor, or NULL if not found
+ */
static LoopHook*
looper_find( Looper* l, int fd )
{
@@ -329,6 +378,7 @@
return NULL;
}
+/* grow the arrays in the looper object */
static void
looper_grow( Looper* l )
{
@@ -351,6 +401,9 @@
}
}
+/* register a file descriptor and its event handler.
+ * no event mask will be enabled
+ */
static void
looper_add( Looper* l, int fd, EventFunc func, void* user )
{
@@ -378,6 +431,8 @@
l->num_fds += 1;
}
+/* unregister a file descriptor and its event handler
+ */
static void
looper_del( Looper* l, int fd )
{
@@ -393,6 +448,10 @@
epoll_ctl( l->epoll_fd, EPOLL_CTL_DEL, fd, NULL );
}
+/* enable monitoring of certain events for a file
+ * descriptor. This adds 'events' to the current
+ * event mask
+ */
static void
looper_enable( Looper* l, int fd, int events )
{
@@ -414,6 +473,10 @@
}
}
+/* disable monitoring of certain events for a file
+ * descriptor. This ignores events that are not
+ * currently enabled.
+ */
static void
looper_disable( Looper* l, int fd, int events )
{
@@ -435,6 +498,9 @@
}
}
+/* wait until an event occurs on one of the registered file
+ * descriptors. Only returns in case of error !!
+ */
static void
looper_loop( Looper* l )
{
@@ -450,6 +516,11 @@
return;
}
+ if (count == 0) {
+ D("%s: huh ? epoll returned count=0", __FUNCTION__);
+ continue;
+ }
+
/* mark all pending hooks */
for (n = 0; n < count; n++) {
LoopHook* hook = l->events[n].data.ptr;
@@ -483,13 +554,87 @@
}
}
+#if T_ACTIVE
+char*
+quote( const void* data, int len )
+{
+ const char* p = data;
+ const char* end = p + len;
+ int count = 0;
+ int phase = 0;
+ static char* buff = NULL;
+
+ for (phase = 0; phase < 2; phase++) {
+ if (phase != 0) {
+ xfree(buff);
+ buff = xalloc(count+1);
+ }
+ count = 0;
+ for (p = data; p < end; p++) {
+ int c = *p;
+
+ if (c == '\\') {
+ if (phase != 0) {
+ buff[count] = buff[count+1] = '\\';
+ }
+ count += 2;
+ continue;
+ }
+
+ if (c >= 32 && c < 127) {
+ if (phase != 0)
+ buff[count] = c;
+ count += 1;
+ continue;
+ }
+
+
+ if (c == '\t') {
+ if (phase != 0) {
+ memcpy(buff+count, "<TAB>", 5);
+ }
+ count += 5;
+ continue;
+ }
+ if (c == '\n') {
+ if (phase != 0) {
+ memcpy(buff+count, "<LN>", 4);
+ }
+ count += 4;
+ continue;
+ }
+ if (c == '\r') {
+ if (phase != 0) {
+ memcpy(buff+count, "<CR>", 4);
+ }
+ count += 4;
+ continue;
+ }
+
+ if (phase != 0) {
+ buff[count+0] = '\\';
+ buff[count+1] = 'x';
+ buff[count+2] = "0123456789abcdef"[(c >> 4) & 15];
+ buff[count+3] = "0123456789abcdef"[ (c) & 15];
+ }
+ count += 4;
+ }
+ }
+ buff[count] = 0;
+ return buff;
+}
+#endif /* T_ACTIVE */
+
/** PACKETS
+ **
+ ** We need a way to buffer data before it can be sent to the
+ ** corresponding file descriptor. We use linked list of Packet
+ ** objects to do this.
**/
typedef struct Packet Packet;
-/* we want to ensure that Packet is no more than a single page */
-#define MAX_PAYLOAD (4096-16-6)
+#define MAX_PAYLOAD 4000
struct Packet {
Packet* next;
@@ -498,8 +643,13 @@
uint8_t data[ MAX_PAYLOAD ];
};
+/* we expect to alloc/free a lot of packets during
+ * operations so use a single linked list of free packets
+ * to keep things speedy and simple.
+ */
static Packet* _free_packets;
+/* Allocate a packet */
static Packet*
packet_alloc(void)
{
@@ -509,12 +659,16 @@
} else {
xnew(p);
}
- p->next = NULL;
- p->len = 0;
+ p->next = NULL;
+ p->len = 0;
p->channel = -1;
return p;
}
+/* Release a packet. This takes the address of a packet
+ * pointer that will be set to NULL on exit (avoids
+ * referencing dangling pointers in case of bugs)
+ */
static void
packet_free( Packet* *ppacket )
{
@@ -526,18 +680,15 @@
}
}
-static Packet*
-packet_dup( Packet* p )
-{
- Packet* p2 = packet_alloc();
-
- p2->len = p->len;
- p2->channel = p->channel;
- memcpy(p2->data, p->data, p->len);
- return p2;
-}
-
/** PACKET RECEIVER
+ **
+ ** Simple abstraction for something that can receive a packet
+ ** from a FDHandler (see below) or something else.
+ **
+ ** Send a packet to it with 'receiver_post'
+ **
+ ** Call 'receiver_close' to indicate that the corresponding
+ ** packet source was closed.
**/
typedef void (*PostFunc) ( void* user, Packet* p );
@@ -549,41 +700,130 @@
void* user;
} Receiver;
+/* post a packet to a receiver. Note that this transfers
+ * ownership of the packet to the receiver.
+ */
static __inline__ void
receiver_post( Receiver* r, Packet* p )
{
- r->post( r->user, p );
+ if (r->post)
+ r->post( r->user, p );
+ else
+ packet_free(&p);
}
+/* tell a receiver the packet source was closed.
+ * this will also prevent further posting to the
+ * receiver.
+ */
static __inline__ void
receiver_close( Receiver* r )
{
- r->close( r->user );
+ if (r->close) {
+ r->close( r->user );
+ r->close = NULL;
+ }
+ r->post = NULL;
}
/** FD HANDLERS
**
** these are smart listeners that send incoming packets to a receiver
- ** and can queue one or more outgoing packets and send them when possible
+ ** and can queue one or more outgoing packets and send them when
+ ** possible to the FD.
+ **
+ ** note that we support clean shutdown of file descriptors,
+ ** i.e. we try to send all outgoing packets before destroying
+ ** the FDHandler.
**/
-typedef struct FDHandler {
- int fd;
+typedef struct FDHandler FDHandler;
+typedef struct FDHandlerList FDHandlerList;
+
+struct FDHandler {
+ int fd;
+ FDHandlerList* list;
+ char closing;
+ Receiver receiver[1];
+
+ /* queue of outgoing packets */
+ int out_pos;
+ Packet* out_first;
+ Packet** out_ptail;
+
+ FDHandler* next;
+ FDHandler** pref;
+
+};
+
+struct FDHandlerList {
+ /* the looper that manages the fds */
Looper* looper;
- Receiver receiver[1];
- int out_pos;
- Packet* out_first;
- Packet** out_ptail;
-} FDHandler;
+ /* list of active FDHandler objects */
+ FDHandler* active;
+ /* list of closing FDHandler objects.
+ * these are waiting to push their
+ * queued packets to the fd before
+ * freeing themselves.
+ */
+ FDHandler* closing;
+};
+
+/* remove a FDHandler from its current list */
static void
-fdhandler_done( FDHandler* f )
+fdhandler_remove( FDHandler* f )
{
- /* get rid of unsent packets */
- if (f->out_first) {
+ f->pref[0] = f->next;
+ if (f->next)
+ f->next->pref = f->pref;
+}
+
+/* add a FDHandler to a given list */
+static void
+fdhandler_prepend( FDHandler* f, FDHandler** list )
+{
+ f->next = list[0];
+ f->pref = list;
+ list[0] = f;
+ if (f->next)
+ f->next->pref = &f->next;
+}
+
+/* initialize a FDHandler list */
+static void
+fdhandler_list_init( FDHandlerList* list, Looper* looper )
+{
+ list->looper = looper;
+ list->active = NULL;
+ list->closing = NULL;
+}
+
+
+/* close a FDHandler (and free it). Note that this will not
+ * perform a graceful shutdown, i.e. all packets in the
+ * outgoing queue will be immediately free.
+ *
+ * this *will* notify the receiver that the file descriptor
+ * was closed.
+ *
+ * you should call fdhandler_shutdown() if you want to
+ * notify the FDHandler that its packet source is closed.
+ */
+static void
+fdhandler_close( FDHandler* f )
+{
+ /* notify receiver */
+ receiver_close(f->receiver);
+
+ /* remove the handler from its list */
+ fdhandler_remove(f);
+
+ /* get rid of outgoing packet queue */
+ if (f->out_first != NULL) {
Packet* p;
while ((p = f->out_first) != NULL) {
f->out_first = p->next;
@@ -593,14 +833,41 @@
/* get rid of file descriptor */
if (f->fd >= 0) {
- looper_del( f->looper, f->fd );
+ looper_del( f->list->looper, f->fd );
close(f->fd);
f->fd = -1;
}
- f->looper = NULL;
+
+ f->list = NULL;
+ xfree(f);
}
+/* Ask the FDHandler to cleanly shutdown the connection,
+ * i.e. send any pending outgoing packets then auto-free
+ * itself.
+ */
+static void
+fdhandler_shutdown( FDHandler* f )
+{
+ if (f->out_first != NULL && !f->closing)
+ {
+ /* move the handler to the 'closing' list */
+ f->closing = 1;
+ fdhandler_remove(f);
+ fdhandler_prepend(f, &f->list->closing);
+
+ /* notify the receiver that we're closing */
+ receiver_close(f->receiver);
+ return;
+ }
+
+ fdhandler_close(f);
+}
+
+/* Enqueue a new packet that the FDHandler will
+ * send through its file descriptor.
+ */
static void
fdhandler_enqueue( FDHandler* f, Packet* p )
{
@@ -612,16 +879,24 @@
if (first == NULL) {
f->out_pos = 0;
- looper_enable( f->looper, f->fd, EPOLLOUT );
+ looper_enable( f->list->looper, f->fd, EPOLLOUT );
}
}
+/* FDHandler file descriptor event callback for read/write ops */
static void
fdhandler_event( FDHandler* f, int events )
{
int len;
+ /* in certain cases, it's possible to have both EPOLLIN and
+ * EPOLLHUP at the same time. This indicates that there is incoming
+ * data to read, but that the connection was nonetheless closed
+ * by the sender. Be sure to read the data before closing
+ * the receiver to avoid packet loss.
+ */
+
if (events & EPOLLIN) {
Packet* p = packet_alloc();
int len;
@@ -629,23 +904,17 @@
if ((len = fd_read(f->fd, p->data, MAX_PAYLOAD)) < 0) {
D("%s: can't recv: %s", __FUNCTION__, strerror(errno));
packet_free(&p);
- } else {
+ } else if (len > 0) {
p->len = len;
- p->channel = -101; /* special debug value */
+ p->channel = -101; /* special debug value, not used */
receiver_post( f->receiver, p );
}
}
- /* in certain cases, it's possible to have both EPOLLIN and
- * EPOLLHUP at the same time. This indicates that there is incoming
- * data to read, but that the connection was nonetheless closed
- * by the sender. Be sure to read the data before closing
- * the receiver to avoid packet loss.
- */
if (events & (EPOLLHUP|EPOLLERR)) {
/* disconnection */
D("%s: disconnect on fd %d", __FUNCTION__, f->fd);
- receiver_close( f->receiver );
+ fdhandler_close(f);
return;
}
@@ -664,7 +933,7 @@
packet_free(&p);
if (f->out_first == NULL) {
f->out_ptail = &f->out_first;
- looper_disable( f->looper, f->fd, EPOLLOUT );
+ looper_disable( f->list->looper, f->fd, EPOLLOUT );
}
}
}
@@ -672,24 +941,34 @@
}
-static void
-fdhandler_init( FDHandler* f,
- int fd,
- Looper* looper,
- Receiver* receiver )
+/* Create a new FDHandler that monitors read/writes */
+static FDHandler*
+fdhandler_new( int fd,
+ FDHandlerList* list,
+ Receiver* receiver )
{
+ FDHandler* f = xalloc0(sizeof(*f));
+
f->fd = fd;
- f->looper = looper;
+ f->list = list;
f->receiver[0] = receiver[0];
f->out_first = NULL;
f->out_ptail = &f->out_first;
f->out_pos = 0;
- looper_add( looper, fd, (EventFunc) fdhandler_event, f );
- looper_enable( looper, fd, EPOLLIN );
+ fdhandler_prepend(f, &list->active);
+
+ looper_add( list->looper, fd, (EventFunc) fdhandler_event, f );
+ looper_enable( list->looper, fd, EPOLLIN );
+
+ return f;
}
+/* event callback function to monitor accepts() on server sockets.
+ * the convention used here is that the receiver will receive a
+ * dummy packet with the new client socket in p->channel
+ */
static void
fdhandler_accept_event( FDHandler* f, int events )
{
@@ -700,296 +979,116 @@
D("%s: accepting on fd %d", __FUNCTION__, f->fd);
p->data[0] = 1;
p->len = 1;
+ p->channel = fd_accept(f->fd);
+ if (p->channel < 0) {
+ D("%s: accept failed ?: %s", __FUNCTION__, strerror(errno));
+ packet_free(&p);
+ return;
+ }
receiver_post( f->receiver, p );
}
if (events & (EPOLLHUP|EPOLLERR)) {
/* disconnecting !! */
- D("%s: closing fd %d", __FUNCTION__, f->fd);
- receiver_close( f->receiver );
+ D("%s: closing accept fd %d", __FUNCTION__, f->fd);
+ fdhandler_close(f);
return;
}
}
-static void
-fdhandler_init_accept( FDHandler* f,
- int fd,
- Looper* looper,
- Receiver* receiver )
+/* Create a new FDHandler used to monitor new connections on a
+ * server socket. The receiver must expect the new connection
+ * fd in the 'channel' field of a dummy packet.
+ */
+static FDHandler*
+fdhandler_new_accept( int fd,
+ FDHandlerList* list,
+ Receiver* receiver )
{
+ FDHandler* f = xalloc0(sizeof(*f));
+
f->fd = fd;
- f->looper = looper;
+ f->list = list;
f->receiver[0] = receiver[0];
- looper_add( looper, fd, (EventFunc) fdhandler_accept_event, f );
- looper_enable( looper, fd, EPOLLIN );
-}
+ fdhandler_prepend(f, &list->active);
-/** CLIENTS
- **/
-
-typedef struct Client {
- struct Client* next;
- struct Client** pref;
- int channel;
- FDHandler fdhandler[1];
- Receiver receiver[1];
-} Client;
-
-static Client* _free_clients;
-
-static void
-client_free( Client* c )
-{
- c->pref[0] = c->next;
- c->next = NULL;
- c->pref = &c->next;
-
- fdhandler_done( c->fdhandler );
- free(c);
-}
-
-static void
-client_receive( Client* c, Packet* p )
-{
- p->channel = c->channel;
- receiver_post( c->receiver, p );
-}
-
-static void
-client_send( Client* c, Packet* p )
-{
- fdhandler_enqueue( c->fdhandler, p );
-}
-
-static void
-client_close( Client* c )
-{
- D("disconnecting client on fd %d", c->fdhandler->fd);
- client_free(c);
-}
-
-static Client*
-client_new( int fd,
- int channel,
- Looper* looper,
- Receiver* receiver )
-{
- Client* c;
- Receiver recv;
-
- xnew(c);
-
- c->next = NULL;
- c->pref = &c->next;
- c->channel = channel;
- c->receiver[0] = receiver[0];
-
- recv.user = c;
- recv.post = (PostFunc) client_receive;
- recv.close = (CloseFunc) client_close;
-
- fdhandler_init( c->fdhandler, fd, looper, &recv );
- return c;
-}
-
-static void
-client_link( Client* c, Client** plist )
-{
- c->next = plist[0];
- c->pref = plist;
- plist[0] = c;
-}
-
-
-/** CHANNELS
- **/
-
-typedef enum {
- CHANNEL_BROADCAST = 0,
- CHANNEL_DUPLEX,
-
- CHANNEL_MAX /* do not remove */
-
-} ChannelType;
-
-#define CHANNEL_CONTROL 0
-
-typedef struct Channel {
- struct Channel* next;
- struct Channel** pref;
- FDHandler fdhandler[1];
- ChannelType ctype;
- const char* name;
- int index;
- Receiver receiver[1];
- Client* clients;
-} Channel;
-
-static void
-channel_free( Channel* c )
-{
- while (c->clients)
- client_free(c->clients);
-
- c->pref[0] = c->next;
- c->pref = &c->next;
- c->next = NULL;
-
- fdhandler_done( c->fdhandler );
- free(c);
-}
-
-static void
-channel_close( Channel* c )
-{
- D("closing channel '%s' on fd %d", c->name, c->fdhandler->fd);
- channel_free(c);
-}
-
-
-static void
-channel_accept( Channel* c, Packet* p )
-{
- int fd;
- struct sockaddr from;
- socklen_t fromlen = sizeof(from);
-
- /* get rid of dummy packet (see fdhandler_event_accept) */
- packet_free(&p);
-
- do {
- fd = accept( c->fdhandler->fd, &from, &fromlen );
- } while (fd < 0 && errno == EINTR);
-
- if (fd >= 0) {
- Client* client;
-
- /* DUPLEX channels can only have one client at a time */
- if (c->ctype == CHANNEL_DUPLEX && c->clients != NULL) {
- D("refusing client connection on duplex channel '%s'", c->name);
- close(fd);
- return;
- }
- client = client_new( fd, c->index, c->fdhandler->looper, c->receiver );
- client_link( client, &c->clients );
- D("new client for channel '%s' on fd %d", c->name, fd);
- }
- else
- D("could not accept connection: %s", strerror(errno));
-}
-
-
-static Channel*
-channel_new( int fd,
- ChannelType ctype,
- const char* name,
- int index,
- Looper* looper,
- Receiver* receiver )
-{
- Channel* c;
- Receiver recv;
-
- xnew(c);
-
- c->next = NULL;
- c->pref = &c->next;
- c->ctype = ctype;
- c->name = name;
- c->index = index;
-
- /* saved for future clients */
- c->receiver[0] = receiver[0];
-
- recv.user = c;
- recv.post = (PostFunc) channel_accept;
- recv.close = (CloseFunc) channel_close;
-
- fdhandler_init_accept( c->fdhandler, fd, looper, &recv );
+ looper_add( list->looper, fd, (EventFunc) fdhandler_accept_event, f );
+ looper_enable( list->looper, fd, EPOLLIN );
listen( fd, 5 );
- return c;
+ return f;
}
-static void
-channel_link( Channel* c, Channel** plist )
-{
- c->next = plist[0];
- c->pref = plist;
- plist[0] = c;
-}
-
-static void
-channel_send( Channel* c, Packet* p )
-{
- Client* client = c->clients;
- for ( ; client; client = client->next ) {
- Packet* q = packet_dup(p);
- client_send( client, q );
- }
- packet_free( &p );
-}
-
+/** SERIAL CONNECTION STATE
+ **
+ ** The following is used to handle the framing protocol
+ ** used on the serial port connection.
+ **/
/* each packet is made of a 6 byte header followed by a payload
* the header looks like:
*
* offset size description
- * 0 4 a 4-char hex string for the size of the payload
- * 4 2 a 2-byte hex string for the channel number
+ * 0 2 a 2-byte hex string for the channel number
+ * 4 4 a 4-char hex string for the size of the payload
* 6 n the payload itself
*/
#define HEADER_SIZE 6
-#define LENGTH_OFFSET 0
-#define LENGTH_SIZE 4
-#define CHANNEL_OFFSET 4
+#define CHANNEL_OFFSET 0
+#define LENGTH_OFFSET 2
#define CHANNEL_SIZE 2
+#define LENGTH_SIZE 4
-#define CHANNEL_INDEX_NONE 0
-#define CHANNEL_INDEX_CONTROL 1
+#define CHANNEL_CONTROL 0
-#define TOSTRING(x) _TOSTRING(x)
-#define _TOSTRING(x) #x
-
-/** SERIAL HANDLER
- **/
-
+/* The Serial object receives data from the serial port,
+ * extracts the payload size and channel index, then sends
+ * the resulting messages as a packet to a generic receiver.
+ *
+ * You can also use serial_send to send a packet through
+ * the serial port.
+ */
typedef struct Serial {
- FDHandler fdhandler[1];
- Receiver receiver[1];
- int in_len;
- int in_datalen;
- int in_channel;
- Packet* in_packet;
+ FDHandler* fdhandler; /* used to monitor serial port fd */
+ Receiver receiver[1]; /* send payload there */
+ int in_len; /* current bytes in input packet */
+ int in_datalen; /* payload size, or 0 when reading header */
+ int in_channel; /* extracted channel number */
+ Packet* in_packet; /* used to read incoming packets */
} Serial;
-static void
-serial_done( Serial* s )
-{
- packet_free(&s->in_packet);
- s->in_len = 0;
- s->in_datalen = 0;
- s->in_channel = 0;
- fdhandler_done(s->fdhandler);
-}
+/* a callback called when the serial port's fd is closed */
static void
-serial_close( Serial* s )
+serial_fd_close( Serial* s )
{
fatal("unexpected serial port close !!");
}
-/* receive packets from the serial port */
static void
-serial_receive( Serial* s, Packet* p )
+serial_dump( Packet* p, const char* funcname )
+{
+ T("%s: %03d bytes: '%s'",
+ funcname, p->len, quote(p->data, p->len));
+}
+
+/* a callback called when a packet arrives from the serial port's FDHandler.
+ *
+ * This will essentially parse the header, extract the channel number and
+ * the payload size and store them in 'in_datalen' and 'in_channel'.
+ *
+ * After that, the payload is sent to the receiver once completed.
+ */
+static void
+serial_fd_receive( Serial* s, Packet* p )
{
int rpos = 0, rcount = p->len;
Packet* inp = s->in_packet;
int inpos = s->in_len;
- //D("received from serial: %d bytes: '%.*s'", p->len, p->len, p->data);
+ serial_dump( p, __FUNCTION__ );
while (rpos < rcount)
{
@@ -1009,8 +1108,11 @@
s->in_datalen = hex2int( inp->data + LENGTH_OFFSET, LENGTH_SIZE );
s->in_channel = hex2int( inp->data + CHANNEL_OFFSET, CHANNEL_SIZE );
- if (s->in_datalen <= 0)
- D("ignoring empty packet from serial port");
+ if (s->in_datalen <= 0) {
+ D("ignoring %s packet from serial port",
+ s->in_datalen ? "empty" : "malformed");
+ s->in_datalen = 0;
+ }
//D("received %d bytes packet for channel %d", s->in_datalen, s->in_channel);
inpos = 0;
@@ -1047,7 +1149,10 @@
}
-/* send a packet to the serial port */
+/* send a packet to the serial port.
+ * this assumes that p->len and p->channel contain the payload's
+ * size and channel and will add the appropriate header.
+ */
static void
serial_send( Serial* s, Packet* p )
{
@@ -1060,54 +1165,446 @@
int2hex( p->len, h->data + LENGTH_OFFSET, LENGTH_SIZE );
int2hex( p->channel, h->data + CHANNEL_OFFSET, CHANNEL_SIZE );
+ serial_dump( h, __FUNCTION__ );
+ serial_dump( p, __FUNCTION__ );
+
fdhandler_enqueue( s->fdhandler, h );
fdhandler_enqueue( s->fdhandler, p );
}
+/* initialize serial reader */
static void
-serial_init( Serial* s,
- int fd,
- Looper* looper,
- Receiver* receiver )
+serial_init( Serial* s,
+ int fd,
+ FDHandlerList* list,
+ Receiver* receiver )
{
Receiver recv;
recv.user = s;
- recv.post = (PostFunc) serial_receive;
- recv.close = (CloseFunc) serial_close;
+ recv.post = (PostFunc) serial_fd_receive;
+ recv.close = (CloseFunc) serial_fd_close;
s->receiver[0] = receiver[0];
- fdhandler_init( s->fdhandler, fd, looper, &recv );
+ s->fdhandler = fdhandler_new( fd, list, &recv );
s->in_len = 0;
s->in_datalen = 0;
s->in_channel = 0;
s->in_packet = packet_alloc();
}
+
+/** CLIENTS
+ **/
+
+typedef struct Client Client;
+typedef struct Multiplexer Multiplexer;
+
+/* A Client object models a single qemud client socket
+ * connection in the emulated system.
+ *
+ * the client first sends the name of the system service
+ * it wants to contact (no framing), then waits for a 2
+ * byte answer from qemud.
+ *
+ * the answer is either "OK" or "KO" to indicate
+ * success or failure.
+ *
+ * In case of success, the client can send messages
+ * to the service.
+ *
+ * In case of failure, it can disconnect or try sending
+ * the name of another service.
+ */
+struct Client {
+ Client* next;
+ Client** pref;
+ int channel;
+ char registered;
+ FDHandler* fdhandler;
+ Multiplexer* multiplexer;
+};
+
+struct Multiplexer {
+ Client* clients;
+ int last_channel;
+ Serial serial[1];
+ Looper looper[1];
+ FDHandlerList fdhandlers[1];
+};
+
+
+static int multiplexer_open_channel( Multiplexer* mult, Packet* p );
+static void multiplexer_close_channel( Multiplexer* mult, int channel );
+static void multiplexer_serial_send( Multiplexer* mult, int channel, Packet* p );
+
+static void
+client_dump( Client* c, Packet* p, const char* funcname )
+{
+ T("%s: client %p (%d): %3d bytes: '%s'",
+ funcname, c, c->fdhandler->fd,
+ p->len, quote(p->data, p->len));
+}
+
+/* destroy a client */
+static void
+client_free( Client* c )
+{
+ /* remove from list */
+ c->pref[0] = c->next;
+ if (c->next)
+ c->next->pref = c->pref;
+
+ c->channel = -1;
+ c->registered = 0;
+
+ /* gently ask the FDHandler to shutdown to
+ * avoid losing queued outgoing packets */
+ if (c->fdhandler != NULL) {
+ fdhandler_shutdown(c->fdhandler);
+ c->fdhandler = NULL;
+ }
+
+ xfree(c);
+}
+
+
+/* a function called when a client socket receives data */
+static void
+client_fd_receive( Client* c, Packet* p )
+{
+ client_dump(c, p, __FUNCTION__);
+
+ if (c->registered) {
+ /* the client is registered, just send the
+ * data through the serial port
+ */
+ multiplexer_serial_send(c->multiplexer, c->channel, p);
+ return;
+ }
+
+ if (c->channel > 0) {
+ /* the client is waiting registration results.
+ * this should not happen because the client
+ * should wait for our 'ok' or 'ko'.
+ * close the connection.
+ */
+ D("%s: bad client sending data before end of registration",
+ __FUNCTION__);
+ BAD_CLIENT:
+ packet_free(&p);
+ client_free(c);
+ return;
+ }
+
+ /* the client hasn't registered a service yet,
+ * so this must be the name of a service, call
+ * the multiplexer to start registration for
+ * it.
+ */
+ D("%s: attempting registration for service '%.*s'",
+ __FUNCTION__, p->len, p->data);
+ c->channel = multiplexer_open_channel(c->multiplexer, p);
+ if (c->channel < 0) {
+ D("%s: service name too long", __FUNCTION__);
+ goto BAD_CLIENT;
+ }
+ D("%s: -> received channel id %d", __FUNCTION__, c->channel);
+ packet_free(&p);
+}
+
+
+/* a function called when the client socket is closed. */
+static void
+client_fd_close( Client* c )
+{
+ T("%s: client %p (%d)", __FUNCTION__, c, c->fdhandler->fd);
+
+ /* no need to shutdown the FDHandler */
+ c->fdhandler = NULL;
+
+ /* tell the emulator we're out */
+ if (c->channel > 0)
+ multiplexer_close_channel(c->multiplexer, c->channel);
+
+ /* free the client */
+ client_free(c);
+}
+
+/* a function called when the multiplexer received a registration
+ * response from the emulator for a given client.
+ */
+static void
+client_registration( Client* c, int registered )
+{
+ Packet* p = packet_alloc();
+
+ /* sends registration status to client */
+ if (!registered) {
+ D("%s: registration failed for client %d", __FUNCTION__, c->channel);
+ memcpy( p->data, "KO", 2 );
+ p->len = 2;
+ } else {
+ D("%s: registration succeeded for client %d", __FUNCTION__, c->channel);
+ memcpy( p->data, "OK", 2 );
+ p->len = 2;
+ }
+ client_dump(c, p, __FUNCTION__);
+ fdhandler_enqueue(c->fdhandler, p);
+
+ /* now save registration state
+ */
+ c->registered = registered;
+ if (!registered) {
+ /* allow the client to try registering another service */
+ c->channel = -1;
+ }
+}
+
+/* send data to a client */
+static void
+client_send( Client* c, Packet* p )
+{
+ client_dump(c, p, __FUNCTION__);
+ fdhandler_enqueue(c->fdhandler, p);
+}
+
+
+/* Create new client socket handler */
+static Client*
+client_new( Multiplexer* mult,
+ int fd,
+ FDHandlerList* pfdhandlers,
+ Client** pclients )
+{
+ Client* c;
+ Receiver recv;
+
+ xnew(c);
+
+ c->multiplexer = mult;
+ c->next = NULL;
+ c->pref = &c->next;
+ c->channel = -1;
+ c->registered = 0;
+
+ recv.user = c;
+ recv.post = (PostFunc) client_fd_receive;
+ recv.close = (CloseFunc) client_fd_close;
+
+ c->fdhandler = fdhandler_new( fd, pfdhandlers, &recv );
+
+ /* add to client list */
+ c->next = *pclients;
+ c->pref = pclients;
+ *pclients = c;
+ if (c->next)
+ c->next->pref = &c->next;
+
+ return c;
+}
+
/** GLOBAL MULTIPLEXER
**/
-typedef struct {
- Looper looper[1];
- Serial serial[1];
- Channel* channels;
- uint16_t channel_last;
-} Multiplexer;
+/* find a client by its channel */
+static Client*
+multiplexer_find_client( Multiplexer* mult, int channel )
+{
+ Client* c = mult->clients;
-/* receive a packet from the serial port, send it to the relevant client/channel */
-static void multiplexer_receive_serial( Multiplexer* m, Packet* p );
+ for ( ; c != NULL; c = c->next ) {
+ if (c->channel == channel)
+ return c;
+ }
+ return NULL;
+}
+
+/* handle control messages coming from the serial port
+ * on CONTROL_CHANNEL.
+ */
+static void
+multiplexer_handle_control( Multiplexer* mult, Packet* p )
+{
+ /* connection registration success */
+ if (p->len == 15 && !memcmp(p->data, "ok:connect:", 11)) {
+ int channel = hex2int(p->data+11, 4);
+ Client* client = multiplexer_find_client(mult, channel);
+
+ /* note that 'client' can be NULL if the corresponding
+ * socket was closed before the emulator response arrived.
+ */
+ if (client != NULL) {
+ client_registration(client, 1);
+ }
+ goto EXIT;
+ }
+
+ /* connection registration failure */
+ if (p->len >= 15 && !memcmp(p->data, "ko:connect:",11)) {
+ int channel = hex2int(p->data+11, 4);
+ Client* client = multiplexer_find_client(mult, channel);
+
+ if (client != NULL)
+ client_registration(client, 0);
+
+ goto EXIT;
+ }
+
+ /* emulator-induced client disconnection */
+ if (p->len == 15 && !memcmp(p->data, "disconnect:",11)) {
+ int channel = hex2int(p->data+11, 4);
+ Client* client = multiplexer_find_client(mult, channel);
+
+ if (client != NULL)
+ client_free(client);
+
+ goto EXIT;
+ }
+
+ D("%s: unknown control message: '%.*s'",
+ __FUNCTION__, p->len, p->data);
+
+EXIT:
+ packet_free(&p);
+}
+
+/* a function called when an incoming packet comes from the serial port */
+static void
+multiplexer_serial_receive( Multiplexer* mult, Packet* p )
+{
+ Client* client;
+
+ if (p->channel == CHANNEL_CONTROL) {
+ multiplexer_handle_control(mult, p);
+ return;
+ }
+
+ client = multiplexer_find_client(mult, p->channel);
+ if (client != NULL) {
+ client_send(client, p);
+ return;
+ }
+
+ D("%s: discarding packet for unknown channel %d", __FUNCTION__, p->channel);
+ packet_free(&p);
+}
+
+/* a function called when the serial reader closes */
+static void
+multiplexer_serial_close( Multiplexer* mult )
+{
+ fatal("unexpected close of serial reader");
+}
+
+/* a function called to send a packet to the serial port */
+static void
+multiplexer_serial_send( Multiplexer* mult, int channel, Packet* p )
+{
+ p->channel = channel;
+ serial_send( mult->serial, p );
+}
+
+
+
+/* a function used by a client to allocate a new channel id and
+ * ask the emulator to open it. 'service' must be a packet containing
+ * the name of the service in its payload.
+ *
+ * returns -1 if the service name is too long.
+ *
+ * notice that client_registration() will be called later when
+ * the answer arrives.
+ */
+static int
+multiplexer_open_channel( Multiplexer* mult, Packet* service )
+{
+ Packet* p = packet_alloc();
+ int len, channel;
+
+ /* find a free channel number, assume we don't have many
+ * clients here. */
+ {
+ Client* c;
+ TRY_AGAIN:
+ channel = (++mult->last_channel) & 0xff;
+
+ for (c = mult->clients; c != NULL; c = c->next)
+ if (c->channel == channel)
+ goto TRY_AGAIN;
+ }
+
+ len = snprintf((char*)p->data, sizeof p->data, "connect:%.*s:%04x", service->len, service->data, channel);
+ if (len >= (int)sizeof(p->data)) {
+ D("%s: weird, service name too long (%d > %d)", __FUNCTION__, len, sizeof(p->data));
+ packet_free(&p);
+ return -1;
+ }
+ p->channel = CHANNEL_CONTROL;
+ p->len = len;
+
+ serial_send(mult->serial, p);
+ return channel;
+}
+
+/* used to tell the emulator a channel was closed by a client */
+static void
+multiplexer_close_channel( Multiplexer* mult, int channel )
+{
+ Packet* p = packet_alloc();
+ int len = snprintf((char*)p->data, sizeof(p->data), "disconnect:%04x", channel);
+
+ if (len > (int)sizeof(p->data)) {
+ /* should not happen */
+ return;
+ }
+
+ p->channel = CHANNEL_CONTROL;
+ p->len = len;
+
+ serial_send(mult->serial, p);
+}
+
+/* this function is used when a new connection happens on the control
+ * socket.
+ */
+static void
+multiplexer_control_accept( Multiplexer* m, Packet* p )
+{
+ /* the file descriptor for the new socket connection is
+ * in p->channel. See fdhandler_accept_event() */
+ int fd = p->channel;
+ Client* client = client_new( m, fd, m->fdhandlers, &m->clients );
+
+ D("created client %p listening on fd %d", client, fd);
+
+ /* free dummy packet */
+ packet_free(&p);
+}
+
+static void
+multiplexer_control_close( Multiplexer* m )
+{
+ fatal("unexpected multiplexer control close");
+}
static void
multiplexer_init( Multiplexer* m, const char* serial_dev )
{
- int fd;
+ int fd, control_fd;
Receiver recv;
+ /* initialize looper and fdhandlers list */
looper_init( m->looper );
+ fdhandler_list_init( m->fdhandlers, m->looper );
- fd = open(serial_dev, O_RDWR);
+ /* open the serial port */
+ do {
+ fd = open(serial_dev, O_RDWR);
+ } while (fd < 0 && errno == EINTR);
+
if (fd < 0) {
fatal( "%s: could not open '%s': %s", __FUNCTION__, serial_dev,
strerror(errno) );
@@ -1120,141 +1617,34 @@
tcsetattr( fd, TCSANOW, &ios );
}
+ /* initialize the serial reader/writer */
recv.user = m;
- recv.post = (PostFunc) multiplexer_receive_serial;
- recv.close = NULL;
+ recv.post = (PostFunc) multiplexer_serial_receive;
+ recv.close = (CloseFunc) multiplexer_serial_close;
- serial_init( m->serial, fd, m->looper, &recv );
+ serial_init( m->serial, fd, m->fdhandlers, &recv );
- m->channels = NULL;
- m->channel_last = CHANNEL_CONTROL+1;
-}
+ /* open the qemud control socket */
+ recv.user = m;
+ recv.post = (PostFunc) multiplexer_control_accept;
+ recv.close = (CloseFunc) multiplexer_control_close;
-static void
-multiplexer_add_channel( Multiplexer* m, int fd, const char* name, ChannelType ctype )
-{
- Channel* c;
- Receiver recv;
-
- /* send channel client data directly to the serial port */
- recv.user = m->serial;
- recv.post = (PostFunc) serial_send;
- recv.close = (CloseFunc) client_close;
-
- /* connect each channel directly to the serial port */
- c = channel_new( fd, ctype, name, m->channel_last, m->looper, &recv );
- channel_link( c, &m->channels );
-
- m->channel_last += 1;
- if (m->channel_last <= CHANNEL_CONTROL)
- m->channel_last += 1;
-}
-
-
-static void
-multiplexer_done( Multiplexer* m )
-{
- while (m->channels)
- channel_close(m->channels);
-
- serial_done( m->serial );
- looper_done( m->looper );
-}
-
-
-static void
-multiplexer_send_answer( Multiplexer* m, Packet* p, const char* answer )
-{
- p->len = strlen( answer );
- if (p->len >= MAX_PAYLOAD)
- p->len = MAX_PAYLOAD-1;
-
- memcpy( (char*)p->data, answer, p->len );
- p->channel = CHANNEL_CONTROL;
-
- serial_send( m->serial, p );
-}
-
-
-static void
-multiplexer_handle_connect( Multiplexer* m, Packet* p, char* name )
-{
- int n;
- Channel* c;
-
- if (p->len >= MAX_PAYLOAD) {
- multiplexer_send_answer( m, p, "ko:connect:bad name" );
- return;
- }
- p->data[p->len] = 0;
-
- for (c = m->channels; c != NULL; c = c->next)
- if ( !strcmp(c->name, name) )
- break;
-
- if (c == NULL) {
- D("can't connect to unknown channel '%s'", name);
- multiplexer_send_answer( m, p, "ko:connect:bad name" );
- return;
+ fd = android_get_control_socket(CONTROL_SOCKET_NAME);
+ if (fd < 0) {
+ fatal("couldn't get fd for control socket '%s'", CONTROL_SOCKET_NAME);
}
- p->channel = CHANNEL_CONTROL;
- p->len = snprintf( (char*)p->data, MAX_PAYLOAD,
- "ok:connect:%s:%02x", c->name, c->index );
+ fdhandler_new_accept( fd, m->fdhandlers, &recv );
- serial_send( m->serial, p );
+ /* initialize clients list */
+ m->clients = NULL;
}
-
-static void
-multiplexer_receive_serial( Multiplexer* m, Packet* p )
-{
- Channel* c = m->channels;
-
- /* check the destination channel index */
- if (p->channel != CHANNEL_CONTROL) {
- Channel* c;
-
- for (c = m->channels; c; c = c->next ) {
- if (c->index == p->channel) {
- channel_send( c, p );
- break;
- }
- }
- if (c == NULL) {
- D("ignoring %d bytes packet for unknown channel index %d",
- p->len, p->channel );
- packet_free(&p);
- }
- }
- else /* packet addressed to the control channel */
- {
- D("received control message: '%.*s'", p->len, p->data);
- if (p->len > 8 && strncmp( (char*)p->data, "connect:", 8) == 0) {
- multiplexer_handle_connect( m, p, (char*)p->data + 8 );
- } else {
- /* unknown command */
- multiplexer_send_answer( m, p, "ko:unknown command" );
- }
- return;
- }
-}
-
-
/** MAIN LOOP
**/
static Multiplexer _multiplexer[1];
-#define QEMUD_PREFIX "qemud_"
-
-static const struct { const char* name; ChannelType ctype; } default_channels[] = {
- { "gsm", CHANNEL_DUPLEX }, /* GSM AT command channel, used by commands/rild/rild.c */
- { "gps", CHANNEL_BROADCAST }, /* GPS NMEA commands, used by libs/hardware_legacy/qemu_gps.c */
- { "control", CHANNEL_DUPLEX }, /* Used for power/leds/vibrator/etc... */
- { NULL, 0 }
-};
-
int main( void )
{
Multiplexer* m = _multiplexer;
@@ -1303,31 +1693,6 @@
multiplexer_init( m, buff );
}
- D("multiplexer inited, creating default channels");
-
- /* now setup all default channels */
- {
- int nn;
-
- for (nn = 0; default_channels[nn].name != NULL; nn++) {
- char control_name[32];
- int fd;
- Channel* chan;
- const char* name = default_channels[nn].name;
- ChannelType ctype = default_channels[nn].ctype;
-
- snprintf(control_name, sizeof(control_name), "%s%s",
- QEMUD_PREFIX, name);
-
- if ((fd = android_get_control_socket(control_name)) < 0) {
- D("couldn't get fd for control socket '%s'", name);
- continue;
- }
- D( "got control socket '%s' on fd %d", control_name, fd);
- multiplexer_add_channel( m, fd, name, ctype );
- }
- }
-
D( "entering main loop");
looper_loop( m->looper );
D( "unexpected termination !!" );