blob: c9561f90437a20a2302a0ccc61e98e806d8a0e9f [file] [log] [blame]
/* 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-qemu1-glue/utils/stream.h"
#include "android/utils/panic.h"
#include "android/utils/stream.h"
#include "android/utils/system.h"
#include "hw/android/goldfish/pipe.h"
#include "hw/android/goldfish/device.h"
#include "hw/android/goldfish/vmem.h"
#include "exec/ram_addr.h"
#include "migration/vmstate.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
#define GOLDFISH_PIPE_SAVE_VERSION 4
#define GOLDFISH_PIPE_SAVE_LEGACY_VERSION 3
/***********************************************************************
***********************************************************************
*****
***** P I P E C O N N E C T I O N S
*****
*****/
typedef struct PipeDevice PipeDevice;
/***********************************************************************
***********************************************************************
*****
***** G O L D F I S H P I P E D E V I C E
*****
*****/
// An HwPipe instance models the virtual hardware view of a given
// Android pipe.
typedef struct HwPipe {
struct HwPipe* next;
struct HwPipe* next_waked;
uint64_t channel;
unsigned char wakes;
unsigned char closed;
void* pipe;
struct PipeDevice* device;
} HwPipe;
static HwPipe* hwpipe_new0(struct PipeDevice* device) {
HwPipe* hwp;
ANEW0(hwp);
hwp->device = device;
return hwp;
}
static HwPipe* hwpipe_new(uint64_t channel, struct PipeDevice* device) {
HwPipe* hwp = hwpipe_new0(device);
hwp->channel = channel;
hwp->pipe = android_pipe_guest_open(hwp);
return hwp;
}
static void hwpipe_free(HwPipe* hwp) {
android_pipe_guest_close(hwp->pipe);
free(hwp);
}
static HwPipe** hwpipe_findp_by_channel(HwPipe** list, uint64_t channel) {
HwPipe** pnode = list;
for (;;) {
HwPipe* node = *pnode;
if (!node) {
break;
}
if (node->channel == channel) {
break;
}
pnode = &node->next;
}
return pnode;
}
static HwPipe** hwpipe_findp_signaled(HwPipe** list, HwPipe* pipe) {
HwPipe** pnode = list;
for (;;) {
HwPipe* node = *pnode;
if (!node || node == pipe) {
break;
}
pnode = &node->next_waked;
}
return pnode;
}
static void hwpipe_remove_signaled(HwPipe** list, HwPipe* pipe) {
HwPipe** pnode = hwpipe_findp_signaled(list, pipe);
if (*pnode) {
*pnode = pipe->next_waked;
pipe->next_waked = NULL;
}
}
static void hwpipe_save(HwPipe* pipe, Stream* file) {
/* Now save other common data */
stream_put_be64(file, pipe->channel);
stream_put_byte(file, (int)pipe->wakes);
stream_put_byte(file, (int)pipe->closed);
android_pipe_guest_save(pipe->pipe, file);
}
static HwPipe* hwpipe_load(PipeDevice* dev, Stream* file, int version_id) {
HwPipe* pipe = hwpipe_new0(dev);
char force_close = 0;
if (version_id == GOLDFISH_PIPE_SAVE_LEGACY_VERSION) {
pipe->pipe = android_pipe_guest_load_legacy(file, pipe, &pipe->channel,
&pipe->wakes, &pipe->closed,
&force_close);
} else {
pipe->channel = stream_get_be64(file);
pipe->wakes = stream_get_byte(file);
pipe->closed = stream_get_byte(file);
pipe->pipe = android_pipe_guest_load(file, pipe, &force_close);
}
// SUBTLE: android_pipe_guest_load() may return NULL if the pipe state
// could not be saved. In this case |force_close| will be set though, so
// always check it first.
if (force_close) {
pipe->closed = 1;
} else if (!pipe->pipe) {
hwpipe_free(pipe);
return NULL;
}
return pipe;
}
struct PipeDevice {
struct goldfish_device dev;
/* the list of all pipes */
HwPipe* pipes;
/* the list of signalled pipes */
HwPipe* signaled_pipes;
/* i/o registers */
uint64_t address;
uint32_t size;
uint32_t status;
uint64_t channel;
uint32_t wakes;
uint64_t params_addr;
};
/* Update this version number if the device's interface changes. */
#define PIPE_DEVICE_VERSION 1
typedef enum { USE_VA, USE_PA } PipeDeviceMode;
/* ============ Backward Compatibility support ============
*
* By default, assume old device behavior which
* uses guest virtual addresses for read/write operations.
* New driver will change the device mode to operate with
* guest physical addresses by reading the version register.
*/
static PipeDeviceMode deviceMode = USE_VA;
/* Map the guest buffer into host memory.
*
* @xaddr - Guest VA or PA depending on the driver version.
* @size - Size of the mapping
* @is_write - Is it a read or write?
*
* @Return - Return a host pointer which should be unmapped later via
* cpu_physical_memory_unmap(), or NULL if mapping failed
* (likely because the paddr doesn't actually point at RAM).
* Note that for RAM the "mapping" process doesn't actually involve a
* data copy.
*/
static void *map_guest_buffer(target_ulong xaddr, size_t size, int is_write)
{
CPUOldState* env = cpu_single_env;
target_ulong page = xaddr & TARGET_PAGE_MASK;
hwaddr l = size;
hwaddr phys = xaddr;
void *ptr;
if (unlikely(deviceMode == USE_VA)) {
phys = safe_get_phys_page_debug(ENV_GET_CPU(env), page);
#ifdef TARGET_X86_64
phys = phys & TARGET_PTE_MASK;
#endif
}
ptr = cpu_physical_memory_map(phys, &l, is_write);
if (!ptr) {
/* Can't happen for RAM */
return NULL;
}
if (l != size) {
/* This will only happen if the address pointed at non-RAM,
* or if the size means the buffer end is beyond the end of
* the RAM block.
*/
cpu_physical_memory_unmap(ptr, l, 0, 0);
return NULL;
}
if (unlikely(deviceMode == USE_VA)) {
ptr += xaddr - page;
}
return ptr;
}
static void close_all_pipes(void* opaque) {
struct PipeDevice* dev = opaque;
HwPipe* pipe = dev->pipes;
while(pipe) {
HwPipe* old_pipe = pipe;
pipe = pipe->next;
hwpipe_remove_signaled(&dev->signaled_pipes, old_pipe);
hwpipe_free(old_pipe);
}
}
static void reset_pipe_device(void* opaque) {
PipeDevice* dev = opaque;
dev->pipes = NULL;
dev->signaled_pipes = NULL;
goldfish_device_set_irq(&dev->dev, 0, 0);
}
static void
pipeDevice_doCommand( PipeDevice* dev, uint32_t command )
{
HwPipe** lookup = hwpipe_findp_by_channel(&dev->pipes, dev->channel);
HwPipe* pipe = *lookup;
/* 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%llx", __FUNCTION__, (unsigned long long)dev->channel);
if (pipe != NULL) {
dev->status = PIPE_ERROR_INVAL;
break;
}
pipe = hwpipe_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%llx", __FUNCTION__, (unsigned long long)dev->channel);
/* Remove from device's lists */
*lookup = pipe->next;
pipe->next = NULL;
hwpipe_remove_signaled(&dev->signaled_pipes, pipe);
hwpipe_free(pipe);
break;
case PIPE_CMD_POLL:
dev->status = android_pipe_guest_poll(pipe->pipe);
DD("%s: CMD_POLL > status=%d", __FUNCTION__, dev->status);
break;
case PIPE_CMD_READ_BUFFER: {
/* Map guest buffer identified by dev->address
* (guest PA or VA depending on the driver version)
* into host memory.
*/
AndroidPipeBuffer buffer;
buffer.data = map_guest_buffer(dev->address, dev->size, 1);
if (!buffer.data) {
dev->status = PIPE_ERROR_INVAL;
break;
}
buffer.size = dev->size;
dev->status = android_pipe_guest_recv(pipe->pipe, &buffer, 1);
DD("%s: CMD_READ_BUFFER channel=0x%llx address=0x%llx size=%d > status=%d",
__FUNCTION__, (unsigned long long)dev->channel, (unsigned long long)dev->address,
dev->size, dev->status);
cpu_physical_memory_unmap(buffer.data, dev->size, 1, dev->size);
break;
}
case PIPE_CMD_WRITE_BUFFER: {
/* Map guest buffer identified by dev->address
* (guest PA or VA depending on the driver version)
* into host memory.
*/
AndroidPipeBuffer buffer;
buffer.data = map_guest_buffer(dev->address, dev->size, 0);
if (!buffer.data) {
dev->status = PIPE_ERROR_INVAL;
break;
}
buffer.size = dev->size;
dev->status = android_pipe_guest_send(pipe->pipe, &buffer, 1);
DD("%s: CMD_WRITE_BUFFER channel=0x%llx address=0x%llx size=%d > status=%d",
__FUNCTION__, (unsigned long long)dev->channel,
(unsigned long long)dev->address,
dev->size, dev->status);
cpu_physical_memory_unmap(buffer.data, dev->size, 0, dev->size);
break;
}
case PIPE_CMD_WAKE_ON_READ:
DD("%s: CMD_WAKE_ON_READ channel=0x%llx", __FUNCTION__,
(unsigned long long)dev->channel);
if ((pipe->wakes & PIPE_WAKE_READ) == 0) {
pipe->wakes |= PIPE_WAKE_READ;
android_pipe_guest_wake_on(pipe->pipe, pipe->wakes);
}
dev->status = 0;
break;
case PIPE_CMD_WAKE_ON_WRITE:
DD("%s: CMD_WAKE_ON_WRITE channel=0x%llx", __FUNCTION__,
(unsigned long long)dev->channel);
if ((pipe->wakes & PIPE_WAKE_WRITE) == 0) {
pipe->wakes |= PIPE_WAKE_WRITE;
android_pipe_guest_wake_on(pipe->pipe, pipe->wakes);
}
dev->status = 0;
break;
default:
D("%s: command=%d (0x%x)\n", __FUNCTION__, command, command);
}
}
static void pipe_dev_write(void *opaque, hwaddr 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);
uint64_set_low(&s->address, value);
break;
case PIPE_REG_ADDRESS_HIGH:
DR("%s: address_high=%d (0x%x)", __FUNCTION__, value, value);
uint64_set_high(&s->address, value);
break;
case PIPE_REG_CHANNEL:
DR("%s: channel=%d (0x%x)", __FUNCTION__, value, value);
uint64_set_low(&s->channel, value);
break;
case PIPE_REG_CHANNEL_HIGH:
DR("%s: channel_high=%d (0x%x)", __FUNCTION__, value, value);
uint64_set_high(&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;
struct access_params_64 aps64;
uint32_t cmd;
/* Don't touch aps.result if anything wrong */
if (s->params_addr == 0)
break;
if (goldfish_guest_is_64bit()) {
cpu_physical_memory_read(s->params_addr, (void*)&aps64,
sizeof(aps64));
} else {
cpu_physical_memory_read(s->params_addr, (void*)&aps,
sizeof(aps));
}
/* sync pipe device state from batch buffer */
if (goldfish_guest_is_64bit()) {
s->channel = aps64.channel;
s->size = aps64.size;
s->address = aps64.address;
cmd = aps64.cmd;
} else {
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);
if (goldfish_guest_is_64bit()) {
aps64.result = s->status;
cpu_physical_memory_write(s->params_addr, (void*)&aps64,
sizeof(aps64));
} else {
aps.result = s->status;
cpu_physical_memory_write(s->params_addr, (void*)&aps,
sizeof(aps));
}
}
break;
default:
D("%s: offset=%lld (0x%llx) value=%lld (0x%llx)\n", __FUNCTION__,
(long long)offset, (long long)offset, (long long)value,
(long long)value);
break;
}
}
/* I/O read */
static uint32_t pipe_dev_read(void *opaque, hwaddr 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) {
HwPipe* pipe = dev->signaled_pipes;
DR("%s: channel=0x%llx wakes=%d", __FUNCTION__,
(unsigned long long)pipe->channel, pipe->wakes);
dev->wakes = pipe->wakes;
pipe->wakes = 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 (uint32_t)(pipe->channel & 0xFFFFFFFFUL);
}
DR("%s: no signaled channels", __FUNCTION__);
return 0;
case PIPE_REG_CHANNEL_HIGH:
if (dev->signaled_pipes != NULL) {
HwPipe* pipe = dev->signaled_pipes;
DR("%s: channel_high=0x%llx wakes=%d", __FUNCTION__,
(unsigned long long)pipe->channel, pipe->wakes);
return (uint32_t)(pipe->channel >> 32);
}
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 (uint32_t)(dev->params_addr >> 32);
case PIPE_REG_PARAMS_ADDR_LOW:
return (uint32_t)(dev->params_addr & 0xFFFFFFFFUL);
case PIPE_REG_VERSION:
deviceMode = USE_PA;
// PIPE_REG_VERSION is issued on probe, which means that
// we should clean up all existing stale pipes.
// This helps keep the right state on rebooting.
close_all_pipes(dev);
reset_pipe_device(dev);
return PIPE_DEVICE_VERSION;
default:
D("%s: offset=%lld (0x%llx)\n", __FUNCTION__, (long long)offset,
(long long)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;
Stream* stream = stream_from_qemufile(file);
stream_put_be64(stream, dev->address);
stream_put_be32(stream, dev->size);
stream_put_be32(stream, dev->status);
stream_put_be64(stream, dev->channel);
stream_put_be32(stream, dev->wakes);
stream_put_be64(stream, dev->params_addr);
/* Count the number of pipe connections */
HwPipe* pipe;
int count = 0;
for (pipe = dev->pipes; pipe; pipe = pipe->next) {
count++;
}
stream_put_be32(stream, count);
/* Now save each pipe one after the other */
for (pipe = dev->pipes; pipe; pipe = pipe->next) {
hwpipe_save(pipe, stream);
}
stream_free(stream);
}
static void goldfish_pipe_wake(void* hwpipe, unsigned flags); // forward
static void goldfish_pipe_close(void* hwpipe); // forward
static int
goldfish_pipe_load( QEMUFile* file, void* opaque, int version_id )
{
PipeDevice* dev = opaque;
if ((version_id != GOLDFISH_PIPE_SAVE_VERSION) &&
(version_id != GOLDFISH_PIPE_SAVE_LEGACY_VERSION)) {
return -EINVAL;
}
Stream* stream = stream_from_qemufile(file);
dev->address = stream_get_be64(stream);
dev->size = stream_get_be32(stream);
dev->status = stream_get_be32(stream);
dev->channel = stream_get_be64(stream);
dev->wakes = stream_get_be32(stream);
dev->params_addr = stream_get_be64(stream);
/* Count the number of pipe connections */
HwPipe* pipe;
int count = stream_get_be32(stream);
/* Load all pipe connections */
for (; count > 0; count--) {
pipe = hwpipe_load(dev, stream, version_id);
if (pipe == NULL) {
stream_free(stream);
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->wakes != 0) {
goldfish_pipe_wake(pipe, pipe->wakes);
}
if (pipe->closed != 0) {
goldfish_pipe_close(pipe);
}
}
stream_free(stream);
return 0;
}
static void goldfish_pipe_reset(void* hwpipe, void* internal_pipe) {
HwPipe* pipe = hwpipe;
pipe->pipe = internal_pipe;
}
static void goldfish_pipe_wake(void* hwpipe, unsigned flags) {
HwPipe* pipe = hwpipe;
HwPipe** lookup;
PipeDevice* dev = pipe->device;
DD("%s: channel=0x%llx flags=%d", __FUNCTION__,
(unsigned long long)pipe->channel, flags);
/* If not already there, add to the list of signaled pipes */
lookup = hwpipe_findp_signaled(&dev->signaled_pipes, pipe);
if (!*lookup) {
pipe->next_waked = dev->signaled_pipes;
dev->signaled_pipes = pipe;
}
pipe->wakes |= (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__);
}
static void goldfish_pipe_close(void* hwpipe) {
HwPipe* pipe = hwpipe;
D("%s: channel=0x%llx (closed=%d)", __FUNCTION__,
(unsigned long long)pipe->channel, pipe->closed);
pipe->closed = 1;
goldfish_pipe_wake(hwpipe, PIPE_WAKE_CLOSED);
}
static const AndroidPipeHwFuncs goldfish_pipe_hw_funcs = {
.resetPipe = goldfish_pipe_reset,
.closeFromHost = goldfish_pipe_close,
.signalWake = goldfish_pipe_wake,
};
/* initialize the trace device */
void pipe_dev_init(bool newDeviceNaming)
{
PipeDevice *s;
s = (PipeDevice *) g_malloc0(sizeof(*s));
s->dev.name = newDeviceNaming ? "goldfish_pipe" : "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(NULL,
"goldfish_pipe",
0,
GOLDFISH_PIPE_SAVE_VERSION,
goldfish_pipe_save,
goldfish_pipe_load,
s);
android_pipe_set_hw_funcs(&goldfish_pipe_hw_funcs);
}