These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / misc / mic / scif / scif_rb.c
diff --git a/kernel/drivers/misc/mic/scif/scif_rb.c b/kernel/drivers/misc/mic/scif/scif_rb.c
new file mode 100644 (file)
index 0000000..637cc46
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Intel MIC Platform Software Stack (MPSS)
+ *
+ * Copyright(c) 2014 Intel Corporation.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * Intel SCIF driver.
+ *
+ */
+#include <linux/circ_buf.h>
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/errno.h>
+
+#include "scif_rb.h"
+
+#define scif_rb_ring_cnt(head, tail, size) CIRC_CNT(head, tail, size)
+#define scif_rb_ring_space(head, tail, size) CIRC_SPACE(head, tail, size)
+
+/**
+ * scif_rb_init - Initializes the ring buffer
+ * @rb: ring buffer
+ * @read_ptr: A pointer to the read offset
+ * @write_ptr: A pointer to the write offset
+ * @rb_base: A pointer to the base of the ring buffer
+ * @size: The size of the ring buffer in powers of two
+ */
+void scif_rb_init(struct scif_rb *rb, u32 *read_ptr, u32 *write_ptr,
+                 void *rb_base, u8 size)
+{
+       rb->rb_base = rb_base;
+       rb->size = (1 << size);
+       rb->read_ptr = read_ptr;
+       rb->write_ptr = write_ptr;
+       rb->current_read_offset = *read_ptr;
+       rb->current_write_offset = *write_ptr;
+}
+
+/* Copies a message to the ring buffer -- handles the wrap around case */
+static void memcpy_torb(struct scif_rb *rb, void *header,
+                       void *msg, u32 size)
+{
+       u32 size1, size2;
+
+       if (header + size >= rb->rb_base + rb->size) {
+               /* Need to call two copies if it wraps around */
+               size1 = (u32)(rb->rb_base + rb->size - header);
+               size2 = size - size1;
+               memcpy_toio((void __iomem __force *)header, msg, size1);
+               memcpy_toio((void __iomem __force *)rb->rb_base,
+                           msg + size1, size2);
+       } else {
+               memcpy_toio((void __iomem __force *)header, msg, size);
+       }
+}
+
+/* Copies a message from the ring buffer -- handles the wrap around case */
+static void memcpy_fromrb(struct scif_rb *rb, void *header,
+                         void *msg, u32 size)
+{
+       u32 size1, size2;
+
+       if (header + size >= rb->rb_base + rb->size) {
+               /* Need to call two copies if it wraps around */
+               size1 = (u32)(rb->rb_base + rb->size - header);
+               size2 = size - size1;
+               memcpy_fromio(msg, (void __iomem __force *)header, size1);
+               memcpy_fromio(msg + size1,
+                             (void __iomem __force *)rb->rb_base, size2);
+       } else {
+               memcpy_fromio(msg, (void __iomem __force *)header, size);
+       }
+}
+
+/**
+ * scif_rb_space - Query space available for writing to the RB
+ * @rb: ring buffer
+ *
+ * Return: size available for writing to RB in bytes.
+ */
+u32 scif_rb_space(struct scif_rb *rb)
+{
+       rb->current_read_offset = *rb->read_ptr;
+       /*
+        * Update from the HW read pointer only once the peer has exposed the
+        * new empty slot. This barrier is paired with the memory barrier
+        * scif_rb_update_read_ptr()
+        */
+       mb();
+       return scif_rb_ring_space(rb->current_write_offset,
+                                 rb->current_read_offset, rb->size);
+}
+
+/**
+ * scif_rb_write - Write a message to the RB
+ * @rb: ring buffer
+ * @msg: buffer to send the message.  Must be at least size bytes long
+ * @size: the size (in bytes) to be copied to the RB
+ *
+ * This API does not block if there isn't enough space in the RB.
+ * Returns: 0 on success or -ENOMEM on failure
+ */
+int scif_rb_write(struct scif_rb *rb, void *msg, u32 size)
+{
+       void *header;
+
+       if (scif_rb_space(rb) < size)
+               return -ENOMEM;
+       header = rb->rb_base + rb->current_write_offset;
+       memcpy_torb(rb, header, msg, size);
+       /*
+        * Wait until scif_rb_commit(). Update the local ring
+        * buffer data, not the shared data until commit.
+        */
+       rb->current_write_offset =
+               (rb->current_write_offset + size) & (rb->size - 1);
+       return 0;
+}
+
+/**
+ * scif_rb_commit - To submit the message to let the peer fetch it
+ * @rb: ring buffer
+ */
+void scif_rb_commit(struct scif_rb *rb)
+{
+       /*
+        * We must ensure ordering between the all the data committed
+        * previously before we expose the new message to the peer by
+        * updating the write_ptr. This write barrier is paired with
+        * the read barrier in scif_rb_count(..)
+        */
+       wmb();
+       ACCESS_ONCE(*rb->write_ptr) = rb->current_write_offset;
+#ifdef CONFIG_INTEL_MIC_CARD
+       /*
+        * X100 Si bug: For the case where a Core is performing an EXT_WR
+        * followed by a Doorbell Write, the Core must perform two EXT_WR to the
+        * same address with the same data before it does the Doorbell Write.
+        * This way, if ordering is violated for the Interrupt Message, it will
+        * fall just behind the first Posted associated with the first EXT_WR.
+        */
+       ACCESS_ONCE(*rb->write_ptr) = rb->current_write_offset;
+#endif
+}
+
+/**
+ * scif_rb_get - To get next message from the ring buffer
+ * @rb: ring buffer
+ * @size: Number of bytes to be read
+ *
+ * Return: NULL if no bytes to be read from the ring buffer, otherwise the
+ *     pointer to the next byte
+ */
+static void *scif_rb_get(struct scif_rb *rb, u32 size)
+{
+       void *header = NULL;
+
+       if (scif_rb_count(rb, size) >= size)
+               header = rb->rb_base + rb->current_read_offset;
+       return header;
+}
+
+/*
+ * scif_rb_get_next - Read from ring buffer.
+ * @rb: ring buffer
+ * @msg: buffer to hold the message.  Must be at least size bytes long
+ * @size: Number of bytes to be read
+ *
+ * Return: number of bytes read if available bytes are >= size, otherwise
+ * returns zero.
+ */
+u32 scif_rb_get_next(struct scif_rb *rb, void *msg, u32 size)
+{
+       void *header = NULL;
+       int read_size = 0;
+
+       header = scif_rb_get(rb, size);
+       if (header) {
+               u32 next_cmd_offset =
+                       (rb->current_read_offset + size) & (rb->size - 1);
+
+               read_size = size;
+               rb->current_read_offset = next_cmd_offset;
+               memcpy_fromrb(rb, header, msg, size);
+       }
+       return read_size;
+}
+
+/**
+ * scif_rb_update_read_ptr
+ * @rb: ring buffer
+ */
+void scif_rb_update_read_ptr(struct scif_rb *rb)
+{
+       u32 new_offset;
+
+       new_offset = rb->current_read_offset;
+       /*
+        * We must ensure ordering between the all the data committed or read
+        * previously before we expose the empty slot to the peer by updating
+        * the read_ptr. This barrier is paired with the memory barrier in
+        * scif_rb_space(..)
+        */
+       mb();
+       ACCESS_ONCE(*rb->read_ptr) = new_offset;
+#ifdef CONFIG_INTEL_MIC_CARD
+       /*
+        * X100 Si Bug: For the case where a Core is performing an EXT_WR
+        * followed by a Doorbell Write, the Core must perform two EXT_WR to the
+        * same address with the same data before it does the Doorbell Write.
+        * This way, if ordering is violated for the Interrupt Message, it will
+        * fall just behind the first Posted associated with the first EXT_WR.
+        */
+       ACCESS_ONCE(*rb->read_ptr) = new_offset;
+#endif
+}
+
+/**
+ * scif_rb_count
+ * @rb: ring buffer
+ * @size: Number of bytes expected to be read
+ *
+ * Return: number of bytes that can be read from the RB
+ */
+u32 scif_rb_count(struct scif_rb *rb, u32 size)
+{
+       if (scif_rb_ring_cnt(rb->current_write_offset,
+                            rb->current_read_offset,
+                            rb->size) < size) {
+               rb->current_write_offset = *rb->write_ptr;
+               /*
+                * Update from the HW write pointer if empty only once the peer
+                * has exposed the new message. This read barrier is paired
+                * with the write barrier in scif_rb_commit(..)
+                */
+               smp_rmb();
+       }
+       return scif_rb_ring_cnt(rb->current_write_offset,
+                               rb->current_read_offset,
+                               rb->size);
+}