From: Roman Kiryanov <r...@google.com> The Goldfish sync driver is designed to provide a interface between the underlying host's sync device and the kernel's fence sync framework.
Signed-off-by: Roman Kiryanov <r...@google.com> --- Changes in v2: - Added a missing include (mod_devicetable.h). - Put in one batch with goldfish_address_space.c to avoid merge comflicts. drivers/platform/goldfish/Kconfig | 7 + drivers/platform/goldfish/Makefile | 1 + drivers/platform/goldfish/goldfish_sync.c | 834 ++++++++++++++++++++ include/uapi/linux/goldfish/goldfish_sync.h | 28 + 4 files changed, 870 insertions(+) create mode 100644 drivers/platform/goldfish/goldfish_sync.c create mode 100644 include/uapi/linux/goldfish/goldfish_sync.h diff --git a/drivers/platform/goldfish/Kconfig b/drivers/platform/goldfish/Kconfig index 60ecec4a3c59..841250235430 100644 --- a/drivers/platform/goldfish/Kconfig +++ b/drivers/platform/goldfish/Kconfig @@ -25,4 +25,11 @@ config GOLDFISH_ADDRESS_SPACE populate them later in the host. This allows sharing host's memory with the guest. +config GOLDFISH_SYNC + tristate "Goldfish AVD Sync Driver" + depends on SW_SYNC + depends on SYNC_FILE + help + Emulated sync fences for the Goldfish Android Virtual Device. + endif # GOLDFISH diff --git a/drivers/platform/goldfish/Makefile b/drivers/platform/goldfish/Makefile index 034abe0727b8..bdff4d6a0ad9 100644 --- a/drivers/platform/goldfish/Makefile +++ b/drivers/platform/goldfish/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_GOLDFISH_PIPE) += goldfish_pipe.o obj-$(CONFIG_GOLDFISH_ADDRESS_SPACE) += goldfish_address_space.o +obj-$(CONFIG_GOLDFISH_SYNC) += goldfish_sync.o diff --git a/drivers/platform/goldfish/goldfish_sync.c b/drivers/platform/goldfish/goldfish_sync.c new file mode 100644 index 000000000000..1e9813d2094a --- /dev/null +++ b/drivers/platform/goldfish/goldfish_sync.c @@ -0,0 +1,834 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* The Goldfish sync driver is designed to provide a interface + * between the underlying host's sync device and the kernel's + * fence sync framework. + * + * The purpose of the device/driver is to enable lightweight creation and + * signaling of timelines and fences in order to synchronize the guest with + * host-side graphics events. + * + * Each time the interrupt trips, the driver may perform a sync operation. + */ + +#include <linux/acpi.h> +#include <linux/dma-fence.h> +#include <linux/fdtable.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/sync_file.h> +#include <linux/syscalls.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#include <uapi/linux/goldfish/goldfish_sync.h> + +struct sync_pt { + struct dma_fence base; /* must be the first field in this struct */ + struct list_head active_list; /* see active_list_head below */ +}; + +struct goldfish_sync_state; + +struct goldfish_sync_timeline { + struct goldfish_sync_state *sync_state; + + /* This object is owned by userspace from open() calls and also each + * sync_pt refers to it. + */ + struct kref kref; + char name[32]; /* for debugging */ + + u64 context; + unsigned int seqno; + /* list of active (unsignaled/errored) sync_pts */ + struct list_head active_list_head; + spinlock_t lock; /* protects the fields above */ +}; + +/* The above definitions (command codes, register layout, ioctl definitions) + * need to be in sync with the following files: + * + * Host-side (emulator): + * external/qemu/android/emulation/goldfish_sync.h + * external/qemu-android/hw/misc/goldfish_sync.c + * + * Guest-side (system image): + * device/generic/goldfish-opengl/system/egl/goldfish_sync.h + * device/generic/goldfish/ueventd.ranchu.rc + * platform/build/target/board/generic/sepolicy/file_contexts + */ +struct goldfish_sync_hostcmd { + /* sorted for alignment */ + u64 handle; + u64 hostcmd_handle; + u32 cmd; + u32 time_arg; +}; + +struct goldfish_sync_guestcmd { + u64 host_command; /* u64 for alignment */ + u64 glsync_handle; + u64 thread_handle; + u64 guest_timeline_handle; +}; + +/* The host operations are: */ +enum cmd_id { + /* Ready signal - used to mark when irq should lower */ + CMD_SYNC_READY = 0, + + /* Create a new timeline. writes timeline handle */ + CMD_CREATE_SYNC_TIMELINE = 1, + + /* Create a fence object. reads timeline handle and time argument. + * Writes fence fd to the SYNC_REG_HANDLE register. + */ + CMD_CREATE_SYNC_FENCE = 2, + + /* Increments timeline. reads timeline handle and time argument */ + CMD_SYNC_TIMELINE_INC = 3, + + /* Destroys a timeline. reads timeline handle */ + CMD_DESTROY_SYNC_TIMELINE = 4, + + /* Starts a wait on the host with the given glsync object and + * sync thread handle. + */ + CMD_TRIGGER_HOST_WAIT = 5, +}; + +/* The host register layout is: */ +enum sync_reg_id { + /* host->guest batch commands */ + SYNC_REG_BATCH_COMMAND = 0x00, + + /* guest->host batch commands */ + SYNC_REG_BATCH_GUESTCOMMAND = 0x04, + + /* communicate physical address of host->guest batch commands */ + SYNC_REG_BATCH_COMMAND_ADDR = 0x08, + SYNC_REG_BATCH_COMMAND_ADDR_HIGH = 0x0C, /* 64-bit part */ + + /* communicate physical address of guest->host commands */ + SYNC_REG_BATCH_GUESTCOMMAND_ADDR = 0x10, + SYNC_REG_BATCH_GUESTCOMMAND_ADDR_HIGH = 0x14, /* 64-bit part */ + + /* signals that the device has been probed */ + SYNC_REG_INIT = 0x18, +}; + +#define GOLDFISH_SYNC_MAX_CMDS 32 + +/* The driver state: */ +struct goldfish_sync_state { + struct miscdevice miscdev; + + char __iomem *reg_base; + int irq; + + /* Used to generate unique names, see goldfish_sync_timeline::name. */ + u64 id_counter; + + /* |mutex_lock| protects all concurrent access + * to timelines for both kernel and user space. + */ + struct mutex mutex_lock; + + /* Buffer holding commands issued from host. */ + struct goldfish_sync_hostcmd to_do[GOLDFISH_SYNC_MAX_CMDS]; + u32 to_do_end; + /* Protects to_do and to_do_end */ + spinlock_t to_do_lock; + + /* Buffers for the reading or writing + * of individual commands. The host can directly write + * to |batch_hostcmd| (and then this driver immediately + * copies contents to |to_do|). This driver either replies + * through |batch_hostcmd| or simply issues a + * guest->host command through |batch_guestcmd|. + */ + struct goldfish_sync_hostcmd batch_hostcmd; + struct goldfish_sync_guestcmd batch_guestcmd; + + /* Used to give this struct itself to a work queue + * function for executing actual sync commands. + */ + struct work_struct work_item; +}; + +static struct goldfish_sync_timeline +*goldfish_dma_fence_parent(struct dma_fence *fence) +{ + return container_of(fence->lock, struct goldfish_sync_timeline, lock); +} + +static struct sync_pt *goldfish_sync_fence_to_sync_pt(struct dma_fence *fence) +{ + return container_of(fence, struct sync_pt, base); +} + +/* sync_state->mutex_lock must be locked. */ +struct goldfish_sync_timeline __must_check +*goldfish_sync_timeline_create(struct goldfish_sync_state *sync_state) +{ + struct goldfish_sync_timeline *tl; + + tl = kzalloc(sizeof(*tl), GFP_KERNEL); + if (!tl) + return NULL; + + tl->sync_state = sync_state; + kref_init(&tl->kref); + snprintf(tl->name, sizeof(tl->name), + "%s:%llu", GOLDFISH_SYNC_DEVICE_NAME, + ++sync_state->id_counter); + tl->context = dma_fence_context_alloc(1); + tl->seqno = 0; + INIT_LIST_HEAD(&tl->active_list_head); + spin_lock_init(&tl->lock); + + return tl; +} + +static void goldfish_sync_timeline_free(struct kref *kref) +{ + struct goldfish_sync_timeline *tl = + container_of(kref, struct goldfish_sync_timeline, kref); + + kfree(tl); +} + +static void goldfish_sync_timeline_get(struct goldfish_sync_timeline *tl) +{ + kref_get(&tl->kref); +} + +void goldfish_sync_timeline_put(struct goldfish_sync_timeline *tl) +{ + kref_put(&tl->kref, goldfish_sync_timeline_free); +} + +void goldfish_sync_timeline_signal(struct goldfish_sync_timeline *tl, + unsigned int inc) +{ + unsigned long flags; + struct sync_pt *pt, *next; + + spin_lock_irqsave(&tl->lock, flags); + tl->seqno += inc; + + list_for_each_entry_safe(pt, next, &tl->active_list_head, active_list) { + /* dma_fence_is_signaled_locked has side effects */ + if (dma_fence_is_signaled_locked(&pt->base)) + list_del_init(&pt->active_list); + } + spin_unlock_irqrestore(&tl->lock, flags); +} + +static const struct dma_fence_ops goldfish_sync_timeline_fence_ops; + +static struct sync_pt __must_check +*goldfish_sync_pt_create(struct goldfish_sync_timeline *tl, + unsigned int value) +{ + struct sync_pt *pt = kzalloc(sizeof(*pt), GFP_KERNEL); + + if (!pt) + return NULL; + + dma_fence_init(&pt->base, + &goldfish_sync_timeline_fence_ops, + &tl->lock, + tl->context, + value); + INIT_LIST_HEAD(&pt->active_list); + goldfish_sync_timeline_get(tl); /* pt refers to tl */ + + return pt; +} + +static void goldfish_sync_pt_destroy(struct sync_pt *pt) +{ + struct goldfish_sync_timeline *tl = + goldfish_dma_fence_parent(&pt->base); + unsigned long flags; + + spin_lock_irqsave(&tl->lock, flags); + if (!list_empty(&pt->active_list)) + list_del(&pt->active_list); + spin_unlock_irqrestore(&tl->lock, flags); + + goldfish_sync_timeline_put(tl); /* unref pt from tl */ + dma_fence_free(&pt->base); +} + +static const char +*goldfish_sync_timeline_fence_get_driver_name(struct dma_fence *fence) +{ + return "sw_sync"; +} + +static const char +*goldfish_sync_timeline_fence_get_timeline_name(struct dma_fence *fence) +{ + struct goldfish_sync_timeline *tl = goldfish_dma_fence_parent(fence); + + return tl->name; +} + +static void goldfish_sync_timeline_fence_release(struct dma_fence *fence) +{ + goldfish_sync_pt_destroy(goldfish_sync_fence_to_sync_pt(fence)); +} + +static bool goldfish_sync_timeline_fence_signaled(struct dma_fence *fence) +{ + struct goldfish_sync_timeline *tl = goldfish_dma_fence_parent(fence); + + return tl->seqno >= fence->seqno; +} + +static bool +goldfish_sync_timeline_fence_enable_signaling(struct dma_fence *fence) +{ + struct sync_pt *pt; + struct goldfish_sync_timeline *tl; + + if (goldfish_sync_timeline_fence_signaled(fence)) + return false; + + pt = goldfish_sync_fence_to_sync_pt(fence); + tl = goldfish_dma_fence_parent(fence); + list_add_tail(&pt->active_list, &tl->active_list_head); + return true; +} + +static void goldfish_sync_timeline_fence_value_str(struct dma_fence *fence, + char *str, int size) +{ + snprintf(str, size, "%d", fence->seqno); +} + +static void goldfish_sync_timeline_fence_timeline_value_str( + struct dma_fence *fence, + char *str, int size) +{ + struct goldfish_sync_timeline *tl = goldfish_dma_fence_parent(fence); + + snprintf(str, size, "%d", tl->seqno); +} + +static const struct dma_fence_ops goldfish_sync_timeline_fence_ops = { + .get_driver_name = goldfish_sync_timeline_fence_get_driver_name, + .get_timeline_name = goldfish_sync_timeline_fence_get_timeline_name, + .enable_signaling = goldfish_sync_timeline_fence_enable_signaling, + .signaled = goldfish_sync_timeline_fence_signaled, + .wait = dma_fence_default_wait, + .release = goldfish_sync_timeline_fence_release, + .fence_value_str = goldfish_sync_timeline_fence_value_str, + .timeline_value_str = goldfish_sync_timeline_fence_timeline_value_str, +}; + +static int __must_check +goldfish_sync_fence_create(struct goldfish_sync_timeline *tl, u32 val) +{ + struct sync_pt *pt; + struct sync_file *sync_file_obj = NULL; + int fd; + + pt = goldfish_sync_pt_create(tl, val); + if (!pt) + return -1; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + goto err_cleanup_pt; + + sync_file_obj = sync_file_create(&pt->base); + if (!sync_file_obj) + goto err_cleanup_fd_pt; + + fd_install(fd, sync_file_obj->file); + + dma_fence_put(&pt->base); /* sync_file_obj now owns the fence */ + return fd; + +err_cleanup_fd_pt: + put_unused_fd(fd); +err_cleanup_pt: + goldfish_sync_pt_destroy(pt); + + return -1; +} + +static inline void +goldfish_sync_cmd_queue(struct goldfish_sync_state *sync_state, + u32 cmd, + u64 handle, + u32 time_arg, + u64 hostcmd_handle) +{ + struct goldfish_sync_hostcmd *to_add; + + WARN_ON(sync_state->to_do_end == GOLDFISH_SYNC_MAX_CMDS); + + to_add = &sync_state->to_do[sync_state->to_do_end]; + + to_add->cmd = cmd; + to_add->handle = handle; + to_add->time_arg = time_arg; + to_add->hostcmd_handle = hostcmd_handle; + + ++sync_state->to_do_end; +} + +static inline void +goldfish_sync_hostcmd_reply(struct goldfish_sync_state *sync_state, + u32 cmd, + u64 handle, + u32 time_arg, + u64 hostcmd_handle) +{ + unsigned long irq_flags; + struct goldfish_sync_hostcmd *batch_hostcmd = + &sync_state->batch_hostcmd; + + spin_lock_irqsave(&sync_state->to_do_lock, irq_flags); + + batch_hostcmd->cmd = cmd; + batch_hostcmd->handle = handle; + batch_hostcmd->time_arg = time_arg; + batch_hostcmd->hostcmd_handle = hostcmd_handle; + writel(0, sync_state->reg_base + SYNC_REG_BATCH_COMMAND); + + spin_unlock_irqrestore(&sync_state->to_do_lock, irq_flags); +} + +static inline void +goldfish_sync_send_guestcmd(struct goldfish_sync_state *sync_state, + u32 cmd, + u64 glsync_handle, + u64 thread_handle, + u64 timeline_handle) +{ + unsigned long irq_flags; + struct goldfish_sync_guestcmd *batch_guestcmd = + &sync_state->batch_guestcmd; + + spin_lock_irqsave(&sync_state->to_do_lock, irq_flags); + + batch_guestcmd->host_command = cmd; + batch_guestcmd->glsync_handle = glsync_handle; + batch_guestcmd->thread_handle = thread_handle; + batch_guestcmd->guest_timeline_handle = timeline_handle; + writel(0, sync_state->reg_base + SYNC_REG_BATCH_GUESTCOMMAND); + + spin_unlock_irqrestore(&sync_state->to_do_lock, irq_flags); +} + +/* |goldfish_sync_interrupt| handles IRQ raises from the virtual device. + * In the context of OpenGL, this interrupt will fire whenever we need + * to signal a fence fd in the guest, with the command + * |CMD_SYNC_TIMELINE_INC|. + * However, because this function will be called in an interrupt context, + * it is necessary to do the actual work of signaling off of interrupt context. + * The shared work queue is used for this purpose. At the end when + * all pending commands are intercepted by the interrupt handler, + * we call |schedule_work|, which will later run the actual + * desired sync command in |goldfish_sync_work_item_fn|. + */ +static irqreturn_t +goldfish_sync_interrupt_impl(struct goldfish_sync_state *sync_state) +{ + struct goldfish_sync_hostcmd *batch_hostcmd = + &sync_state->batch_hostcmd; + + spin_lock(&sync_state->to_do_lock); + for (;;) { + u32 nextcmd; + u32 command_r; + u64 handle_rw; + u32 time_r; + u64 hostcmd_handle_rw; + + readl(sync_state->reg_base + SYNC_REG_BATCH_COMMAND); + nextcmd = batch_hostcmd->cmd; + + if (nextcmd == 0) + break; + + command_r = nextcmd; + handle_rw = batch_hostcmd->handle; + time_r = batch_hostcmd->time_arg; + hostcmd_handle_rw = batch_hostcmd->hostcmd_handle; + + goldfish_sync_cmd_queue(sync_state, + command_r, + handle_rw, + time_r, + hostcmd_handle_rw); + } + spin_unlock(&sync_state->to_do_lock); + + schedule_work(&sync_state->work_item); + return IRQ_HANDLED; +} + +static const struct file_operations goldfish_sync_fops; + +static irqreturn_t goldfish_sync_interrupt(int irq, void *dev_id) +{ + struct goldfish_sync_state *sync_state = dev_id; + + return (sync_state->miscdev.fops == &goldfish_sync_fops) ? + goldfish_sync_interrupt_impl(sync_state) : IRQ_NONE; +} + +/* We expect that commands will come in at a slow enough rate + * so that incoming items will not be more than + * GOLDFISH_SYNC_MAX_CMDS. + * + * This is because the way the sync device is used, + * it's only for managing buffer data transfers per frame, + * with a sequential dependency between putting things in + * to_do and taking them out. Once a set of commands is + * queued up in to_do, the user of the device waits for + * them to be processed before queuing additional commands, + * which limits the rate at which commands come in + * to the rate at which we take them out here. + * + * We also don't expect more than MAX_CMDS to be issued + * at once; there is a correspondence between + * which buffers need swapping to the (display / buffer queue) + * to particular commands, and we don't expect there to be + * enough display or buffer queues in operation at once + * to overrun GOLDFISH_SYNC_MAX_CMDS. + */ +static u32 __must_check +goldfish_sync_grab_commands(struct goldfish_sync_state *sync_state, + struct goldfish_sync_hostcmd *dst) +{ + u32 to_do_end; + u32 i; + unsigned long irq_flags; + + spin_lock_irqsave(&sync_state->to_do_lock, irq_flags); + + to_do_end = sync_state->to_do_end; + for (i = 0; i < to_do_end; i++) + dst[i] = sync_state->to_do[i]; + sync_state->to_do_end = 0; + + spin_unlock_irqrestore(&sync_state->to_do_lock, irq_flags); + + return to_do_end; +} + +void goldfish_sync_run_hostcmd(struct goldfish_sync_state *sync_state, + struct goldfish_sync_hostcmd *todo) +{ + struct goldfish_sync_timeline *tl = + (struct goldfish_sync_timeline *)(uintptr_t)todo->handle; + int sync_fence_fd; + + switch (todo->cmd) { + case CMD_SYNC_READY: + break; + + case CMD_CREATE_SYNC_TIMELINE: + tl = goldfish_sync_timeline_create(sync_state); + WARN_ON(!tl); + goldfish_sync_hostcmd_reply(sync_state, + CMD_CREATE_SYNC_TIMELINE, + (uintptr_t)tl, + 0, + todo->hostcmd_handle); + break; + + case CMD_CREATE_SYNC_FENCE: + WARN_ON(!tl); + sync_fence_fd = goldfish_sync_fence_create(tl, todo->time_arg); + goldfish_sync_hostcmd_reply(sync_state, + CMD_CREATE_SYNC_FENCE, + sync_fence_fd, + 0, + todo->hostcmd_handle); + break; + + case CMD_SYNC_TIMELINE_INC: + WARN_ON(!tl); + goldfish_sync_timeline_signal(tl, todo->time_arg); + break; + + case CMD_DESTROY_SYNC_TIMELINE: + WARN_ON(!tl); + goldfish_sync_timeline_put(tl); + break; + } +} + +/* |goldfish_sync_work_item_fn| does the actual work of servicing + * host->guest sync commands. This function is triggered whenever + * the IRQ for the goldfish sync device is raised. Once it starts + * running, it grabs the contents of the buffer containing the + * commands it needs to execute (there may be multiple, because + * our IRQ is active high and not edge triggered), and then + * runs all of them one after the other. + */ +static void goldfish_sync_work_item_fn(struct work_struct *input) +{ + struct goldfish_sync_state *sync_state = + container_of(input, struct goldfish_sync_state, work_item); + + struct goldfish_sync_hostcmd to_run[GOLDFISH_SYNC_MAX_CMDS]; + u32 to_do_end; + u32 i; + + mutex_lock(&sync_state->mutex_lock); + + to_do_end = goldfish_sync_grab_commands(sync_state, to_run); + + for (i = 0; i < to_do_end; i++) + goldfish_sync_run_hostcmd(sync_state, &to_run[i]); + + mutex_unlock(&sync_state->mutex_lock); +} + +static int goldfish_sync_open(struct inode *inode, struct file *filp) +{ + struct goldfish_sync_state *sync_state = + container_of(filp->private_data, + struct goldfish_sync_state, + miscdev); + + if (mutex_lock_interruptible(&sync_state->mutex_lock)) + return -ERESTARTSYS; + + filp->private_data = goldfish_sync_timeline_create(sync_state); + mutex_unlock(&sync_state->mutex_lock); + + return filp->private_data ? 0 : -ENOMEM; +} + +static int goldfish_sync_release(struct inode *inode, struct file *filp) +{ + struct goldfish_sync_timeline *tl = filp->private_data; + + goldfish_sync_timeline_put(tl); + return 0; +} + +/* |goldfish_sync_ioctl| is the guest-facing interface of goldfish sync + * and is used in conjunction with eglCreateSyncKHR to queue up the + * actual work of waiting for the EGL sync command to complete, + * possibly returning a fence fd to the guest. + */ +static long +goldfish_sync_ioctl_locked(struct goldfish_sync_timeline *tl, + unsigned int cmd, + unsigned long arg) +{ + struct goldfish_sync_ioctl_info ioctl_data; + int fd_out = -1; + + switch (cmd) { + case GOLDFISH_SYNC_IOC_QUEUE_WORK: + if (copy_from_user(&ioctl_data, + (void __user *)arg, + sizeof(ioctl_data))) + return -EFAULT; + + if (!ioctl_data.host_syncthread_handle_in) + return -EFAULT; + + fd_out = goldfish_sync_fence_create(tl, tl->seqno + 1); + ioctl_data.fence_fd_out = fd_out; + + if (copy_to_user((void __user *)arg, + &ioctl_data, + sizeof(ioctl_data))) { + ksys_close(fd_out); + return -EFAULT; + } + + /* We are now about to trigger a host-side wait; + * accumulate on |pending_waits|. + */ + goldfish_sync_send_guestcmd(tl->sync_state, + CMD_TRIGGER_HOST_WAIT, + ioctl_data.host_glsync_handle_in, + ioctl_data.host_syncthread_handle_in, + (u64)(uintptr_t)tl); + return 0; + + default: + return -ENOTTY; + } +} + +static long goldfish_sync_ioctl(struct file *filp, + unsigned int cmd, + unsigned long arg) +{ + struct goldfish_sync_timeline *tl = filp->private_data; + struct goldfish_sync_state *x = tl->sync_state; + long res; + + if (mutex_lock_interruptible(&x->mutex_lock)) + return -ERESTARTSYS; + + res = goldfish_sync_ioctl_locked(tl, cmd, arg); + mutex_unlock(&x->mutex_lock); + + return res; +} + +static bool setup_verify_batch_cmd_addr(char *reg_base, + void *batch_addr, + u32 addr_offset, + u32 addr_offset_high) +{ + u64 batch_addr_phys; + u64 batch_addr_phys_test_lo; + u64 batch_addr_phys_test_hi; + + batch_addr_phys = virt_to_phys(batch_addr); + writel(lower_32_bits(batch_addr_phys), reg_base + addr_offset); + writel(upper_32_bits(batch_addr_phys), reg_base + addr_offset_high); + + batch_addr_phys_test_lo = readl(reg_base + addr_offset); + batch_addr_phys_test_hi = readl(reg_base + addr_offset_high); + + batch_addr_phys = batch_addr_phys_test_lo | + (batch_addr_phys_test_hi << 32); + + return virt_to_phys(batch_addr) == batch_addr_phys; +} + +static const struct file_operations goldfish_sync_fops = { + .owner = THIS_MODULE, + .open = goldfish_sync_open, + .release = goldfish_sync_release, + .unlocked_ioctl = goldfish_sync_ioctl, + .compat_ioctl = goldfish_sync_ioctl, +}; + +static void fill_miscdevice(struct miscdevice *misc) +{ + misc->name = GOLDFISH_SYNC_DEVICE_NAME; + misc->minor = MISC_DYNAMIC_MINOR; + misc->fops = &goldfish_sync_fops; +} + +static int goldfish_sync_probe(struct platform_device *pdev) +{ + struct goldfish_sync_state *sync_state; + struct resource *ioresource; + int result; + + sync_state = devm_kzalloc(&pdev->dev, sizeof(*sync_state), GFP_KERNEL); + if (!sync_state) + return -ENOMEM; + + spin_lock_init(&sync_state->to_do_lock); + mutex_init(&sync_state->mutex_lock); + INIT_WORK(&sync_state->work_item, goldfish_sync_work_item_fn); + + ioresource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!ioresource) + return -ENODEV; + + sync_state->reg_base = + devm_ioremap(&pdev->dev, ioresource->start, PAGE_SIZE); + if (!sync_state->reg_base) + return -ENOMEM; + + result = platform_get_irq(pdev, 0); + if (result < 0) + return -ENODEV; + + sync_state->irq = result; + + result = devm_request_irq(&pdev->dev, + sync_state->irq, + goldfish_sync_interrupt, + IRQF_SHARED, + pdev->name, + sync_state); + if (result) + return -ENODEV; + + if (!setup_verify_batch_cmd_addr(sync_state->reg_base, + &sync_state->batch_hostcmd, + SYNC_REG_BATCH_COMMAND_ADDR, + SYNC_REG_BATCH_COMMAND_ADDR_HIGH)) + return -ENODEV; + + if (!setup_verify_batch_cmd_addr(sync_state->reg_base, + &sync_state->batch_guestcmd, + SYNC_REG_BATCH_GUESTCOMMAND_ADDR, + SYNC_REG_BATCH_GUESTCOMMAND_ADDR_HIGH)) + return -ENODEV; + + fill_miscdevice(&sync_state->miscdev); + result = misc_register(&sync_state->miscdev); + if (result) + return -ENODEV; + + writel(0, sync_state->reg_base + SYNC_REG_INIT); + + platform_set_drvdata(pdev, sync_state); + + return 0; +} + +static int goldfish_sync_remove(struct platform_device *pdev) +{ + struct goldfish_sync_state *sync_state = platform_get_drvdata(pdev); + + misc_deregister(&sync_state->miscdev); + return 0; +} + +static const struct of_device_id goldfish_sync_of_match[] = { + { .compatible = "google,goldfish-sync", }, + {}, +}; +MODULE_DEVICE_TABLE(of, goldfish_sync_of_match); + +static const struct acpi_device_id goldfish_sync_acpi_match[] = { + { "GFSH0006", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, goldfish_sync_acpi_match); + +static struct platform_driver goldfish_sync = { + .probe = goldfish_sync_probe, + .remove = goldfish_sync_remove, + .driver = { + .name = GOLDFISH_SYNC_DEVICE_NAME, + .of_match_table = goldfish_sync_of_match, + .acpi_match_table = ACPI_PTR(goldfish_sync_acpi_match), + } +}; +module_platform_driver(goldfish_sync); + +MODULE_AUTHOR("Google, Inc."); +MODULE_DESCRIPTION("Android QEMU Sync Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("2.0"); diff --git a/include/uapi/linux/goldfish/goldfish_sync.h b/include/uapi/linux/goldfish/goldfish_sync.h new file mode 100644 index 000000000000..01d762f77308 --- /dev/null +++ b/include/uapi/linux/goldfish/goldfish_sync.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef UAPI_GOLDFISH_SYNC_H +#define UAPI_GOLDFISH_SYNC_H + +#include <linux/types.h> + +#define GOLDFISH_SYNC_DEVICE_NAME "goldfish_sync" + +struct goldfish_sync_ioctl_info { + __u64 host_glsync_handle_in; + __u64 host_syncthread_handle_in; + __s32 fence_fd_out; +}; + +/* There is an ioctl associated with goldfish sync driver. + * Make it conflict with ioctls that are not likely to be used + * in the emulator. + * + * '@' 00-0F linux/radeonfb.h conflict! + * '@' 00-0F drivers/video/aty/aty128fb.c conflict! + */ +#define GOLDFISH_SYNC_IOC_MAGIC '@' + +#define GOLDFISH_SYNC_IOC_QUEUE_WORK \ + _IOWR(GOLDFISH_SYNC_IOC_MAGIC, 0, struct goldfish_sync_ioctl_info) + +#endif /* UAPI_GOLDFISH_SYNC_H */ -- 2.20.1.97.g81188d93c3-goog