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