// SPDX-License-Identifier: GPL-2.0 /* * zfcp device driver * * Functions to handle diagnostics. * * Copyright IBM Corp. 2018 */ #include #include #include #include #include #include #include #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_sysfs_setup() - Setup the sysfs-group for adapter-diagnostics. * @adapter: target adapter to which the group should be added. * * Return: 0 on success; Something else otherwise (see sysfs_create_group()). */ int zfcp_diag_sysfs_setup(struct zfcp_adapter *const adapter) { int rc = sysfs_create_group(&adapter->ccw_device->dev.kobj, &zfcp_sysfs_diag_attr_group); if (rc == 0) adapter->diagnostics->sysfs_established = 1; return rc; } /** * zfcp_diag_sysfs_destroy() - Remove the sysfs-group for adapter-diagnostics. * @adapter: target adapter from which the group should be removed. */ void zfcp_diag_sysfs_destroy(struct zfcp_adapter *const adapter) { if (adapter->diagnostics == NULL || !adapter->diagnostics->sysfs_established) return; /* * We need this state-handling so we can prevent warnings being printed * on the kernel-console in case we have to abort a halfway done * zfcp_adapter_enqueue(), in which the sysfs-group was not yet * established. sysfs_remove_group() does this checking as well, but * still prints a warning in case we try to remove a group that has not * been established before */ adapter->diagnostics->sysfs_established = 0; sysfs_remove_group(&adapter->ccw_device->dev.kobj, &zfcp_sysfs_diag_attr_group); } /** * 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; }