diff options
Diffstat (limited to 'hw/misc/android_pipe_test.c')
-rw-r--r-- | hw/misc/android_pipe_test.c | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/hw/misc/android_pipe_test.c b/hw/misc/android_pipe_test.c new file mode 100644 index 0000000000..dc4a6e5330 --- /dev/null +++ b/hw/misc/android_pipe_test.c @@ -0,0 +1,521 @@ +/* Copyright (C) 2011 The Android Open Source Project +** Copyright (C) 2014 Linaro Limited +** +** 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. +** +** Description +** +** Example uses of Android Pipes. +*/ +#include "hw/misc/android_pipe.h" + +#define DEBUG 0 + +/* Set to 1 to debug i/o register reads/writes */ +#define DEBUG_REGS 0 + +#if DEBUG >= 1 +# define D(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n") +#else +# define D(...) (void)0 +#endif + +#if DEBUG >= 2 +# define DD(...) fprintf(stderr, __VA_ARGS__), fprintf(stderr, "\n") +#else +# define DD(...) (void)0 +#endif + +#if DEBUG_REGS >= 1 +# define DR(...) D(__VA_ARGS__) +#else +# define DR(...) (void)0 +#endif + +#define E(...) fprintf(stderr, "ERROR:" __VA_ARGS__), fprintf(stderr, "\n") + +/* Set to 1 to enable the 'zero' pipe type, useful for debugging */ +#define DEBUG_ZERO_PIPE 1 + +/* Set to 1 to enable the 'pingpong' pipe type, useful for debugging */ +#define DEBUG_PINGPONG_PIPE 1 + +/* Set to 1 to enable the 'throttle' pipe type, useful for debugging */ +#define DEBUG_THROTTLE_PIPE 1 + + +/*********************************************************************** + *********************************************************************** + ***** + ***** Z E R O P I P E S + ***** + *****/ + +/* A simple pipe service that mimics /dev/zero, you can write anything to + * it, and you can always read any number of zeros from it. Useful for debugging + * the kernel driver. + */ + +typedef struct { + void* hwpipe; +} ZeroPipe; + +static void* +zeroPipe_init( void* hwpipe, void* svcOpaque, const char* args ) +{ + ZeroPipe* zpipe; + + D("%s: hwpipe=%p", __FUNCTION__, hwpipe); + zpipe = g_malloc0(sizeof(ZeroPipe)); + zpipe->hwpipe = hwpipe; + return zpipe; +} + +static void +zeroPipe_close( void* opaque ) +{ + ZeroPipe* zpipe = opaque; + + D("%s: hwpipe=%p", __FUNCTION__, zpipe->hwpipe); + g_free(zpipe); +} + +static int +zeroPipe_sendBuffers( void* opaque, const AndroidPipeBuffer* buffers, int numBuffers ) +{ + int ret = 0; + while (numBuffers > 0) { + ret += buffers[0].size; + buffers++; + numBuffers--; + } + return ret; +} + +static int +zeroPipe_recvBuffers( void* opaque, AndroidPipeBuffer* buffers, int numBuffers ) +{ + int ret = 0; + while (numBuffers > 0) { + ret += buffers[0].size; + memset(buffers[0].data, 0, buffers[0].size); + buffers++; + numBuffers--; + } + return ret; +} + +static unsigned +zeroPipe_poll( void* opaque ) +{ + return PIPE_POLL_IN | PIPE_POLL_OUT; +} + +static void +zeroPipe_wakeOn( void* opaque, int flags ) +{ + /* nothing to do here */ +} + +static const AndroidPipeFuncs zeroPipe_funcs = { + zeroPipe_init, + zeroPipe_close, + zeroPipe_sendBuffers, + zeroPipe_recvBuffers, + zeroPipe_poll, + zeroPipe_wakeOn, +}; + +#if DEBUG_ZERO_PIPE +void android_zero_pipe_init(void) +{ + android_pipe_add_type("zero", NULL, &zeroPipe_funcs); +} +#else +void android_zero_pipe_init(void) { } +#endif /* DEBUG_ZERO */ + +/*********************************************************************** + *********************************************************************** + ***** + ***** P I N G P O N G P I P E S + ***** + *****/ + +/* Similar debug service that sends back anything it receives */ +/* All data is kept in a circular dynamic buffer */ + +/* Initial buffer size */ +#define PINGPONG_SIZE 1024 + +typedef struct { + void* hwpipe; + uint8_t* buffer; + size_t size; + size_t pos; + size_t count; + unsigned flags; +} PingPongPipe; + +static void +pingPongPipe_init0( PingPongPipe* pipe, void* hwpipe, void* svcOpaque ) +{ + pipe->hwpipe = hwpipe; + pipe->size = PINGPONG_SIZE; + pipe->buffer = malloc(pipe->size); + pipe->pos = 0; + pipe->count = 0; +} + +static void* +pingPongPipe_init( void* hwpipe, void* svcOpaque, const char* args ) +{ + PingPongPipe* ppipe; + + D("%s: hwpipe=%p", __FUNCTION__, hwpipe); + ppipe = g_malloc0(sizeof(PingPongPipe)); + pingPongPipe_init0(ppipe, hwpipe, svcOpaque); + return ppipe; +} + +static void +pingPongPipe_close( void* opaque ) +{ + PingPongPipe* ppipe = opaque; + + D("%s: hwpipe=%p (pos=%zd count=%zd size=%zd)", __FUNCTION__, + ppipe->hwpipe, ppipe->pos, ppipe->count, ppipe->size); + free(ppipe->buffer); + g_free(ppipe); +} + +static int +pingPongPipe_sendBuffers( void* opaque, const AndroidPipeBuffer* buffers, int numBuffers ) +{ + PingPongPipe* pipe = opaque; + int ret = 0; + int count; + const AndroidPipeBuffer* buff = buffers; + const AndroidPipeBuffer* buffEnd = buff + numBuffers; + + count = 0; + for ( ; buff < buffEnd; buff++ ) + count += buff->size; + + /* Do we need to grow the pingpong buffer? */ + while (count > pipe->size - pipe->count) { + size_t newsize = pipe->size*2; + uint8_t* newbuff = realloc(pipe->buffer, newsize); + int wpos = pipe->pos + pipe->count; + if (newbuff == NULL) { + break; + } + if (wpos > pipe->size) { + wpos -= pipe->size; + memcpy(newbuff + pipe->size, newbuff, wpos); + } + pipe->buffer = newbuff; + pipe->size = newsize; + D("pingpong buffer is now %zd bytes", newsize); + } + + for ( buff = buffers; buff < buffEnd; buff++ ) { + int avail = pipe->size - pipe->count; + if (avail <= 0) { + if (ret == 0) + ret = PIPE_ERROR_AGAIN; + break; + } + if (avail > buff->size) { + avail = buff->size; + } + + int wpos = pipe->pos + pipe->count; + if (wpos >= pipe->size) { + wpos -= pipe->size; + } + if (wpos + avail <= pipe->size) { + memcpy(pipe->buffer + wpos, buff->data, avail); + } else { + int avail2 = pipe->size - wpos; + memcpy(pipe->buffer + wpos, buff->data, avail2); + memcpy(pipe->buffer, buff->data + avail2, avail - avail2); + } + pipe->count += avail; + ret += avail; + } + + /* Wake up any waiting readers if we wrote something */ + if (pipe->count > 0 && (pipe->flags & PIPE_WAKE_READ)) { + android_pipe_wake(pipe->hwpipe, PIPE_WAKE_READ); + } + + return ret; +} + +static int +pingPongPipe_recvBuffers( void* opaque, AndroidPipeBuffer* buffers, int numBuffers ) +{ + PingPongPipe* pipe = opaque; + int ret = 0; + + while (numBuffers > 0) { + int avail = pipe->count; + if (avail <= 0) { + if (ret == 0) + ret = PIPE_ERROR_AGAIN; + break; + } + if (avail > buffers[0].size) { + avail = buffers[0].size; + } + + int rpos = pipe->pos; + + if (rpos + avail <= pipe->size) { + memcpy(buffers[0].data, pipe->buffer + rpos, avail); + } else { + int avail2 = pipe->size - rpos; + memcpy(buffers[0].data, pipe->buffer + rpos, avail2); + memcpy(buffers[0].data + avail2, pipe->buffer, avail - avail2); + } + pipe->count -= avail; + pipe->pos += avail; + if (pipe->pos >= pipe->size) { + pipe->pos -= pipe->size; + } + ret += avail; + numBuffers--; + buffers++; + } + + /* Wake up any waiting readers if we wrote something */ + if (pipe->count < PINGPONG_SIZE && (pipe->flags & PIPE_WAKE_WRITE)) { + android_pipe_wake(pipe->hwpipe, PIPE_WAKE_WRITE); + } + + return ret; +} + +static unsigned +pingPongPipe_poll( void* opaque ) +{ + PingPongPipe* pipe = opaque; + unsigned ret = 0; + + if (pipe->count < pipe->size) + ret |= PIPE_POLL_OUT; + + if (pipe->count > 0) + ret |= PIPE_POLL_IN; + + return ret; +} + +static void +pingPongPipe_wakeOn( void* opaque, int flags ) +{ + PingPongPipe* pipe = opaque; + pipe->flags |= (unsigned)flags; +} + +static const AndroidPipeFuncs pingPongPipe_funcs = { + pingPongPipe_init, + pingPongPipe_close, + pingPongPipe_sendBuffers, + pingPongPipe_recvBuffers, + pingPongPipe_poll, + pingPongPipe_wakeOn, +}; + +#if DEBUG_PINGPONG_PIPE +void android_pingpong_init(void) +{ + android_pipe_add_type("pingpong", NULL, &pingPongPipe_funcs); +} +#else +void android_pingpong_init(void) { } +#endif /* DEBUG_PINGPONG_PIPE */ + +/*********************************************************************** + *********************************************************************** + ***** + ***** T H R O T T L E P I P E S + ***** + *****/ + +/* Similar to PingPongPipe, but will throttle the bandwidth to test + * blocking I/O. + */ + +typedef struct { + PingPongPipe pingpong; + double sendRate; + int64_t sendExpiration; + double recvRate; + int64_t recvExpiration; + QEMUTimer* timer; +} ThrottlePipe; + +/* forward declaration */ +static void throttlePipe_timerFunc( void* opaque ); + +static void* +throttlePipe_init( void* hwpipe, void* svcOpaque, const char* args ) +{ + ThrottlePipe* pipe; + + pipe = g_malloc0(sizeof(ThrottlePipe)); + pingPongPipe_init0(&pipe->pingpong, hwpipe, svcOpaque); + pipe->timer = timer_new(QEMU_CLOCK_VIRTUAL, SCALE_NS, throttlePipe_timerFunc, pipe); + /* For now, limit to 500 KB/s in both directions */ + pipe->sendRate = 1e9 / (500*1024*8); + pipe->recvRate = pipe->sendRate; + return pipe; +} + +static void +throttlePipe_close( void* opaque ) +{ + ThrottlePipe* pipe = opaque; + + timer_del(pipe->timer); + timer_free(pipe->timer); + pingPongPipe_close(&pipe->pingpong); +} + +static void +throttlePipe_rearm( ThrottlePipe* pipe ) +{ + int64_t minExpiration = 0; + + DD("%s: sendExpiration=%" PRId64 " recvExpiration=%" PRId64"\n", __FUNCTION__, pipe->sendExpiration, pipe->recvExpiration); + + if (pipe->sendExpiration) { + if (minExpiration == 0 || pipe->sendExpiration < minExpiration) + minExpiration = pipe->sendExpiration; + } + + if (pipe->recvExpiration) { + if (minExpiration == 0 || pipe->recvExpiration < minExpiration) + minExpiration = pipe->recvExpiration; + } + + if (minExpiration != 0) { + DD("%s: Arming for %" PRId64 "\n", __FUNCTION__, minExpiration); + timer_mod(pipe->timer, minExpiration); + } +} + +static void +throttlePipe_timerFunc( void* opaque ) +{ + ThrottlePipe* pipe = opaque; + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + DD("%s: TICK! now=%" PRId64 " sendExpiration=%" PRId64 " recvExpiration=%" PRId64 "\n", + __FUNCTION__, now, pipe->sendExpiration, pipe->recvExpiration); + + /* Timer has expired, signal wake up if needed */ + int flags = 0; + + if (pipe->sendExpiration && now > pipe->sendExpiration) { + flags |= PIPE_WAKE_WRITE; + pipe->sendExpiration = 0; + } + if (pipe->recvExpiration && now > pipe->recvExpiration) { + flags |= PIPE_WAKE_READ; + pipe->recvExpiration = 0; + } + flags &= pipe->pingpong.flags; + if (flags != 0) { + DD("%s: WAKE %d\n", __FUNCTION__, flags); + android_pipe_wake(pipe->pingpong.hwpipe, flags); + } + + throttlePipe_rearm(pipe); +} + +static int +throttlePipe_sendBuffers( void* opaque, const AndroidPipeBuffer* buffers, int numBuffers ) +{ + ThrottlePipe* pipe = opaque; + int ret; + + if (pipe->sendExpiration > 0) { + return PIPE_ERROR_AGAIN; + } + + ret = pingPongPipe_sendBuffers(&pipe->pingpong, buffers, numBuffers); + if (ret > 0) { + /* Compute next send expiration time */ + pipe->sendExpiration = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ret*pipe->sendRate; + throttlePipe_rearm(pipe); + } + return ret; +} + +static int +throttlePipe_recvBuffers( void* opaque, AndroidPipeBuffer* buffers, int numBuffers ) +{ + ThrottlePipe* pipe = opaque; + int ret; + + if (pipe->recvExpiration > 0) { + return PIPE_ERROR_AGAIN; + } + + ret = pingPongPipe_recvBuffers(&pipe->pingpong, buffers, numBuffers); + if (ret > 0) { + pipe->recvExpiration = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ret*pipe->recvRate; + throttlePipe_rearm(pipe); + } + return ret; +} + +static unsigned +throttlePipe_poll( void* opaque ) +{ + ThrottlePipe* pipe = opaque; + unsigned ret = pingPongPipe_poll(&pipe->pingpong); + + if (pipe->sendExpiration > 0) + ret &= ~PIPE_POLL_OUT; + + if (pipe->recvExpiration > 0) + ret &= ~PIPE_POLL_IN; + + return ret; +} + +static void +throttlePipe_wakeOn( void* opaque, int flags ) +{ + ThrottlePipe* pipe = opaque; + pingPongPipe_wakeOn(&pipe->pingpong, flags); +} + +static const AndroidPipeFuncs throttlePipe_funcs = { + throttlePipe_init, + throttlePipe_close, + throttlePipe_sendBuffers, + throttlePipe_recvBuffers, + throttlePipe_poll, + throttlePipe_wakeOn, +}; + +#ifdef DEBUG_THROTTLE_PIPE +void android_throttle_init(void) +{ + android_pipe_add_type("throttle", NULL, &throttlePipe_funcs); +} +#else +void android_throttle_init(void) { } +#endif /* DEBUG_THROTTLE_PIPE */ |