diff options
Diffstat (limited to 'drivers/visorbus/visorchannel.c')
-rw-r--r-- | drivers/visorbus/visorchannel.c | 434 |
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); |