| /* |
| * Test driver for OKL4 Microvisor inter-cell communication |
| * |
| * Copyright (c) 2012-2014 General Dynamics |
| * Copyright (c) 2014 Open Kernel Labs, Inc. |
| * |
| * 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/init.h> |
| #include <linux/module.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/wait.h> |
| #include <linux/device.h> |
| #include <linux/cdev.h> |
| #include <linux/fs.h> |
| #include <linux/interrupt.h> |
| #include <linux/sysfs.h> |
| #include <linux/ioport.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_platform.h> |
| #include <linux/io.h> |
| #include <linux/uaccess.h> |
| #include <linux/version.h> |
| #include <microvisor/microvisor.h> |
| |
| /* Linux version compatibility */ |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3,11,0) |
| #define DEVICE_ATTR_RO(_name) \ |
| struct device_attribute dev_attr_##_name = __ATTR_RO(_name) |
| #define OLD_ATTRS |
| #endif |
| |
| struct ringbuf_data { |
| struct platform_device *pdev; |
| unsigned int irq; |
| u32 virqline; |
| struct mutex lock; |
| |
| struct resource buffer_res; |
| void *buffer_map; |
| |
| u32 *head; |
| u32 *tail; |
| char *base; |
| size_t size; |
| |
| size_t count; |
| |
| union { |
| wait_queue_head_t tx_wq; |
| struct work_struct rx_work; |
| }; |
| |
| struct cdev cdev; |
| }; |
| |
| dev_t ringbuf_tx_dev, ringbuf_rx_dev; |
| |
| /* VIRQ payload flags */ |
| #define VIRQ_FLAG_RESET 1UL /* Head has been set to 0 */ |
| #define VIRQ_FLAG_DATA 2UL /* Head or tail has been advanced */ |
| |
| /* |
| * Common probe / remove code |
| */ |
| static int ringbuf_probe(struct platform_device *pdev, const char *shbuf_name, |
| const char *virqline_name, struct ringbuf_data **ringbuf_data) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct device_node *virqline_node = NULL; |
| struct ringbuf_data *priv; |
| int r = 0; |
| |
| if (!node) |
| return -ENODEV; |
| if (of_property_match_string(node, "label", shbuf_name) < 0) |
| return -ENODEV; |
| |
| priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| priv->pdev = pdev; |
| mutex_init(&priv->lock); |
| |
| r = of_address_to_resource(node, 0, &priv->buffer_res); |
| if (r < 0) |
| return r; |
| |
| /* |
| * For a useful ring buffer, we need enough space for the head |
| * and tail pointers, plus at least two bytes of data. |
| */ |
| if (resource_size(&priv->buffer_res) < (2 * sizeof(u32) + 2)) |
| return -ENODEV; |
| |
| /* |
| * Locate the virqline. Since we don't have a microvisor bus yet, |
| * we have to do this by searching the DT manually. |
| */ |
| /* FIXME: Jira ticket SDK-5565 - philipd. */ |
| for_each_compatible_node(virqline_node, NULL, |
| "okl,microvisor-interrupt-line") |
| if (of_property_match_string(virqline_node, "label", |
| virqline_name) >= 0) |
| break; |
| if (!virqline_node) |
| return -ENODEV; |
| if (of_property_read_u32(virqline_node, "reg", &priv->virqline) < 0) |
| return -ENODEV; |
| |
| /* Map the shared buffer's memory. */ |
| if (!devm_request_mem_region(&pdev->dev, priv->buffer_res.start, |
| resource_size(&priv->buffer_res), |
| dev_name(&pdev->dev))) |
| return -EBUSY; |
| |
| dev_dbg(&pdev->dev, "Ring buffer @ 0x%llx, size 0x%llx\n", |
| priv->buffer_res.start, |
| resource_size(&priv->buffer_res)); |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0)) || \ |
| defined(CONFIG_NO_DEPRECATED_IOREMAP) |
| priv->buffer_map = ioremap_cache(priv->buffer_res.start, |
| resource_size(&priv->buffer_res)); |
| #else |
| priv->buffer_map = ioremap_cached(priv->buffer_res.start, |
| resource_size(&priv->buffer_res)); |
| #endif |
| if (priv->buffer_map == NULL) |
| return -EIO; |
| |
| priv->head = (u32 *)priv->buffer_map; |
| priv->tail = (u32 *)priv->buffer_map + 1; |
| priv->base = (char *)priv->buffer_map + (2 * sizeof(u32)); |
| priv->size = resource_size(&priv->buffer_res) - (2 * sizeof(u32)); |
| priv->count = 0; |
| |
| dev_set_drvdata(&pdev->dev, priv); |
| |
| *ringbuf_data = priv; |
| |
| return 0; |
| } |
| |
| static int ringbuf_remove(struct platform_device *pdev) |
| { |
| struct ringbuf_data *priv = dev_get_drvdata(&pdev->dev); |
| |
| iounmap(priv->buffer_map); |
| |
| return 0; |
| } |
| |
| /* |
| * Sending side |
| * |
| * This registers a simple character device to accept writes from userland. |
| * There is also a sysfs interface to ask the kernel to send arbitrary data. |
| */ |
| static size_t ringbuf_write_room(struct ringbuf_data *priv) |
| { |
| return (priv->size + *priv->tail - *priv->head - 1) % priv->size; |
| } |
| |
| static int ringbuf_tx_write(struct file *file, const char __user *buffer, |
| size_t count, loff_t *ppos) |
| { |
| struct inode *inode = file_inode(file); |
| struct cdev *cdev = inode->i_cdev; |
| struct ringbuf_data *priv; |
| int r; |
| u32 head, new_head; |
| size_t to_send; |
| |
| priv = container_of(cdev, struct ringbuf_data, cdev); |
| |
| /* Truncate the message if it's too big. */ |
| count = min(count, priv->size - 1); |
| |
| /* Wait until there is space to write the data. */ |
| mutex_lock(&priv->lock); |
| r = wait_event_interruptible(priv->tx_wq, |
| ringbuf_write_room(priv) >= count); |
| if (r < 0) { |
| mutex_unlock(&priv->lock); |
| return r; |
| } |
| |
| /* |
| * Note: wait_event_* has an implicit memory barrier, which prevents the |
| * copy below beginning before the wait completes. This matches the |
| * mb() before the cmpxchg() at the end of the read. |
| */ |
| |
| head = *priv->head; |
| to_send = count; |
| new_head = (head + to_send) % priv->size; |
| |
| if (new_head < head) { |
| if (copy_from_user(&priv->base[head], buffer, |
| priv->size - head)) { |
| mutex_unlock(&priv->lock); |
| return -EFAULT; |
| } |
| buffer += priv->size - head; |
| to_send -= priv->size - head; |
| head = 0; |
| } |
| |
| if (to_send > 0) { |
| if (copy_from_user(&priv->base[head], buffer, to_send)) { |
| mutex_unlock(&priv->lock); |
| return -EFAULT; |
| } |
| } |
| |
| /* |
| * Ensure that the other side sees the copy before the head update. |
| * This matches the rmb() at the start of the read. |
| */ |
| wmb(); |
| |
| *priv->head = new_head; |
| mutex_unlock(&priv->lock); |
| |
| _okl4_sys_vinterrupt_raise(priv->virqline, VIRQ_FLAG_DATA); |
| |
| return count; |
| } |
| |
| static int ringbuf_tx_fsync(struct file *file, loff_t start, loff_t end, |
| int datasync) |
| { |
| struct inode *inode = file_inode(file); |
| struct cdev *cdev = inode->i_cdev; |
| struct ringbuf_data *priv; |
| |
| priv = container_of(cdev, struct ringbuf_data, cdev); |
| |
| /* Wait until the ring buffer has emptied. */ |
| return wait_event_interruptible(priv->tx_wq, |
| *priv->head == *priv->tail); |
| } |
| |
| static const struct file_operations ringbuf_tx_fops = { |
| .owner = THIS_MODULE, |
| .write = ringbuf_tx_write, |
| .fsync = ringbuf_tx_fsync, |
| }; |
| |
| static int ringbuf_tx_send_zeros(struct ringbuf_data *priv, size_t count) |
| { |
| int r; |
| u32 head, new_head; |
| size_t to_send; |
| |
| /* Truncate the message if it's too big. */ |
| count = min(count, priv->size - 1); |
| |
| /* Wait until there is space to write the data. */ |
| mutex_lock(&priv->lock); |
| r = wait_event_interruptible(priv->tx_wq, |
| ringbuf_write_room(priv) >= count); |
| if (r < 0) { |
| mutex_unlock(&priv->lock); |
| return r; |
| } |
| |
| /* |
| * Note: wait_event_* has an implicit memory barrier, which prevents the |
| * copy below beginning before the wait completes. This matches the |
| * mb() before the cmpxchg() at the end of the read. |
| */ |
| |
| head = *priv->head; |
| to_send = count; |
| new_head = (head + to_send) % priv->size; |
| |
| if (new_head < head) { |
| memset(&priv->base[head], 0, priv->size - head); |
| to_send -= priv->size - head; |
| head = 0; |
| } |
| |
| if (to_send > 0) |
| memset(&priv->base[head], 0, to_send); |
| |
| /* |
| * Ensure that the other side sees the copy before the head update. |
| * This matches the rmb() at the start of the read. |
| */ |
| wmb(); |
| |
| *priv->head = new_head; |
| mutex_unlock(&priv->lock); |
| |
| _okl4_sys_vinterrupt_raise(priv->virqline, VIRQ_FLAG_DATA); |
| |
| return count; |
| } |
| |
| static irqreturn_t ringbuf_tx_irq_handler(int irq, void *data) |
| { |
| struct ringbuf_data *priv = data; |
| |
| wake_up_interruptible(&priv->tx_wq); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static ssize_t send_zeros_store(struct device *dev, |
| struct device_attribute *attr, const char *buffer, size_t count) |
| { |
| unsigned long val; |
| struct ringbuf_data *priv = dev_get_drvdata(dev); |
| int r; |
| |
| r = kstrtoul(buffer, 0, &val); |
| if (r < 0) |
| return -EINVAL; |
| |
| r = ringbuf_tx_send_zeros(priv, val); |
| if (r < 0) |
| return r; |
| |
| return count; |
| } |
| |
| static ssize_t flush_store(struct device *dev, |
| struct device_attribute *attr, const char *buffer, size_t count) |
| { |
| struct ringbuf_data *priv = dev_get_drvdata(dev); |
| |
| /* Wait until the ring buffer has emptied. */ |
| return wait_event_interruptible(priv->tx_wq, |
| *priv->head == *priv->tail); |
| } |
| |
| #ifndef OLD_ATTRS |
| static DEVICE_ATTR_WO(send_zeros); |
| static DEVICE_ATTR_WO(flush); |
| |
| static struct attribute *ringbuf_tx_test_attrs[] = { |
| &dev_attr_send_zeros.attr, |
| &dev_attr_flush.attr, |
| NULL |
| }; |
| ATTRIBUTE_GROUPS(ringbuf_tx_test); |
| #else |
| static struct device_attribute ringbuf_tx_test_attrs[] = { |
| __ATTR(send_zeros, S_IWUGO, NULL, send_zeros_store), |
| __ATTR(flush, S_IWUGO, NULL, flush_store), |
| __ATTR_NULL |
| }; |
| #endif |
| |
| static struct class *ringbuf_tx_class; |
| |
| static int ringbuf_tx_probe(struct platform_device *pdev) |
| { |
| struct ringbuf_data *priv; |
| int r; |
| okl4_error_t err; |
| struct device *child; |
| |
| r = ringbuf_probe(pdev, "ringbuf", "ringbuf_tx", &priv); |
| if (r < 0) |
| return r; |
| |
| /* |
| * Make the tail temporarily invalid to prevent the receiver getting |
| * confused when we clear head and tail, in case it's still reading data |
| * sent by a previous instance of the driver. |
| */ |
| *priv->tail = ~0; |
| wmb(); |
| |
| /* |
| * Zero the head and tail, and ensure that the writes are visible |
| * remotely (and in that order) before any new data is written. |
| */ |
| *priv->head = 0; |
| wmb(); |
| *priv->tail = 0; |
| wmb(); |
| |
| init_waitqueue_head(&priv->tx_wq); |
| |
| cdev_init(&priv->cdev, &ringbuf_tx_fops); |
| r = cdev_add(&priv->cdev, ringbuf_tx_dev, 1); |
| if (r < 0) |
| goto fail_cdev; |
| |
| #ifndef OLD_ATTRS |
| child = device_create_with_groups(ringbuf_tx_class, &pdev->dev, |
| ringbuf_tx_dev, priv, ringbuf_tx_test_groups, "ringbuf"); |
| #else |
| child = device_create(ringbuf_tx_class, &pdev->dev, ringbuf_tx_dev, |
| priv, "ringbuf"); |
| #endif |
| if (IS_ERR(child)) { |
| r = PTR_ERR(child); |
| goto fail_device_create; |
| } |
| |
| /* Hard-coded IRQ number; also hard-coded in system XML */ |
| /* FIXME: Jira ticket SDK-5566 - philipd. */ |
| priv->irq = 128; |
| r = devm_request_irq(&pdev->dev, priv->irq, ringbuf_tx_irq_handler, 0, |
| dev_name(&pdev->dev), priv); |
| if (r < 0) |
| goto fail_request_irq; |
| |
| /* |
| * Signal to the receiver that the head and tail have been reset. |
| * Note that the rx driver below doesn't actually make use of this. |
| */ |
| err = _okl4_sys_vinterrupt_raise(priv->virqline, VIRQ_FLAG_RESET); |
| if (err != OKL4_OK) { |
| r = -EIO; |
| goto fail_raise; |
| } |
| |
| return 0; |
| |
| fail_request_irq: |
| device_destroy(ringbuf_tx_class, ringbuf_tx_dev); |
| fail_device_create: |
| fail_raise: |
| cdev_del(&priv->cdev); |
| fail_cdev: |
| ringbuf_remove(pdev); |
| |
| return r; |
| } |
| |
| static int ringbuf_tx_remove(struct platform_device *pdev) |
| { |
| struct ringbuf_data *priv = dev_get_drvdata(&pdev->dev); |
| |
| device_destroy(ringbuf_tx_class, ringbuf_tx_dev); |
| cdev_del(&priv->cdev); |
| ringbuf_remove(pdev); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id of_platform_shared_buffer_table[] = { |
| { .compatible = "okl,ringbuffer" }, |
| { /* end */ }, |
| }; |
| MODULE_DEVICE_TABLE(of, of_platform_shared_buffer_table); |
| |
| static struct platform_driver of_platform_ringbuf_tx_driver = { |
| .driver = { |
| .name = "okl4_ringbuf_tx", |
| .of_match_table = of_platform_shared_buffer_table, |
| }, |
| .probe = ringbuf_tx_probe, |
| .remove = ringbuf_tx_remove, |
| }; |
| |
| static irqreturn_t ringbuf_rx_irq_handler(int irq, void *data) |
| { |
| struct ringbuf_data *priv = data; |
| |
| schedule_work(&priv->rx_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* |
| * Read the incoming data, in order to measure the cost of doing so, and then |
| * discard it. |
| */ |
| static void read_and_discard(const char *buf, size_t count) |
| { |
| while (count && ((uintptr_t)buf & (sizeof(u32) - 1))) { |
| const char *data = buf++; |
| (void)ACCESS_ONCE(data); |
| count--; |
| } |
| |
| while (count > sizeof(u32)) { |
| const u32 *data = (const u32 *)buf; |
| (void)ACCESS_ONCE(data); |
| count -= sizeof(u32); |
| buf += sizeof(u32); |
| } |
| |
| while (count) { |
| const char *data = buf++; |
| (void)ACCESS_ONCE(data); |
| count--; |
| } |
| } |
| |
| static void ringbuf_rx_work(struct work_struct *work) |
| { |
| struct ringbuf_data *priv = container_of(work, struct ringbuf_data, |
| rx_work); |
| u32 head, tail, old_tail; |
| |
| head = *priv->head; |
| old_tail = tail = *priv->tail; |
| |
| /* If the tail is invalid, stop here */ |
| if (tail == ~0UL) |
| return; |
| |
| /* Ensure we can see all data written before the head was updated */ |
| rmb(); |
| |
| if (head < tail) { |
| read_and_discard(&priv->base[tail], priv->size - tail); |
| tail = 0; |
| } |
| |
| if (head != tail) { |
| read_and_discard(&priv->base[tail], head - tail); |
| tail = head; |
| } |
| |
| /* The tail update must occur after the data has been read. */ |
| mb(); |
| |
| /* |
| * We must use cmpxchg to update the tail in case it was reset to 0 |
| * by the sending side. |
| */ |
| if (cmpxchg(priv->tail, old_tail, tail) == old_tail) { |
| _okl4_sys_vinterrupt_raise(priv->virqline, VIRQ_FLAG_DATA); |
| priv->count += (priv->size + tail - old_tail) % priv->size; |
| } else { |
| dev_err(&priv->pdev->dev, "Read stale data!\n"); |
| } |
| } |
| |
| static int ringbuf_rx_probe(struct platform_device *pdev) |
| { |
| struct ringbuf_data *priv; |
| int r; |
| |
| r = ringbuf_probe(pdev, "ringbuf_remote", "ringbuf_rx", &priv); |
| if (r < 0) |
| return r; |
| |
| INIT_WORK(&priv->rx_work, ringbuf_rx_work); |
| |
| /* Hard-coded IRQ number; also hard-coded in system XML */ |
| /* FIXME: Jira ticket SDK-5566 - philipd. */ |
| priv->irq = 129; |
| r = devm_request_irq(&pdev->dev, priv->irq, ringbuf_rx_irq_handler, 0, |
| dev_name(&pdev->dev), priv); |
| if (r < 0) { |
| ringbuf_remove(pdev); |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static struct platform_driver of_platform_ringbuf_rx_driver = { |
| .driver = { |
| .name = "okl4_ringbuf_rx", |
| .of_match_table = of_platform_shared_buffer_table, |
| }, |
| .probe = ringbuf_rx_probe, |
| .remove = ringbuf_remove, |
| }; |
| |
| static int __init ringbuf_init(void) |
| { |
| int err = 0; |
| dev_t dev; |
| |
| err = alloc_chrdev_region(&dev, 0, 2, "ringbuf"); |
| if (err) |
| goto fail_chrdev_region; |
| |
| ringbuf_tx_dev = dev; |
| ringbuf_rx_dev = MKDEV(MAJOR(dev), MINOR(dev) + 1); |
| |
| ringbuf_tx_class = class_create(THIS_MODULE, "ringbuf_tx"); |
| if (!ringbuf_tx_class) |
| goto fail_class_create; |
| #ifdef OLD_ATTRS |
| ringbuf_tx_class->dev_attrs = ringbuf_tx_test_attrs; |
| #endif |
| err = platform_driver_register(&of_platform_ringbuf_tx_driver); |
| if (err) |
| goto fail_tx_driver; |
| |
| err = platform_driver_register(&of_platform_ringbuf_rx_driver); |
| if (err) |
| goto fail_rx_driver; |
| |
| return 0; |
| |
| fail_rx_driver: |
| platform_driver_unregister(&of_platform_ringbuf_tx_driver); |
| fail_tx_driver: |
| class_destroy(ringbuf_tx_class); |
| fail_class_create: |
| unregister_chrdev_region(ringbuf_tx_dev, 2); |
| fail_chrdev_region: |
| |
| return err; |
| } |
| module_init(ringbuf_init); |
| |
| static void __exit ringbuf_exit(void) |
| { |
| platform_driver_unregister(&of_platform_ringbuf_rx_driver); |
| platform_driver_unregister(&of_platform_ringbuf_tx_driver); |
| class_destroy(ringbuf_tx_class); |
| unregister_chrdev_region(ringbuf_tx_dev, 2); |
| } |
| module_exit(ringbuf_exit); |
| |
| MODULE_AUTHOR("Philip Derrin <philipd@ok-labs.com>"); |
| MODULE_DESCRIPTION("Test driver for OKL4 Microvisor inter-cell communication"); |