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