diff options
| author | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
| commit | 1ac731c529cd4d6adbce134754b51ff7d822b145 (patch) | |
| tree | 143ab3f35ca5f3b69f583c84e6964b17139c2ec1 /drivers/crypto/intel/qat/qat_common/adf_aer.c | |
| parent | 07b4c950f27bef0362dc6ad7ee713aab61d58149 (diff) | |
| parent | 54116d442e001e1b6bd482122043b1870998a1f3 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.6 merge window.
Diffstat (limited to 'drivers/crypto/intel/qat/qat_common/adf_aer.c')
| -rw-r--r-- | drivers/crypto/intel/qat/qat_common/adf_aer.c | 185 | 
1 files changed, 185 insertions, 0 deletions
diff --git a/drivers/crypto/intel/qat/qat_common/adf_aer.c b/drivers/crypto/intel/qat/qat_common/adf_aer.c new file mode 100644 index 000000000000..04af32a2811c --- /dev/null +++ b/drivers/crypto/intel/qat/qat_common/adf_aer.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +/* Copyright(c) 2014 - 2020 Intel Corporation */ +#include <linux/kernel.h> +#include <linux/pci.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); + +	dev_info(&pdev->dev, "Acceleration driver hardware error detected.\n"); +	if (!accel_dev) { +		dev_err(&pdev->dev, "Can't find acceleration device\n"); +		return PCI_ERS_RESULT_DISCONNECT; +	} + +	if (state == pci_channel_io_perm_failure) { +		dev_err(&pdev->dev, "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; +}; + +void adf_reset_sbr(struct adf_accel_dev *accel_dev) +{ +	struct pci_dev *pdev = accel_to_pci_dev(accel_dev); +	struct pci_dev *parent = pdev->bus->self; +	u16 bridge_ctl = 0; + +	if (!parent) +		parent = pdev; + +	if (!pci_wait_for_pending_transaction(pdev)) +		dev_info(&GET_DEV(accel_dev), +			 "Transaction still in progress. Proceeding\n"); + +	dev_info(&GET_DEV(accel_dev), "Secondary bus reset\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); +} +EXPORT_SYMBOL_GPL(adf_reset_sbr); + +void adf_reset_flr(struct adf_accel_dev *accel_dev) +{ +	pcie_flr(accel_to_pci_dev(accel_dev)); +} +EXPORT_SYMBOL_GPL(adf_reset_flr); + +void adf_dev_restore(struct adf_accel_dev *accel_dev) +{ +	struct adf_hw_device_data *hw_device = accel_dev->hw_device; +	struct pci_dev *pdev = accel_to_pci_dev(accel_dev); + +	if (hw_device->reset_device) { +		dev_info(&GET_DEV(accel_dev), "Resetting device qat_dev%d\n", +			 accel_dev->accel_id); +		hw_device->reset_device(accel_dev); +		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); +	if (adf_dev_restart(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_KERNEL); +	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) { +			dev_err(&GET_DEV(accel_dev), +				"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; +	} +	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) +{ +	dev_info(&pdev->dev, "Acceleration driver reset completed\n"); +	dev_info(&pdev->dev, "Device is up and running\n"); +} + +const struct pci_error_handlers adf_err_handler = { +	.error_detected = adf_error_detected, +	.slot_reset = adf_slot_reset, +	.resume = adf_resume, +}; +EXPORT_SYMBOL_GPL(adf_err_handler); + +int adf_init_aer(void) +{ +	device_reset_wq = alloc_workqueue("qat_device_reset_wq", +					  WQ_MEM_RECLAIM, 0); +	return !device_reset_wq ? -EFAULT : 0; +} + +void adf_exit_aer(void) +{ +	if (device_reset_wq) +		destroy_workqueue(device_reset_wq); +	device_reset_wq = NULL; +}  |