diff options
Diffstat (limited to 'drivers/crypto/qat/qat_common/adf_aer.c')
| -rw-r--r-- | drivers/crypto/qat/qat_common/adf_aer.c | 259 | 
1 files changed, 259 insertions, 0 deletions
diff --git a/drivers/crypto/qat/qat_common/adf_aer.c b/drivers/crypto/qat/qat_common/adf_aer.c new file mode 100644 index 000000000000..c29d4c3926bf --- /dev/null +++ b/drivers/crypto/qat/qat_common/adf_aer.c @@ -0,0 +1,259 @@ +/* +  This file is provided under a dual BSD/GPLv2 license.  When using or +  redistributing this file, you may do so under either license. + +  GPL LICENSE SUMMARY +  Copyright(c) 2014 Intel Corporation. +  This program is free software; you can redistribute it and/or modify +  it under the terms of version 2 of the GNU General Public License as +  published by the Free Software Foundation. + +  This program is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  Contact Information: +  qat-linux@intel.com + +  BSD LICENSE +  Copyright(c) 2014 Intel Corporation. +  Redistribution and use in source and binary forms, with or without +  modification, are permitted provided that the following conditions +  are met: + +    * Redistributions of source code must retain the above copyright +      notice, this list of conditions and the following disclaimer. +    * Redistributions in binary form must reproduce the above copyright +      notice, this list of conditions and the following disclaimer in +      the documentation and/or other materials provided with the +      distribution. +    * Neither the name of Intel Corporation nor the names of its +      contributors may be used to endorse or promote products derived +      from this software without specific prior written permission. + +  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/aer.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include "adf_accel_devices.h" +#include "adf_common_drv.h" + +static struct workqueue_struct *device_reset_wq; + +static pci_ers_result_t adf_error_detected(struct pci_dev *pdev, +					   pci_channel_state_t state) +{ +	struct adf_accel_dev *accel_dev = adf_devmgr_pci_to_accel_dev(pdev); + +	pr_info("QAT: Acceleration driver hardware error detected.\n"); +	if (!accel_dev) { +		pr_err("QAT: Can't find acceleration device\n"); +		return PCI_ERS_RESULT_DISCONNECT; +	} + +	if (state == pci_channel_io_perm_failure) { +		pr_err("QAT: Can't recover from device error\n"); +		return PCI_ERS_RESULT_DISCONNECT; +	} + +	return PCI_ERS_RESULT_NEED_RESET; +} + +/* reset dev data */ +struct adf_reset_dev_data { +	int mode; +	struct adf_accel_dev *accel_dev; +	struct completion compl; +	struct work_struct reset_work; +}; + +#define PPDSTAT_OFFSET 0x7E +static void adf_dev_restore(struct adf_accel_dev *accel_dev) +{ +	struct pci_dev *pdev = accel_to_pci_dev(accel_dev); +	struct pci_dev *parent = pdev->bus->self; +	uint16_t ppdstat = 0, bridge_ctl = 0; +	int pending = 0; + +	pr_info("QAT: Reseting device qat_dev%d\n", accel_dev->accel_id); +	pci_read_config_word(pdev, PPDSTAT_OFFSET, &ppdstat); +	pending = ppdstat & PCI_EXP_DEVSTA_TRPND; +	if (pending) { +		int ctr = 0; + +		do { +			msleep(100); +			pci_read_config_word(pdev, PPDSTAT_OFFSET, &ppdstat); +			pending = ppdstat & PCI_EXP_DEVSTA_TRPND; +		} while (pending && ctr++ < 10); +	} + +	if (pending) +		pr_info("QAT: Transaction still in progress. Proceeding\n"); + +	pci_read_config_word(parent, PCI_BRIDGE_CONTROL, &bridge_ctl); +	bridge_ctl |= PCI_BRIDGE_CTL_BUS_RESET; +	pci_write_config_word(parent, PCI_BRIDGE_CONTROL, bridge_ctl); +	msleep(100); +	bridge_ctl &= ~PCI_BRIDGE_CTL_BUS_RESET; +	pci_write_config_word(parent, PCI_BRIDGE_CONTROL, bridge_ctl); +	msleep(100); +	pci_restore_state(pdev); +	pci_save_state(pdev); +} + +static void adf_device_reset_worker(struct work_struct *work) +{ +	struct adf_reset_dev_data *reset_data = +		  container_of(work, struct adf_reset_dev_data, reset_work); +	struct adf_accel_dev *accel_dev = reset_data->accel_dev; + +	adf_dev_restarting_notify(accel_dev); +	adf_dev_stop(accel_dev); +	adf_dev_restore(accel_dev); +	if (adf_dev_start(accel_dev)) { +		/* The device hanged and we can't restart it so stop here */ +		dev_err(&GET_DEV(accel_dev), "Restart device failed\n"); +		kfree(reset_data); +		WARN(1, "QAT: device restart failed. Device is unusable\n"); +		return; +	} +	adf_dev_restarted_notify(accel_dev); +	clear_bit(ADF_STATUS_RESTARTING, &accel_dev->status); + +	/* The dev is back alive. Notify the caller if in sync mode */ +	if (reset_data->mode == ADF_DEV_RESET_SYNC) +		complete(&reset_data->compl); +	else +		kfree(reset_data); +} + +static int adf_dev_aer_schedule_reset(struct adf_accel_dev *accel_dev, +				      enum adf_dev_reset_mode mode) +{ +	struct adf_reset_dev_data *reset_data; + +	if (adf_dev_started(accel_dev) && +	    !test_bit(ADF_STATUS_RESTARTING, &accel_dev->status)) +		return 0; + +	set_bit(ADF_STATUS_RESTARTING, &accel_dev->status); +	reset_data = kzalloc(sizeof(*reset_data), GFP_ATOMIC); +	if (!reset_data) +		return -ENOMEM; +	reset_data->accel_dev = accel_dev; +	init_completion(&reset_data->compl); +	reset_data->mode = mode; +	INIT_WORK(&reset_data->reset_work, adf_device_reset_worker); +	queue_work(device_reset_wq, &reset_data->reset_work); + +	/* If in sync mode wait for the result */ +	if (mode == ADF_DEV_RESET_SYNC) { +		int ret = 0; +		/* Maximum device reset time is 10 seconds */ +		unsigned long wait_jiffies = msecs_to_jiffies(10000); +		unsigned long timeout = wait_for_completion_timeout( +				   &reset_data->compl, wait_jiffies); +		if (!timeout) { +			pr_err("QAT: Reset device timeout expired\n"); +			ret = -EFAULT; +		} +		kfree(reset_data); +		return ret; +	} +	return 0; +} + +static pci_ers_result_t adf_slot_reset(struct pci_dev *pdev) +{ +	struct adf_accel_dev *accel_dev = adf_devmgr_pci_to_accel_dev(pdev); + +	if (!accel_dev) { +		pr_err("QAT: Can't find acceleration device\n"); +		return PCI_ERS_RESULT_DISCONNECT; +	} +	pci_cleanup_aer_uncorrect_error_status(pdev); +	if (adf_dev_aer_schedule_reset(accel_dev, ADF_DEV_RESET_SYNC)) +		return PCI_ERS_RESULT_DISCONNECT; + +	return PCI_ERS_RESULT_RECOVERED; +} + +static void adf_resume(struct pci_dev *pdev) +{ +	pr_info("QAT: Acceleration driver reset completed\n"); +	pr_info("QAT: Device is up and runnig\n"); +} + +static struct pci_error_handlers adf_err_handler = { +	.error_detected = adf_error_detected, +	.slot_reset = adf_slot_reset, +	.resume = adf_resume, +}; + +/** + * adf_enable_aer() - Enable Advance Error Reporting for acceleration device + * @accel_dev:  Pointer to acceleration device. + * @adf:        PCI device driver owning the given acceleration device. + * + * Function enables PCI Advance Error Reporting for the + * QAT acceleration device accel_dev. + * To be used by QAT device specific drivers. + * + * Return: 0 on success, error code othewise. + */ +int adf_enable_aer(struct adf_accel_dev *accel_dev, struct pci_driver *adf) +{ +	struct pci_dev *pdev = accel_to_pci_dev(accel_dev); + +	adf->err_handler = &adf_err_handler; +	pci_enable_pcie_error_reporting(pdev); +	return 0; +} +EXPORT_SYMBOL_GPL(adf_enable_aer); + +/** + * adf_disable_aer() - Enable Advance Error Reporting for acceleration device + * @accel_dev:  Pointer to acceleration device. + * + * Function disables PCI Advance Error Reporting for the + * QAT acceleration device accel_dev. + * To be used by QAT device specific drivers. + * + * Return: void + */ +void adf_disable_aer(struct adf_accel_dev *accel_dev) +{ +	struct pci_dev *pdev = accel_to_pci_dev(accel_dev); + +	pci_disable_pcie_error_reporting(pdev); +} +EXPORT_SYMBOL_GPL(adf_disable_aer); + +int adf_init_aer(void) +{ +	device_reset_wq = create_workqueue("qat_device_reset_wq"); +	return (device_reset_wq == NULL) ? -EFAULT : 0; +} + +void adf_exit_aer(void) +{ +	if (device_reset_wq) +		destroy_workqueue(device_reset_wq); +	device_reset_wq = NULL; +}  | 
