aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/visorbus/visorchannel.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/visorbus/visorchannel.c')
-rw-r--r--drivers/visorbus/visorchannel.c434
1 files changed, 434 insertions, 0 deletions
diff --git a/drivers/visorbus/visorchannel.c b/drivers/visorbus/visorchannel.c
new file mode 100644
index 000000000000..bd890e0f456b
--- /dev/null
+++ b/drivers/visorbus/visorchannel.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2010 - 2015 UNISYS CORPORATION
+ * All rights reserved.
+ */
+
+/*
+ * This provides s-Par channel communication primitives, which are
+ * independent of the mechanism used to access the channel data.
+ */
+
+#include <linux/uuid.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/visorbus.h>
+
+#include "visorbus_private.h"
+#include "controlvmchannel.h"
+
+#define VISOR_DRV_NAME "visorchannel"
+
+#define VISOR_CONSOLEVIDEO_CHANNEL_GUID \
+ GUID_INIT(0x3cd6e705, 0xd6a2, 0x4aa5, \
+ 0xad, 0x5c, 0x7b, 0x8, 0x88, 0x9d, 0xff, 0xe2)
+
+static const guid_t visor_video_guid = VISOR_CONSOLEVIDEO_CHANNEL_GUID;
+
+struct visorchannel {
+ u64 physaddr;
+ ulong nbytes;
+ void *mapped;
+ bool requested;
+ struct channel_header chan_hdr;
+ guid_t guid;
+ /*
+ * channel creator knows if more than one thread will be inserting or
+ * removing
+ */
+ bool needs_lock;
+ /* protect head writes in chan_hdr */
+ spinlock_t insert_lock;
+ /* protect tail writes in chan_hdr */
+ spinlock_t remove_lock;
+ guid_t type;
+ guid_t inst;
+};
+
+void visorchannel_destroy(struct visorchannel *channel)
+{
+ if (!channel)
+ return;
+
+ if (channel->mapped) {
+ memunmap(channel->mapped);
+ if (channel->requested)
+ release_mem_region(channel->physaddr, channel->nbytes);
+ }
+ kfree(channel);
+}
+
+u64 visorchannel_get_physaddr(struct visorchannel *channel)
+{
+ return channel->physaddr;
+}
+
+ulong visorchannel_get_nbytes(struct visorchannel *channel)
+{
+ return channel->nbytes;
+}
+
+char *visorchannel_guid_id(const guid_t *guid, char *s)
+{
+ sprintf(s, "%pUL", guid);
+ return s;
+}
+
+char *visorchannel_id(struct visorchannel *channel, char *s)
+{
+ return visorchannel_guid_id(&channel->guid, s);
+}
+
+char *visorchannel_zoneid(struct visorchannel *channel, char *s)
+{
+ return visorchannel_guid_id(&channel->chan_hdr.zone_guid, s);
+}
+
+u64 visorchannel_get_clientpartition(struct visorchannel *channel)
+{
+ return channel->chan_hdr.partition_handle;
+}
+
+int visorchannel_set_clientpartition(struct visorchannel *channel,
+ u64 partition_handle)
+{
+ channel->chan_hdr.partition_handle = partition_handle;
+ return 0;
+}
+
+/**
+ * visorchannel_get_guid() - queries the GUID of the designated channel
+ * @channel: the channel to query
+ *
+ * Return: the GUID of the provided channel
+ */
+const guid_t *visorchannel_get_guid(struct visorchannel *channel)
+{
+ return &channel->guid;
+}
+EXPORT_SYMBOL_GPL(visorchannel_get_guid);
+
+int visorchannel_read(struct visorchannel *channel, ulong offset, void *dest,
+ ulong nbytes)
+{
+ if (offset + nbytes > channel->nbytes)
+ return -EIO;
+
+ memcpy(dest, channel->mapped + offset, nbytes);
+ return 0;
+}
+
+int visorchannel_write(struct visorchannel *channel, ulong offset, void *dest,
+ ulong nbytes)
+{
+ size_t chdr_size = sizeof(struct channel_header);
+ size_t copy_size;
+
+ if (offset + nbytes > channel->nbytes)
+ return -EIO;
+
+ if (offset < chdr_size) {
+ copy_size = min(chdr_size - offset, nbytes);
+ memcpy(((char *)(&channel->chan_hdr)) + offset,
+ dest, copy_size);
+ }
+ memcpy(channel->mapped + offset, dest, nbytes);
+ return 0;
+}
+
+void *visorchannel_get_header(struct visorchannel *channel)
+{
+ return &channel->chan_hdr;
+}
+
+/*
+ * Return offset of a specific SIGNAL_QUEUE_HEADER from the beginning of a
+ * channel header
+ */
+static int sig_queue_offset(struct channel_header *chan_hdr, int q)
+{
+ return ((chan_hdr)->ch_space_offset +
+ ((q) * sizeof(struct signal_queue_header)));
+}
+
+/*
+ * Return offset of a specific queue entry (data) from the beginning of a
+ * channel header
+ */
+static int sig_data_offset(struct channel_header *chan_hdr, int q,
+ struct signal_queue_header *sig_hdr, int slot)
+{
+ return (sig_queue_offset(chan_hdr, q) + sig_hdr->sig_base_offset +
+ (slot * sig_hdr->signal_size));
+}
+
+/*
+ * Write the contents of a specific field within a SIGNAL_QUEUE_HEADER back into
+ * host memory
+ */
+#define SIG_WRITE_FIELD(channel, queue, sig_hdr, FIELD) \
+ visorchannel_write(channel, \
+ sig_queue_offset(&channel->chan_hdr, queue) + \
+ offsetof(struct signal_queue_header, FIELD), \
+ &((sig_hdr)->FIELD), \
+ sizeof((sig_hdr)->FIELD))
+
+static int sig_read_header(struct visorchannel *channel, u32 queue,
+ struct signal_queue_header *sig_hdr)
+{
+ if (channel->chan_hdr.ch_space_offset < sizeof(struct channel_header))
+ return -EINVAL;
+
+ /* Read the appropriate SIGNAL_QUEUE_HEADER into local memory. */
+ return visorchannel_read(channel,
+ sig_queue_offset(&channel->chan_hdr, queue),
+ sig_hdr, sizeof(struct signal_queue_header));
+}
+
+static int sig_read_data(struct visorchannel *channel, u32 queue,
+ struct signal_queue_header *sig_hdr, u32 slot,
+ void *data)
+{
+ int signal_data_offset = sig_data_offset(&channel->chan_hdr, queue,
+ sig_hdr, slot);
+
+ return visorchannel_read(channel, signal_data_offset,
+ data, sig_hdr->signal_size);
+}
+
+static int sig_write_data(struct visorchannel *channel, u32 queue,
+ struct signal_queue_header *sig_hdr, u32 slot,
+ void *data)
+{
+ int signal_data_offset = sig_data_offset(&channel->chan_hdr, queue,
+ sig_hdr, slot);
+
+ return visorchannel_write(channel, signal_data_offset,
+ data, sig_hdr->signal_size);
+}
+
+static int signalremove_inner(struct visorchannel *channel, u32 queue,
+ void *msg)
+{
+ struct signal_queue_header sig_hdr;
+ int error;
+
+ error = sig_read_header(channel, queue, &sig_hdr);
+ if (error)
+ return error;
+ /* No signals to remove; have caller try again. */
+ if (sig_hdr.head == sig_hdr.tail)
+ return -EAGAIN;
+ sig_hdr.tail = (sig_hdr.tail + 1) % sig_hdr.max_slots;
+ error = sig_read_data(channel, queue, &sig_hdr, sig_hdr.tail, msg);
+ if (error)
+ return error;
+ sig_hdr.num_received++;
+ /*
+ * For each data field in SIGNAL_QUEUE_HEADER that was modified, update
+ * host memory. Required for channel sync.
+ */
+ mb();
+ error = SIG_WRITE_FIELD(channel, queue, &sig_hdr, tail);
+ if (error)
+ return error;
+ error = SIG_WRITE_FIELD(channel, queue, &sig_hdr, num_received);
+ if (error)
+ return error;
+ return 0;
+}
+
+/**
+ * visorchannel_signalremove() - removes a message from the designated
+ * channel/queue
+ * @channel: the channel the message will be removed from
+ * @queue: the queue the message will be removed from
+ * @msg: the message to remove
+ *
+ * Return: integer error code indicating the status of the removal
+ */
+int visorchannel_signalremove(struct visorchannel *channel, u32 queue,
+ void *msg)
+{
+ int rc;
+ unsigned long flags;
+
+ if (channel->needs_lock) {
+ spin_lock_irqsave(&channel->remove_lock, flags);
+ rc = signalremove_inner(channel, queue, msg);
+ spin_unlock_irqrestore(&channel->remove_lock, flags);
+ } else {
+ rc = signalremove_inner(channel, queue, msg);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(visorchannel_signalremove);
+
+static bool queue_empty(struct visorchannel *channel, u32 queue)
+{
+ struct signal_queue_header sig_hdr;
+
+ if (sig_read_header(channel, queue, &sig_hdr))
+ return true;
+ return (sig_hdr.head == sig_hdr.tail);
+}
+
+/**
+ * visorchannel_signalempty() - checks if the designated channel/queue contains
+ * any messages
+ * @channel: the channel to query
+ * @queue: the queue in the channel to query
+ *
+ * Return: boolean indicating whether any messages in the designated
+ * channel/queue are present
+ */
+bool visorchannel_signalempty(struct visorchannel *channel, u32 queue)
+{
+ bool rc;
+ unsigned long flags;
+
+ if (!channel->needs_lock)
+ return queue_empty(channel, queue);
+ spin_lock_irqsave(&channel->remove_lock, flags);
+ rc = queue_empty(channel, queue);
+ spin_unlock_irqrestore(&channel->remove_lock, flags);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(visorchannel_signalempty);
+
+static int signalinsert_inner(struct visorchannel *channel, u32 queue,
+ void *msg)
+{
+ struct signal_queue_header sig_hdr;
+ int err;
+
+ err = sig_read_header(channel, queue, &sig_hdr);
+ if (err)
+ return err;
+ sig_hdr.head = (sig_hdr.head + 1) % sig_hdr.max_slots;
+ if (sig_hdr.head == sig_hdr.tail) {
+ sig_hdr.num_overflows++;
+ err = SIG_WRITE_FIELD(channel, queue, &sig_hdr, num_overflows);
+ if (err)
+ return err;
+ return -EIO;
+ }
+ err = sig_write_data(channel, queue, &sig_hdr, sig_hdr.head, msg);
+ if (err)
+ return err;
+ sig_hdr.num_sent++;
+ /*
+ * For each data field in SIGNAL_QUEUE_HEADER that was modified, update
+ * host memory. Required for channel sync.
+ */
+ mb();
+ err = SIG_WRITE_FIELD(channel, queue, &sig_hdr, head);
+ if (err)
+ return err;
+ err = SIG_WRITE_FIELD(channel, queue, &sig_hdr, num_sent);
+ if (err)
+ return err;
+ return 0;
+}
+
+/*
+ * visorchannel_create() - creates the struct visorchannel abstraction for a
+ * data area in memory, but does NOT modify this data
+ * area
+ * @physaddr: physical address of start of channel
+ * @gfp: gfp_t to use when allocating memory for the data struct
+ * @guid: GUID that identifies channel type;
+ * @needs_lock: must specify true if you have multiple threads of execution
+ * that will be calling visorchannel methods of this
+ * visorchannel at the same time
+ *
+ * Return: pointer to visorchannel that was created if successful,
+ * otherwise NULL
+ */
+struct visorchannel *visorchannel_create(u64 physaddr, gfp_t gfp,
+ const guid_t *guid, bool needs_lock)
+{
+ struct visorchannel *channel;
+ int err;
+ size_t size = sizeof(struct channel_header);
+
+ if (physaddr == 0)
+ return NULL;
+
+ channel = kzalloc(sizeof(*channel), gfp);
+ if (!channel)
+ return NULL;
+ channel->needs_lock = needs_lock;
+ spin_lock_init(&channel->insert_lock);
+ spin_lock_init(&channel->remove_lock);
+ /*
+ * Video driver constains the efi framebuffer so it will get a conflict
+ * resource when requesting its full mem region. Since we are only
+ * using the efi framebuffer for video we can ignore this. Remember that
+ * we haven't requested it so we don't try to release later on.
+ */
+ channel->requested = request_mem_region(physaddr, size, VISOR_DRV_NAME);
+ if (!channel->requested && !guid_equal(guid, &visor_video_guid))
+ /* we only care about errors if this is not the video channel */
+ goto err_destroy_channel;
+ channel->mapped = memremap(physaddr, size, MEMREMAP_WB);
+ if (!channel->mapped) {
+ release_mem_region(physaddr, size);
+ goto err_destroy_channel;
+ }
+ channel->physaddr = physaddr;
+ channel->nbytes = size;
+ err = visorchannel_read(channel, 0, &channel->chan_hdr, size);
+ if (err)
+ goto err_destroy_channel;
+ size = (ulong)channel->chan_hdr.size;
+ memunmap(channel->mapped);
+ if (channel->requested)
+ release_mem_region(channel->physaddr, channel->nbytes);
+ channel->mapped = NULL;
+ channel->requested = request_mem_region(channel->physaddr, size,
+ VISOR_DRV_NAME);
+ if (!channel->requested && !guid_equal(guid, &visor_video_guid))
+ /* we only care about errors if this is not the video channel */
+ goto err_destroy_channel;
+ channel->mapped = memremap(channel->physaddr, size, MEMREMAP_WB);
+ if (!channel->mapped) {
+ release_mem_region(channel->physaddr, size);
+ goto err_destroy_channel;
+ }
+ channel->nbytes = size;
+ guid_copy(&channel->guid, guid);
+ return channel;
+
+err_destroy_channel:
+ visorchannel_destroy(channel);
+ return NULL;
+}
+
+/**
+ * visorchannel_signalinsert() - inserts a message into the designated
+ * channel/queue
+ * @channel: the channel the message will be added to
+ * @queue: the queue the message will be added to
+ * @msg: the message to insert
+ *
+ * Return: integer error code indicating the status of the insertion
+ */
+int visorchannel_signalinsert(struct visorchannel *channel, u32 queue,
+ void *msg)
+{
+ int rc;
+ unsigned long flags;
+
+ if (channel->needs_lock) {
+ spin_lock_irqsave(&channel->insert_lock, flags);
+ rc = signalinsert_inner(channel, queue, msg);
+ spin_unlock_irqrestore(&channel->insert_lock, flags);
+ } else {
+ rc = signalinsert_inner(channel, queue, msg);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(visorchannel_signalinsert);