drivers misc: add okl4-link-shbuf driver
A simple driver for using a shared-memory bridge between two VMs. Change-Id: I3dac6dd88d1f658c95e799262a9720a44af00291 Signed-off-by: Carl van Schaik <carl@cog.systems> Git-commit: 18b0192bb16d90974af0401f1a46f20f0f5b9502 Git-repo: https://github.com/CogSystems/linux-msm/commits/msm-4.9-hyp [mnalajal@codeaurora: Resolve trivial merge conflicts] Signed-off-by: Murali Nalajala <mnalajal@codeaurora.org> Signed-off-by: Prakruthi Deepak Heragu <pheragu@codeaurora.org>
This commit is contained in:
parent
17607ebf91
commit
8ceb0ea6d8
5 changed files with 718 additions and 0 deletions
|
@ -549,3 +549,13 @@ source "drivers/misc/cxl/Kconfig"
|
|||
source "drivers/misc/ocxl/Kconfig"
|
||||
source "drivers/misc/cardreader/Kconfig"
|
||||
endmenu
|
||||
|
||||
config OKL4_LINK_SHBUF
|
||||
tristate "OKL4 link with shared buffer transport"
|
||||
default y
|
||||
depends on OKL4_GUEST
|
||||
help
|
||||
Enable driver for OKL4 inter-cell links using the "shared-buffer"
|
||||
transport. This driver presents the link to Linux as a character device
|
||||
which can be written to or read from to access the shared memory. An ioctl
|
||||
on the device is used to send a virtual interrupt to the partner cell.
|
||||
|
|
|
@ -60,3 +60,4 @@ obj-$(CONFIG_OCXL) += ocxl/
|
|||
obj-$(CONFIG_MISC_RTSX) += cardreader/
|
||||
obj-$(CONFIG_UID_SYS_STATS) += uid_sys_stats.o
|
||||
obj-$(CONFIG_MEMORY_STATE_TIME) += memory_state_time.o
|
||||
obj-$(CONFIG_OKL4_LINK_SHBUF) += okl4-link-shbuf.o
|
||||
|
|
666
drivers/misc/okl4-link-shbuf.c
Normal file
666
drivers/misc/okl4-link-shbuf.c
Normal file
|
@ -0,0 +1,666 @@
|
|||
/*
|
||||
* Driver for inter-cell links using the shared-buffer transport.
|
||||
*
|
||||
* Copyright (c) 2016 Cog Systems Pty Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/version.h>
|
||||
#include <microvisor/microvisor.h>
|
||||
#include <uapi/linux/okl4-link-shbuf.h>
|
||||
|
||||
static const char DEVICE_NAME[] = "okl4_link_shbuf";
|
||||
|
||||
/* Created devices will appear as /dev/<DEV_PREFIX><name> */
|
||||
static const char DEV_PREFIX[] = "okl4-";
|
||||
|
||||
static const struct of_device_id okl4_link_shbuf_match[] = {
|
||||
{
|
||||
.compatible = "okl,microvisor-link-shbuf",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, okl4_link_shbuf_match);
|
||||
|
||||
static struct class *link_shbuf_class;
|
||||
static dev_t link_shbuf_dev;
|
||||
|
||||
/* A lock used to protect access to link_shbuf_dev */
|
||||
static spinlock_t device_number_allocate;
|
||||
|
||||
/* Sentinel values for indicating missing communication channels */
|
||||
static const u32 NO_OUTGOING_IRQ = 0;
|
||||
static const int NO_INCOMING_IRQ = -1;
|
||||
|
||||
/* Private data for this driver */
|
||||
struct link_shbuf_data {
|
||||
|
||||
/* Outgoing vIRQ */
|
||||
u32 virqline;
|
||||
|
||||
/* Incoming vIRQ */
|
||||
int virq;
|
||||
atomic64_t virq_payload;
|
||||
bool virq_pending;
|
||||
wait_queue_head_t virq_wq;
|
||||
|
||||
/* Shared memory region */
|
||||
void *base;
|
||||
fmode_t permissions;
|
||||
struct resource buffer;
|
||||
|
||||
/* Device data */
|
||||
dev_t devt;
|
||||
struct device *dev;
|
||||
struct cdev cdev;
|
||||
|
||||
};
|
||||
|
||||
static bool link_shbuf_data_invariant(const struct link_shbuf_data *priv)
|
||||
{
|
||||
if (!priv)
|
||||
return false;
|
||||
|
||||
if (!priv->base || (uintptr_t)priv->base % PAGE_SIZE != 0)
|
||||
return false;
|
||||
|
||||
if (resource_size(&priv->buffer) == 0)
|
||||
return false;
|
||||
|
||||
if (!priv->dev)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool link_shbuf_valid_access(size_t size, loff_t pos, size_t count)
|
||||
{
|
||||
return pos < size && count <= size && size - count >= pos;
|
||||
}
|
||||
|
||||
static ssize_t link_shbuf_read(struct file *file, char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
long remaining;
|
||||
const struct link_shbuf_data *priv;
|
||||
|
||||
/* The file should have been opened with read access to reach here */
|
||||
if (WARN_ON(!(file->f_mode & FMODE_READ)))
|
||||
return -EINVAL;
|
||||
|
||||
priv = file->private_data;
|
||||
if (WARN_ON(!link_shbuf_data_invariant(priv)))
|
||||
return -EINVAL;
|
||||
|
||||
if (!link_shbuf_valid_access(resource_size(&priv->buffer), *ppos, count))
|
||||
return -EINVAL;
|
||||
|
||||
remaining = copy_to_user(buffer, priv->base + *ppos, count);
|
||||
*ppos += count - remaining;
|
||||
return count - remaining;
|
||||
}
|
||||
|
||||
static ssize_t link_shbuf_write(struct file *file, const char __user *buffer,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
long remaining;
|
||||
const struct link_shbuf_data *priv;
|
||||
|
||||
/* The file should have been opened with write access to reach here */
|
||||
if (WARN_ON(!(file->f_mode & FMODE_WRITE)))
|
||||
return -EINVAL;
|
||||
|
||||
priv = file->private_data;
|
||||
if (WARN_ON(!link_shbuf_data_invariant(priv)))
|
||||
return -EINVAL;
|
||||
|
||||
if (!link_shbuf_valid_access(resource_size(&priv->buffer), *ppos, count))
|
||||
return -EINVAL;
|
||||
|
||||
remaining = copy_from_user(priv->base + *ppos, buffer, count);
|
||||
*ppos += count - remaining;
|
||||
return count - remaining;
|
||||
}
|
||||
|
||||
static unsigned int link_shbuf_poll(struct file *file, poll_table *table)
|
||||
{
|
||||
struct link_shbuf_data *priv;
|
||||
unsigned int mask;
|
||||
|
||||
priv = file->private_data;
|
||||
if (WARN_ON(!link_shbuf_data_invariant(priv)))
|
||||
return POLLERR;
|
||||
|
||||
poll_wait(file, &priv->virq_wq, table);
|
||||
|
||||
/* The shared memory is always considered ready for reading and writing. */
|
||||
mask = POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
|
||||
|
||||
if (priv->virq_pending)
|
||||
mask |= POLLPRI;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static long link_shbuf_ioctl_irq_tx(const struct link_shbuf_data *priv,
|
||||
unsigned long arg)
|
||||
{
|
||||
okl4_error_t err;
|
||||
u64 payload;
|
||||
const u64 __user *user_arg = (const u64 __user*)arg;
|
||||
|
||||
if (priv->virqline == NO_OUTGOING_IRQ)
|
||||
return -EINVAL;
|
||||
|
||||
#if defined(CONFIG_ARM) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
|
||||
if (copy_from_user(&payload, user_arg, sizeof(payload)))
|
||||
return -EFAULT;
|
||||
#else
|
||||
if (get_user(payload, user_arg))
|
||||
return -EFAULT;
|
||||
#endif
|
||||
|
||||
err = _okl4_sys_vinterrupt_raise(priv->virqline, payload);
|
||||
if (WARN_ON(err != OKL4_OK))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long link_shbuf_ioctl_irq_clr(struct link_shbuf_data *priv,
|
||||
unsigned long arg)
|
||||
{
|
||||
u64 payload;
|
||||
u64 __user *user_arg = (u64 __user*)arg;
|
||||
|
||||
/*
|
||||
* Check validity of the user pointer before clearing the interrupt to avoid
|
||||
* races involved with having to undo the latter.
|
||||
*/
|
||||
if (!access_ok(VERIFY_WRITE, user_arg, sizeof(*user_arg)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Note that the clearing of the pending flag can race with the setting of
|
||||
* this flag in the IRQ handler. It is up to the user to coordinate these
|
||||
* actions.
|
||||
*/
|
||||
priv->virq_pending = false;
|
||||
smp_rmb();
|
||||
payload = atomic64_xchg(&priv->virq_payload, 0);
|
||||
|
||||
/* We've already checked that this access is OK, so no need for put_user. */
|
||||
if (__put_user(payload, user_arg))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long link_shbuf_ioctl(struct file *file, unsigned int request,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct link_shbuf_data *priv;
|
||||
|
||||
priv = file->private_data;
|
||||
if (WARN_ON(!link_shbuf_data_invariant(priv)))
|
||||
return -EINVAL;
|
||||
|
||||
/* We only support two ioctls */
|
||||
switch (request) {
|
||||
|
||||
case OKL4_LINK_SHBUF_IOCTL_IRQ_TX:
|
||||
return link_shbuf_ioctl_irq_tx(priv, arg);
|
||||
|
||||
case OKL4_LINK_SHBUF_IOCTL_IRQ_CLR:
|
||||
return link_shbuf_ioctl_irq_clr(priv, arg);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Handy for debugging when userspace is linking against ioctl headers from
|
||||
* a different kernel revision.
|
||||
*/
|
||||
dev_dbg(priv->dev, "ioctl request 0x%x received which did not match either "
|
||||
"OKL4_LINK_SHBUF_IOCTL_IRQ_TX (0x%x) or OKL4_LINK_SHBUF_IOCTL_IRQ_CLR "
|
||||
"(0x%x)\n", request, (unsigned)OKL4_LINK_SHBUF_IOCTL_IRQ_TX,
|
||||
(unsigned)OKL4_LINK_SHBUF_IOCTL_IRQ_CLR);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int link_shbuf_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
const struct link_shbuf_data *priv;
|
||||
unsigned long offset, pfn, flags;
|
||||
size_t size;
|
||||
pgprot_t prot;
|
||||
|
||||
/* Our caller should have taken the MM semaphore. */
|
||||
if (WARN_ON(!rwsem_is_locked(&vma->vm_mm->mmap_sem)))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The file should have been opened with a superset of the mmap requested
|
||||
* permissions.
|
||||
*/
|
||||
flags = vma->vm_flags;
|
||||
if (WARN_ON((flags & VM_READ) && !(file->f_mode & FMODE_READ)))
|
||||
return -EINVAL;
|
||||
if (WARN_ON((flags & VM_WRITE) && !(file->f_mode & FMODE_WRITE)))
|
||||
return -EINVAL;
|
||||
if (WARN_ON((flags & VM_EXEC) && !(file->f_mode & FMODE_EXEC)))
|
||||
return -EINVAL;
|
||||
|
||||
/* Retrieve our private data. */
|
||||
priv = file->private_data;
|
||||
if (WARN_ON(!link_shbuf_data_invariant(priv)))
|
||||
return -EINVAL;
|
||||
|
||||
/* Check the mmap request is within bounds. */
|
||||
size = vma->vm_end - vma->vm_start;
|
||||
offset = vma->vm_pgoff << PAGE_SHIFT;
|
||||
if (!link_shbuf_valid_access(resource_size(&priv->buffer), offset, size))
|
||||
return -EINVAL;
|
||||
|
||||
pfn = (priv->buffer.start + offset) >> PAGE_SHIFT;
|
||||
prot = vm_get_page_prot(flags);
|
||||
|
||||
return remap_pfn_range(vma, vma->vm_start, pfn, size, prot);
|
||||
}
|
||||
|
||||
static bool link_shbuf_access_ok(fmode_t allowed, fmode_t request)
|
||||
{
|
||||
static const fmode_t ACCESS_MASK = FMODE_READ|FMODE_WRITE|FMODE_EXEC;
|
||||
fmode_t relevant = request & ACCESS_MASK;
|
||||
return (relevant & allowed) == relevant;
|
||||
}
|
||||
|
||||
static int link_shbuf_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct cdev *cdev;
|
||||
struct link_shbuf_data *priv;
|
||||
|
||||
/* Retrieve a pointer to our private data */
|
||||
cdev = inode->i_cdev;
|
||||
priv = container_of(cdev, struct link_shbuf_data, cdev);
|
||||
if (WARN_ON(!link_shbuf_data_invariant(priv)))
|
||||
return -EINVAL;
|
||||
|
||||
if (!link_shbuf_access_ok(priv->permissions, file->f_mode))
|
||||
return -EACCES;
|
||||
|
||||
file->private_data = priv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations link_shbuf_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.read = link_shbuf_read,
|
||||
.write = link_shbuf_write,
|
||||
.poll = link_shbuf_poll,
|
||||
.unlocked_ioctl = link_shbuf_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = link_shbuf_ioctl,
|
||||
#endif
|
||||
#ifdef CONFIG_MMU
|
||||
.mmap = link_shbuf_mmap,
|
||||
#endif
|
||||
.open = link_shbuf_open,
|
||||
};
|
||||
|
||||
/*
|
||||
* Interrupt handler.
|
||||
*
|
||||
* This function will be called when our link partner uses the ioctl on their
|
||||
* shared memory device to send an outgoing interrupt.
|
||||
*/
|
||||
static irqreturn_t link_shbuf_irq_handler(int irq, void *data)
|
||||
{
|
||||
u64 payload, old, new;
|
||||
struct _okl4_sys_interrupt_get_payload_return _payload;
|
||||
|
||||
/* Retrieve a pointer to our private data. */
|
||||
struct link_shbuf_data *priv = data;
|
||||
if (WARN_ON(!link_shbuf_data_invariant(priv)))
|
||||
return IRQ_NONE;
|
||||
|
||||
/*
|
||||
* We should only ever be handling a single interrupt, and only if there
|
||||
* was an incoming interrupt in the configuration.
|
||||
*/
|
||||
if (WARN_ON(priv->virq < 0 || priv->virq != irq))
|
||||
return IRQ_NONE;
|
||||
|
||||
_payload = _okl4_sys_interrupt_get_payload(irq);
|
||||
payload = (u64)_payload.payload;
|
||||
|
||||
/*
|
||||
* At this point, it is possible the pending flag is already set. It is up to
|
||||
* the user to synchronise their transmission and acknowledgement of
|
||||
* interrupts.
|
||||
*/
|
||||
|
||||
/* We open code atomic64_or which is not universally available. */
|
||||
do {
|
||||
old = atomic64_read(&priv->virq_payload);
|
||||
new = old | payload;
|
||||
} while (atomic64_cmpxchg(&priv->virq_payload, old, new) != old);
|
||||
smp_wmb();
|
||||
priv->virq_pending = true;
|
||||
|
||||
wake_up_interruptible(&priv->virq_wq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a unique device number for this device.
|
||||
*
|
||||
* Note that this function needs to lock its access to link_shbuf_dev as there
|
||||
* may be multiple threads attempting to acquire a new device number.
|
||||
*/
|
||||
static int link_shbuf_allocate_device(dev_t *devt)
|
||||
{
|
||||
int ret = 0;
|
||||
dev_t next;
|
||||
|
||||
spin_lock(&device_number_allocate);
|
||||
|
||||
*devt = link_shbuf_dev;
|
||||
next = MKDEV(MAJOR(link_shbuf_dev), MINOR(link_shbuf_dev) + 1);
|
||||
/* Check for overflow */
|
||||
if (MINOR(next) != MINOR(link_shbuf_dev) + 1)
|
||||
ret = -ENOSPC;
|
||||
else
|
||||
link_shbuf_dev = next;
|
||||
|
||||
spin_unlock(&device_number_allocate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Discover and add a new shared-buffer link.
|
||||
*
|
||||
* In the following function, we are expecting to parse device tree entries
|
||||
* looking like the following:
|
||||
*
|
||||
* hypervisor {
|
||||
* ...
|
||||
* interrupt-line@1d {
|
||||
* compatible = "okl,microvisor-interrupt-line",
|
||||
* "okl,microvisor-capability";
|
||||
* phandle = <0x7>;
|
||||
* reg = <0x1d>;
|
||||
* label = "foo_virqline";
|
||||
* };
|
||||
* ;
|
||||
*
|
||||
* foo@41003000 {
|
||||
* compatible = "okl,microvisor-link-shbuf",
|
||||
* "okl,microvisor-shared-memory";
|
||||
* phandle = <0xd>;
|
||||
* reg = <0x0 0x41003000 0x2000>;
|
||||
* label = "foo";
|
||||
* okl,rwx = <0x6>;
|
||||
* okl,interrupt-line = <0x7>;
|
||||
* interrupts = <0x0 0x4 0x1>;
|
||||
* interrupt-parent = <0x1>;
|
||||
* };
|
||||
*/
|
||||
static int link_shbuf_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct device_node *node, *virqline;
|
||||
struct link_shbuf_data *priv;
|
||||
const char *name;
|
||||
u32 permissions;
|
||||
|
||||
node = pdev->dev.of_node;
|
||||
|
||||
if (!node)
|
||||
return -ENODEV;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* Retrieve the outgoing vIRQ cap. Note, this is configurable and we
|
||||
* anticipate that it may not exist.
|
||||
*/
|
||||
virqline = of_parse_phandle(node, "okl,interrupt-line", 0);
|
||||
if (!virqline) {
|
||||
priv->virqline = NO_OUTGOING_IRQ;
|
||||
} else {
|
||||
ret = of_property_read_u32(virqline, "reg", &priv->virqline);
|
||||
if (ret < 0 || priv->virqline == OKL4_KCAP_INVALID) {
|
||||
of_node_put(virqline);
|
||||
ret = -ENODEV;
|
||||
goto err_free_dev;
|
||||
}
|
||||
}
|
||||
of_node_put(virqline);
|
||||
|
||||
/* Retrieve the incoming vIRQ number. Again, this is configurable and we
|
||||
* anticipate that it may not exist.
|
||||
*/
|
||||
priv->virq = platform_get_irq(pdev, 0);
|
||||
if (priv->virq < 0)
|
||||
priv->virq = NO_INCOMING_IRQ;
|
||||
|
||||
/* If we have a valid incoming vIRQ, register to handle it. */
|
||||
if (priv->virq >= 0) {
|
||||
ret = devm_request_irq(&pdev->dev, priv->virq, link_shbuf_irq_handler,
|
||||
0, dev_name(&pdev->dev), priv);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed request for IRQ\n");
|
||||
goto err_free_dev;
|
||||
}
|
||||
}
|
||||
|
||||
init_waitqueue_head(&priv->virq_wq);
|
||||
priv->virq_pending = false;
|
||||
|
||||
/* Retrieve information about the shared memory region. */
|
||||
ret = of_address_to_resource(node, 0, &priv->buffer);
|
||||
if (ret < 0)
|
||||
goto err_free_irq;
|
||||
/*
|
||||
* We expect the Elfweaver to have validated that we have a non-NULL,
|
||||
* page-aligned region.
|
||||
*/
|
||||
if (WARN_ON(priv->buffer.start == 0) ||
|
||||
WARN_ON(resource_size(&priv->buffer) % PAGE_SIZE != 0))
|
||||
goto err_free_irq;
|
||||
if (!devm_request_mem_region(&pdev->dev, priv->buffer.start,
|
||||
resource_size(&priv->buffer), dev_name(&pdev->dev))) {
|
||||
ret = -ENODEV;
|
||||
goto err_free_irq;
|
||||
}
|
||||
priv->base = devm_ioremap(&pdev->dev, priv->buffer.start,
|
||||
resource_size(&priv->buffer));
|
||||
if (!priv->base)
|
||||
goto err_release_region;
|
||||
|
||||
/* Read the permissions of the shared memory region. */
|
||||
ret = of_property_read_u32(node, "okl,rwx", &permissions);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to read shared memory permissions\n");
|
||||
goto err_unmap_dev;
|
||||
}
|
||||
if (permissions & ~S_IRWXO) {
|
||||
ret = -EINVAL;
|
||||
goto err_unmap_dev;
|
||||
}
|
||||
priv->permissions = ((permissions & S_IROTH) ? FMODE_READ : 0) |
|
||||
((permissions & S_IWOTH) ? FMODE_WRITE : 0) |
|
||||
((permissions & S_IXOTH) ? FMODE_EXEC : 0);
|
||||
if (WARN_ON(priv->permissions == 0)) {
|
||||
ret = -EINVAL;
|
||||
goto err_unmap_dev;
|
||||
}
|
||||
|
||||
/* Retrieve the label of this device. This will be the "name" attribute of
|
||||
* the corresponding "link" tag in the system's XML specification.
|
||||
*/
|
||||
ret = of_property_read_string(node, "label", &name);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to read label\n");
|
||||
goto err_unmap_dev;
|
||||
}
|
||||
|
||||
cdev_init(&priv->cdev, &link_shbuf_ops);
|
||||
ret = cdev_add(&priv->cdev, link_shbuf_dev, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to add char dev region\n");
|
||||
goto err_unmap_dev;
|
||||
}
|
||||
|
||||
ret = link_shbuf_allocate_device(&priv->devt);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to allocate new device number\n");
|
||||
goto err_unmap_dev;
|
||||
}
|
||||
|
||||
/* We're now ready to create the device itself. */
|
||||
BUG_ON(name == NULL);
|
||||
priv->dev = device_create(link_shbuf_class, &pdev->dev, priv->devt,
|
||||
priv, "%s%s", DEV_PREFIX, name);
|
||||
if (IS_ERR(priv->dev)) {
|
||||
dev_err(&pdev->dev, "failed to create device\n");
|
||||
ret = PTR_ERR(priv->dev);
|
||||
goto err_del_dev;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&pdev->dev, priv);
|
||||
|
||||
return 0;
|
||||
|
||||
err_del_dev:
|
||||
cdev_del(&priv->cdev);
|
||||
err_unmap_dev:
|
||||
devm_iounmap(&pdev->dev, priv->base);
|
||||
err_release_region:
|
||||
devm_release_mem_region(&pdev->dev, priv->buffer.start,
|
||||
resource_size(&priv->buffer));
|
||||
err_free_irq:
|
||||
if (priv->virq != NO_INCOMING_IRQ)
|
||||
devm_free_irq(&pdev->dev, priv->virq, priv);
|
||||
err_free_dev:
|
||||
devm_kfree(&pdev->dev, priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int link_shbuf_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct link_shbuf_data *priv;
|
||||
|
||||
priv = dev_get_drvdata(&pdev->dev);
|
||||
WARN_ON(!link_shbuf_data_invariant(priv));
|
||||
|
||||
device_destroy(link_shbuf_class, priv->devt);
|
||||
|
||||
cdev_del(&priv->cdev);
|
||||
|
||||
/*
|
||||
* None of the following is strictly required, as these are all managed
|
||||
* resources, but we clean it up anyway for clarity.
|
||||
*/
|
||||
|
||||
devm_iounmap(&pdev->dev, priv->base);
|
||||
|
||||
devm_release_mem_region(&pdev->dev, priv->buffer.start,
|
||||
resource_size(&priv->buffer));
|
||||
|
||||
if (priv->virq != NO_INCOMING_IRQ)
|
||||
devm_free_irq(&pdev->dev, priv->virq, priv);
|
||||
|
||||
devm_kfree(&pdev->dev, priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver of_plat_link_shbuf_driver = {
|
||||
.driver = {
|
||||
.name = "okl4-shbuf",
|
||||
.of_match_table = okl4_link_shbuf_match,
|
||||
},
|
||||
.probe = link_shbuf_probe,
|
||||
.remove = link_shbuf_remove,
|
||||
};
|
||||
|
||||
/* Maximum number of minor device numbers */
|
||||
enum {
|
||||
MAX_MINOR = 1 << MINORBITS,
|
||||
};
|
||||
|
||||
static int __init okl4_link_shbuf_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
link_shbuf_class = class_create(THIS_MODULE, DEVICE_NAME);
|
||||
if (IS_ERR(link_shbuf_class)) {
|
||||
pr_err("failed to create class\n");
|
||||
ret = PTR_ERR(link_shbuf_class);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = alloc_chrdev_region(&link_shbuf_dev, 0, MAX_MINOR, DEVICE_NAME);
|
||||
if (ret < 0) {
|
||||
pr_err("failed to allocate char dev region\n");
|
||||
goto err_destroy_class;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&of_plat_link_shbuf_driver);
|
||||
if (ret < 0) {
|
||||
pr_err("failed to register driver\n");
|
||||
goto err_unregister_dev_region;
|
||||
}
|
||||
|
||||
spin_lock_init(&device_number_allocate);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_dev_region:
|
||||
unregister_chrdev_region(link_shbuf_dev, MAX_MINOR);
|
||||
err_destroy_class:
|
||||
class_destroy(link_shbuf_class);
|
||||
return ret;
|
||||
}
|
||||
module_init(okl4_link_shbuf_init);
|
||||
|
||||
static void __exit okl4_link_shbuf_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&of_plat_link_shbuf_driver);
|
||||
unregister_chrdev_region(link_shbuf_dev, MAX_MINOR);
|
||||
class_destroy(link_shbuf_class);
|
||||
}
|
||||
module_exit(okl4_link_shbuf_exit);
|
||||
|
||||
MODULE_DESCRIPTION("OKL4 shared buffer link driver");
|
||||
MODULE_AUTHOR("Cog Systems Pty Ltd");
|
|
@ -15,3 +15,4 @@ endif
|
|||
ifneq ($(VSERVICES_SUPPORT), "")
|
||||
include include/linux/Kbuild.vservices
|
||||
endif
|
||||
header-y += okl4-link-shbuf.h
|
||||
|
|
40
include/uapi/linux/okl4-link-shbuf.h
Normal file
40
include/uapi/linux/okl4-link-shbuf.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* User-visible interface to driver for inter-cell links using the
|
||||
* shared-buffer transport.
|
||||
*
|
||||
* Copyright (c) 2016 Cog Systems Pty Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
#ifndef _LINUX_OKL4_LINK_SHBUF_H
|
||||
#define _LINUX_OKL4_LINK_SHBUF_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/*
|
||||
* Ioctl that indicates a request to raise the outgoing vIRQ. This value is
|
||||
* chosen to avoid conflict with the numbers documented in Linux 4.1's
|
||||
* ioctl-numbers.txt. The argument is a payload to transmit to the receiver.
|
||||
* Note that consecutive transmissions without an interleaved clear of the
|
||||
* interrupt results in the payloads being ORed together.
|
||||
*/
|
||||
#define OKL4_LINK_SHBUF_IOCTL_IRQ_TX _IOW(0x8d, 1, __u64)
|
||||
|
||||
/*
|
||||
* Ioctl that indicates a request to clear any pending incoming vIRQ. The value
|
||||
* returned through the argument to the ioctl is the payload, which is also
|
||||
* cleared.
|
||||
*
|
||||
* The caller cannot distinguish between the cases of no pending interrupt and
|
||||
* a pending interrupt with payload 0. It is expected that the caller is
|
||||
* communicating with a cooperative sender and has polled their file descriptor
|
||||
* to determine there is a pending interrupt before using this ioctl.
|
||||
*/
|
||||
#define OKL4_LINK_SHBUF_IOCTL_IRQ_CLR _IOR(0x8d, 2, __u64)
|
||||
|
||||
#endif /* _LINUX_OKL4_LINK_SHBUF_H */
|
Loading…
Reference in a new issue