| /* 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. |
| ** |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "hw/hw.h" |
| #include "hw/sysbus.h" |
| |
| #include "hw/misc/goldfish_pipe.h" |
| #include "qemu-common.h" |
| #include "qemu/log.h" |
| #include "qemu/timer.h" |
| #include "qemu/error-report.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.c |
| */ |
| |
| /* pipe device registers */ |
| #define PIPE_REG_COMMAND 0x00 /* write: value = command */ |
| #define PIPE_REG_STATUS 0x04 /* read */ |
| #define PIPE_REG_CHANNEL 0x08 /* read/write: channel id */ |
| #define PIPE_REG_SIZE 0x0c /* read/write: buffer size */ |
| #define PIPE_REG_ADDRESS 0x10 /* write: physical address */ |
| #define PIPE_REG_WAKES 0x14 /* read: wake flags */ |
| /* read/write: parameter buffer address */ |
| #define PIPE_REG_PARAMS_ADDR_LOW 0x18 |
| #define PIPE_REG_PARAMS_ADDR_HIGH 0x1c |
| /* write: access with paremeter buffer */ |
| #define PIPE_REG_ACCESS_PARAMS 0x20 |
| #define PIPE_REG_VERSION 0x24 /* read: device version */ |
| #define PIPE_REG_CHANNEL_HIGH 0x30 /* read/write: high 32 bit channel id */ |
| #define PIPE_REG_ADDRESS_HIGH 0x34 /* write: high 32 bit physical address */ |
| |
| /* list of commands for PIPE_REG_COMMAND */ |
| #define PIPE_CMD_OPEN 1 /* open new channel */ |
| #define PIPE_CMD_CLOSE 2 /* close channel (from guest) */ |
| #define PIPE_CMD_POLL 3 /* poll read/write status */ |
| |
| /* The following commands are related to write operations */ |
| #define PIPE_CMD_WRITE_BUFFER 4 /* send a user buffer to the emulator */ |
| #define PIPE_CMD_WAKE_ON_WRITE 5 /* tell the emulator to wake us when writing is possible */ |
| |
| /* The following commands are related to read operations, they must be |
| * listed in the same order than the corresponding write ones, since we |
| * will use (CMD_READ_BUFFER - CMD_WRITE_BUFFER) as a special offset |
| * in qemu_pipe_read_write() below. |
| */ |
| #define PIPE_CMD_READ_BUFFER 6 /* receive a page-contained buffer from the emulator */ |
| #define PIPE_CMD_WAKE_ON_READ 7 /* tell the emulator to wake us when reading is possible */ |
| |
| 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; |
| }; |
| |
| /* Maximum length of pipe service name, in characters (excluding final 0) */ |
| #define MAX_PIPE_SERVICE_NAME_SIZE 255 |
| |
| static const GoldfishPipeServiceOps* service_ops = NULL; |
| |
| void goldfish_pipe_set_service_ops(const GoldfishPipeServiceOps* ops) { |
| service_ops = ops; |
| } |
| |
| 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); |
| } |
| |
| #define TYPE_GOLDFISH_PIPE "goldfish_pipe" |
| #define GOLDFISH_PIPE(obj) \ |
| OBJECT_CHECK(GoldfishPipeState, (obj), TYPE_GOLDFISH_PIPE) |
| |
| #define GOLDFISH_PIPE_SAVE_VERSION 3 |
| |
| typedef struct PipeDevice PipeDevice; |
| |
| typedef struct { |
| SysBusDevice parent; |
| MemoryRegion iomem; |
| qemu_irq irq; |
| |
| /* TODO: roll into shared state */ |
| PipeDevice *dev; |
| } GoldfishPipeState; |
| |
| |
| /*********************************************************************** |
| *********************************************************************** |
| ***** |
| ***** P I P E C O N N E C T I O N S |
| ***** |
| *****/ |
| |
| struct GoldfishHwPipe { |
| GoldfishHwPipe *next; |
| GoldfishHwPipe *wanted_next; |
| GoldfishHwPipe *wanted_prev; |
| PipeDevice *device; |
| uint64_t channel; /* opaque kernel handle */ |
| unsigned char wanted; |
| char closed; |
| GoldfishHostPipe *host_pipe; |
| }; |
| |
| /* Convenience typedefs to save typing */ |
| typedef GoldfishHwPipe HwPipe; |
| typedef GoldfishHostPipe HostPipe; |
| |
| static HwPipe* hwpipe_new0(PipeDevice* dev) |
| { |
| HwPipe *pipe = g_malloc0(sizeof(HwPipe)); |
| pipe->device = dev; |
| return pipe; |
| } |
| |
| static HwPipe* hwpipe_new(uint64_t channel, PipeDevice* dev) |
| { |
| HwPipe* pipe = hwpipe_new0(dev); |
| pipe->channel = channel; |
| if (service_ops) |
| pipe->host_pipe = service_ops->guest_open(pipe); |
| |
| return pipe; |
| } |
| |
| static void hwpipe_free(HwPipe* hwp) |
| { |
| if (service_ops) |
| service_ops->guest_close(hwp->host_pipe); |
| |
| g_free(hwp); |
| } |
| |
| static unsigned char hwpipe_get_and_clear_wanted(HwPipe* pipe) |
| { |
| unsigned char val = pipe->wanted; |
| pipe->wanted = 0; |
| return val; |
| } |
| |
| static void hwpipe_add_wanted(HwPipe* pipe, unsigned char val) { |
| pipe->wanted |= val; |
| } |
| |
| /*********************************************************************** |
| *********************************************************************** |
| ***** |
| ***** G O L D F I S H P I P E D E V I C E |
| ***** |
| *****/ |
| |
| struct PipeDevice { |
| GoldfishPipeState *ps; // FIXME: backlink to instance state |
| |
| // The list of all pipes. |
| HwPipe* pipes; |
| // The list of the pipes that signalled some 'wanted' state. |
| HwPipe* wanted_pipes_first; |
| // 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; |
| }; |
| |
| // 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 |
| |
| // Wanted pipe linked list operations |
| static HwPipe* wanted_pipes_pop_first(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(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(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; |
| } |
| |
| /* Update this version number if the device's interface changes. */ |
| #define GOLDFISH_PIPE_DEVICE_VERSION 1 |
| |
| /* 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 pipe_device_do_command(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", __FUNCTION__, |
| (unsigned long long)dev->channel); |
| if (pipe != NULL) { |
| dev->status = GOLDFISH_PIPE_ERROR_INVAL; |
| break; |
| } |
| if (!service_ops) { |
| /* Cannot open a pipe from the guest if no host-side service |
| * was registered yet. */ |
| dev->status = GOLDFISH_PIPE_ERROR_IO; |
| break; |
| } |
| pipe = hwpipe_new(dev->channel, dev); |
| pipe->next = dev->pipes; |
| dev->pipes = 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", __FUNCTION__, |
| (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; |
| 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(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", __FUNCTION__, dev->status); |
| break; |
| |
| case PIPE_CMD_READ_BUFFER: { |
| /* Translate guest physical address into emulator memory. */ |
| GoldfishPipeBuffer buffer; |
| buffer.size = dev->size; |
| buffer.data = map_guest_buffer(dev->address, dev->size, 1); |
| if (!buffer.data) { |
| dev->status = GOLDFISH_PIPE_ERROR_INVAL; |
| break; |
| } |
| dev->status = service_ops->guest_recv(pipe->host_pipe, &buffer, 1); |
| DD("%s: CMD_READ_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, 1, dev->size); |
| break; |
| } |
| |
| case PIPE_CMD_WRITE_BUFFER: { |
| /* Translate guest physical address into emulator memory. */ |
| GoldfishPipeBuffer buffer; |
| buffer.size = dev->size; |
| buffer.data = map_guest_buffer(dev->address, dev->size, 0); |
| if (!buffer.data) { |
| dev->status = GOLDFISH_PIPE_ERROR_INVAL; |
| break; |
| } |
| 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, 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->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", __FUNCTION__, |
| (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", __FUNCTION__, command, command); |
| } |
| } |
| |
| static void goldfish_pipe_iomem_write(void *opaque, hwaddr offset, |
| uint64_t value, unsigned size) |
| { |
| GoldfishPipeState *state = (GoldfishPipeState *) opaque; |
| PipeDevice *s = state->dev; |
| |
| DR("%s: offset = 0x%" HWADDR_PRIx " value=%" PRIu64 "/0x%" PRIx64, |
| __func__, offset, value, value); |
| switch (offset) { |
| case PIPE_REG_COMMAND: |
| pipe_device_do_command(s, value); |
| break; |
| |
| case PIPE_REG_SIZE: |
| s->size = value; |
| break; |
| |
| case PIPE_REG_ADDRESS: |
| uint64_set_low(&s->address, value); |
| break; |
| |
| case PIPE_REG_ADDRESS_HIGH: |
| uint64_set_high(&s->address, value); |
| break; |
| |
| case PIPE_REG_CHANNEL: |
| uint64_set_low(&s->channel, value); |
| break; |
| |
| case PIPE_REG_CHANNEL_HIGH: |
| uint64_set_high(&s->channel, value); |
| break; |
| |
| case PIPE_REG_PARAMS_ADDR_HIGH: |
| uint64_set_high(&s->params_addr, value); |
| break; |
| |
| case PIPE_REG_PARAMS_ADDR_LOW: |
| uint64_set_low(&s->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 (s->params_addr == 0) |
| break; |
| |
| cpu_physical_memory_read(s->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(s->params_addr, |
| (void*)&aps, sizeof(aps.aps64)); |
| } |
| |
| if (is_64bit) { |
| s->channel = aps.aps64.channel; |
| s->size = aps.aps64.size; |
| s->address = aps.aps64.address; |
| cmd = aps.aps64.cmd; |
| } else { |
| s->channel = aps.aps32.channel; |
| s->size = aps.aps32.size; |
| s->address = aps.aps32.address; |
| cmd = aps.aps32.cmd; |
| } |
| |
| if ((cmd != PIPE_CMD_READ_BUFFER) && (cmd != PIPE_CMD_WRITE_BUFFER)) |
| break; |
| |
| pipe_device_do_command(s, cmd); |
| |
| if (is_64bit) { |
| aps.aps64.result = s->status; |
| cpu_physical_memory_write(s->params_addr, (void*)&aps, |
| sizeof(aps.aps64)); |
| } else { |
| aps.aps32.result = s->status; |
| cpu_physical_memory_write(s->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_device_close_all(PipeDevice *dev) { |
| HwPipe* pipe = dev->pipes; |
| while (pipe) { |
| HwPipe* old_pipe = pipe; |
| pipe = pipe->next; |
| hwpipe_free(old_pipe); |
| } |
| } |
| |
| static void pipe_device_reset(PipeDevice *dev) { |
| dev->pipes = NULL; |
| 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); |
| } |
| |
| /* I/O read */ |
| static uint64_t goldfish_pipe_iomem_read(void *opaque, hwaddr offset, |
| unsigned size) |
| { |
| GoldfishPipeState *s = (GoldfishPipeState *)opaque; |
| PipeDevice *dev = s->dev; |
| |
| 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: { |
| HwPipe* wanted_pipe = wanted_pipes_pop_first(dev); |
| if (wanted_pipe != NULL) { |
| dev->wakes = hwpipe_get_and_clear_wanted(wanted_pipe); |
| DR("%s: channel=0x%llx wanted=%d", __FUNCTION__, |
| (unsigned long long)wanted_pipe->channel, dev->wakes); |
| return (uint32_t)(wanted_pipe->channel & 0xFFFFFFFFUL); |
| } else { |
| qemu_set_irq(s->irq, 0); |
| DD("%s: no signaled channels, lowering IRQ", __FUNCTION__); |
| return 0; |
| } |
| } |
| |
| case PIPE_REG_CHANNEL_HIGH: { |
| // TODO(zyy): 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. |
| // I think we should create a new pipe protocol to deal with this |
| // issue and also reduce chattiness of the pipe communication at the |
| // same time. |
| HwPipe* wanted_pipe = wanted_pipes_pop_first(dev); |
| if (wanted_pipe != NULL) { |
| dev->wanted_pipe_after_channel_high = wanted_pipe; |
| DR("%s: channel_high=0x%llx wanted=%d", __FUNCTION__, |
| (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(s->irq, 0); |
| DD("%s: no signaled channels (for high), lowering IRQ", __func__); |
| 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: |
| // 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. |
| pipe_device_close_all(dev); |
| pipe_device_reset(dev); |
| return GOLDFISH_PIPE_DEVICE_VERSION; |
| |
| default: |
| qemu_log_mask(LOG_GUEST_ERROR, "%s: unknown register %" HWADDR_PRId |
| " (0x%" HWADDR_PRIx ")\n", __FUNCTION__, offset, offset); |
| } |
| return 0; |
| } |
| |
| static const MemoryRegionOps goldfish_pipe_iomem_ops = { |
| .read = goldfish_pipe_iomem_read, |
| .write = goldfish_pipe_iomem_write, |
| .endianness = DEVICE_NATIVE_ENDIAN |
| }; |
| |
| static void goldfish_pipe_save(QEMUFile* file, void* opaque) { |
| GoldfishPipeState* s = opaque; |
| PipeDevice* dev = s->dev; |
| |
| /* Save the device version first*/ |
| qemu_put_be32(file, GOLDFISH_PIPE_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; pipe; pipe = pipe->next) { |
| ++pipe_count; |
| } |
| qemu_put_be32(file, pipe_count); |
| for (pipe = dev->pipes; 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 int goldfish_pipe_load(QEMUFile* file, void* opaque, int version_id) { |
| GoldfishPipeState* s = opaque; |
| PipeDevice* dev = s->dev; |
| HwPipe* pipe; |
| |
| /* Load and verify the device version */ |
| uint32_t version = qemu_get_be32(file); |
| if (version != GOLDFISH_PIPE_DEVICE_VERSION) { |
| return -EIO; |
| } |
| |
| /* 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. */ |
| pipe_device_close_all(dev); |
| |
| /* Restore pipes. */ |
| int pipe_count = qemu_get_be32(file); |
| int pipe_n; |
| HwPipe** pipe_list_end = &(dev->pipes); |
| *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 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; 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_add_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 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->wanted_pipes_first = NULL; |
| s->dev->wanted_pipe_after_channel_high = NULL; |
| 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", 0x1000 /*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->device; |
| |
| DD("%s: channel=0x%llx flags=%d", __FUNCTION__, |
| (unsigned long long)pipe->channel, flags); |
| |
| hwpipe_add_wanted(pipe, (unsigned char)flags); |
| wanted_pipes_add(dev, pipe); |
| |
| /* Raise IRQ to indicate there are items on our list ! */ |
| qemu_set_irq(dev->ps->irq, 1); |
| DD("%s: raising IRQ", __FUNCTION__); |
| } |
| |
| void goldfish_pipe_close_from_host(GoldfishHwPipe *pipe) |
| { |
| D("%s: channel=0x%llx (closed=%d)", __FUNCTION__, |
| (unsigned long long)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); |