| // 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); |