aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES.TXT8
-rw-r--r--android/avd/info.c15
-rw-r--r--android/hw-qemud.c282
-rw-r--r--android/main.c35
-rw-r--r--docs/ANDROID-QEMUD-SERVICES.TXT155
-rw-r--r--docs/ANDROID-QEMUD.TXT18
6 files changed, 418 insertions, 95 deletions
diff --git a/CHANGES.TXT b/CHANGES.TXT
index 93d3689..d3c301d 100644
--- a/CHANGES.TXT
+++ b/CHANGES.TXT
@@ -121,6 +121,14 @@ OTHER:
- Using '-no-audio' no longer disables sound hardware emulation. It simply
mutes the emulator program on the host.
+- The window title bar changes when you toggle persistent trackball mode
+ (F6 by default). It will display something like the following:
+
+ "Press F6 to exit trackball mode ..."
+
+ The actual text depends on your key binding configuration. This is to help
+ people toggle the mode by accident.
+
==============================================================================
Changes between 1.6 and 1.7
diff --git a/android/avd/info.c b/android/avd/info.c
index ec74249..1bdb59c 100644
--- a/android/avd/info.c
+++ b/android/avd/info.c
@@ -1281,9 +1281,18 @@ _getBuildSkin( AvdInfo* i, AvdInfoParams* params )
#define PREBUILT_SKINS_DIR "development/emulator/skins"
- /* the (current) default skin directory */
- p = bufprint( temp, end, "%s/%s",
- i->androidBuildRoot, PREBUILT_SKINS_DIR );
+ do {
+ /* try in <sysdir>/../skins first */
+ p = bufprint( temp, end, "%s/../skins",
+ i->androidBuildRoot );
+ if (path_exists(temp))
+ break;
+
+ /* the (current) default skin directory */
+ p = bufprint( temp, end, "%s/%s",
+ i->androidBuildRoot, PREBUILT_SKINS_DIR );
+ } while (0);
+
} else {
p = bufprint( temp, end, "%s", skinDir );
}
diff --git a/android/hw-qemud.c b/android/hw-qemud.c
index 25d8717..cc28e63 100644
--- a/android/hw-qemud.c
+++ b/android/hw-qemud.c
@@ -40,93 +40,19 @@
#define MAX_FRAME_PAYLOAD 65535
+/* define SUPPORT_LEGACY_QEMUD to 1 if you want to support
+ * talking to a legacy qemud daemon. See docs/ANDROID-QEMUD.TXT
+ * for details.
+ */
+#define SUPPORT_LEGACY_QEMUD 1
+
/*
- * 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 binds one unix local stream socket (/dev/socket/qemud, created
- * by init through /system/etc/init.goldfish.rc).
- *
- *
- * emulator <==serial==> qemud <---> /dev/socket/qemud <-+--> client1
- * |
- * +--> client2
- *
- * - the protocol used on the serial connection is pretty simple:
- *
- * offset size description
- * 0 2 2-char hex string giving the destination or
- * source channel
- * 2 4 4-char hex string giving the payload size
- * 6 n the message payload
- *
- * for emulator->system messages, the 'channel' index indicates
- * to which channel the payload must be sent
- *
- * for system->emulator messages, the 'channel' index indicates from
- * which channel the payload comes from.
- *
- * the payload size is limited to MAX_SERIAL_PAYLOAD bytes.
- *
- * - communication between a client and qemud is stream based, but supports
- * an optional framing mode which can be used to send packets larger than
- * MAX_SERIAL_PAYLOAD bytes and provides packet bounding.
- *
- * offset size description
- * 0 4 4-char hex string giving the payload size
- * 6 n the message payload
- *
- * The size of the payload is then limited to 65535 bytes.
- *
- * - 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:
- *
- * * the client connects to /dev/socket/qemud
- *
- * * the client sends the service name through the socket, as
- * <service-name>
- *
- * * 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):
- *
- * connect:<id>:<name>
+ * This implements support for the 'qemud' multiplexing communication
+ * channel between clients running in the emulated system and 'services'
+ * provided by the emulator.
*
- * where <name> is the service name, and <id> is a 2-hexchar
- * number corresponding to the channel number.
+ * For additional details, please read docs/ANDROID-QEMUD.TXT
*
- * * in case of success, the emulator responds through channel 0
- * with:
- *
- * ok:connect:<id>
- *
- * after this, all messages between the client and the emulator
- * are passed in pass-through mode. If the client closes the
- * connection, qemud sends the following to the emulator:
- *
- * disconnect:<id>
- *
- * * if the emulator refuses the service connection, it will
- * send the following through channel 0:
- *
- * ko:connect:<id>:reason-for-failure
- *
- * * any command sent through channel 0 to the emulator that is
- * not properly recognized will be answered by:
- *
- * ko:unknown command
- *
- * Internally, the daemon maintains a "Client" object for each client
- * connection (i.e. accepting socket connection).
*/
/*
@@ -154,12 +80,18 @@
/** HANDLING INCOMING DATA FRAMES
**/
+/* A QemudSink is just a handly data structure that is used to
+ * read a fixed amount of bytes into a buffer
+ */
typedef struct QemudSink {
int len;
int size;
uint8_t* buff;
} QemudSink;
+/* reset a QemudSink, i.e. provide a new destination buffer address
+ * and its size in bytes.
+ */
static void
qemud_sink_reset( QemudSink* ss, int size, uint8_t* buffer )
{
@@ -168,6 +100,12 @@ qemud_sink_reset( QemudSink* ss, int size, uint8_t* buffer )
ss->buff = buffer;
}
+/* try to fill the sink by reading bytes from the source buffer
+ * '*pmsg' which contains '*plen' bytes
+ *
+ * this functions updates '*pmsg' and '*plen', and returns
+ * 1 if the sink's destination buffer is full, or 0 otherwise.
+ */
static int
qemud_sink_fill( QemudSink* ss, const uint8_t* *pmsg, int *plen)
{
@@ -187,6 +125,9 @@ qemud_sink_fill( QemudSink* ss, const uint8_t* *pmsg, int *plen)
return (ss->len == ss->size);
}
+/* returns the number of bytes needed to fill a sink's destination
+ * buffer.
+ */
static int
qemud_sink_needed( QemudSink* ss )
{
@@ -215,6 +156,17 @@ qemud_sink_needed( QemudSink* ss )
#define CHANNEL_OFFSET 0
#define CHANNEL_SIZE 2
+#if SUPPORT_LEGACY_QEMUD
+typedef enum {
+ QEMUD_VERSION_UNKNOWN,
+ QEMUD_VERSION_LEGACY,
+ QEMUD_VERSION_NORMAL
+} QemudVersion;
+
+# define LEGACY_LENGTH_OFFSET 0
+# define LEGACY_CHANNEL_OFFSET 4
+#endif
+
/* length of the framed header */
#define FRAME_HEADER_SIZE 4
@@ -233,6 +185,9 @@ typedef struct QemudSerial {
int overflow;
int in_size;
int in_channel;
+#if SUPPORT_LEGACY_QEMUD
+ QemudVersion version;
+#endif
QemudSink header[1];
QemudSink payload[1];
uint8_t data0[MAX_SERIAL_PAYLOAD+1];
@@ -293,9 +248,33 @@ qemud_serial_read( void* opaque, const uint8_t* from, int len )
if (!qemud_sink_fill(s->header, (const uint8_t**)&from, &len))
break;
+#if SUPPORT_LEGACY_QEMUD
+ if (s->version == QEMUD_VERSION_UNKNOWN) {
+ /* if we receive "001200" as the first header, then we
+ * detected a legacy qemud daemon. See the comments
+ * in qemud_serial_send_legacy_probe() for details.
+ */
+ if ( !memcmp(s->data0, "001200", 6) ) {
+ D("%s: legacy qemud detected.", __FUNCTION__);
+ s->version = QEMUD_VERSION_LEGACY;
+ } else {
+ D("%s: normal qemud detected.", __FUNCTION__);
+ s->version = QEMUD_VERSION_NORMAL;
+ }
+ }
+
+ if (s->version == QEMUD_VERSION_LEGACY) {
+ s->in_size = hex2int( s->data0 + LEGACY_LENGTH_OFFSET, LENGTH_SIZE );
+ s->in_channel = hex2int( s->data0 + LEGACY_CHANNEL_OFFSET, CHANNEL_SIZE );
+ } else {
+ s->in_size = hex2int( s->data0 + LENGTH_OFFSET, LENGTH_SIZE );
+ s->in_channel = hex2int( s->data0 + CHANNEL_OFFSET, CHANNEL_SIZE );
+ }
+#else
/* extract payload length + channel id */
s->in_size = hex2int( s->data0 + LENGTH_OFFSET, LENGTH_SIZE );
s->in_channel = hex2int( s->data0 + CHANNEL_OFFSET, CHANNEL_SIZE );
+#endif
s->header->len = 0;
if (s->in_size <= 0 || s->in_channel < 0) {
@@ -332,6 +311,68 @@ qemud_serial_read( void* opaque, const uint8_t* from, int len )
}
}
+
+#if SUPPORT_LEGACY_QEMUD
+static void
+qemud_serial_send_legacy_probe( QemudSerial* s )
+{
+ /* we're going to send a specially crafted packet to the qemud
+ * daemon, this will help us determine whether we're talking
+ * to a legacy or a normal daemon.
+ *
+ * the trick is to known that a legacy daemon uses the following
+ * header:
+ *
+ * <length><channel><payload>
+ *
+ * while the normal one uses:
+ *
+ * <channel><length><payload>
+ *
+ * where <channel> is a 2-hexchar string, and <length> a 4-hexchar
+ * string.
+ *
+ * if we send a header of "000100", it is interpreted:
+ *
+ * - as the header of a 1-byte payload by the legacy daemon
+ * - as the header of a 256-byte payload by the normal one.
+ *
+ * we're going to send something that looks like:
+ *
+ * "000100" + "X" +
+ * "000b00" + "connect:gsm" +
+ * "000b00" + "connect:gps" +
+ * "000f00" + "connect:control" +
+ * "00c210" + "0"*194
+ *
+ * the normal daemon will interpret this as a 256-byte payload
+ * for channel 0, with garbage content ("X000b00conn...") which
+ * will be silently ignored.
+ *
+ * on the other hand, the legacy daemon will see it as a
+ * series of packets:
+ *
+ * one message "X" on channel 0, which will force the daemon
+ * to send back "001200ko:unknown command" as its first answer.
+ *
+ * three "connect:<xxx>" messages used to receive the channel
+ * numbers of the three legacy services implemented by the daemon.
+ *
+ * a garbage packet of 194 zeroes for channel 16, which will be
+ * silently ignored.
+ */
+ uint8_t tab[194];
+
+ memset(tab, 0, sizeof(tab));
+ qemu_chr_write(s->cs, (uint8_t*)"000100X", 7);
+ qemu_chr_write(s->cs, (uint8_t*)"000b00connect:gsm", 17);
+ qemu_chr_write(s->cs, (uint8_t*)"000b00connect:gps", 17);
+ qemu_chr_write(s->cs, (uint8_t*)"000f00connect:control", 21);
+ qemu_chr_write(s->cs, (uint8_t*)"00c210", 6);
+ qemu_chr_write(s->cs, tab, sizeof(tab));
+}
+#endif /* SUPPORT_LEGACY_QEMUD */
+
/* intialize a QemudSerial object with a charpipe endpoint
* and a receiver.
*/
@@ -351,6 +392,11 @@ qemud_serial_init( QemudSerial* s,
s->in_size = 0;
s->in_channel = -1;
+#if SUPPORT_LEGACY_QEMUD
+ s->version = QEMUD_VERSION_UNKNOWN;
+ qemud_serial_send_legacy_probe(s);
+#endif
+
qemu_chr_add_handlers( cs,
qemud_serial_can_read,
qemud_serial_read,
@@ -391,13 +437,25 @@ qemud_serial_send( QemudSerial* s,
avail = MAX_SERIAL_PAYLOAD;
/* write this packet's header */
+#if SUPPORT_LEGACY_QEMUD
+ if (s->version == QEMUD_VERSION_LEGACY) {
+ int2hex(header + LEGACY_LENGTH_OFFSET, LENGTH_SIZE, avail);
+ int2hex(header + LEGACY_CHANNEL_OFFSET, CHANNEL_SIZE, channel);
+ } else {
+ int2hex(header + LENGTH_OFFSET, LENGTH_SIZE, avail);
+ int2hex(header + CHANNEL_OFFSET, CHANNEL_SIZE, channel);
+ }
+#else
int2hex(header + LENGTH_OFFSET, LENGTH_SIZE, avail);
int2hex(header + CHANNEL_OFFSET, CHANNEL_SIZE, channel);
+#endif
+ T("%s: '%.*s'", __FUNCTION__, HEADER_SIZE, header);
qemu_chr_write(s->cs, header, HEADER_SIZE);
/* insert frame header when needed */
if (framing) {
int2hex(frame, FRAME_HEADER_SIZE, msglen);
+ T("%s: '%.*s'", __FUNCTION__, FRAME_HEADER_SIZE, frame);
qemu_chr_write(s->cs, frame, FRAME_HEADER_SIZE);
avail -= FRAME_HEADER_SIZE;
len -= FRAME_HEADER_SIZE;
@@ -405,6 +463,7 @@ qemud_serial_send( QemudSerial* s,
}
/* write message content */
+ T("%s: '%.*s'", __FUNCTION__, avail, msg);
qemu_chr_write(s->cs, msg, avail);
msg += avail;
len -= avail;
@@ -894,6 +953,61 @@ qemud_multiplexer_control_recv( void* opaque,
return;
}
+#if SUPPORT_LEGACY_QEMUD
+ /* an ok:connect:<service>:<id> message can be received if we're
+ * talking to a legacy qemud daemon, i.e. one running in a 1.0 or
+ * 1.1 system image.
+ *
+ * we should treat is as a normal "connect:" attempt, except that
+ * we must not send back any acknowledgment.
+ */
+ if (msglen > 11 && !memcmp(msg, "ok:connect:", 11)) {
+ const char* service_name = (const char*)msg + 11;
+ char* q = strchr(service_name, ':');
+ int channel;
+
+ if (q == NULL || q+3 != (char*)msgend) {
+ D("%s: malformed legacy connect message: '%.*s' (offset=%d)",
+ __FUNCTION__, msglen, (const char*)msg, q ? q-(char*)msg : -1);
+ return;
+ }
+ *q++ = 0; /* zero-terminate service name */
+ channel = hex2int((uint8_t*)q, 2);
+ if (channel <= 0) {
+ D("%s: malformed legacy channel id '%.*s",
+ __FUNCTION__, 2, q);
+ return;
+ }
+
+ switch (mult->serial->version) {
+ case QEMUD_VERSION_UNKNOWN:
+ mult->serial->version = QEMUD_VERSION_LEGACY;
+ D("%s: legacy qemud daemon detected.", __FUNCTION__);
+ break;
+
+ case QEMUD_VERSION_LEGACY:
+ /* nothing unusual */
+ break;
+
+ default:
+ D("%s: weird, ignoring legacy qemud control message: '%.*s'",
+ __FUNCTION__, msglen, msg);
+ return;
+ }
+
+ /* "hw-control" was called "control" in 1.0/1.1 */
+ if (!strcmp(service_name,"control"))
+ service_name = "hw-control";
+
+ qemud_multiplexer_connect(mult, service_name, channel);
+ return;
+ }
+
+ /* anything else, don't answer for legacy */
+ if (mult->serial->version == QEMUD_VERSION_LEGACY)
+ return;
+#endif /* SUPPORT_LEGACY_QEMUD */
+
/* anything else is a problem */
p = bufprint(tmp, end, "ko:unknown command");
qemud_serial_send(mult->serial, 0, 0, (uint8_t*)tmp, p-tmp);
diff --git a/android/main.c b/android/main.c
index efa79aa..ed005d2 100644
--- a/android/main.c
+++ b/android/main.c
@@ -484,14 +484,40 @@ android_emulator_set_window_scale( double scale, int is_dpi )
static void
qemulator_set_title( QEmulator* emulator )
{
- char temp[64];
+ char temp[128], *p=temp, *end=p+sizeof temp;;
if (emulator->window == NULL)
return;
- snprintf( temp, sizeof(temp), "Android Emulator (%s:%d)",
- avdInfo_getName( android_avdInfo ),
- android_base_port );
+ if (emulator->show_trackball) {
+ SkinKeyBinding bindings[ SKIN_KEY_COMMAND_MAX_BINDINGS ];
+ int count;
+
+ count = skin_keyset_get_bindings( android_keyset,
+ SKIN_KEY_COMMAND_TOGGLE_TRACKBALL,
+ bindings );
+
+ if (count > 0) {
+ int nn;
+ p = bufprint( p, end, "Press " );
+ for (nn = 0; nn < count; nn++) {
+ if (nn > 0) {
+ if (nn < count-1)
+ p = bufprint(p, end, ", ");
+ else
+ p = bufprint(p, end, " or ");
+ }
+ p = bufprint(p, end, "%s",
+ skin_key_symmod_to_str( bindings[nn].sym,
+ bindings[nn].mod ) );
+ }
+ p = bufprint(p, end, " to leave trackball mode. ");
+ }
+ }
+
+ p = bufprint(p, end, "Android Emulator (%s:%d)",
+ avdInfo_getName( android_avdInfo ),
+ android_base_port );
skin_window_set_title( emulator->window, temp );
}
@@ -778,6 +804,7 @@ handle_key_command( void* opaque, SkinKeyCommand command, int down )
case SKIN_KEY_COMMAND_TOGGLE_TRACKBALL:
emulator->show_trackball = !emulator->show_trackball;
skin_window_show_trackball( emulator->window, emulator->show_trackball );
+ qemulator_set_title( emulator );
break;
case SKIN_KEY_COMMAND_ONION_ALPHA_UP:
diff --git a/docs/ANDROID-QEMUD-SERVICES.TXT b/docs/ANDROID-QEMUD-SERVICES.TXT
new file mode 100644
index 0000000..24636e5
--- /dev/null
+++ b/docs/ANDROID-QEMUD-SERVICES.TXT
@@ -0,0 +1,155 @@
+ANDROID QEMUD SERVICES
+
+The docs/ANDROID-QEMUD.TXT document describes how clients running in the
+emulated system can communicate with the emulator through the 'qemud'
+multiplexing daemon.
+
+This document lists all services officially provided by the emulator to
+clients. Each service is identified by a unique name. There is no provision
+for versioning. Instead, if you need new features, create a new service
+with a slightly different name and modify clients to use it in the system
+image.
+
+
+"gsm" service:
+--------------
+
+ The GSM service provides a communication channel where GSM/GPRS AT commands
+ can be exchanged between the emulated system and an emulated modem.
+
+ The messages consist in un-framed character messages as they appear in a
+ regular AT command channel (i.e. terminated by \r\n most of the time).
+
+ There can be only 1 client talking to the modem, since the AT command
+ protocol does not allow for anything more complex.
+
+ Implementation: telephony/modem_driver.c
+ Since: SDK 1.0
+
+"gps" service:
+--------------
+
+ The GPS service is used to broadcast NMEA 0183 sentences containing fix
+ information to all clients. The service doesn't listen to clients at all.
+
+ All sentences are un-framed and end with a \n character.
+
+ Implementation: android/gps.c
+ Since: SDK 1.0
+
+
+"hw-control" / "control" service:
+---------------------
+
+ This service is named "control" in 1.0 and 1.1, and "hw-control"
+ in 1.5 and later releases of the SDK.
+
+ This service is used to allow the emulated system to control various aspects
+ of hardware emulation. The corresponding clients are in
+ hardware/libhardware_legacy in the Android source tree.
+
+ All messages use the optional framing described in ANDROID-QEMUD.TXT.
+ Only one client can talk with the service at any one time, but clients
+ quickly connect/disconnect to the service anyway.
+
+ Supported messages are:
+
+ 1/ Client sends "power:light:brightness:<lightname>:<val>", where
+ <lightname> is the name of a light and <val> is an decimal value
+ between 0 (off) and 255 (brightest). Valid values for 'light' are:
+
+ lcd_backlight
+ keyboard_backlight
+ button_backlight
+
+ Currently, only 'lcd_backlight' is supported by the emulator.
+ Note that the brightness value doesn't scale linearly on physical
+ devices.
+
+ 2/ Client sends "set_led_state:<color-rgb>:<on-ms>:<off-ms>" where
+ <color-rgb> is an 8-hexchar value describing an xRGB color, and
+ <on-ms> and <off-ms> are decimal values expressing timeout in
+ milliseconds.
+
+ This is used to modify the color of the notification LED on the
+ emulated phone as well as provide on/off timeouts for flashing.
+
+ cCrrently unsupported by the emulator.
+
+ 3/ Client sends "vibrator:<timeout>", where <timeout> is a decimal value.
+ used to enable vibrator emulation for <timeout> milli-seconds.
+
+ Currently unsupported by the emulator.
+
+
+ Implementation: android/hw-control.c
+ Since: SDK 1.0
+
+
+"sensors" service:
+------------------
+
+ This service is used for sensor emulation. All messages are framed and the
+ protocol is the following:
+
+ 1/ Clients initially sends "list-sensors" and receives a single string
+ containing a decimal mask value. The value is a set of bit-flags
+ indicating which sensors are provided by the hardware emulation. The
+ bit flags are defined as:
+
+ bit 0: accelerometer
+ bit 1: compass
+ bit 2: orientation
+ bit 3: temperature
+
+ 2/ Client sends "set-delay:<delay-ms>", where <delay-ms> is a decimal
+ string, to set the minimum delay in milliseconds between sensor event
+ reports it wants to receive.
+
+ 3/ Client sends "wake", the service must immediately send back "wake" as
+ an answer. This is used to simplify parts of the client emulation.
+
+ 4/ Client sends "set:<sensor-name>:<flag>", where <sensor-name> is the
+ name of one of the emulated sensors, and <flag> is either "0" or "1".
+ This is used to enable or disable the report of data from a given
+ emulated sensor hardware.
+
+ the list of valid <sensor-name> values is the following:
+
+ acceleration : for the accelerometer
+ magnetic-field : for the compass
+ orientation : for the orientation sensor
+ temperature : for the temperature sensor
+
+
+ 5/ If at least one of the emulated sensors has been enabled with
+ "set:<name>:1", then the service should broadcast periodically the
+ state of sensors.
+
+ this is done by broadcasting a series of framed messages that look
+ like:
+
+ acceleration:<x>:<y>:<z>
+ magnetic-field:<x>:<y>:<z>
+ orientation:<azimuth>:<pitch>:<roll>
+ temperature:<celsius>
+ sync:<time_us>
+
+ Note that each line, corresponds to an independent framed message.
+ Each line, except the last one, is optional and should only be
+ produced if the corresponding sensor reporting has been enabled
+ through "set:<name>:1".
+
+ <x>, <y>, <z>, <azimuth>, <pitch>, <roll> and <celsius> are floating
+ point values written as strings with the "%g" printf formatting option.
+
+ The last 'sync' message is required to end the broadcast and contains
+ the current VM clock time in micro-seconds. The client will adjust it
+ internally to only care about differences between sensor broadcasts.
+
+ If reporting is disabled for all sensors, no broadcast message needs
+ to be sent back to clients.
+
+
+ Implementation: android/hw-sensors.c
+ Since: SDK 1.5 (cupcake)
diff --git a/docs/ANDROID-QEMUD.TXT b/docs/ANDROID-QEMUD.TXT
index 6415a47..1364553 100644
--- a/docs/ANDROID-QEMUD.TXT
+++ b/docs/ANDROID-QEMUD.TXT
@@ -92,7 +92,7 @@ Since the "cupcake" platform, this works as follows:
connect:<service>:<id>
- where <service> is the service name, and <id> is a 4-hexchar string
+ where <service> is the service name, and <id> is a 2-hexchar string
giving the allocated channel index for the client.
@@ -181,9 +181,19 @@ This is documented here since this explains some subtleties in the
implementation code of android/hw-qemud.c
The old scheme also used a serial port to allow the daemon and the emulator
-to communicate. Besides, the same multiplexing protocol was used to pass
-messages through multiple channels. However, several other differences
-exist, best illustrated by the following graphics:
+to communicate. However, the multiplexing protocol swaps the position of
+'channel' and 'length' in the header:
+
+ 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
+
+Several other differences, best illustrated by the following graphics:
emulator <==serial==> qemud <-+--> /dev/socket/qemud_gsm <--> GSM client
|