blob: a35c7c6ad65999b51a9784bf816cc9bfbee06f6d [file] [log] [blame]
/*
* Copyright 2007 The Android Open Source Project
*
* Simulator interactions.
*
* TODO: for multi-process we probably need to switch to a new process
* group if we are the first process (could be runtime, could be gdb),
* rather than wait for the simulator to tell us to switch.
*/
#include "Common.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/un.h>
#include <signal.h>
#include <assert.h>
// fwd
static int connectToSim(void);
static void listenToSim(void);
/*
* Env var to restrict who tries to talk to the front end.
*/
#define kWrapSimConnectedEnv "WRAP_SIM_CONNECTED"
/*
* Signal the main thread that we're ready to continue.
*/
static void signalMainThread(void)
{
int cc;
cc = pthread_mutex_lock(&gWrapSim.startLock);
assert(cc == 0);
gWrapSim.startReady = 1;
cc = pthread_cond_signal(&gWrapSim.startCond);
assert(cc == 0);
cc = pthread_mutex_unlock(&gWrapSim.startLock);
assert(cc == 0);
}
/*
* Entry point for the sim management thread.
*
* Once we have established a connection to the simulator and are ready
* for other threads to send messages, we signal the main thread.
*/
static void* simThreadEntry(void* arg)
{
wsLog("--- sim manager thread started\n");
/*
* Establish a connection to the simulator front-end. If we can't do
* that, we have no access to input or output devices, and we might
* as well give up.
*/
if (connectToSim() != 0) {
signalMainThread();
return NULL;
}
/* success! */
wsLog("--- sim manager thread ready\n");
gWrapSim.simulatorInitFailed = 0;
signalMainThread();
listenToSim();
wsLog("--- sim manager thread exiting\n");
return NULL;
}
/*
* If we think we're not yet connected to the sim, do so now. We only
* want to do this once per process *group*, so we control access with
* an environment variable.
*/
int wsSimConnect(void)
{
/*
* If the environment variable hasn't been set, assume we're the first
* to get here, and should attach to the simulator. We set the env
* var here whether or not we succeed in connecting to the sim.
*
* (For correctness we should wrap the getenv/setenv in a semaphore.)
*/
if (getenv(kWrapSimConnectedEnv) == NULL) {
pthread_attr_t threadAttr;
pthread_t threadHandle;
int cc;
gWrapSim.simulatorInitFailed = 1;
setenv(kWrapSimConnectedEnv, "1", 1);
cc = pthread_mutex_lock(&gWrapSim.startLock);
assert(cc == 0);
pthread_attr_init(&threadAttr);
pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
cc = pthread_create(&threadHandle, &threadAttr, simThreadEntry, NULL);
if (cc != 0) {
wsLog("Unable to create new thread: %s\n", strerror(errno));
abort();
}
while (!gWrapSim.startReady) {
cc = pthread_cond_wait(&gWrapSim.startCond, &gWrapSim.startLock);
assert(cc == 0);
}
cc = pthread_mutex_unlock(&gWrapSim.startLock);
assert(cc == 0);
if (gWrapSim.simulatorInitFailed) {
wsLog("Simulator initialization failed, bailing\n");
/* this *should* be okay to do */
fprintf(stderr, "Fatal error:"
" unable to connect to sim front-end (not running?)\n");
abort();
}
}
wsLog("+++ continuing\n");
return 0;
}
/*
* ===========================================================================
* Message / MessageStream
* ===========================================================================
*/
/*
* This is a quick & dirty rewrite of the C++ Message and MessageStream
* classes, ported to C, reduced in generality, with syscalls stubbed
* where necessary. I didn't fix the API to make it more sensible in C
* (which lacks destructors), so some of this is a little fragile or
* awkward.
*/
/* values for message type byte; must match android::Message constants */
typedef enum MessageType {
kTypeUnknown = 0,
kTypeRaw, // chunk of raw data
kTypeConfig, // send a name=value pair to peer
kTypeCommand, // simple command w/arg
kTypeCommandExt, // slightly more complicated command
kTypeLogBundle, // multi-part log message
} MessageType;
/*
* Reusable message object.
*/
typedef struct Message {
MessageType mType;
unsigned char* mData;
int mLength;
} Message;
/* magic init messages; must match android::MessageStream constants */
enum {
kHelloMsg = 0x4e303047, // 'N00G'
kHelloAckMsg = 0x31455221, // '1ER!'
};
/*
* Clear out a Message.
*/
static void Message_clear(Message* msg)
{
memset(msg, 0, sizeof(Message));
}
/*
* Keep reading until we get all bytes or hit EOF/error. "fd" is expected
* to be in blocking mode.
*
* Returns 0 on success.
*/
static int readAll(int fd, void* buf, size_t count)
{
ssize_t actual;
ssize_t have;
have = 0;
while (have != (ssize_t) count) {
actual = _ws_read(fd, ((char*) buf) + have, count - have);
if (actual < 0) {
if (errno == EINTR)
continue;
wsLog("read %d failed: %s\n", fd, strerror(errno));
} else if (actual == 0) {
wsLog("early EOF on %d\n", fd);
return -1;
} else {
have += actual;
}
assert(have <= (ssize_t)count);
}
return 0;
}
#if 0
/*
* Keep writing until we put all bytes or hit an error. "fd" is expected
* to be in blocking mode.
*
* Returns 0 on success.
*/
static int writeAll(int fd, const void* buf, size_t count)
{
ssize_t actual;
ssize_t have;
have = 0;
while (have != count) {
actual = _ws_write(fd, ((const char*) buf) + have, count - have);
if (actual < 0) {
if (errno == EINTR)
continue;
wsLog("write %d failed: %s\n", fd, strerror(errno));
} else if (actual == 0) {
wsLog("wrote zero on %d\n", fd);
return -1;
} else {
have += actual;
}
assert(have <= count);
}
return 0;
}
#endif
/*
* Read a message from the specified file descriptor.
*
* The caller must Message_release(&msg).
*
* We guarantee 32-bit alignment for msg->mData.
*/
static int Message_read(Message* msg, int fd)
{
unsigned char header[4];
readAll(fd, header, 4);
msg->mType = (MessageType) header[2];
msg->mLength = header[0] | header[1] << 8;
msg->mLength -= 2; // we already read two of them in the header
if (msg->mLength > 0) {
int actual;
/* Linux malloc guarantees at least 32-bit alignment */
msg->mData = (unsigned char*) malloc(msg->mLength);
if (msg->mData == NULL) {
wsLog("alloc %d failed\n", msg->mLength);
return -1;
}
if (readAll(fd, msg->mData, msg->mLength) != 0) {
wsLog("failed reading message body (wanted %d)\n", msg->mLength);
return -1;
}
} else {
msg->mData = NULL;
}
return 0;
}
/*
* Write a message to the specified file descriptor.
*
* The caller must Message_release(&msg).
*/
static int Message_write(Message* msg, int fd)
{
struct iovec writeVec[2];
unsigned char header[4];
int len, numVecs;
ssize_t actual;
len = msg->mLength + 2;
header[0] = len & 0xff;
header[1] = (len >> 8) & 0xff;
header[2] = msg->mType;
header[3] = 0;
writeVec[0].iov_base = header;
writeVec[0].iov_len = 4;
numVecs = 1;
if (msg->mLength > 0) {
assert(msg->mData != NULL);
writeVec[1].iov_base = msg->mData;
writeVec[1].iov_len = msg->mLength;
numVecs++;
}
/* write it all in one shot; not worrying about partial writes for now */
actual = _ws_writev(fd, writeVec, numVecs);
if (actual != len+2) {
wsLog("failed writing message to fd %d: %d of %d %s\n",
fd, actual, len+2, strerror(errno));
return -1;
}
return 0;
}
/*
* Release storage associated with a Message.
*/
static void Message_release(Message* msg)
{
free(msg->mData);
msg->mData = NULL;
}
/*
* Extract a name/value pair from a message.
*/
static int getConfig(const Message* msg, const char** name, const char** val)
{
if (msg->mLength < 2) {
wsLog("message len (%d) is too short\n", msg->mLength);
return -1;
}
const char* ptr = (const char*) msg->mData;
*name = (const char*) ptr;
*val = (const char*) (ptr + strlen((char*)ptr) +1);
return 0;
}
/*
* Extract a command from a message.
*/
static int getCommand(const Message* msg, int* pCmd, int* pArg)
{
if (msg->mLength != sizeof(int) * 2) {
wsLog("message len (%d) is wrong for cmd (%d)\n",
msg->mLength, sizeof(int) * 2);
return -1;
}
/* this assumes 32-bit alignment on mData */
const int* ptr = (const int*) msg->mData;
*pCmd = ptr[0];
*pArg = ptr[1];
return 0;
}
/*
* Extract an extended command from a message.
*/
static int getCommandExt(const Message* msg, int* pCmd, int* pArg0,
int* pArg1, int* pArg2)
{
if (msg->mLength != sizeof(int) * 4) {
wsLog("message len (%d) is wrong for cmd (%d)\n",
msg->mLength, sizeof(int) * 4);
return -1;
}
/* this assumes 32-bit alignment on mData */
const int* ptr = (const int*) msg->mData;
*pCmd = ptr[0];
*pArg0 = ptr[1];
*pArg1 = ptr[2];
*pArg2 = ptr[3];
return 0;
}
/*
* Attach 8 bytes of data with "cmd" and "arg" to "msg".
*
* "msg->mData" will need to be freed by the caller. (This approach made
* more sense when C++ destructors were available, but it's just not worth
* reworking it.)
*/
static int setCommand(Message* msg, int cmd, int arg)
{
Message_clear(msg);
msg->mLength = 8;
msg->mData = malloc(msg->mLength);
msg->mType = kTypeCommand;
/* assumes 32-bit alignment on malloc blocks */
int* pInt = (int*) msg->mData;
pInt[0] = cmd;
pInt[1] = arg;
return 0;
}
/*
* Construct the full path. The caller must free() the return value.
*/
static char* makeFilename(const char* name)
{
static const char* kBasePath = "/tmp/android-";
char* fileName;
assert(name != NULL && name[0] != '\0');
fileName = (char*) malloc(strlen(kBasePath) + strlen(name) + 1);
strcpy(fileName, kBasePath);
strcat(fileName, name);
return fileName;
}
/*
* Attach to a SysV shared memory segment.
*/
static int attachToShmem(int key, int* pShmid, void** pAddr, long* pLength)
{
int shmid;
shmid = shmget(key, 0, 0);
if (shmid == -1) {
wsLog("ERROR: failed to find shmem key=%d\n", key);
return -1;
}
void* addr = shmat(shmid, NULL, 0);
if (addr == (void*) -1) {
wsLog("ERROR: could not attach to key=%d shmid=%d\n", key, shmid);
return -1;
}
struct shmid_ds shmids;
int cc;
cc = shmctl(shmid, IPC_STAT, &shmids);
if (cc != 0) {
wsLog("ERROR: could not IPC_STAT shmid=%d\n", shmid);
return -1;
}
*pLength = shmids.shm_segsz;
*pAddr = addr;
*pShmid = shmid;
return 0;
}
/*
* Attach to a SysV semaphore.
*/
static int attachToSem(int key, int* pSemid)
{
int semid;
semid = semget(key, 0, 0);
if (semid == -1) {
wsLog("ERROR: failed to attach to semaphore key=%d\n", key);
return -1;
}
*pSemid = semid;
return 0;
}
/*
* "Adjust" a semaphore.
*/
static int adjustSem(int semid, int adj)
{
const int wait = 1;
struct sembuf op;
int cc;
op.sem_num = 0;
op.sem_op = adj;
op.sem_flg = SEM_UNDO;
if (!wait)
op.sem_flg |= IPC_NOWAIT;
cc = semop(semid, &op, 1);
if (cc != 0) {
if (wait || errno != EAGAIN) {
wsLog("Warning:"
" semaphore adjust by %d failed for semid=%d (errno=%d)\n",
adj, semid, errno);
}
return -1;
}
return 0;
}
/*
* Acquire the semaphore associated with a display.
*/
void wsLockDisplay(int displayIdx)
{
assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays);
int semid = gWrapSim.display[displayIdx].semid;
(void) adjustSem(semid, -1);
}
/*
* Acquire the semaphore associated with a display.
*/
void wsUnlockDisplay(int displayIdx)
{
assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays);
int semid = gWrapSim.display[displayIdx].semid;
(void) adjustSem(semid, 1);
}
/*
* Process the display config from the simulator
*
* Right now this is a blob of raw data that looks like this:
* +00 magic number
* +04 #of displays
* +08 display 0:
* +00 width
* +04 height
* +08 format
* +0c refresh rate
* +10 shmem key
* +1c display 1...
*/
static int handleDisplayConfig(const int* pData, int length)
{
int numDisplays;
if (length < 8) {
wsLog("Bad display config: length is %d\n", length);
return -1;
}
assert(*pData == kDisplayConfigMagic);
/*
* Pull out the #of displays. If it looks valid, configure the runtime.
*/
pData++; // skip over magic
numDisplays = *pData++;
if (numDisplays <= 0 || numDisplays > kMaxDisplays) {
wsLog("Bizarre display count %d\n", numDisplays);
return -1;
}
if (length != 8 + numDisplays * kValuesPerDisplay * (int)sizeof(int)) {
wsLog("Bad display config: length is %d (expected %d)\n",
length, 8 + numDisplays * kValuesPerDisplay * (int)sizeof(int));
return -1;
}
/*
* Extract the config values.
*
* The runtime doesn't support multiple devices, so we don't either.
*/
int i;
for (i = 0; i < numDisplays; i++) {
gWrapSim.display[i].width = pData[0];
gWrapSim.display[i].height = pData[1];
gWrapSim.display[i].shmemKey = pData[4];
/* format/refresh no longer needed */
void* addr;
int shmid, semid;
long length;
if (attachToShmem(gWrapSim.display[i].shmemKey, &shmid, &addr,
&length) != 0)
{
wsLog("Unable to connect to shared memory\n");
return -1;
}
if (attachToSem(gWrapSim.display[i].shmemKey, &semid) != 0) {
wsLog("Unable to attach to sempahore\n");
return -1;
}
gWrapSim.display[i].shmid = shmid;
gWrapSim.display[i].addr = addr;
gWrapSim.display[i].length = length;
gWrapSim.display[i].semid = semid;
wsLog("Display %d: width=%d height=%d\n",
i,
gWrapSim.display[i].width,
gWrapSim.display[i].height);
wsLog(" shmem=0x%08x addr=%p len=%ld semid=%d\n",
gWrapSim.display[i].shmemKey,
gWrapSim.display[i].addr,
gWrapSim.display[i].length,
gWrapSim.display[i].semid);
pData += kValuesPerDisplay;
}
gWrapSim.numDisplays = numDisplays;
return 0;
}
/*
* Initialize our connection to the simulator, which will be listening on
* a UNIX domain socket.
*
* On success, this configures gWrapSim.simulatorFd and returns 0.
*/
static int openSimConnection(const char* name)
{
int result = -1;
char* fileName = NULL;
int sock = -1;
int cc;
assert(gWrapSim.simulatorFd == -1);
fileName = makeFilename(name);
struct sockaddr_un addr;
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
wsLog("UNIX domain socket create failed (errno=%d)\n", errno);
goto bail;
}
/* connect to socket; fails if file doesn't exist */
strcpy(addr.sun_path, fileName); // max 108 bytes
addr.sun_family = AF_UNIX;
cc = connect(sock, (struct sockaddr*) &addr, SUN_LEN(&addr));
if (cc < 0) {
// ENOENT means socket file doesn't exist
// ECONNREFUSED means socket exists but nobody is listening
wsLog("AF_UNIX connect failed for '%s': %s\n",
fileName, strerror(errno));
goto bail;
}
gWrapSim.simulatorFd = sock;
sock = -1;
result = 0;
wsLog("+++ connected to '%s'\n", fileName);
bail:
if (sock >= 0)
_ws_close(sock);
free(fileName);
return result;
}
/*
* Prepare communication with the front end. We wait for a "hello" from
* the other side, and respond in kind.
*/
static int prepSimConnection(void)
{
/* NOTE: this is endian-specific; we're x86 Linux only, so no problem */
static const unsigned int hello = kHelloMsg;
static const unsigned int helloAck = kHelloAckMsg;
Message msg;
if (Message_read(&msg, gWrapSim.simulatorFd) != 0) {
wsLog("hello read failed\n");
return -1;
}
if (memcmp(msg.mData, &hello, 4) != 0) {
wsLog("Got bad hello from peer\n");
return -1;
}
Message_release(&msg);
msg.mType = kTypeRaw;
msg.mData = (unsigned char*) &helloAck;
msg.mLength = 4;
if (Message_write(&msg, gWrapSim.simulatorFd) != 0) {
wsLog("hello ack write failed\n");
return -1;
}
return 0;
}
/*
* Get the sim front-end configuration. We loop here until the sim claims
* to be done with us.
*/
static int getSimConfig(void)
{
Message msg;
int joinNewGroup, grabTerminal, done;
int result = -1;
joinNewGroup = grabTerminal = done = 0;
Message_clear(&msg); // clear out msg->mData
wsLog("+++ awaiting hardware configuration\n");
while (!done) {
if (Message_read(&msg, gWrapSim.simulatorFd) != 0) {
wsLog("failed receiving config from parent\n");
goto bail;
}
if (msg.mType == kTypeCommand) {
int cmd, arg;
if (getCommand(&msg, &cmd, &arg) != 0)
goto bail;
switch (cmd) {
case kCommandGoAway:
wsLog("Simulator front-end is busy\n");
goto bail;
case kCommandNewPGroup:
joinNewGroup = 1;
grabTerminal = (arg != 0);
wsLog("Simulator wants us to be in a new pgrp (term=%d)\n",
grabTerminal);
break;
case kCommandConfigDone:
done = 1;
break;
default:
wsLog("Got unexpected command %d/%d\n", cmd, arg);
break;
}
} else if (msg.mType == kTypeRaw) {
/* assumes 32-bit alignment and identical byte ordering */
int* pData = (int*) msg.mData;
if (msg.mLength >= 4 && *pData == kDisplayConfigMagic) {
if (handleDisplayConfig(pData, msg.mLength) != 0)
goto bail;
}
} else if (msg.mType == kTypeConfig) {
const char *name;
const char *val;
getConfig(&msg, &name, &val);
if(strcmp(name, "keycharmap") == 0) {
free((void*)gWrapSim.keyMap);
gWrapSim.keyMap = strdup(val);
}
} else {
wsLog("Unexpected msg type %d during startup\n", msg.mType);
goto bail;
}
/* clear out the data field if necessary */
Message_release(&msg);
}
wsLog("Configuration received from simulator\n");
if (joinNewGroup) {
/* set pgid to pid */
pid_t pgid = getpid();
setpgid(0, pgid);
/*
* Put our pgrp in the foreground.
* tcsetpgrp() from background process causes us to get a SIGTTOU,
* which is mostly harmless but makes tcsetpgrp() fail with EINTR.
*/
signal(SIGTTOU, SIG_IGN);
if (grabTerminal) {
if (tcsetpgrp(fileno(stdin), getpgrp()) != 0) {
wsLog("tcsetpgrp(%d, %d) failed (errno=%d)\n",
fileno(stdin), getpgrp(), errno);
}
wsLog("Set pgrp %d as foreground\n", (int) getpgrp());
}
/* tell the sim where we're at */
Message msg;
setCommand(&msg, kCommandNewPGroupCreated, pgid);
Message_write(&msg, gWrapSim.simulatorFd);
Message_release(&msg);
}
result = 0;
bail:
/* make sure the data was freed */
Message_release(&msg);
//wsLog("bailing, result=%d\n", result);
return result;
}
/*
* Connect to the simulator and exchange pleasantries.
*
* Returns 0 on success.
*/
static int connectToSim(void)
{
if (openSimConnection(kAndroidPipeName) != 0)
return -1;
if (prepSimConnection() != 0)
return -1;
if (getSimConfig() != 0)
return -1;
wsLog("+++ sim is ready to go\n");
return 0;
}
/*
* Listen to the sim forever or until the front end shuts down, whichever
* comes first.
*
* All we're really getting here are key events.
*/
static void listenToSim(void)
{
wsLog("--- listening for input events from front end\n");
while (1) {
Message msg;
Message_clear(&msg);
if (Message_read(&msg, gWrapSim.simulatorFd) != 0) {
wsLog("--- sim message read failed\n");
return;
}
if (msg.mType == kTypeCommand) {
int cmd, arg;
if (getCommand(&msg, &cmd, &arg) != 0) {
wsLog("bad command from sim?\n");
continue;
}
switch (cmd) {
case kCommandQuit:
wsLog("--- sim sent us a QUIT message\n");
return;
case kCommandKeyDown:
wsLog("KEY DOWN: %d\n", arg);
wsSendSimKeyEvent(arg, 1);
break;
case kCommandKeyUp:
wsLog("KEY UP: %d\n", arg);
wsSendSimKeyEvent(arg, 0);
break;
default:
wsLog("--- sim sent unrecognized command %d\n", cmd);
break;
}
Message_release(&msg);
} else if (msg.mType == kTypeCommandExt) {
int cmd, arg0, arg1, arg2;
if (getCommandExt(&msg, &cmd, &arg0, &arg1, &arg2) != 0) {
wsLog("bad ext-command from sim?\n");
continue;
}
switch (cmd) {
case kCommandTouch:
wsSendSimTouchEvent(arg0, arg1, arg2);
break;
}
Message_release(&msg);
} else {
wsLog("--- sim sent non-command message, type=%d\n", msg.mType);
}
}
assert(0); // not reached
}
/*
* Tell the simulator front-end that the display has been updated.
*/
void wsPostDisplayUpdate(int displayIdx)
{
if (gWrapSim.simulatorFd < 0) {
wsLog("Not posting display update -- sim not ready\n");
return;
}
Message msg;
setCommand(&msg, kCommandUpdateDisplay, displayIdx);
Message_write(&msg, gWrapSim.simulatorFd);
Message_release(&msg);
}
/*
* Send a log message to the front-end.
*/
void wsPostLogMessage(int logPrio, const char* tag, const char* message)
{
if (gWrapSim.simulatorFd < 0) {
wsLog("Not posting log message -- sim not ready\n");
return;
}
time_t when = time(NULL);
int pid = (int) getpid();
int tagLen, messageLen, totalLen;
tagLen = strlen(tag) +1;
messageLen = strlen(message) +1;
totalLen = sizeof(int) * 3 + tagLen + messageLen;
unsigned char outBuf[totalLen];
unsigned char* cp = outBuf;
/* See Message::set/getLogBundle() in simulator/MessageStream.cpp. */
memcpy(cp, &when, sizeof(int));
cp += sizeof(int);
memcpy(cp, &logPrio, sizeof(int));
cp += sizeof(int);
memcpy(cp, &pid, sizeof(int));
cp += sizeof(int);
memcpy(cp, tag, tagLen);
cp += tagLen;
memcpy(cp, message, messageLen);
cp += messageLen;
assert(cp - outBuf == totalLen);
Message msg;
msg.mType = kTypeLogBundle;
msg.mData = outBuf;
msg.mLength = totalLen;
Message_write(&msg, gWrapSim.simulatorFd);
msg.mData = NULL; // don't free
Message_release(&msg);
}
/*
* Turn the vibrating notification device on or off.
*/
void wsEnableVibration(int vibrateOn)
{
if (gWrapSim.simulatorFd < 0) {
wsLog("Not posting vibrator update -- sim not ready\n");
return;
}
Message msg;
//wsLog("+++ sending vibrate:%d\n", vibrateOn);
setCommand(&msg, kCommandVibrate, vibrateOn);
Message_write(&msg, gWrapSim.simulatorFd);
Message_release(&msg);
}