// SPDX-License-Identifier: GPL-2.0
/*
* drivers/android/staging/vsoc.c
*
* Android Virtual System on a Chip (VSoC) driver
*
* Copyright (C) 2017 Google, Inc.
*
* Author: ghartman@google.com
*
* Based on drivers/char/kvm_ivshmem.c - driver for KVM Inter-VM shared memory
* Copyright 2009 Cam Macdonell <cam@cs.ualberta.ca>
*
* Based on cirrusfb.c and 8139cp.c:
* Copyright 1999-2001 Jeff Garzik
* Copyright 2001-2004 Jeff Garzik
*/
#include <linux/dma-mapping.h>
#include <linux/freezer.h>
#include <linux/futex.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/file.h>
#include "uapi/vsoc_shm.h"
#define VSOC_DEV_NAME "vsoc"
/*
* Description of the ivshmem-doorbell PCI device used by QEmu. These
* constants follow docs/specs/ivshmem-spec.txt, which can be found in
* the QEmu repository. This was last reconciled with the version that
* came out with 2.8
*/
/*
* These constants are determined KVM Inter-VM shared memory device
* register offsets
*/
enum {
INTR_MASK = 0x00, /* Interrupt Mask */
INTR_STATUS = 0x04, /* Interrupt Status */
IV_POSITION = 0x08, /* VM ID */
DOORBELL = 0x0c, /* Doorbell */
};
static const int REGISTER_BAR; /* Equal to 0 */
static const int MAX_REGISTER_BAR_LEN = 0x100;
/*
* The MSI-x BAR is not used directly.
*
* static const int MSI_X_BAR = 1;
*/
static const int SHARED_MEMORY_BAR = 2;
struct vsoc_region_data {
char name[VSOC_DEVICE_NAME_SZ + 1];
wait_queue_head_t interrupt_wait_queue;
/* TODO(b/73664181): Use multiple futex wait queues */
wait_queue_head_t futex_wait_queue;
/* Flag indicating that an interrupt has been signalled by the host. */
atomic_t *incoming_signalled;
/* Flag indicating the guest has signalled the host. */
atomic_t *outgoing_signalled;
bool irq_requested;
bool device_created;
};
struct vsoc_device {
/* Kernel virtual address of REGISTER_BAR. */
void __iomem *regs;
/* Physical address of SHARED_MEMORY_BAR. */
phys_addr_t shm_phys_start;
/* Kernel virtual address of SHARED_MEMORY_BAR. */
void __iomem *kernel_mapped_shm;
/* Size of the entire shared memory window in bytes. */
size_t shm_size;
/*
* Pointer to the virtual address of the shared memory layout structure.
* This is probably identical to kernel_mapped_shm, but saving this
* here saves a lot of annoying casts.
*/
struct vsoc_shm_layout_descriptor *layout;
/*
* Points to a table of region descriptors in the kernel's virtual
* address space. Calculated from
* vsoc_shm_layout_descriptor.vsoc_region_desc_offset
*/
struct vsoc_device_region *regions;
/* Head of a list of permissions that have been granted. */
struct list_head permissions;
struct pci_dev *dev;
/* Per-region (and therefore per-interrupt) information. */
struct vsoc_region_data *regions_data;
/*
* Table of msi-x entries. This has to be separated from struct
* vsoc_region_data because the kernel deals with them as an array.
*/
struct msix_entry *msix_entries;
/* Mutex that protectes the permission list */
struct mutex mtx;
/* Major number assigned by the kernel */
int major;
/* Character device assigned by the kernel */
struct cdev cdev;
/* Device class assigned by the kernel */
struct class *class;
/*
* Flags that indicate what we've initialized. These are used to do an
* orderly cleanup of the device.
*/
bool enabled_device;
bool requested_regions;
bool cdev_added;
bool class_added;
bool msix_enabled;
};
static struct vsoc_device vsoc_dev;
/*
* TODO(ghartman): Add a /sys filesystem entry that summarizes the permissions.
*/
struct fd_scoped_permission_node {
struct fd_scoped_permission permission;
struct list_head list;
};
struct vsoc_private_data {
struct fd_scoped_permission_node *fd_scoped_permission_node;
};
static long vsoc_ioctl(struct file *, unsigned int, unsigned long);
static int vsoc_mmap(struct file *, struct vm_area_struct *);
static int vsoc_open(struct inode *, struct file *);
static int vsoc_release(struct inode *, struct file *);
static ssize_t vsoc_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t vsoc_write(struct file *, const