| /* Copyright (C) 2011 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/utils/panic.h" |
| #include "android/utils/system.h" |
| #include "hw/goldfish_pipe.h" |
| #include "hw/goldfish_device.h" |
| #include "qemu-timer.h" |
| #ifdef CONFIG_KVM |
| #include "kvm.h" |
| #endif |
| |
| #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 |
| |
| /* Maximum length of pipe service name, in characters (excluding final 0) */ |
| #define MAX_PIPE_SERVICE_NAME_SIZE 255 |
| |
| #define GOLDFISH_PIPE_SAVE_VERSION 2 |
| |
| /*********************************************************************** |
| *********************************************************************** |
| ***** |
| ***** P I P E S E R V I C E R E G I S T R A T I O N |
| ***** |
| *****/ |
| |
| #define MAX_PIPE_SERVICES 8 |
| typedef struct { |
| const char* name; |
| void* opaque; |
| GoldfishPipeFuncs funcs; |
| } PipeService; |
| |
| typedef struct { |
| int count; |
| PipeService services[MAX_PIPE_SERVICES]; |
| } PipeServices; |
| |
| static PipeServices _pipeServices[1]; |
| |
| void |
| goldfish_pipe_add_type(const char* pipeName, |
| void* pipeOpaque, |
| const GoldfishPipeFuncs* pipeFuncs ) |
| { |
| PipeServices* list = _pipeServices; |
| int count = list->count; |
| |
| if (count >= MAX_PIPE_SERVICES) { |
| APANIC("Too many goldfish pipe services (%d)", count); |
| } |
| |
| if (strlen(pipeName) > MAX_PIPE_SERVICE_NAME_SIZE) { |
| APANIC("Pipe service name too long: '%s'", pipeName); |
| } |
| |
| list->services[count].name = pipeName; |
| list->services[count].opaque = pipeOpaque; |
| list->services[count].funcs = pipeFuncs[0]; |
| |
| list->count++; |
| } |
| |
| static const PipeService* |
| goldfish_pipe_find_type(const char* pipeName) |
| { |
| PipeServices* list = _pipeServices; |
| int count = list->count; |
| int nn; |
| |
| for (nn = 0; nn < count; nn++) { |
| if (!strcmp(list->services[nn].name, pipeName)) { |
| return &list->services[nn]; |
| } |
| } |
| return NULL; |
| } |
| |
| |
| /*********************************************************************** |
| *********************************************************************** |
| ***** |
| ***** P I P E C O N N E C T I O N S |
| ***** |
| *****/ |
| |
| typedef struct PipeDevice PipeDevice; |
| |
| typedef struct Pipe { |
| struct Pipe* next; |
| struct Pipe* next_waked; |
| PipeDevice* device; |
| uint32_t channel; |
| void* opaque; |
| const GoldfishPipeFuncs* funcs; |
| const PipeService* service; |
| char* args; |
| unsigned char wanted; |
| char closed; |
| } Pipe; |
| |
| /* Forward */ |
| static void* pipeConnector_new(Pipe* pipe); |
| |
| static Pipe* |
| pipe_new0(PipeDevice* dev) |
| { |
| Pipe* pipe; |
| ANEW0(pipe); |
| pipe->device = dev; |
| return pipe; |
| } |
| |
| static Pipe* |
| pipe_new(uint32_t channel, PipeDevice* dev) |
| { |
| Pipe* pipe = pipe_new0(dev); |
| pipe->channel = channel; |
| pipe->opaque = pipeConnector_new(pipe); |
| return pipe; |
| } |
| |
| static Pipe** |
| pipe_list_findp_channel( Pipe** list, uint32_t channel ) |
| { |
| Pipe** pnode = list; |
| for (;;) { |
| Pipe* node = *pnode; |
| if (node == NULL || node->channel == channel) { |
| break; |
| } |
| pnode = &node->next; |
| } |
| return pnode; |
| } |
| |
| #if 0 |
| static Pipe** |
| pipe_list_findp_opaque( Pipe** list, void* opaque ) |
| { |
| Pipe** pnode = list; |
| for (;;) { |
| Pipe* node = *pnode; |
| if (node == NULL || node->opaque == opaque) { |
| break; |
| } |
| pnode = &node->next; |
| } |
| return pnode; |
| } |
| #endif |
| |
| static Pipe** |
| pipe_list_findp_waked( Pipe** list, Pipe* pipe ) |
| { |
| Pipe** pnode = list; |
| for (;;) { |
| Pipe* node = *pnode; |
| if (node == NULL || node == pipe) { |
| break; |
| } |
| pnode = &node->next_waked; |
| } |
| return pnode; |
| } |
| |
| |
| static void |
| pipe_list_remove_waked( Pipe** list, Pipe* pipe ) |
| { |
| Pipe** lookup = pipe_list_findp_waked(list, pipe); |
| Pipe* node = *lookup; |
| |
| if (node != NULL) { |
| (*lookup) = node->next_waked; |
| node->next_waked = NULL; |
| } |
| } |
| |
| static void |
| pipe_save( Pipe* pipe, QEMUFile* file ) |
| { |
| if (pipe->service == NULL) { |
| /* pipe->service == NULL means we're still using a PipeConnector */ |
| /* Write a zero to indicate this condition */ |
| qemu_put_byte(file, 0); |
| } else { |
| /* Otherwise, write a '1' then the service name */ |
| qemu_put_byte(file, 1); |
| qemu_put_string(file, pipe->service->name); |
| } |
| |
| /* Now save other common data */ |
| qemu_put_be32(file, (unsigned int)pipe->channel); |
| qemu_put_byte(file, (int)pipe->wanted); |
| qemu_put_byte(file, (int)pipe->closed); |
| |
| /* Write 1 + args, if any, or simply 0 otherwise */ |
| if (pipe->args != NULL) { |
| qemu_put_byte(file, 1); |
| qemu_put_string(file, pipe->args); |
| } else { |
| qemu_put_byte(file, 0); |
| } |
| |
| if (pipe->funcs->save) { |
| pipe->funcs->save(pipe->opaque, file); |
| } |
| } |
| |
| static Pipe* |
| pipe_load( PipeDevice* dev, QEMUFile* file ) |
| { |
| Pipe* pipe; |
| const PipeService* service = NULL; |
| int state = qemu_get_byte(file); |
| uint32_t channel; |
| |
| if (state != 0) { |
| /* Pipe is associated with a service. */ |
| char* name = qemu_get_string(file); |
| if (name == NULL) |
| return NULL; |
| |
| service = goldfish_pipe_find_type(name); |
| if (service == NULL) { |
| D("No QEMU pipe service named '%s'", name); |
| AFREE(name); |
| return NULL; |
| } |
| } |
| |
| channel = qemu_get_be32(file); |
| pipe = pipe_new(channel, dev); |
| pipe->wanted = qemu_get_byte(file); |
| pipe->closed = qemu_get_byte(file); |
| if (qemu_get_byte(file) != 0) { |
| pipe->args = qemu_get_string(file); |
| } |
| |
| pipe->service = service; |
| if (service != NULL) { |
| pipe->funcs = &service->funcs; |
| } |
| |
| if (pipe->funcs->load) { |
| pipe->opaque = pipe->funcs->load(pipe, service ? service->opaque : NULL, pipe->args, file); |
| if (pipe->opaque == NULL) { |
| AFREE(pipe); |
| return NULL; |
| } |
| } else { |
| /* Force-close the pipe on load */ |
| pipe->closed = 1; |
| } |
| return pipe; |
| } |
| |
| static void |
| pipe_free( Pipe* pipe ) |
| { |
| /* Call close callback */ |
| if (pipe->funcs->close) { |
| pipe->funcs->close(pipe->opaque); |
| } |
| /* Free stuff */ |
| AFREE(pipe->args); |
| AFREE(pipe); |
| } |
| |
| /*********************************************************************** |
| *********************************************************************** |
| ***** |
| ***** P I P E C O N N E C T O R S |
| ***** |
| *****/ |
| |
| /* These are used to handle the initial connection attempt, where the |
| * client is going to write the name of the pipe service it wants to |
| * connect to, followed by a terminating zero. |
| */ |
| typedef struct { |
| Pipe* pipe; |
| char buffer[128]; |
| int buffpos; |
| } PipeConnector; |
| |
| static const GoldfishPipeFuncs pipeConnector_funcs; // forward |
| |
| void* |
| pipeConnector_new(Pipe* pipe) |
| { |
| PipeConnector* pcon; |
| |
| ANEW0(pcon); |
| pcon->pipe = pipe; |
| pipe->funcs = &pipeConnector_funcs; |
| return pcon; |
| } |
| |
| static void |
| pipeConnector_close( void* opaque ) |
| { |
| PipeConnector* pcon = opaque; |
| AFREE(pcon); |
| } |
| |
| static int |
| pipeConnector_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) |
| { |
| PipeConnector* pcon = opaque; |
| const GoldfishPipeBuffer* buffers_limit = buffers + numBuffers; |
| int ret = 0; |
| |
| DD("%s: channel=0x%x numBuffers=%d", __FUNCTION__, |
| pcon->pipe->channel, |
| numBuffers); |
| |
| while (buffers < buffers_limit) { |
| int avail; |
| |
| DD("%s: buffer data (%3d bytes): '%.*s'", __FUNCTION__, |
| buffers[0].size, buffers[0].size, buffers[0].data); |
| |
| if (buffers[0].size == 0) { |
| buffers++; |
| continue; |
| } |
| |
| avail = sizeof(pcon->buffer) - pcon->buffpos; |
| if (avail > buffers[0].size) |
| avail = buffers[0].size; |
| |
| if (avail > 0) { |
| memcpy(pcon->buffer + pcon->buffpos, buffers[0].data, avail); |
| pcon->buffpos += avail; |
| ret += avail; |
| } |
| buffers++; |
| } |
| |
| /* Now check that our buffer contains a zero-terminated string */ |
| if (memchr(pcon->buffer, '\0', pcon->buffpos) != NULL) { |
| /* Acceptable formats for the connection string are: |
| * |
| * pipe:<name> |
| * pipe:<name>:<arguments> |
| */ |
| char* pipeName; |
| char* pipeArgs; |
| |
| D("%s: connector: '%s'", __FUNCTION__, pcon->buffer); |
| |
| if (memcmp(pcon->buffer, "pipe:", 5) != 0) { |
| /* Nope, we don't handle these for now. */ |
| D("%s: Unknown pipe connection: '%s'", __FUNCTION__, pcon->buffer); |
| return PIPE_ERROR_INVAL; |
| } |
| |
| pipeName = pcon->buffer + 5; |
| pipeArgs = strchr(pipeName, ':'); |
| |
| if (pipeArgs != NULL) { |
| *pipeArgs++ = '\0'; |
| if (!*pipeArgs) |
| pipeArgs = NULL; |
| } |
| |
| Pipe* pipe = pcon->pipe; |
| const PipeService* svc = goldfish_pipe_find_type(pipeName); |
| if (svc == NULL) { |
| D("%s: Unknown server!", __FUNCTION__); |
| return PIPE_ERROR_INVAL; |
| } |
| |
| void* peer = svc->funcs.init(pipe, svc->opaque, pipeArgs); |
| if (peer == NULL) { |
| D("%s: Initialization failed!", __FUNCTION__); |
| return PIPE_ERROR_INVAL; |
| } |
| |
| /* Do the evil switch now */ |
| pipe->opaque = peer; |
| pipe->service = svc; |
| pipe->funcs = &svc->funcs; |
| pipe->args = ASTRDUP(pipeArgs); |
| AFREE(pcon); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| pipeConnector_recvBuffers( void* opaque, GoldfishPipeBuffer* buffers, int numBuffers ) |
| { |
| return PIPE_ERROR_IO; |
| } |
| |
| static unsigned |
| pipeConnector_poll( void* opaque ) |
| { |
| return PIPE_POLL_OUT; |
| } |
| |
| static void |
| pipeConnector_wakeOn( void* opaque, int flags ) |
| { |
| /* nothing, really should never happen */ |
| } |
| |
| static void |
| pipeConnector_save( void* pipe, QEMUFile* file ) |
| { |
| PipeConnector* pcon = pipe; |
| qemu_put_sbe32(file, pcon->buffpos); |
| qemu_put_sbuffer(file, (const int8_t*)pcon->buffer, pcon->buffpos); |
| } |
| |
| static void* |
| pipeConnector_load( void* hwpipe, void* pipeOpaque, const char* args, QEMUFile* file ) |
| { |
| PipeConnector* pcon; |
| |
| int len = qemu_get_sbe32(file); |
| if (len < 0 || len > sizeof(pcon->buffer)) { |
| return NULL; |
| } |
| pcon = pipeConnector_new(hwpipe); |
| pcon->buffpos = len; |
| if (qemu_get_buffer(file, (uint8_t*)pcon->buffer, pcon->buffpos) != pcon->buffpos) { |
| AFREE(pcon); |
| return NULL; |
| } |
| return pcon; |
| } |
| |
| static const GoldfishPipeFuncs pipeConnector_funcs = { |
| NULL, /* init */ |
| pipeConnector_close, /* should rarely happen */ |
| pipeConnector_sendBuffers, /* the interesting stuff */ |
| pipeConnector_recvBuffers, /* should not happen */ |
| pipeConnector_poll, /* should not happen */ |
| pipeConnector_wakeOn, /* should not happen */ |
| pipeConnector_save, |
| pipeConnector_load, |
| }; |
| |
| /*********************************************************************** |
| *********************************************************************** |
| ***** |
| ***** 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. |
| */ |
| #if DEBUG_ZERO_PIPE |
| |
| typedef struct { |
| void* hwpipe; |
| } ZeroPipe; |
| |
| static void* |
| zeroPipe_init( void* hwpipe, void* svcOpaque, const char* args ) |
| { |
| ZeroPipe* zpipe; |
| |
| D("%s: hwpipe=%p", __FUNCTION__, hwpipe); |
| ANEW0(zpipe); |
| zpipe->hwpipe = hwpipe; |
| return zpipe; |
| } |
| |
| static void |
| zeroPipe_close( void* opaque ) |
| { |
| ZeroPipe* zpipe = opaque; |
| |
| D("%s: hwpipe=%p", __FUNCTION__, zpipe->hwpipe); |
| AFREE(zpipe); |
| } |
| |
| static int |
| zeroPipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) |
| { |
| int ret = 0; |
| while (numBuffers > 0) { |
| ret += buffers[0].size; |
| buffers++; |
| numBuffers--; |
| } |
| return ret; |
| } |
| |
| static int |
| zeroPipe_recvBuffers( void* opaque, GoldfishPipeBuffer* 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 GoldfishPipeFuncs zeroPipe_funcs = { |
| zeroPipe_init, |
| zeroPipe_close, |
| zeroPipe_sendBuffers, |
| zeroPipe_recvBuffers, |
| zeroPipe_poll, |
| zeroPipe_wakeOn, |
| }; |
| |
| #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 */ |
| |
| #if DEBUG_PINGPONG_PIPE |
| |
| /* 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); |
| ANEW0(ppipe); |
| pingPongPipe_init0(ppipe, hwpipe, svcOpaque); |
| return ppipe; |
| } |
| |
| static void |
| pingPongPipe_close( void* opaque ) |
| { |
| PingPongPipe* ppipe = opaque; |
| |
| D("%s: hwpipe=%p (pos=%d count=%d size=%d)", __FUNCTION__, |
| ppipe->hwpipe, ppipe->pos, ppipe->count, ppipe->size); |
| free(ppipe->buffer); |
| AFREE(ppipe); |
| } |
| |
| static int |
| pingPongPipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* buffers, int numBuffers ) |
| { |
| PingPongPipe* pipe = opaque; |
| int ret = 0; |
| int count; |
| const GoldfishPipeBuffer* buff = buffers; |
| const GoldfishPipeBuffer* 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 %d 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)) { |
| goldfish_pipe_wake(pipe->hwpipe, PIPE_WAKE_READ); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| pingPongPipe_recvBuffers( void* opaque, GoldfishPipeBuffer* 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)) { |
| goldfish_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 GoldfishPipeFuncs pingPongPipe_funcs = { |
| pingPongPipe_init, |
| pingPongPipe_close, |
| pingPongPipe_sendBuffers, |
| pingPongPipe_recvBuffers, |
| pingPongPipe_poll, |
| pingPongPipe_wakeOn, |
| }; |
| |
| #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. |
| */ |
| |
| #ifdef DEBUG_THROTTLE_PIPE |
| |
| 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; |
| |
| ANEW0(pipe); |
| pingPongPipe_init0(&pipe->pingpong, hwpipe, svcOpaque); |
| pipe->timer = qemu_new_timer_ns(vm_clock, 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; |
| |
| qemu_del_timer(pipe->timer); |
| qemu_free_timer(pipe->timer); |
| pingPongPipe_close(&pipe->pingpong); |
| } |
| |
| static void |
| throttlePipe_rearm( ThrottlePipe* pipe ) |
| { |
| int64_t minExpiration = 0; |
| |
| DD("%s: sendExpiration=%lld recvExpiration=%lld\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 %lld\n", __FUNCTION__, minExpiration); |
| qemu_mod_timer(pipe->timer, minExpiration); |
| } |
| } |
| |
| static void |
| throttlePipe_timerFunc( void* opaque ) |
| { |
| ThrottlePipe* pipe = opaque; |
| int64_t now = qemu_get_clock_ns(vm_clock); |
| |
| DD("%s: TICK! now=%lld sendExpiration=%lld recvExpiration=%lld\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); |
| goldfish_pipe_wake(pipe->pingpong.hwpipe, flags); |
| } |
| |
| throttlePipe_rearm(pipe); |
| } |
| |
| static int |
| throttlePipe_sendBuffers( void* opaque, const GoldfishPipeBuffer* 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_get_clock_ns(vm_clock) + ret*pipe->sendRate; |
| throttlePipe_rearm(pipe); |
| } |
| return ret; |
| } |
| |
| static int |
| throttlePipe_recvBuffers( void* opaque, GoldfishPipeBuffer* 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_get_clock_ns(vm_clock) + 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 GoldfishPipeFuncs throttlePipe_funcs = { |
| throttlePipe_init, |
| throttlePipe_close, |
| throttlePipe_sendBuffers, |
| throttlePipe_recvBuffers, |
| throttlePipe_poll, |
| throttlePipe_wakeOn, |
| }; |
| |
| #endif /* DEBUG_THROTTLE_PIPE */ |
| |
| /*********************************************************************** |
| *********************************************************************** |
| ***** |
| ***** G O L D F I S H P I P E D E V I C E |
| ***** |
| *****/ |
| |
| struct PipeDevice { |
| struct goldfish_device dev; |
| |
| /* the list of all pipes */ |
| Pipe* pipes; |
| |
| /* the list of signalled pipes */ |
| Pipe* signaled_pipes; |
| |
| /* i/o registers */ |
| uint32_t address; |
| uint32_t size; |
| uint32_t status; |
| uint32_t channel; |
| uint32_t wakes; |
| uint64_t params_addr; |
| }; |
| |
| static void |
| pipeDevice_doCommand( PipeDevice* dev, uint32_t command ) |
| { |
| Pipe** lookup = pipe_list_findp_channel(&dev->pipes, dev->channel); |
| Pipe* pipe = *lookup; |
| CPUState* env = cpu_single_env; |
| |
| /* Check that we're referring a known pipe channel */ |
| if (command != PIPE_CMD_OPEN && pipe == NULL) { |
| dev->status = PIPE_ERROR_INVAL; |
| return; |
| } |
| |
| /* If the pipe is closed by the host, return an error */ |
| if (pipe != NULL && pipe->closed && command != PIPE_CMD_CLOSE) { |
| dev->status = PIPE_ERROR_IO; |
| return; |
| } |
| |
| switch (command) { |
| case PIPE_CMD_OPEN: |
| DD("%s: CMD_OPEN channel=0x%x", __FUNCTION__, dev->channel); |
| if (pipe != NULL) { |
| dev->status = PIPE_ERROR_INVAL; |
| break; |
| } |
| pipe = pipe_new(dev->channel, dev); |
| pipe->next = dev->pipes; |
| dev->pipes = pipe; |
| dev->status = 0; |
| break; |
| |
| case PIPE_CMD_CLOSE: |
| DD("%s: CMD_CLOSE channel=0x%x", __FUNCTION__, dev->channel); |
| /* Remove from device's lists */ |
| *lookup = pipe->next; |
| pipe->next = NULL; |
| pipe_list_remove_waked(&dev->signaled_pipes, pipe); |
| pipe_free(pipe); |
| break; |
| |
| case PIPE_CMD_POLL: |
| dev->status = pipe->funcs->poll(pipe->opaque); |
| DD("%s: CMD_POLL > status=%d", __FUNCTION__, dev->status); |
| break; |
| |
| case PIPE_CMD_READ_BUFFER: { |
| /* Translate virtual address into physical one, into emulator memory. */ |
| GoldfishPipeBuffer buffer; |
| uint32_t address = dev->address; |
| uint32_t page = address & TARGET_PAGE_MASK; |
| target_phys_addr_t phys; |
| #ifdef CONFIG_KVM |
| if(kvm_enabled()) { |
| cpu_synchronize_state(env, 0); |
| } |
| #endif |
| phys = cpu_get_phys_page_debug(env, page); |
| buffer.data = qemu_get_ram_ptr(phys) + (address - page); |
| buffer.size = dev->size; |
| dev->status = pipe->funcs->recvBuffers(pipe->opaque, &buffer, 1); |
| DD("%s: CMD_READ_BUFFER channel=0x%x address=0x%08x size=%d > status=%d", |
| __FUNCTION__, dev->channel, dev->address, dev->size, dev->status); |
| break; |
| } |
| |
| case PIPE_CMD_WRITE_BUFFER: { |
| /* Translate virtual address into physical one, into emulator memory. */ |
| GoldfishPipeBuffer buffer; |
| uint32_t address = dev->address; |
| uint32_t page = address & TARGET_PAGE_MASK; |
| target_phys_addr_t phys; |
| #ifdef CONFIG_KVM |
| if(kvm_enabled()) { |
| cpu_synchronize_state(env, 0); |
| } |
| #endif |
| phys = cpu_get_phys_page_debug(env, page); |
| buffer.data = qemu_get_ram_ptr(phys) + (address - page); |
| buffer.size = dev->size; |
| dev->status = pipe->funcs->sendBuffers(pipe->opaque, &buffer, 1); |
| DD("%s: CMD_WRITE_BUFFER channel=0x%x address=0x%08x size=%d > status=%d", |
| __FUNCTION__, dev->channel, dev->address, dev->size, dev->status); |
| break; |
| } |
| |
| case PIPE_CMD_WAKE_ON_READ: |
| DD("%s: CMD_WAKE_ON_READ channel=0x%x", __FUNCTION__, dev->channel); |
| if ((pipe->wanted & PIPE_WAKE_READ) == 0) { |
| pipe->wanted |= PIPE_WAKE_READ; |
| pipe->funcs->wakeOn(pipe->opaque, pipe->wanted); |
| } |
| dev->status = 0; |
| break; |
| |
| case PIPE_CMD_WAKE_ON_WRITE: |
| DD("%s: CMD_WAKE_ON_WRITE channel=0x%x", __FUNCTION__, dev->channel); |
| if ((pipe->wanted & PIPE_WAKE_WRITE) == 0) { |
| pipe->wanted |= PIPE_WAKE_WRITE; |
| pipe->funcs->wakeOn(pipe->opaque, pipe->wanted); |
| } |
| dev->status = 0; |
| break; |
| |
| default: |
| D("%s: command=%d (0x%x)\n", __FUNCTION__, command, command); |
| } |
| } |
| |
| static void pipe_dev_write(void *opaque, target_phys_addr_t offset, uint32_t value) |
| { |
| PipeDevice *s = (PipeDevice *)opaque; |
| |
| switch (offset) { |
| case PIPE_REG_COMMAND: |
| DR("%s: command=%d (0x%x)", __FUNCTION__, value, value); |
| pipeDevice_doCommand(s, value); |
| break; |
| |
| case PIPE_REG_SIZE: |
| DR("%s: size=%d (0x%x)", __FUNCTION__, value, value); |
| s->size = value; |
| break; |
| |
| case PIPE_REG_ADDRESS: |
| DR("%s: address=%d (0x%x)", __FUNCTION__, value, value); |
| s->address = value; |
| break; |
| |
| case PIPE_REG_CHANNEL: |
| DR("%s: channel=%d (0x%x)", __FUNCTION__, value, value); |
| s->channel = value; |
| break; |
| |
| case PIPE_REG_PARAMS_ADDR_HIGH: |
| s->params_addr = (s->params_addr & ~(0xFFFFFFFFULL << 32) ) | |
| ((uint64_t)value << 32); |
| break; |
| |
| case PIPE_REG_PARAMS_ADDR_LOW: |
| s->params_addr = (s->params_addr & ~(0xFFFFFFFFULL) ) | value; |
| break; |
| |
| case PIPE_REG_ACCESS_PARAMS: |
| { |
| struct access_params aps; |
| uint32_t cmd; |
| |
| /* Don't touch aps.result if anything wrong */ |
| if (s->params_addr == 0) |
| break; |
| |
| cpu_physical_memory_read(s->params_addr, (void*)&aps, |
| sizeof(struct access_params)); |
| |
| /* sync pipe device state from batch buffer */ |
| s->channel = aps.channel; |
| s->size = aps.size; |
| s->address = aps.address; |
| cmd = aps.cmd; |
| if ((cmd != PIPE_CMD_READ_BUFFER) && (cmd != PIPE_CMD_WRITE_BUFFER)) |
| break; |
| |
| pipeDevice_doCommand(s, cmd); |
| aps.result = s->status; |
| cpu_physical_memory_write(s->params_addr, (void*)&aps, |
| sizeof(struct access_params)); |
| } |
| break; |
| |
| default: |
| D("%s: offset=%d (0x%x) value=%d (0x%x)\n", __FUNCTION__, offset, |
| offset, value, value); |
| break; |
| } |
| } |
| |
| /* I/O read */ |
| static uint32_t pipe_dev_read(void *opaque, target_phys_addr_t offset) |
| { |
| PipeDevice *dev = (PipeDevice *)opaque; |
| |
| switch (offset) { |
| case PIPE_REG_STATUS: |
| DR("%s: REG_STATUS status=%d (0x%x)", __FUNCTION__, dev->status, dev->status); |
| return dev->status; |
| |
| case PIPE_REG_CHANNEL: |
| if (dev->signaled_pipes != NULL) { |
| Pipe* pipe = dev->signaled_pipes; |
| DR("%s: channel=0x%x wanted=%d", __FUNCTION__, |
| pipe->channel, pipe->wanted); |
| dev->wakes = pipe->wanted; |
| pipe->wanted = 0; |
| dev->signaled_pipes = pipe->next_waked; |
| pipe->next_waked = NULL; |
| if (dev->signaled_pipes == NULL) { |
| goldfish_device_set_irq(&dev->dev, 0, 0); |
| DD("%s: lowering IRQ", __FUNCTION__); |
| } |
| return pipe->channel; |
| } |
| DR("%s: no signaled channels", __FUNCTION__); |
| return 0; |
| |
| case PIPE_REG_WAKES: |
| DR("%s: wakes %d", __FUNCTION__, dev->wakes); |
| return dev->wakes; |
| |
| case PIPE_REG_PARAMS_ADDR_HIGH: |
| return dev->params_addr >> 32; |
| |
| case PIPE_REG_PARAMS_ADDR_LOW: |
| return dev->params_addr & 0xFFFFFFFFUL; |
| |
| default: |
| D("%s: offset=%d (0x%x)\n", __FUNCTION__, offset, offset); |
| } |
| return 0; |
| } |
| |
| static CPUReadMemoryFunc *pipe_dev_readfn[] = { |
| pipe_dev_read, |
| pipe_dev_read, |
| pipe_dev_read |
| }; |
| |
| static CPUWriteMemoryFunc *pipe_dev_writefn[] = { |
| pipe_dev_write, |
| pipe_dev_write, |
| pipe_dev_write |
| }; |
| |
| static void |
| goldfish_pipe_save( QEMUFile* file, void* opaque ) |
| { |
| PipeDevice* dev = opaque; |
| Pipe* pipe; |
| |
| qemu_put_be32(file, dev->address); |
| qemu_put_be32(file, dev->size); |
| qemu_put_be32(file, dev->status); |
| qemu_put_be32(file, dev->channel); |
| qemu_put_be32(file, dev->wakes); |
| qemu_put_be64(file, dev->params_addr); |
| |
| /* Count the number of pipe connections */ |
| int count = 0; |
| for ( pipe = dev->pipes; pipe; pipe = pipe->next ) |
| count++; |
| |
| qemu_put_sbe32(file, count); |
| |
| /* Now save each pipe one after the other */ |
| for ( pipe = dev->pipes; pipe; pipe = pipe->next ) { |
| pipe_save(pipe, file); |
| } |
| } |
| |
| static int |
| goldfish_pipe_load( QEMUFile* file, void* opaque, int version_id ) |
| { |
| PipeDevice* dev = opaque; |
| Pipe* pipe; |
| |
| if (version_id != GOLDFISH_PIPE_SAVE_VERSION) |
| return -EINVAL; |
| |
| dev->address = qemu_get_be32(file); |
| dev->size = qemu_get_be32(file); |
| dev->status = qemu_get_be32(file); |
| dev->channel = qemu_get_be32(file); |
| dev->wakes = qemu_get_be32(file); |
| dev->params_addr = qemu_get_be64(file); |
| |
| /* Count the number of pipe connections */ |
| int count = qemu_get_sbe32(file); |
| |
| /* Load all pipe connections */ |
| for ( ; count > 0; count-- ) { |
| pipe = pipe_load(dev, file); |
| if (pipe == NULL) { |
| return -EIO; |
| } |
| pipe->next = dev->pipes; |
| dev->pipes = pipe; |
| } |
| |
| /* Now we need to wake/close all relevant pipes */ |
| for ( pipe = dev->pipes; pipe; pipe = pipe->next ) { |
| if (pipe->wanted != 0) |
| goldfish_pipe_wake(pipe, pipe->wanted); |
| if (pipe->closed != 0) |
| goldfish_pipe_close(pipe); |
| } |
| return 0; |
| } |
| |
| /* initialize the trace device */ |
| void pipe_dev_init() |
| { |
| PipeDevice *s; |
| |
| s = (PipeDevice *) qemu_mallocz(sizeof(*s)); |
| |
| s->dev.name = "qemu_pipe"; |
| s->dev.id = -1; |
| s->dev.base = 0; // will be allocated dynamically |
| s->dev.size = 0x2000; |
| s->dev.irq = 0; |
| s->dev.irq_count = 1; |
| |
| goldfish_device_add(&s->dev, pipe_dev_readfn, pipe_dev_writefn, s); |
| |
| register_savevm( "goldfish_pipe", 0, GOLDFISH_PIPE_SAVE_VERSION, |
| goldfish_pipe_save, goldfish_pipe_load, s); |
| |
| #if DEBUG_ZERO_PIPE |
| goldfish_pipe_add_type("zero", NULL, &zeroPipe_funcs); |
| #endif |
| #if DEBUG_PINGPONG_PIPE |
| goldfish_pipe_add_type("pingpong", NULL, &pingPongPipe_funcs); |
| #endif |
| #if DEBUG_THROTTLE_PIPE |
| goldfish_pipe_add_type("throttle", NULL, &throttlePipe_funcs); |
| #endif |
| } |
| |
| void |
| goldfish_pipe_wake( void* hwpipe, unsigned flags ) |
| { |
| Pipe* pipe = hwpipe; |
| Pipe** lookup; |
| PipeDevice* dev = pipe->device; |
| |
| DD("%s: channel=0x%x flags=%d", __FUNCTION__, pipe->channel, flags); |
| |
| /* If not already there, add to the list of signaled pipes */ |
| lookup = pipe_list_findp_waked(&dev->signaled_pipes, pipe); |
| if (!*lookup) { |
| pipe->next_waked = dev->signaled_pipes; |
| dev->signaled_pipes = pipe; |
| } |
| pipe->wanted |= (unsigned)flags; |
| |
| /* Raise IRQ to indicate there are items on our list ! */ |
| goldfish_device_set_irq(&dev->dev, 0, 1); |
| DD("%s: raising IRQ", __FUNCTION__); |
| } |
| |
| void |
| goldfish_pipe_close( void* hwpipe ) |
| { |
| Pipe* pipe = hwpipe; |
| |
| D("%s: channel=0x%x (closed=%d)", __FUNCTION__, pipe->channel, pipe->closed); |
| |
| if (!pipe->closed) { |
| pipe->closed = 1; |
| goldfish_pipe_wake( hwpipe, PIPE_WAKE_CLOSED ); |
| } |
| } |