| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * zfcp device driver | 
 |  * | 
 |  * Functions to handle diagnostics. | 
 |  * | 
 |  * Copyright IBM Corp. 2018 | 
 |  */ | 
 |  | 
 | #include <linux/spinlock.h> | 
 | #include <linux/jiffies.h> | 
 | #include <linux/string.h> | 
 | #include <linux/errno.h> | 
 | #include <linux/slab.h> | 
 |  | 
 | #include "zfcp_diag.h" | 
 | #include "zfcp_ext.h" | 
 | #include "zfcp_def.h" | 
 |  | 
 | static DECLARE_WAIT_QUEUE_HEAD(__zfcp_diag_publish_wait); | 
 |  | 
 | /** | 
 |  * zfcp_diag_adapter_setup() - Setup storage for adapter diagnostics. | 
 |  * @adapter: the adapter to setup diagnostics for. | 
 |  * | 
 |  * Creates the data-structures to store the diagnostics for an adapter. This | 
 |  * overwrites whatever was stored before at &zfcp_adapter->diagnostics! | 
 |  * | 
 |  * Return: | 
 |  * * 0	     - Everyting is OK | 
 |  * * -ENOMEM - Could not allocate all/parts of the data-structures; | 
 |  *	       &zfcp_adapter->diagnostics remains unchanged | 
 |  */ | 
 | int zfcp_diag_adapter_setup(struct zfcp_adapter *const adapter) | 
 | { | 
 | 	struct zfcp_diag_adapter *diag; | 
 | 	struct zfcp_diag_header *hdr; | 
 |  | 
 | 	diag = kzalloc(sizeof(*diag), GFP_KERNEL); | 
 | 	if (diag == NULL) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	diag->max_age = (5 * 1000); /* default value: 5 s */ | 
 |  | 
 | 	/* setup header for port_data */ | 
 | 	hdr = &diag->port_data.header; | 
 |  | 
 | 	spin_lock_init(&hdr->access_lock); | 
 | 	hdr->buffer = &diag->port_data.data; | 
 | 	hdr->buffer_size = sizeof(diag->port_data.data); | 
 | 	/* set the timestamp so that the first test on age will always fail */ | 
 | 	hdr->timestamp = jiffies - msecs_to_jiffies(diag->max_age); | 
 |  | 
 | 	/* setup header for config_data */ | 
 | 	hdr = &diag->config_data.header; | 
 |  | 
 | 	spin_lock_init(&hdr->access_lock); | 
 | 	hdr->buffer = &diag->config_data.data; | 
 | 	hdr->buffer_size = sizeof(diag->config_data.data); | 
 | 	/* set the timestamp so that the first test on age will always fail */ | 
 | 	hdr->timestamp = jiffies - msecs_to_jiffies(diag->max_age); | 
 |  | 
 | 	adapter->diagnostics = diag; | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * zfcp_diag_adapter_free() - Frees all adapter diagnostics allocations. | 
 |  * @adapter: the adapter whose diagnostic structures should be freed. | 
 |  * | 
 |  * Frees all data-structures in the given adapter that store diagnostics | 
 |  * information. Can savely be called with partially setup diagnostics. | 
 |  */ | 
 | void zfcp_diag_adapter_free(struct zfcp_adapter *const adapter) | 
 | { | 
 | 	kfree(adapter->diagnostics); | 
 | 	adapter->diagnostics = NULL; | 
 | } | 
 |  | 
 | /** | 
 |  * zfcp_diag_update_xdata() - Update a diagnostics buffer. | 
 |  * @hdr: the meta data to update. | 
 |  * @data: data to use for the update. | 
 |  * @incomplete: flag stating whether the data in @data is incomplete. | 
 |  */ | 
 | void zfcp_diag_update_xdata(struct zfcp_diag_header *const hdr, | 
 | 			    const void *const data, const bool incomplete) | 
 | { | 
 | 	const unsigned long capture_timestamp = jiffies; | 
 | 	unsigned long flags; | 
 |  | 
 | 	spin_lock_irqsave(&hdr->access_lock, flags); | 
 |  | 
 | 	/* make sure we never go into the past with an update */ | 
 | 	if (!time_after_eq(capture_timestamp, hdr->timestamp)) | 
 | 		goto out; | 
 |  | 
 | 	hdr->timestamp = capture_timestamp; | 
 | 	hdr->incomplete = incomplete; | 
 | 	memcpy(hdr->buffer, data, hdr->buffer_size); | 
 | out: | 
 | 	spin_unlock_irqrestore(&hdr->access_lock, flags); | 
 | } | 
 |  | 
 | /** | 
 |  * zfcp_diag_update_port_data_buffer() - Implementation of | 
 |  *					 &typedef zfcp_diag_update_buffer_func | 
 |  *					 to collect and update Port Data. | 
 |  * @adapter: Adapter to collect Port Data from. | 
 |  * | 
 |  * This call is SYNCHRONOUS ! It blocks till the respective command has | 
 |  * finished completely, or has failed in some way. | 
 |  * | 
 |  * Return: | 
 |  * * 0		- Successfully retrieved new Diagnostics and Updated the buffer; | 
 |  *		  this also includes cases where data was retrieved, but | 
 |  *		  incomplete; you'll have to check the flag ``incomplete`` | 
 |  *		  of &struct zfcp_diag_header. | 
 |  * * see zfcp_fsf_exchange_port_data_sync() for possible error-codes ( | 
 |  *   excluding -EAGAIN) | 
 |  */ | 
 | int zfcp_diag_update_port_data_buffer(struct zfcp_adapter *const adapter) | 
 | { | 
 | 	int rc; | 
 |  | 
 | 	rc = zfcp_fsf_exchange_port_data_sync(adapter->qdio, NULL); | 
 | 	if (rc == -EAGAIN) | 
 | 		rc = 0; /* signaling incomplete via struct zfcp_diag_header */ | 
 |  | 
 | 	/* buffer-data was updated in zfcp_fsf_exchange_port_data_handler() */ | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | /** | 
 |  * zfcp_diag_update_config_data_buffer() - Implementation of | 
 |  *					   &typedef zfcp_diag_update_buffer_func | 
 |  *					   to collect and update Config Data. | 
 |  * @adapter: Adapter to collect Config Data from. | 
 |  * | 
 |  * This call is SYNCHRONOUS ! It blocks till the respective command has | 
 |  * finished completely, or has failed in some way. | 
 |  * | 
 |  * Return: | 
 |  * * 0		- Successfully retrieved new Diagnostics and Updated the buffer; | 
 |  *		  this also includes cases where data was retrieved, but | 
 |  *		  incomplete; you'll have to check the flag ``incomplete`` | 
 |  *		  of &struct zfcp_diag_header. | 
 |  * * see zfcp_fsf_exchange_config_data_sync() for possible error-codes ( | 
 |  *   excluding -EAGAIN) | 
 |  */ | 
 | int zfcp_diag_update_config_data_buffer(struct zfcp_adapter *const adapter) | 
 | { | 
 | 	int rc; | 
 |  | 
 | 	rc = zfcp_fsf_exchange_config_data_sync(adapter->qdio, NULL); | 
 | 	if (rc == -EAGAIN) | 
 | 		rc = 0; /* signaling incomplete via struct zfcp_diag_header */ | 
 |  | 
 | 	/* buffer-data was updated in zfcp_fsf_exchange_config_data_handler() */ | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int __zfcp_diag_update_buffer(struct zfcp_adapter *const adapter, | 
 | 				     struct zfcp_diag_header *const hdr, | 
 | 				     zfcp_diag_update_buffer_func buffer_update, | 
 | 				     unsigned long *const flags) | 
 | 	__must_hold(hdr->access_lock) | 
 | { | 
 | 	int rc; | 
 |  | 
 | 	if (hdr->updating == 1) { | 
 | 		rc = wait_event_interruptible_lock_irq(__zfcp_diag_publish_wait, | 
 | 						       hdr->updating == 0, | 
 | 						       hdr->access_lock); | 
 | 		rc = (rc == 0 ? -EAGAIN : -EINTR); | 
 | 	} else { | 
 | 		hdr->updating = 1; | 
 | 		spin_unlock_irqrestore(&hdr->access_lock, *flags); | 
 |  | 
 | 		/* unlocked, because update function sleeps */ | 
 | 		rc = buffer_update(adapter); | 
 |  | 
 | 		spin_lock_irqsave(&hdr->access_lock, *flags); | 
 | 		hdr->updating = 0; | 
 |  | 
 | 		/* | 
 | 		 * every thread waiting here went via an interruptible wait, | 
 | 		 * so its fine to only wake those | 
 | 		 */ | 
 | 		wake_up_interruptible_all(&__zfcp_diag_publish_wait); | 
 | 	} | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static bool | 
 | __zfcp_diag_test_buffer_age_isfresh(const struct zfcp_diag_adapter *const diag, | 
 | 				    const struct zfcp_diag_header *const hdr) | 
 | 	__must_hold(hdr->access_lock) | 
 | { | 
 | 	const unsigned long now = jiffies; | 
 |  | 
 | 	/* | 
 | 	 * Should not happen (data is from the future).. if it does, still | 
 | 	 * signal that it needs refresh | 
 | 	 */ | 
 | 	if (!time_after_eq(now, hdr->timestamp)) | 
 | 		return false; | 
 |  | 
 | 	if (jiffies_to_msecs(now - hdr->timestamp) >= diag->max_age) | 
 | 		return false; | 
 |  | 
 | 	return true; | 
 | } | 
 |  | 
 | /** | 
 |  * zfcp_diag_update_buffer_limited() - Collect diagnostics and update a | 
 |  *				       diagnostics buffer rate limited. | 
 |  * @adapter: Adapter to collect the diagnostics from. | 
 |  * @hdr: buffer-header for which to update with the collected diagnostics. | 
 |  * @buffer_update: Specific implementation for collecting and updating. | 
 |  * | 
 |  * This function will cause an update of the given @hdr by calling the also | 
 |  * given @buffer_update function. If called by multiple sources at the same | 
 |  * time, it will synchornize the update by only allowing one source to call | 
 |  * @buffer_update and the others to wait for that source to complete instead | 
 |  * (the wait is interruptible). | 
 |  * | 
 |  * Additionally this version is rate-limited and will only exit if either the | 
 |  * buffer is fresh enough (within the limit) - it will do nothing if the buffer | 
 |  * is fresh enough to begin with -, or if the source/thread that started this | 
 |  * update is the one that made the update (to prevent endless loops). | 
 |  * | 
 |  * Return: | 
 |  * * 0		- If the update was successfully published and/or the buffer is | 
 |  *		  fresh enough | 
 |  * * -EINTR	- If the thread went into the wait-state and was interrupted | 
 |  * * whatever @buffer_update returns | 
 |  */ | 
 | int zfcp_diag_update_buffer_limited(struct zfcp_adapter *const adapter, | 
 | 				    struct zfcp_diag_header *const hdr, | 
 | 				    zfcp_diag_update_buffer_func buffer_update) | 
 | { | 
 | 	unsigned long flags; | 
 | 	int rc; | 
 |  | 
 | 	spin_lock_irqsave(&hdr->access_lock, flags); | 
 |  | 
 | 	for (rc = 0; | 
 | 	     !__zfcp_diag_test_buffer_age_isfresh(adapter->diagnostics, hdr); | 
 | 	     rc = 0) { | 
 | 		rc = __zfcp_diag_update_buffer(adapter, hdr, buffer_update, | 
 | 					       &flags); | 
 | 		if (rc != -EAGAIN) | 
 | 			break; | 
 | 	} | 
 |  | 
 | 	spin_unlock_irqrestore(&hdr->access_lock, flags); | 
 |  | 
 | 	return rc; | 
 | } |