blob: b127fc938834943021983b702b8930334b26058f [file] [log] [blame]
/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "android/qemud.h"
#include "android/utils/debug.h"
#include "android/utils/misc.h"
#include "qemu-char.h"
#include "charpipe.h"
#include "cbuffer.h"
#define D(...) VERBOSE_PRINT(qemud,__VA_ARGS__)
#define D_ACTIVE VERBOSE_CHECK(qemud)
/* the T(...) macro is used to dump traffic */
#define T_ACTIVE 0
#if T_ACTIVE
#define T(...) VERBOSE_PRINT(qemud,__VA_ARGS__)
#else
#define T(...) ((void)0)
#endif
#define MAX_PAYLOAD 4000
#define MAX_CHANNELS 8
#define CHANNEL_CONTROL_INDEX 0
/** packets
**/
#define HEADER_SIZE 6
typedef struct Packet {
struct Packet* next;
int len;
uint8_t header[HEADER_SIZE];
uint8_t data[MAX_PAYLOAD];
} Packet;
static Packet* _free_packets;
static void
packet_free( Packet* p )
{
p->next = _free_packets;
_free_packets = p;
}
static Packet*
packet_alloc( void )
{
Packet* p = _free_packets;
if (p != NULL) {
_free_packets = p->next;
} else {
p = malloc(sizeof(*p));
if (p == NULL) {
derror("%s: not enough memory", __FUNCTION__);
exit(1);
}
}
p->next = NULL;
p->len = 0;
return p;
}
/** channels
**/
typedef void (*EnqueueFunc)( void* user, Packet* p );
typedef struct {
const char* name;
int index;
CharDriverState* cs;
EnqueueFunc enq_func;
void* enq_user;
} Channel;
static int
channel_can_read( void* opaque )
{
Channel* c = opaque;
return c->index < 0 ? 0 : MAX_PAYLOAD;
}
/* here, the data comes from the emulated device (e.g. GSM modem) through
* a charpipe, we simply need to send it through the multiplexer */
static void
channel_read( void* opaque, const uint8_t* from, int len )
{
Channel* c = opaque;
if (c->enq_func != NULL) {
Packet* p = packet_alloc();
if (len > MAX_PAYLOAD)
len = MAX_PAYLOAD;
memcpy( p->data, from, len );
p->len = len + HEADER_SIZE;
int2hex( p->header+0, 4, len );
int2hex( p->header+4, 2, c->index );
c->enq_func( c->enq_user, p );
}
else
{
D("%s: discarding %d bytes for channel '%s'",
__FUNCTION__, len, c->name);
}
}
static void
channel_init( Channel* c, const char* name, CharDriverState* peer_cs )
{
c->name = name;
c->index = -1;
c->enq_func = NULL;
c->enq_user = NULL;
c->cs = peer_cs;
}
static void
channel_set_peer( Channel* c, int index, EnqueueFunc enq_func, void* enq_user )
{
c->index = index;
qemu_chr_add_handlers( c->cs,
channel_can_read,
channel_read,
NULL,
c );
c->enq_func = enq_func;
c->enq_user = enq_user;
}
static int
channel_write( Channel*c , const uint8_t* buf, int len )
{
return qemu_chr_write( c->cs, buf, len );
}
/** multiplexer
**/
#define IN_BUFF_SIZE (2*MAX_PAYLOAD)
typedef struct {
CharDriverState* cs;
CBuffer in_cbuffer[1];
int in_datalen;
int in_channel;
int count;
Channel channels[MAX_CHANNELS];
uint8_t in_buff[ IN_BUFF_SIZE + HEADER_SIZE ];
} Multiplexer;
/* called by channel_read when data comes from an emulated
* device, and needs to be multiplexed through the serial
* port
*/
static void
multiplexer_enqueue( Multiplexer* m, Packet* p )
{
T("%s: sending %d bytes: '%s'", __FUNCTION__,
p->len - HEADER_SIZE, quote_bytes( p->data, p->len - HEADER_SIZE ) );
qemu_chr_write( m->cs, p->header, HEADER_SIZE );
qemu_chr_write( m->cs, p->data, p->len - HEADER_SIZE );
packet_free(p);
}
/* called when we received a channel registration from the
* qemud daemon
*/
static void
multiplexer_register_channel( Multiplexer* m,
const char* name,
int index )
{
Channel* c = m->channels;
Channel* c_end = c + m->count;
for ( ; c < c_end; c++ ) {
if ( !strcmp(c->name, name) )
break;
}
if (c >= c_end) {
D( "%s: unknown channel name '%s'",
__FUNCTION__, name );
return;
}
if (c->index >= 0) {
D( "%s: channel '%s' re-assigned index %d",
__FUNCTION__, name, index );
c->index = index;
return;
}
channel_set_peer( c, index, (EnqueueFunc) multiplexer_enqueue, m );
D( "%s: channel '%s' registered as index %d",
__FUNCTION__, c->name, c->index );
}
/* handle answers from the control channel */
static void
multiplexer_handle_control( Multiplexer* m, Packet* p )
{
int len = p->len - HEADER_SIZE;
/* for now, the only supported answer is 'ok:connect:<name>:<XX>' where
* <XX> is a hexdecimal channel numner */
D( "%s: received '%s'", __FUNCTION__, quote_bytes( (const void*)p->data, (unsigned)len ) );
if ( !memcmp( p->data, "ok:connect:", 11 ) ) do {
char* name = (char*)p->data + 11;
char* q = strchr( name, ':' );
int index;
if (q == NULL)
break;
q[0] = 0;
if (q + 3 > (char*)p->data + len)
break;
index = hex2int( (uint8_t*)q+1, 2 );
if (index < 0)
break;
multiplexer_register_channel( m, name, index );
goto Exit;
}
while(0);
D( "%s: unsupported message !!", __FUNCTION__ );
Exit:
packet_free(p);
}
static int
multiplexer_can_read( void* opaque )
{
Multiplexer* m = opaque;
return cbuffer_write_avail( m->in_cbuffer );
}
/* the data comes from the serial port, we need to reconstruct packets then
* dispatch them to the appropriate channel */
static void
multiplexer_read( void* opaque, const uint8_t* from, int len )
{
Multiplexer* m = opaque;
CBuffer* cb = m->in_cbuffer;
int ret = 0;
T("%s: received %d bytes from serial: '%s'",
__FUNCTION__, len, quote_bytes( from, len ));
ret = cbuffer_write( cb, from, len );
if (ret == 0)
return;
for (;;) {
int len = cbuffer_read_avail( cb );
if (m->in_datalen == 0) {
uint8_t header[HEADER_SIZE];
if (len < HEADER_SIZE)
break;
cbuffer_read( cb, header, HEADER_SIZE );
m->in_datalen = hex2int( header+0, 4 );
m->in_channel = hex2int( header+4, 2 );
}
else
{
Packet* p;
if (len < m->in_datalen)
break;
/* a full packet was received */
p = packet_alloc();
cbuffer_read( cb, p->data, m->in_datalen );
p->len = HEADER_SIZE + m->in_datalen;
/* find the channel for this packet */
if (m->in_channel == CHANNEL_CONTROL_INDEX)
multiplexer_handle_control( m, p );
else {
Channel* c = m->channels;
Channel* c_end = c + m->count;
for ( ; c < c_end; c++ ) {
if (c->index == m->in_channel) {
channel_write( c, p->data, m->in_datalen );
break;
}
}
packet_free(p);
}
m->in_datalen = 0;
}
}
return;
}
static void
multiplexer_query_channel( Multiplexer* m, const char* name )
{
Packet* p = packet_alloc();
int len;
len = snprintf( (char*)p->data, MAX_PAYLOAD, "connect:%s", name );
int2hex( p->header+0, 4, len );
int2hex( p->header+4, 2, CHANNEL_CONTROL_INDEX );
p->len = HEADER_SIZE + len;
multiplexer_enqueue( m, p );
}
static Channel*
multiplexer_find_channel( Multiplexer* m, const char* name )
{
int n;
for (n = 0; n < m->count; n++)
if ( !strcmp(m->channels[n].name, name) )
return m->channels + n;
return NULL;
}
static Multiplexer _multiplexer[1];
static CharDriverState* android_qemud_cs;
extern void
android_qemud_init( void )
{
Multiplexer* m = _multiplexer;
if (android_qemud_cs != NULL)
return;
m->count = 0;
cbuffer_reset( m->in_cbuffer, m->in_buff, sizeof(m->in_buff) );
m->in_datalen = 0;
m->in_channel = 0;
if (qemu_chr_open_charpipe( &android_qemud_cs, &m->cs ) < 0) {
derror( "%s: can't create charpipe to serial port",
__FUNCTION__ );
exit(1);
}
qemu_chr_add_handlers( m->cs, multiplexer_can_read,
multiplexer_read, NULL, m );
}
CharDriverState* android_qemud_get_cs( void )
{
if (android_qemud_cs == NULL)
android_qemud_init();
return android_qemud_cs;
}
extern int
android_qemud_get_channel( const char* name, CharDriverState** pcs )
{
Multiplexer* m = _multiplexer;
Channel* c;
CharDriverState* peer_cs;
int ret;
if (m->cs == NULL)
android_qemud_init();
c = multiplexer_find_channel( m, name );
if (c) {
derror( "%s: trying to get already-opened qemud channel '%s'",
__FUNCTION__, name );
return -1;
}
if (m->count >= MAX_CHANNELS) {
derror( "%s: too many registered channels (%d)",
__FUNCTION__, m->count );
return -1;
}
c = m->channels + m->count;
ret = qemu_chr_open_charpipe( &peer_cs, pcs );
if (ret == 0) {
channel_init(c, name, peer_cs);
m->count += 1;
multiplexer_query_channel( m, c->name );
}
return ret;
}
extern int
android_qemud_set_channel( const char* name, CharDriverState* peer_cs )
{
Multiplexer* m = _multiplexer;
Channel* c;
if (m->cs == NULL)
android_qemud_init();
c = multiplexer_find_channel(m, name);
if (c != NULL) {
derror( "%s: trying to set opened qemud channel '%s'",
__FUNCTION__, name );
return -1;
}
if (m->count >= MAX_CHANNELS) {
derror( "%s: too many registered channels (%d)",
__FUNCTION__, m->count );
return -1;
}
c = m->channels + m->count;
channel_init(c, name, peer_cs);
m->count += 1;
multiplexer_query_channel( m, c->name );
return 0;
}