| /* visorchannel_funcs.c |
| * |
| * Copyright (C) 2010 - 2015 UNISYS CORPORATION |
| * All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions 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, GOOD TITLE or |
| * NON INFRINGEMENT. See the GNU General Public License for more |
| * details. |
| */ |
| |
| /* |
| * 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 "visorbus.h" |
| #include "controlvmchannel.h" |
| |
| #define MYDRVNAME "visorchannel" |
| |
| #define SPAR_CONSOLEVIDEO_CHANNEL_PROTOCOL_GUID \ |
| UUID_LE(0x3cd6e705, 0xd6a2, 0x4aa5, \ |
| 0xad, 0x5c, 0x7b, 0x8, 0x88, 0x9d, 0xff, 0xe2) |
| static const uuid_le spar_video_guid = SPAR_CONSOLEVIDEO_CHANNEL_PROTOCOL_GUID; |
| |
| struct visorchannel { |
| u64 physaddr; |
| ulong nbytes; |
| void *mapped; |
| bool requested; |
| struct channel_header chan_hdr; |
| uuid_le guid; |
| bool needs_lock; /* channel creator knows if more than one */ |
| /* thread will be inserting or removing */ |
| spinlock_t insert_lock; /* protect head writes in chan_hdr */ |
| spinlock_t remove_lock; /* protect tail writes in chan_hdr */ |
| |
| struct { |
| struct signal_queue_header req_queue; |
| struct signal_queue_header rsp_queue; |
| struct signal_queue_header event_queue; |
| struct signal_queue_header ack_queue; |
| } safe_uis_queue; |
| uuid_le type; |
| uuid_le 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_uuid_id(uuid_le *guid, char *s) |
| { |
| sprintf(s, "%pUL", guid); |
| return s; |
| } |
| |
| char * |
| visorchannel_id(struct visorchannel *channel, char *s) |
| { |
| return visorchannel_uuid_id(&channel->guid, s); |
| } |
| |
| char * |
| visorchannel_zoneid(struct visorchannel *channel, char *s) |
| { |
| return visorchannel_uuid_id(&channel->chan_hdr.zone_uuid, 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_uuid() - queries the UUID of the designated channel |
| * @channel: the channel to query |
| * |
| * Return: the UUID of the provided channel |
| */ |
| uuid_le |
| visorchannel_get_uuid(struct visorchannel *channel) |
| { |
| return channel->guid; |
| } |
| EXPORT_SYMBOL_GPL(visorchannel_get_uuid); |
| |
| int |
| visorchannel_read(struct visorchannel *channel, ulong offset, |
| void *local, ulong nbytes) |
| { |
| if (offset + nbytes > channel->nbytes) |
| return -EIO; |
| |
| memcpy(local, channel->mapped + offset, nbytes); |
| |
| return 0; |
| } |
| |
| int |
| visorchannel_write(struct visorchannel *channel, ulong offset, |
| void *local, 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, |
| local, copy_size); |
| } |
| |
| memcpy(channel->mapped + offset, local, nbytes); |
| |
| return 0; |
| } |
| |
| void __iomem * |
| visorchannel_get_header(struct visorchannel *channel) |
| { |
| return (void __iomem *)&channel->chan_hdr; |
| } |
| |
| /* |
| * Return offset of a specific SIGNAL_QUEUE_HEADER from the beginning of a |
| * channel header |
| */ |
| #define SIG_QUEUE_OFFSET(chan_hdr, q) \ |
| ((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 |
| */ |
| #define SIG_DATA_OFFSET(chan_hdr, q, sig_hdr, slot) \ |
| (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 inline 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 inline 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; |
| |
| if (sig_hdr.head == sig_hdr.tail) |
| return -EIO; /* no signals to remove */ |
| |
| 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. |
| */ |
| mb(); /* required for channel synch */ |
| |
| 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); |
| |
| /** |
| * 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) |
| { |
| unsigned long flags = 0; |
| struct signal_queue_header sig_hdr; |
| bool rc = false; |
| |
| if (channel->needs_lock) |
| spin_lock_irqsave(&channel->remove_lock, flags); |
| |
| if (sig_read_header(channel, queue, &sig_hdr)) |
| rc = true; |
| if (sig_hdr.head == sig_hdr.tail) |
| rc = true; |
| if (channel->needs_lock) |
| 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 error; |
| |
| error = sig_read_header(channel, queue, &sig_hdr); |
| if (error) |
| return error; |
| |
| sig_hdr.head = (sig_hdr.head + 1) % sig_hdr.max_slots; |
| if (sig_hdr.head == sig_hdr.tail) { |
| sig_hdr.num_overflows++; |
| visorchannel_write(channel, |
| SIG_QUEUE_OFFSET(&channel->chan_hdr, queue) + |
| offsetof(struct signal_queue_header, |
| num_overflows), |
| &sig_hdr.num_overflows, |
| sizeof(sig_hdr.num_overflows)); |
| return -EIO; |
| } |
| |
| error = sig_write_data(channel, queue, &sig_hdr, sig_hdr.head, msg); |
| if (error) |
| return error; |
| |
| sig_hdr.num_sent++; |
| |
| /* |
| * For each data field in SIGNAL_QUEUE_HEADER that was modified, |
| * update host memory. |
| */ |
| mb(); /* required for channel synch */ |
| |
| error = SIG_WRITE_FIELD(channel, queue, &sig_hdr, head); |
| if (error) |
| return error; |
| error = SIG_WRITE_FIELD(channel, queue, &sig_hdr, num_sent); |
| if (error) |
| return error; |
| |
| return 0; |
| } |
| |
| /** |
| * visorchannel_create_guts() - 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 |
| * @channel_bytes: size of the channel in bytes; this may 0 if the channel has |
| * already been initialized in memory (which is true for all |
| * channels provided to guest environments by the s-Par |
| * back-end), in which case the actual channel size will be |
| * read from the channel header in memory |
| * @gfp: gfp_t to use when allocating memory for the data struct |
| * @guid: uuid that identifies channel type; this may 0 if the channel |
| * has already been initialized in memory (which is true for all |
| * channels provided to guest environments by the s-Par |
| * back-end), in which case the actual channel guid will be |
| * read from the channel header in memory |
| * @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 |
| */ |
| static struct visorchannel * |
| visorchannel_create_guts(u64 physaddr, unsigned long channel_bytes, |
| gfp_t gfp, uuid_le 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, MYDRVNAME); |
| if (!channel->requested) { |
| if (uuid_le_cmp(guid, spar_video_guid)) { |
| /* Not the video channel we care about this */ |
| 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, |
| sizeof(struct channel_header)); |
| if (err) |
| goto err_destroy_channel; |
| |
| /* we had better be a CLIENT of this channel */ |
| if (channel_bytes == 0) |
| channel_bytes = (ulong)channel->chan_hdr.size; |
| if (uuid_le_cmp(guid, NULL_UUID_LE) == 0) |
| guid = channel->chan_hdr.chtype; |
| |
| memunmap(channel->mapped); |
| if (channel->requested) |
| release_mem_region(channel->physaddr, channel->nbytes); |
| channel->mapped = NULL; |
| channel->requested = request_mem_region(channel->physaddr, |
| channel_bytes, MYDRVNAME); |
| if (!channel->requested) { |
| if (uuid_le_cmp(guid, spar_video_guid)) { |
| /* Different we care about this */ |
| goto err_destroy_channel; |
| } |
| } |
| |
| channel->mapped = memremap(channel->physaddr, channel_bytes, |
| MEMREMAP_WB); |
| if (!channel->mapped) { |
| release_mem_region(channel->physaddr, channel_bytes); |
| goto err_destroy_channel; |
| } |
| |
| channel->nbytes = channel_bytes; |
| channel->guid = guid; |
| return channel; |
| |
| err_destroy_channel: |
| visorchannel_destroy(channel); |
| return NULL; |
| } |
| |
| struct visorchannel * |
| visorchannel_create(u64 physaddr, unsigned long channel_bytes, |
| gfp_t gfp, uuid_le guid) |
| { |
| return visorchannel_create_guts(physaddr, channel_bytes, gfp, guid, |
| false); |
| } |
| |
| struct visorchannel * |
| visorchannel_create_with_lock(u64 physaddr, unsigned long channel_bytes, |
| gfp_t gfp, uuid_le guid) |
| { |
| return visorchannel_create_guts(physaddr, channel_bytes, gfp, guid, |
| true); |
| } |
| |
| /** |
| * 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); |