/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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", .owner = THIS_MODULE, .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", .owner = THIS_MODULE, .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 "); MODULE_DESCRIPTION("Test driver for OKL4 Microvisor inter-cell communication");