blob: f5988574e3e6d4b25a4a0b1474d4ffd6288252c9 [file] [log] [blame]
/* Copyright (C) 2011 The Android Open Source Project
** Copyright (C) 2014 Linaro Limited
** Copyright (C) 2015 Intel Corporation
**
** 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
**
** This device provides a virtual pipe device (originally called
** goldfish_pipe and latterly qemu_pipe). This allows the android
** running under the emulator to open a fast connection to the host
** for various purposes including the adb debug bridge and
** (eventually) the opengles pass-through. This file contains only the
** basic pipe infrastructure and a couple of test pipes. Additional
** pipes are registered with the goldfish_pipe_add_type() call.
**
** Open Questions
**
** Since this was originally written there have been a number of other
** virtual devices added to QEMU using the virtio infrastructure. We
** should give some thought to if this needs re-writing to take
** advantage of that infrastructure to create the pipes.
*/
#include "hw/misc/goldfish_pipe.h"
#include "qemu/osdep.h"
#include "hw/hw.h"
#include "hw/sysbus.h"
#include "qemu-common.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "qemu/error-report.h"
#include <assert.h>
#include <glib.h>
/* Set to > 0 for debug output */
#define PIPE_DEBUG 0
/* Set to 1 to debug i/o register reads/writes */
#define PIPE_DEBUG_REGS 0
#if PIPE_DEBUG >= 1
#define D(fmt, ...) \
do { fprintf(stdout, "goldfish_pipe: " fmt "\n", ## __VA_ARGS__); } while (0)
#else
#define D(fmt, ...) do { /* nothing */ } while (0)
#endif
#if PIPE_DEBUG >= 2
#define DD(fmt, ...) \
do { fprintf(stdout, "goldfish_pipe: " fmt "\n", ## __VA_ARGS__); } while (0)
#else
#define DD(fmt, ...) do { /* nothing */ } while (0)
#endif
#if PIPE_DEBUG_REGS >= 1
# define DR(...) D(__VA_ARGS__)
#else
# define DR(...) do { /* nothing */ } while (0)
#endif
#define E(fmt, ...) \
do { fprintf(stdout, "ERROR:" fmt "\n", ## __VA_ARGS__); } while (0)
#define APANIC(...) \
do { \
error_report(__VA_ARGS__); \
exit(1); \
} while (0)
/* The following definitions must match those in the kernel driver
* found at android.googlesource.com/kernel/goldfish.git
*
* drivers/platform/goldfish/goldfish_pipe*
*/
typedef enum PipeRegs {
PIPE_REG_CMD = 0,
PIPE_REG_SIGNAL_BUFFER_HIGH = 4,
PIPE_REG_SIGNAL_BUFFER = 8,
PIPE_REG_SIGNAL_BUFFER_COUNT = 12,
PIPE_REG_OPEN_BUFFER_HIGH = 20,
PIPE_REG_OPEN_BUFFER = 24,
PIPE_REG_VERSION = 36,
PIPE_REG_GET_SIGNALLED = 48,
// v1-specific registers
PIPE_REG_STATUS = 0x04, /* read */
PIPE_REG_CHANNEL = 0x08, /* read/write: channel id */
PIPE_REG_SIZE = 0x0c, /* read/write: buffer size */
PIPE_REG_ADDRESS = 0x10, /* write: physical address */
PIPE_REG_WAKES = 0x14, /* read: wake flags */
/* read/write: parameter buffer address */
PIPE_REG_PARAMS_ADDR_LOW = 0x18,
PIPE_REG_PARAMS_ADDR_HIGH = 0x1c,
/* write: access with paremeter buffer */
PIPE_REG_ACCESS_PARAMS = 0x20,
PIPE_REG_CHANNEL_HIGH = 0x30, /* read/write: high 32 bit channel id */
PIPE_REG_ADDRESS_HIGH = 0x34, /* write: high 32 bit physical address */
} PipeRegs;
typedef enum PipeCmd {
PIPE_CMD_OPEN = 1, // to be used by the pipe device itself
PIPE_CMD_CLOSE,
PIPE_CMD_POLL,
PIPE_CMD_WRITE,
PIPE_CMD_WAKE_ON_WRITE,
PIPE_CMD_READ,
PIPE_CMD_WAKE_ON_READ,
PIPE_CMD_WAKE_ON_DONE_IO,
} PipeCmd;
enum {
MAX_BUFFERS_COUNT = 160,
COMMAND_BUFFER_SIZE = 4096,
};
#define TYPE_GOLDFISH_PIPE "goldfish_pipe"
#define GOLDFISH_PIPE(obj) \
OBJECT_CHECK(GoldfishPipeState, (obj), TYPE_GOLDFISH_PIPE)
/* Update this version number if the device's interface changes. */
enum {
PIPE_DEVICE_VERSION = 2,
PIPE_DEVICE_VERSION_v1 = 1,
MAX_SUPPORTED_DRIVER_VERSION = 2,
PIPE_DRIVER_VERSION_v1 = 0, // used to not report its version at all
};
/* These default callbacks are provided to detect when emulation setup
* didn't register a pipe service implementation correctly! There is no
* need to provide other GoldfishPipeServiceOps callbacks, since they
* cannot be called if one could not open or create a host pipe.
*
* NOTE: Returning NULL will force-close the pipe as soon as it is
* opened by the guest. */
static GoldfishHostPipe *null_guest_open(GoldfishHwPipe *hw_pipe)
{
E("Android guest tried to open a pipe before service registration!\n"
"Please call goldfish_pipe_set_service_ops() at setup time!");
(void)hw_pipe;
return NULL;
}
static GoldfishHostPipe *null_guest_load(QEMUFile *file,
GoldfishHwPipe *hw_pipe,
char *force_close)
{
E("Trying to load a pipe before service registration!\n"
"Please call goldfish_pipe_set_service_ops() at setup time!");
(void)file;
(void)hw_pipe;
(void)force_close;
return NULL;
}
static const GoldfishPipeServiceOps s_null_service_ops = {
.guest_open = null_guest_open,
.guest_load = null_guest_load,
};
static const GoldfishPipeServiceOps* service_ops = &s_null_service_ops;
void goldfish_pipe_set_service_ops(const GoldfishPipeServiceOps* ops) {
service_ops = ops;
}
/* from AOSP version include/hw/android/goldfish/device.h
* FIXME?: needs to use proper qemu abstractions
*/
static inline void uint64_set_low(uint64_t* addr, uint32_t value) {
*addr = (*addr & ~(0xFFFFFFFFULL)) | value;
}
static inline void uint64_set_high(uint64_t* addr, uint32_t value) {
*addr = (*addr & 0xFFFFFFFFULL) | ((uint64_t)value << 32);
}
typedef struct PipeDevice PipeDevice;
struct access_params_32 {
uint32_t channel; /* 0x00 */
uint32_t size; /* 0x04 */
uint32_t address; /* 0x08 */
uint32_t cmd; /* 0x0c */
uint32_t result; /* 0x10 */
/* reserved for future extension */
uint32_t flags; /* 0x14 */
};
struct access_params_64 {
uint64_t channel; /* 0x00 */
uint32_t size; /* 0x08 */
uint64_t address; /* 0x0c */
uint32_t cmd; /* 0x14 */
uint32_t result; /* 0x18 */
/* reserved for future extension */
uint32_t flags; /* 0x1c */
};
union access_params {
struct access_params_32 aps32;
struct access_params_64 aps64;
};
/* A set of version-specific pipe operations */
typedef struct {
void (*wanted_list_add)(PipeDevice* dev, GoldfishHwPipe* pipe);
void (*close_all)(PipeDevice* dev);
void (*save)(QEMUFile* file, PipeDevice* dev);
void (*dev_write)(PipeDevice* dev, hwaddr offset, uint64_t value);
uint64_t (*dev_read)(PipeDevice* dev, hwaddr offset);
} PipeOperations;
/* Operations for the old and the latest pipe device versions */
static const PipeOperations pipe_ops_v2;
static const PipeOperations pipe_ops_v1;
typedef struct {
SysBusDevice parent;
MemoryRegion iomem;
qemu_irq irq;
/* TODO: roll into shared state */
PipeDevice *dev;
} GoldfishPipeState;
typedef struct PipeCommand {
int32_t cmd;
int32_t id;
int32_t status;
int32_t padding;
union {
struct {
uint64_t ptrs[MAX_BUFFERS_COUNT];
uint32_t sizes[MAX_BUFFERS_COUNT];
uint32_t buffers_count;
int32_t consumed_size;
} rw_params;
};
} PipeCommand;
struct GoldfishHwPipe {
struct GoldfishHwPipe *wanted_next;
struct GoldfishHwPipe *wanted_prev;
PipeDevice* dev;
uint32_t id; // pipe ID is its index into the PipeDevice::pipes array
unsigned char wanted;
char closed;
GoldfishHostPipe *host_pipe;
uint64_t command_buffer_addr;
PipeCommand* command_buffer;
// v1-specific fields
struct GoldfishHwPipe* next;
uint64_t channel; /* opaque kernel handle */
};
typedef GoldfishHwPipe HwPipe;
typedef GoldfishHostPipe HostPipe;
typedef struct GuestSignalledPipe {
uint32_t id;
uint32_t flags;
} GuestSignalledPipe;
typedef struct OpenCommandParams {
uint64_t command_buffer_ptr;
} OpenCommandParams;
typedef struct PipeDevice {
GoldfishPipeState* ps; // backlink to instance state
int device_version; // host device verion
int driver_version; // guest's driver version
const PipeOperations* ops; // a set of version-specific pipe operations
// A vector of all pipes.
HwPipe** pipes;
// Capacity of the vector
unsigned pipes_capacity;
// The list of the pipes that signalled some 'wanted' state.
HwPipe* wanted_pipes_first;
uint64_t signalled_pipe_buffer_addr;
uint64_t open_command_addr;
GuestSignalledPipe* signalled_pipe_buffer;
unsigned signalled_pipe_buffer_size;
OpenCommandParams* open_command;
// v1-specific fields
// The list of all pipes.
HwPipe* pipes_list;
// A cached wanted pipe after the guest's _CHANNEL_HIGH call. We need to
// return the very same pipe during the following *_CHANNEL call.
HwPipe* wanted_pipe_after_channel_high;
// Cache of the pipes by channel for a faster lookup.
GHashTable* pipes_by_channel;
// i/o registers
uint64_t address;
uint32_t size;
uint32_t status;
uint64_t channel;
uint32_t wakes;
uint64_t params_addr;
} PipeDevice;
// hashtable-related functions
// 64-bit emulator just casts uint64_t to gpointer to get a key for the
// GLib's hash table. 32-bit one has to dynamically allocate an 8-byte chunk
// of memory and copy the channel value there, storing pointer as a key.
static uint64_t hash_channel_from_key(gconstpointer key) {
#ifdef __x86_64__
// keys are just channels
return (uint64_t)key;
#else
// keys are pointers to channels
return *(uint64_t*)key;
#endif
}
static gpointer hash_create_key_from_channel(uint64_t channel) {
#ifdef __x86_64__
return (gpointer)channel;
#else
uint64_t* on_heap = (uint64_t*)malloc(sizeof(channel));
if (!on_heap) {
APANIC("%s: failed to allocate RAM for a channel value\n", __func__);
}
*on_heap = channel;
return on_heap;
#endif
}
static gconstpointer hash_cast_key_from_channel(const uint64_t* channel) {
#ifdef __x86_64__
return (gconstpointer)*channel;
#else
return (gconstpointer)channel;
#endif
}
static guint hash_channel(gconstpointer key) {
uint64_t channel = hash_channel_from_key(key);
return (guint)(channel ^ (channel >> 6));
}
static gboolean hash_channel_equal(gconstpointer a, gconstpointer b) {
return hash_channel_from_key(a) == hash_channel_from_key(b);
}
#ifdef __x86_64__
// we don't need to free channels in 64-bit emulator as we store them in-place
static void (*hash_channel_destroy)(gpointer a) = NULL;
#else
static void hash_channel_destroy(gpointer a) {
free((uint64_t*)a);
}
#endif
static unsigned char hwpipe_get_and_clear_wanted(HwPipe* pipe) {
unsigned char val = pipe->wanted;
pipe->wanted = 0;
return val;
}
static void hwpipe_set_wanted(HwPipe* pipe, unsigned char val) {
pipe->wanted |= val;
}
static HwPipe* hwpipe_new0(PipeDevice* dev) {
HwPipe* pipe;
pipe = g_malloc0(sizeof(HwPipe));
pipe->dev = dev;
return pipe;
}
static HwPipe* hwpipe_new(uint32_t id, uint64_t channel, PipeDevice* dev) {
HwPipe* pipe = hwpipe_new0(dev);
pipe->id = id;
pipe->channel = channel;
pipe->host_pipe = service_ops->guest_open(pipe);
return pipe;
}
static void hwpipe_free(HwPipe* pipe) {
if (pipe->host_pipe)
service_ops->guest_close(pipe->host_pipe);
g_free(pipe);
}
// Wanted pipe linked list operations
static HwPipe* wanted_pipes_pop_first_v2(PipeDevice* dev) {
HwPipe* pipe = dev->wanted_pipes_first;
if (pipe) {
dev->wanted_pipes_first = pipe->wanted_next;
assert(pipe->wanted_prev == NULL);
pipe->wanted_next = NULL;
if (dev->wanted_pipes_first) {
dev->wanted_pipes_first->wanted_prev = NULL;
}
}
return pipe;
}
static void wanted_pipes_add_v2(PipeDevice* dev, HwPipe* pipe) {
if (!pipe->wanted_next && !pipe->wanted_prev &&
dev->wanted_pipes_first != pipe) {
pipe->wanted_next = dev->wanted_pipes_first;
if (dev->wanted_pipes_first) {
dev->wanted_pipes_first->wanted_prev = pipe;
}
dev->wanted_pipes_first = pipe;
}
}
static void wanted_pipes_remove_v2(PipeDevice* dev, HwPipe* pipe) {
if (pipe->wanted_next) {
pipe->wanted_next->wanted_prev = pipe->wanted_prev;
}
if (pipe->wanted_prev) {
pipe->wanted_prev->wanted_next = pipe->wanted_next;
pipe->wanted_prev = NULL;
} else if (dev->wanted_pipes_first == pipe) {
dev->wanted_pipes_first = pipe->wanted_next;
}
pipe->wanted_next = NULL;
}
// same for v1
static HwPipe* wanted_pipes_pop_first_v1(PipeDevice* dev) {
if (dev->wanted_pipe_after_channel_high) {
HwPipe* val = dev->wanted_pipe_after_channel_high;
dev->wanted_pipe_after_channel_high = NULL;
return val;
}
HwPipe* pipe = dev->wanted_pipes_first;
if (pipe) {
dev->wanted_pipes_first = pipe->wanted_next;
assert(pipe->wanted_prev == NULL);
pipe->wanted_next = NULL;
if (dev->wanted_pipes_first) {
dev->wanted_pipes_first->wanted_prev = NULL;
}
}
return pipe;
}
static void wanted_pipes_add_v1(PipeDevice* dev, HwPipe* pipe) {
if (!pipe->wanted_next && !pipe->wanted_prev &&
dev->wanted_pipes_first != pipe &&
dev->wanted_pipe_after_channel_high != pipe) {
pipe->wanted_next = dev->wanted_pipes_first;
if (dev->wanted_pipes_first) {
dev->wanted_pipes_first->wanted_prev = pipe;
}
dev->wanted_pipes_first = pipe;
}
}
static void wanted_pipes_remove_v1(PipeDevice* dev, HwPipe* pipe) {
if (dev->wanted_pipe_after_channel_high == pipe) {
dev->wanted_pipe_after_channel_high = NULL;
}
if (pipe->wanted_next) {
pipe->wanted_next->wanted_prev = pipe->wanted_prev;
}
if (pipe->wanted_prev) {
pipe->wanted_prev->wanted_next = pipe->wanted_next;
pipe->wanted_prev = NULL;
} else if (dev->wanted_pipes_first == pipe) {
dev->wanted_pipes_first = pipe->wanted_next;
}
pipe->wanted_next = NULL;
}
/* Map the guest buffer specified by the guest paddr 'phys'.
* Returns 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(hwaddr phys, size_t size, int is_write) {
hwaddr l = size;
void* ptr;
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;
}
return ptr;
}
static void unmap_command_buffer(void* buffer) {
cpu_physical_memory_unmap(buffer, COMMAND_BUFFER_SIZE, 1,
COMMAND_BUFFER_SIZE);
}
static void close_all_pipes_v1(PipeDevice* dev) {
HwPipe* pipe = dev->pipes_list;
while (pipe) {
HwPipe* next = pipe->next;
hwpipe_free(pipe);
pipe = next;
}
dev->pipes_list = NULL;
}
static void close_all_pipes_v2(PipeDevice* dev) {
int i = 0;
for (; i < dev->pipes_capacity; ++i) {
HwPipe* pipe = dev->pipes[i];
if (pipe) {
unmap_command_buffer(pipe->command_buffer);
hwpipe_free(pipe);
dev->pipes[i] = NULL;
}
}
}
static void reset_pipe_device(PipeDevice* dev) {
dev->wanted_pipes_first = NULL;
dev->wanted_pipe_after_channel_high = NULL;
g_hash_table_remove_all(dev->pipes_by_channel);
qemu_set_irq(dev->ps->irq, 0);
}
static void pipeDevice_doCommand_v1(PipeDevice* dev, uint32_t command) {
HwPipe* pipe = (HwPipe*)g_hash_table_lookup(
dev->pipes_by_channel, hash_cast_key_from_channel(&dev->channel));
/* Check that we're referring a known pipe channel */
if (command != PIPE_CMD_OPEN && pipe == NULL) {
dev->status = GOLDFISH_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 = GOLDFISH_PIPE_ERROR_IO;
return;
}
switch (command) {
case PIPE_CMD_OPEN:
DD("%s: CMD_OPEN channel=0x%llx", __func__,
(unsigned long long)dev->channel);
if (pipe != NULL) {
dev->status = GOLDFISH_PIPE_ERROR_INVAL;
break;
}
pipe = hwpipe_new(0, dev->channel, dev);
if (!pipe->host_pipe) {
hwpipe_free(pipe);
dev->status = GOLDFISH_PIPE_ERROR_INVAL;
break;
}
pipe->next = dev->pipes_list;
dev->pipes_list = pipe;
dev->status = 0;
g_hash_table_insert(dev->pipes_by_channel,
hash_create_key_from_channel(dev->channel), pipe);
break;
case PIPE_CMD_CLOSE: {
DD("%s: CMD_CLOSE channel=0x%llx", __func__,
(unsigned long long)dev->channel);
// Remove from device's lists.
// This linear lookup is potentially slow, but we don't delete pipes
// often enough for it to become noticable.
HwPipe** pnode = &dev->pipes_list;
while (*pnode && *pnode != pipe) {
pnode = &(*pnode)->next;
}
if (!*pnode) {
dev->status = GOLDFISH_PIPE_ERROR_INVAL;
break;
}
*pnode = pipe->next;
pipe->next = NULL;
g_hash_table_remove(dev->pipes_by_channel,
hash_cast_key_from_channel(&pipe->channel));
wanted_pipes_remove_v1(dev, pipe);
hwpipe_free(pipe);
break;
}
case PIPE_CMD_POLL:
dev->status = service_ops->guest_poll(pipe->host_pipe);
DD("%s: CMD_POLL > status=%d", __func__, dev->status);
break;
case PIPE_CMD_READ: {
/* Translate guest physical address into emulator memory. */
GoldfishPipeBuffer buffer;
buffer.data = map_guest_buffer(dev->address, dev->size, /*is_write*/1);
if (!buffer.data) {
dev->status = GOLDFISH_PIPE_ERROR_INVAL;
break;
}
buffer.size = dev->size;
dev->status = service_ops->guest_recv(pipe->host_pipe, &buffer, 1);
DD("%s: CMD_READ channel=0x%llx address=0x%16llx size=%d > status=%d",
__func__, (unsigned long long)dev->channel,
(unsigned long long)dev->address,
dev->size, dev->status);
cpu_physical_memory_unmap(buffer.data, dev->size,
/*is_write*/1, dev->size);
break;
}
case PIPE_CMD_WRITE: {
/* Translate guest physical address into emulator memory. */
GoldfishPipeBuffer buffer;
buffer.data = map_guest_buffer(dev->address, dev->size, /*is_write*/0);
if (!buffer.data) {
dev->status = GOLDFISH_PIPE_ERROR_INVAL;
break;
}
buffer.size = dev->size;
dev->status = service_ops->guest_send(pipe->host_pipe, &buffer, 1);
DD("%s: CMD_WRITE_BUFFER channel=0x%llx address=0x%16llx size=%d > "
"status=%d", __func__, (unsigned long long)dev->channel,
(unsigned long long)dev->address, dev->size, dev->status);
cpu_physical_memory_unmap(buffer.data, dev->size,
/*is_write*/0, dev->size);
break;
}
case PIPE_CMD_WAKE_ON_READ:
DD("%s: CMD_WAKE_ON_READ channel=0x%llx", __func__,
(unsigned long long)dev->channel);
if ((pipe->wanted & GOLDFISH_PIPE_WAKE_READ) == 0) {
pipe->wanted |= GOLDFISH_PIPE_WAKE_READ;
service_ops->guest_wake_on(pipe->host_pipe, pipe->wanted);
}
dev->status = 0;
break;
case PIPE_CMD_WAKE_ON_WRITE:
DD("%s: CMD_WAKE_ON_WRITE channel=0x%llx", __func__,
(unsigned long long)dev->channel);
if ((pipe->wanted & GOLDFISH_PIPE_WAKE_WRITE) == 0) {
pipe->wanted |= GOLDFISH_PIPE_WAKE_WRITE;
service_ops->guest_wake_on(pipe->host_pipe, pipe->wanted);
}
dev->status = 0;
break;
default:
D("%s: command=%d (0x%x)\n", __func__, command, command);
}
}
static void pipeDevice_doOpenClose_v2(PipeDevice* dev, uint32_t id) {
PipeCommand* commandBuffer = (PipeCommand*)map_guest_buffer(
dev->open_command->command_buffer_ptr,
COMMAND_BUFFER_SIZE, /*is_write*/1);
if (!commandBuffer) {
// well, what can we do here?
return;
}
if (commandBuffer->id != id) {
commandBuffer->status = GOLDFISH_PIPE_ERROR_INVAL;
unmap_command_buffer(commandBuffer);
return;
}
if (commandBuffer->cmd == PIPE_CMD_CLOSE) {
// it's OK to close an already closed pipe
commandBuffer->status = 0;
unmap_command_buffer(commandBuffer);
return;
}
if (commandBuffer->cmd != PIPE_CMD_OPEN) {
commandBuffer->status = GOLDFISH_PIPE_ERROR_INVAL;
unmap_command_buffer(commandBuffer);
return;
}
if (id >= dev->pipes_capacity) {
int newCapacity = (id + 1 > 2 * dev->pipes_capacity)
? id + 1
: 2 * dev->pipes_capacity;
HwPipe** pipes = calloc(newCapacity, sizeof(HwPipe*));
if (!pipes) {
commandBuffer->status = GOLDFISH_PIPE_ERROR_NOMEM;
unmap_command_buffer(commandBuffer);
return;
}
memcpy(pipes, dev->pipes, sizeof(HwPipe*) * dev->pipes_capacity);
dev->pipes = pipes;
dev->pipes_capacity = newCapacity;
}
HwPipe* pipe = hwpipe_new(id, 0, dev);
if (!pipe || !pipe->host_pipe) {
hwpipe_free(pipe);
commandBuffer->status = GOLDFISH_PIPE_ERROR_NOMEM;
unmap_command_buffer(commandBuffer);
return;
}
pipe->command_buffer_addr = dev->open_command->command_buffer_ptr;
pipe->command_buffer = commandBuffer;
dev->pipes[id] = pipe;
commandBuffer->status = 0;
}
static void pipeDevice_doCommand_v2(HwPipe* pipe) {
assert(pipe);
assert(pipe->command_buffer->cmd != PIPE_CMD_OPEN);
PipeDevice* dev = pipe->dev;
PipeCmd command = pipe->command_buffer->cmd;
/* If the pipe is closed by the host, return an error */
if (pipe->closed && command != PIPE_CMD_CLOSE) {
pipe->command_buffer->status = GOLDFISH_PIPE_ERROR_IO;
return;
}
switch (command) {
case PIPE_CMD_CLOSE: {
DD("%s: CMD_CLOSE id=%d", __func__, (int)pipe->id);
// Remove from device's lists.
dev->pipes[pipe->id] = NULL;
wanted_pipes_remove_v2(dev, pipe);
pipe->command_buffer->status = 0;
unmap_command_buffer(pipe->command_buffer);
hwpipe_free(pipe);
break;
}
case PIPE_CMD_POLL:
pipe->command_buffer->status =
service_ops->guest_poll(pipe->host_pipe);
DD("%s: CMD_POLL > status=%d", __func__,
pipe->command_buffer->status);
break;
case PIPE_CMD_READ:
case PIPE_CMD_WRITE: {
bool willModifyData = command == PIPE_CMD_READ;
pipe->command_buffer->rw_params.consumed_size = 0;
unsigned buffers_count =
pipe->command_buffer->rw_params.buffers_count;
if (buffers_count > MAX_BUFFERS_COUNT) {
buffers_count = MAX_BUFFERS_COUNT;
}
GoldfishPipeBuffer buffers[MAX_BUFFERS_COUNT];
unsigned i;
for (i = 0; i < buffers_count; ++i) {
buffers[i].size = pipe->command_buffer->rw_params.sizes[i];
buffers[i].data = map_guest_buffer(
pipe->command_buffer->rw_params.ptrs[i],
pipe->command_buffer->rw_params.sizes[i],
willModifyData);
if (!buffers[i].data) {
pipe->command_buffer->status = GOLDFISH_PIPE_ERROR_INVAL;
goto done;
}
}
pipe->command_buffer->status =
willModifyData
? service_ops->guest_recv(pipe->host_pipe,
buffers,
buffers_count)
: service_ops->guest_send(pipe->host_pipe,
buffers,
buffers_count);
// TODO(zyy): create an extended version of send()/recv() functions
// to
// return both transferred size and resulting status in single
// call.
pipe->command_buffer->rw_params.consumed_size =
pipe->command_buffer->status < 0
? 0
: pipe->command_buffer->status;
DD("%s: CMD_%s id=%d buffers=%d > status=%d", __func__,
(willModifyData ? "READ" : "WRITE"), (int)pipe->id,
(int)buffers_count, pipe->command_buffer->status);
// C doesn't allow a label on a declaration, so this has to be
// before the labelled statement.
unsigned j;
done:
for (j = 0; j < i; ++j) {
cpu_physical_memory_unmap(buffers[j].data, buffers[j].size,
willModifyData, buffers[j].size);
}
break;
}
case PIPE_CMD_WAKE_ON_READ:
case PIPE_CMD_WAKE_ON_WRITE: {
bool read = (command == PIPE_CMD_WAKE_ON_READ);
int wake_flags = read
? GOLDFISH_PIPE_WAKE_READ : GOLDFISH_PIPE_WAKE_WRITE;
DD("%s: CMD_WAKE_ON_%s id=%d", __func__, (read ? "READ" : "WRITE"),
(int)pipe->id);
if ((pipe->wanted & wake_flags) == 0) {
pipe->wanted |= wake_flags;
service_ops->guest_wake_on(pipe->host_pipe, pipe->wanted);
}
pipe->command_buffer->status = 0;
break;
}
default:
D("%s: command=%d (0x%x)\n", __func__, command, command);
}
}
static void pipe_dev_write(void* opaque,
hwaddr offset,
uint64_t value,
unsigned size) {
GoldfishPipeState* state = opaque;
PipeDevice* dev = state->dev;
DR("%s: offset = 0x%" HWADDR_PRIx " value=%" PRIu64 "/0x%" PRIx64, __func__,
offset, value, value);
if (offset == PIPE_REG_VERSION) {
dev->driver_version = value;
} else {
dev->ops->dev_write(dev, offset, value);
}
}
static uint64_t pipe_dev_read(void* opaque, hwaddr offset, unsigned size) {
GoldfishPipeState* s = (GoldfishPipeState*)opaque;
PipeDevice* dev = s->dev;
if (offset == PIPE_REG_VERSION) {
// 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.
dev->ops->close_all(dev);
reset_pipe_device(dev);
if (dev->driver_version < MAX_SUPPORTED_DRIVER_VERSION) {
// Old driver used to not report its version at all.
dev->device_version = PIPE_DEVICE_VERSION_v1;
dev->ops = &pipe_ops_v1;
} else {
dev->device_version = PIPE_DEVICE_VERSION;
dev->ops = &pipe_ops_v2;
}
return dev->device_version;
}
return dev->ops->dev_read(dev, offset);
}
static void pipe_dev_write_v1(PipeDevice* dev,
hwaddr offset,
uint64_t value) {
switch (offset) {
case PIPE_REG_CMD:
pipeDevice_doCommand_v1(dev, value);
break;
case PIPE_REG_SIZE:
dev->size = value;
break;
case PIPE_REG_ADDRESS:
uint64_set_low(&dev->address, value);
break;
case PIPE_REG_ADDRESS_HIGH:
uint64_set_high(&dev->address, value);
break;
case PIPE_REG_CHANNEL:
uint64_set_low(&dev->channel, value);
break;
case PIPE_REG_CHANNEL_HIGH:
uint64_set_high(&dev->channel, value);
break;
case PIPE_REG_PARAMS_ADDR_HIGH:
uint64_set_high(&dev->params_addr, value);
break;
case PIPE_REG_PARAMS_ADDR_LOW:
uint64_set_low(&dev->params_addr, value);
break;
case PIPE_REG_ACCESS_PARAMS:
{
union access_params aps;
uint32_t cmd;
bool is_64bit = true;
/* Don't touch aps.result if anything wrong */
if (dev->params_addr == 0)
break;
cpu_physical_memory_read(dev->params_addr, (void*)&aps, sizeof(aps.aps32));
/* This auto-detection of 32bit/64bit ness relies on the
* currently unused flags parameter. As the 32 bit flags
* overlaps with the 64 bit cmd parameter. As cmd != 0 if we
* find it as 0 it's 32bit
*/
if (aps.aps32.flags == 0) {
is_64bit = false;
} else {
cpu_physical_memory_read(dev->params_addr, (void*)&aps, sizeof(aps.aps64));
}
if (is_64bit) {
dev->channel = aps.aps64.channel;
dev->size = aps.aps64.size;
dev->address = aps.aps64.address;
cmd = aps.aps64.cmd;
} else {
dev->channel = aps.aps32.channel;
dev->size = aps.aps32.size;
dev->address = aps.aps32.address;
cmd = aps.aps32.cmd;
}
if ((cmd != PIPE_CMD_READ) && (cmd != PIPE_CMD_WRITE))
break;
pipeDevice_doCommand_v1(dev, cmd);
if (is_64bit) {
aps.aps64.result = dev->status;
cpu_physical_memory_write(dev->params_addr, (void*)&aps, sizeof(aps.aps64));
} else {
aps.aps32.result = dev->status;
cpu_physical_memory_write(dev->params_addr, (void*)&aps, sizeof(aps.aps32));
}
}
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown register offset = 0x%"
HWADDR_PRIx " value=%" PRIu64 "/0x%" PRIx64 "\n",
__func__, offset, value, value);
break;
}
}
static void pipe_dev_write_v2(PipeDevice* dev,
hwaddr offset,
uint64_t value) {
switch (offset) {
case PIPE_REG_SIGNAL_BUFFER_HIGH:
dev->signalled_pipe_buffer_addr = value << 32;
break;
case PIPE_REG_SIGNAL_BUFFER:
dev->signalled_pipe_buffer_addr |= (uintptr_t)value;
break;
case PIPE_REG_SIGNAL_BUFFER_COUNT:
dev->signalled_pipe_buffer_size = value;
dev->signalled_pipe_buffer = (GuestSignalledPipe*)map_guest_buffer(
dev->signalled_pipe_buffer_addr,
sizeof(*dev->signalled_pipe_buffer) *
dev->signalled_pipe_buffer_size,
/*is_write*/1);
if (!dev->signalled_pipe_buffer) {
APANIC("%s: failed to map signalled pipe buffer\n", __func__);
}
break;
case PIPE_REG_OPEN_BUFFER_HIGH:
dev->open_command_addr = value << 32;
break;
case PIPE_REG_OPEN_BUFFER:
dev->open_command_addr |= (uintptr_t)value;
dev->open_command = (OpenCommandParams*)map_guest_buffer(
dev->open_command_addr, sizeof(*dev->open_command),
/*is_write*/0);
if (!dev->open_command) {
APANIC("%s: failed to map opend command buffer\n", __func__);
}
break;
case PIPE_REG_CMD: {
unsigned id = value;
if (id < dev->pipes_capacity && dev->pipes[id]) {
pipeDevice_doCommand_v2(dev->pipes[id]);
} else {
pipeDevice_doOpenClose_v2(dev, id);
}
break;
}
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: unknown register offset = 0x%" HWADDR_PRIx
" value=%" PRIu64 "/0x%" PRIx64 "\n",
__func__, offset, value, value);
break;
}
}
static uint64_t pipe_dev_read_v1(PipeDevice* dev, hwaddr offset)
{
switch (offset) {
case PIPE_REG_STATUS:
DR("%s: REG_STATUS status=%d (0x%x)", __func__, dev->status, dev->status);
return dev->status;
case PIPE_REG_CHANNEL: {
HwPipe* wanted_pipe = wanted_pipes_pop_first_v1(dev);
if (wanted_pipe != NULL) {
dev->wakes = hwpipe_get_and_clear_wanted(wanted_pipe);
DR("%s: channel=0x%llx wanted=%d", __func__,
(unsigned long long)wanted_pipe->channel, dev->wakes);
return (uint32_t)(wanted_pipe->channel & 0xFFFFFFFFUL);
} else {
qemu_set_irq(dev->ps->irq, 0);
DD("%s: no signaled channels, lowering IRQ", __func__);
return 0;
}
}
case PIPE_REG_CHANNEL_HIGH: {
// This call is really dangerous; currently the device would
// stop the calls as soon as we return 0 here; but it means that if the
// channel's upper 32 bits are zeroes (that happens), we won't be able
// to wake either that channel or any following ones.
// It is gone in v2.
HwPipe* wanted_pipe = wanted_pipes_pop_first_v1(dev);
if (wanted_pipe != NULL) {
dev->wanted_pipe_after_channel_high = wanted_pipe;
DR("%s: channel_high=0x%llx wanted=%d", __func__,
(unsigned long long)wanted_pipe->channel, wanted_pipe->wanted);
assert((uint32_t)(wanted_pipe->channel >> 32) != 0);
return (uint32_t)(wanted_pipe->channel >> 32);
} else {
qemu_set_irq(dev->ps->irq, 0);
DD("%s: no signaled channels (for high), lowering IRQ", __func__);
return 0;
}
}
case PIPE_REG_WAKES:
DR("%s: wakes %d", __func__, 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);
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown register %" HWADDR_PRId
" (0x%" HWADDR_PRIx ")\n", __func__, offset, offset);
}
return 0;
}
static uint64_t pipe_dev_read_v2(PipeDevice* dev, hwaddr offset) {
switch (offset) {
case PIPE_REG_GET_SIGNALLED: {
int count = 0;
HwPipe* pipe;
while (count < dev->signalled_pipe_buffer_size &&
(pipe = wanted_pipes_pop_first_v2(dev)) != NULL) {
dev->signalled_pipe_buffer[count].id = pipe->id;
dev->signalled_pipe_buffer[count].flags =
hwpipe_get_and_clear_wanted(pipe);
++count;
}
if (!dev->wanted_pipes_first) {
qemu_set_irq(dev->ps->irq, 0); // we've passed all wanted pipes
}
return count;
}
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown register %" HWADDR_PRId
" (0x%" HWADDR_PRIx ")\n",
__func__, offset, offset);
}
return 0;
}
static const MemoryRegionOps goldfish_pipe_iomem_ops = {
.read = pipe_dev_read,
.write = pipe_dev_write,
.endianness = DEVICE_NATIVE_ENDIAN
};
// Don't change this version unless you want to break forward compatibility.
// Instead, use the different device version as a first saved field.
enum {
GOLDFISH_PIPE_SAVE_VERSION = 1,
};
static void goldfish_pipe_save(QEMUFile* f, void* opaque) {
GoldfishPipeState* s = opaque;
PipeDevice* dev = s->dev;
dev->ops->save(f, dev);
}
static void goldfish_pipe_save_v1(QEMUFile* file, PipeDevice* dev) {
assert(dev->device_version == PIPE_DEVICE_VERSION_v1);
qemu_put_be32(file, dev->device_version);
/* Save the device version */
/* Save i/o registers. */
qemu_put_be64(file, dev->address);
qemu_put_be32(file, dev->size);
qemu_put_be32(file, dev->status);
qemu_put_be64(file, dev->channel);
qemu_put_be32(file, dev->wakes);
qemu_put_be64(file, dev->params_addr);
/* Save the pipe count and state of the pipes. */
int pipe_count = 0;
HwPipe* pipe;
for (pipe = dev->pipes_list; pipe; pipe = pipe->next) {
++pipe_count;
}
qemu_put_be32(file, pipe_count);
for (pipe = dev->pipes_list; pipe; pipe = pipe->next) {
qemu_put_be64(file, pipe->channel);
qemu_put_byte(file, pipe->closed);
qemu_put_byte(file, pipe->wanted);
service_ops->guest_save(pipe->host_pipe, file);
}
/* Save wanted pipes list. */
int wanted_pipes_count = 0;
for (pipe = dev->wanted_pipes_first; pipe; pipe = pipe->wanted_next) {
wanted_pipes_count++;
}
qemu_put_be32(file, wanted_pipes_count);
for (pipe = dev->wanted_pipes_first; pipe; pipe = pipe->wanted_next) {
qemu_put_be64(file, pipe->channel);
}
if (dev->wanted_pipe_after_channel_high) {
qemu_put_byte(file, 1);
qemu_put_be64(file, dev->wanted_pipe_after_channel_high->channel);
} else {
qemu_put_byte(file, 0);
}
}
static void goldfish_pipe_save_v2(QEMUFile* file, PipeDevice* dev) {
assert(dev->device_version == PIPE_DEVICE_VERSION);
qemu_put_be32(file, dev->device_version);
/* Save the device data. */
qemu_put_be32(file, dev->driver_version);
qemu_put_be32(file, dev->signalled_pipe_buffer_size);
qemu_put_be64(file, dev->signalled_pipe_buffer_addr);
qemu_put_be64(file, dev->open_command_addr);
qemu_put_be32(file, dev->pipes_capacity);
/* Save the pipes. */
int i;
int pipe_count = 0;
for (i = 0; i < dev->pipes_capacity; ++i) {
if (dev->pipes[i]) {
++pipe_count;
}
}
qemu_put_be32(file, pipe_count);
for (i = 0; i < dev->pipes_capacity; ++i) {
HwPipe* pipe = dev->pipes[i];
if (!pipe) {
continue;
}
qemu_put_be32(file, pipe->id);
qemu_put_be64(file, pipe->command_buffer_addr);
qemu_put_byte(file, pipe->closed);
qemu_put_byte(file, pipe->wanted);
service_ops->guest_save(pipe->host_pipe, file);
}
/* Save wanted pipes list. */
HwPipe* pipe;
int wanted_pipes_count = 0;
for (pipe = dev->wanted_pipes_first; pipe; pipe = pipe->wanted_next) {
wanted_pipes_count++;
}
qemu_put_be32(file, wanted_pipes_count);
for (pipe = dev->wanted_pipes_first; pipe; pipe = pipe->wanted_next) {
qemu_put_be32(file, pipe->id);
}
}
static int goldfish_pipe_load_v1(QEMUFile* file, PipeDevice* dev) {
HwPipe* pipe;
assert(dev->device_version == PIPE_DEVICE_VERSION_v1);
dev->driver_version = PIPE_DRIVER_VERSION_v1;
/* Load i/o registers. */
dev->address = qemu_get_be64(file);
dev->size = qemu_get_be32(file);
dev->status = qemu_get_be32(file);
dev->channel = qemu_get_be64(file);
dev->wakes = qemu_get_be32(file);
dev->params_addr = qemu_get_be64(file);
/* Clean up old pipe objects. */
dev->ops->close_all(dev);
dev->ops = &pipe_ops_v1;
/* Restore pipes. */
int pipe_count = qemu_get_be32(file);
int pipe_n;
HwPipe** pipe_list_end = &dev->pipes_list;
*pipe_list_end = NULL;
uint64_t* force_closed_pipes = malloc(sizeof(uint64_t) * pipe_count);
int force_closed_pipes_count = 0;
for (pipe_n = 0; pipe_n < pipe_count; pipe_n++) {
HwPipe* pipe = hwpipe_new0(dev);
char force_close = 0;
pipe->channel = qemu_get_be64(file);
pipe->closed = qemu_get_byte(file);
pipe->wanted = qemu_get_byte(file);
pipe->host_pipe = service_ops->guest_load(file, pipe, &force_close);
pipe->wanted_next = pipe->wanted_prev = NULL;
// |pipe| might be NULL in case it couldn't be saved. However,
// in that case |force_close| will be set by goldfish_pipe_guest_load,
// so |force_close| should be checked first so that the function
// can continue.
if (force_close) {
pipe->closed = 1;
force_closed_pipes[force_closed_pipes_count++] = pipe->channel;
} else if (!pipe->host_pipe) {
hwpipe_free(pipe);
free(force_closed_pipes);
return -EIO;
}
pipe->next = NULL;
*pipe_list_end = pipe;
pipe_list_end = &(pipe->next);
}
/* Rebuild the pipes-by-channel table. */
g_hash_table_remove_all(dev->pipes_by_channel); /* Clean up old data. */
for(pipe = dev->pipes_list; pipe; pipe = pipe->next) {
g_hash_table_insert(dev->pipes_by_channel,
hash_create_key_from_channel(pipe->channel),
pipe);
}
/* Reconstruct wanted pipes list. */
HwPipe** wanted_pipe_list_end = &dev->wanted_pipes_first;
*wanted_pipe_list_end = NULL;
int wanted_pipes_count = qemu_get_be32(file);
uint64_t channel;
for (pipe_n = 0; pipe_n < wanted_pipes_count; ++pipe_n) {
channel = qemu_get_be64(file);
HwPipe* pipe = g_hash_table_lookup(dev->pipes_by_channel,
hash_cast_key_from_channel(&channel));
if (pipe) {
pipe->wanted_prev = *wanted_pipe_list_end;
pipe->wanted_next = NULL;
*wanted_pipe_list_end = pipe;
wanted_pipe_list_end = &(pipe->wanted_next);
} else {
free(force_closed_pipes);
return -EIO;
}
}
if (qemu_get_byte(file)) {
channel = qemu_get_be64(file);
dev->wanted_pipe_after_channel_high =
g_hash_table_lookup(dev->pipes_by_channel,
hash_cast_key_from_channel(&channel));
if (!dev->wanted_pipe_after_channel_high) {
free(force_closed_pipes);
return -EIO;
}
} else {
dev->wanted_pipe_after_channel_high = NULL;
}
/* Add forcibly closed pipes to wanted pipes list */
for (pipe_n = 0; pipe_n < force_closed_pipes_count; pipe_n++) {
HwPipe* pipe =
g_hash_table_lookup(dev->pipes_by_channel,
hash_cast_key_from_channel(
&force_closed_pipes[pipe_n]));
hwpipe_set_wanted(pipe, GOLDFISH_PIPE_WAKE_CLOSED);
pipe->closed = 1;
if (!pipe->wanted_next &&
!pipe->wanted_prev &&
pipe != dev->wanted_pipe_after_channel_high) {
pipe->wanted_prev = *wanted_pipe_list_end;
*wanted_pipe_list_end = pipe;
wanted_pipe_list_end = &(pipe->wanted_next);
}
}
free(force_closed_pipes);
return 0;
}
static int goldfish_pipe_load_v2(QEMUFile* file, PipeDevice* dev) {
int res = -EIO;
uint32_t* force_closed_pipes = NULL;
/* Load the device. */
dev->device_version = qemu_get_be32(file);
if (dev->device_version > PIPE_DEVICE_VERSION) {
goto done;
} else if (dev->device_version == PIPE_DEVICE_VERSION_v1) {
// redirect to the v1 loader if this is an old pipe.
return goldfish_pipe_load_v1(file, dev);
}
dev->driver_version = qemu_get_be32(file);
if (dev->driver_version > MAX_SUPPORTED_DRIVER_VERSION) {
goto done;
}
dev->signalled_pipe_buffer_size = qemu_get_be32(file);
dev->signalled_pipe_buffer_addr = qemu_get_be64(file);
dev->signalled_pipe_buffer = (GuestSignalledPipe*)map_guest_buffer(
dev->signalled_pipe_buffer_addr,
sizeof(*dev->signalled_pipe_buffer) *
dev->signalled_pipe_buffer_size,
/*is_write*/1);
if (!dev->signalled_pipe_buffer) {
goto done;
}
dev->open_command_addr = qemu_get_be64(file);
dev->open_command = (OpenCommandParams*)map_guest_buffer(
dev->open_command_addr, sizeof(*dev->open_command), /*is_write*/0);
if (!dev->open_command) {
goto done;
}
/* Clean up old pipe objects. */
dev->wanted_pipes_first = NULL;
dev->ops->close_all(dev);
dev->ops = &pipe_ops_v2;
int pipes_capacity = qemu_get_be32(file);
if (dev->pipes_capacity < pipes_capacity) {
void* pipes = calloc(pipes_capacity, sizeof(*dev->pipes));
if (!pipes) {
goto done;
}
// No need to memcpy() the old array - we've already freed
// all pipes there.
dev->pipes = pipes;
}
dev->pipes_capacity = pipes_capacity;
int pipe_count = qemu_get_be32(file);
force_closed_pipes = malloc(sizeof(*force_closed_pipes) * pipe_count);
if (!force_closed_pipes) {
goto done;
}
int i;
int force_closed_pipes_count = 0;
for (i = 0; i < pipe_count; ++i) {
HwPipe* pipe = hwpipe_new0(dev);
pipe->id = qemu_get_be32(file);
pipe->command_buffer_addr = qemu_get_be64(file);
pipe->command_buffer = (PipeCommand*)map_guest_buffer(
pipe->command_buffer_addr, COMMAND_BUFFER_SIZE, /*is_write*/1);
if (!pipe->command_buffer) {
hwpipe_free(pipe);
goto done;
}
pipe->closed = qemu_get_byte(file);
pipe->wanted = qemu_get_byte(file);
char force_close = 0;
pipe->host_pipe = service_ops->guest_load(file, pipe, &force_close);
// |pipe| might be NULL in case it couldn't be saved. However,
// in that case |force_close| will be set by goldfish_pipe_guest_load,
// so |force_close| should be checked first so that the function
// can continue.
if (force_close) {
pipe->closed = 1;
force_closed_pipes[force_closed_pipes_count++] = pipe->id;
} else if (!pipe->host_pipe) {
unmap_command_buffer(pipe->command_buffer);
hwpipe_free(pipe);
goto done;
}
if (dev->pipes[pipe->id]) {
unmap_command_buffer(pipe->command_buffer);
hwpipe_free(pipe);
goto done;
}
dev->pipes[pipe->id] = pipe;
}
/* Reconstruct wanted pipes list. */
int wanted_pipes_count = qemu_get_be32(file);
for (i = 0; i < wanted_pipes_count; ++i) {
int id = qemu_get_be32(file);
HwPipe* pipe = dev->pipes[id];
if (!pipe) {
goto done;
}
wanted_pipes_add_v2(dev, pipe);
}
/* Add forcibly closed pipes to wanted pipes list */
for (i = 0; i < force_closed_pipes_count; ++i) {
HwPipe* pipe = dev->pipes[force_closed_pipes[i]];
hwpipe_set_wanted(pipe, GOLDFISH_PIPE_WAKE_CLOSED);
pipe->closed = 1;
wanted_pipes_add_v2(dev, pipe);
}
res = 0;
done:
free(force_closed_pipes);
return res;
}
static int goldfish_pipe_load(QEMUFile* f, void* opaque, int version_id) {
GoldfishPipeState* s = opaque;
PipeDevice* dev = s->dev;
int res = goldfish_pipe_load_v2(f, dev);
return res;
}
static void goldfish_pipe_post_load(void* opaque) {
/* This function gets invoked after all VM state has
* been loaded. Raising IRQ in the load handler causes
* problems.
*/
PipeDevice* dev = ((GoldfishPipeState*)opaque)->dev;
if (dev->wanted_pipes_first) {
qemu_set_irq(dev->ps->irq, 1);
} else {
qemu_set_irq(dev->ps->irq, 0);
}
}
static void goldfish_pipe_realize(DeviceState* dev, Error** errp) {
SysBusDevice* sbdev = SYS_BUS_DEVICE(dev);
GoldfishPipeState* s = GOLDFISH_PIPE(dev);
s->dev = (PipeDevice*)g_malloc0(sizeof(PipeDevice));
s->dev->ps = s; /* HACK: backlink */
s->dev->ops = &pipe_ops_v2;
s->dev->device_version = PIPE_DEVICE_VERSION;
s->dev->driver_version = PIPE_DRIVER_VERSION_v1;
s->dev->pipes_capacity = 64;
s->dev->pipes = calloc(s->dev->pipes_capacity, sizeof(*s->dev->pipes));
if (!s->dev->pipes) {
APANIC("%s: failed to initialize pipes array\n", __func__);
}
s->dev->pipes_by_channel = g_hash_table_new_full(
hash_channel, hash_channel_equal, hash_channel_destroy, NULL);
if (!s->dev->pipes_by_channel) {
APANIC("%s: failed to initialize pipes hash\n", __func__);
}
memory_region_init_io(&s->iomem, OBJECT(s), &goldfish_pipe_iomem_ops, s,
"goldfish_pipe", 0x2000 /*TODO: ?how big?*/);
sysbus_init_mmio(sbdev, &s->iomem);
sysbus_init_irq(sbdev, &s->irq);
register_savevm_with_post_load(
dev, "goldfish_pipe", 0, GOLDFISH_PIPE_SAVE_VERSION,
goldfish_pipe_save, goldfish_pipe_load, goldfish_pipe_post_load, s);
}
void goldfish_pipe_reset(GoldfishHwPipe *pipe, GoldfishHostPipe *host_pipe) {
pipe->host_pipe = host_pipe;
}
void goldfish_pipe_signal_wake(GoldfishHwPipe *pipe,
GoldfishPipeWakeFlags flags) {
PipeDevice *dev = pipe->dev;
DD("%s: id=%d channel=0x%llx flags=%d", __func__, (int)pipe->id,
pipe->channel, flags);
hwpipe_set_wanted(pipe, (unsigned char)flags);
dev->ops->wanted_list_add(dev, pipe);
/* Raise IRQ to indicate there are items on our list ! */
qemu_set_irq(dev->ps->irq, 1);
DD("%s: raising IRQ", __func__);
}
void goldfish_pipe_close_from_host(GoldfishHwPipe *pipe)
{
D("%s: id=%d channel=0x%llx (closed=%d)", __func__, (int)pipe->id,
pipe->channel, pipe->closed);
if (!pipe->closed) {
pipe->closed = 1;
goldfish_pipe_signal_wake(pipe, GOLDFISH_PIPE_WAKE_CLOSED);
}
}
static void goldfish_pipe_class_init(ObjectClass* klass, void* data) {
DeviceClass* dc = DEVICE_CLASS(klass);
dc->realize = goldfish_pipe_realize;
dc->desc = "goldfish pipe";
}
static const TypeInfo goldfish_pipe_info = {
.name = TYPE_GOLDFISH_PIPE,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(GoldfishPipeState),
.class_init = goldfish_pipe_class_init
};
static void goldfish_pipe_register(void) {
type_register_static(&goldfish_pipe_info);
}
type_init(goldfish_pipe_register);
static const PipeOperations pipe_ops_v2 = {
.wanted_list_add = &wanted_pipes_add_v2,
.close_all = &close_all_pipes_v2,
.save = &goldfish_pipe_save_v2,
.dev_read = &pipe_dev_read_v2,
.dev_write = &pipe_dev_write_v2
};
static const PipeOperations pipe_ops_v1 = {
.wanted_list_add = &wanted_pipes_add_v1,
.close_all = &close_all_pipes_v1,
.save = &goldfish_pipe_save_v1,
.dev_read = &pipe_dev_read_v1,
.dev_write = &pipe_dev_write_v1
};