162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PCI Error Disconnect Recover support 462306a36Sopenharmony_ci * Author: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2020 Intel Corp. 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define dev_fmt(fmt) "EDR: " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/pci.h> 1262306a36Sopenharmony_ci#include <linux/pci-acpi.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "portdrv.h" 1562306a36Sopenharmony_ci#include "../pci.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define EDR_PORT_DPC_ENABLE_DSM 0x0C 1862306a36Sopenharmony_ci#define EDR_PORT_LOCATE_DSM 0x0D 1962306a36Sopenharmony_ci#define EDR_OST_SUCCESS 0x80 2062306a36Sopenharmony_ci#define EDR_OST_FAILED 0x81 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* 2362306a36Sopenharmony_ci * _DSM wrapper function to enable/disable DPC 2462306a36Sopenharmony_ci * @pdev : PCI device structure 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * returns 0 on success or errno on failure. 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_cistatic int acpi_enable_dpc(struct pci_dev *pdev) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 3162306a36Sopenharmony_ci union acpi_object *obj, argv4, req; 3262306a36Sopenharmony_ci int status = 0; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci /* 3562306a36Sopenharmony_ci * Behavior when calling unsupported _DSM functions is undefined, 3662306a36Sopenharmony_ci * so check whether EDR_PORT_DPC_ENABLE_DSM is supported. 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_ci if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 5, 3962306a36Sopenharmony_ci 1ULL << EDR_PORT_DPC_ENABLE_DSM)) 4062306a36Sopenharmony_ci return 0; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci req.type = ACPI_TYPE_INTEGER; 4362306a36Sopenharmony_ci req.integer.value = 1; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci argv4.type = ACPI_TYPE_PACKAGE; 4662306a36Sopenharmony_ci argv4.package.count = 1; 4762306a36Sopenharmony_ci argv4.package.elements = &req; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* 5062306a36Sopenharmony_ci * Per Downstream Port Containment Related Enhancements ECN to PCI 5162306a36Sopenharmony_ci * Firmware Specification r3.2, sec 4.6.12, EDR_PORT_DPC_ENABLE_DSM is 5262306a36Sopenharmony_ci * optional. Return success if it's not implemented. 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_ci obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 5, 5562306a36Sopenharmony_ci EDR_PORT_DPC_ENABLE_DSM, &argv4); 5662306a36Sopenharmony_ci if (!obj) 5762306a36Sopenharmony_ci return 0; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (obj->type != ACPI_TYPE_INTEGER) { 6062306a36Sopenharmony_ci pci_err(pdev, FW_BUG "Enable DPC _DSM returned non integer\n"); 6162306a36Sopenharmony_ci status = -EIO; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci if (obj->integer.value != 1) { 6562306a36Sopenharmony_ci pci_err(pdev, "Enable DPC _DSM failed to enable DPC\n"); 6662306a36Sopenharmony_ci status = -EIO; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci ACPI_FREE(obj); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci return status; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci/* 7562306a36Sopenharmony_ci * _DSM wrapper function to locate DPC port 7662306a36Sopenharmony_ci * @pdev : Device which received EDR event 7762306a36Sopenharmony_ci * 7862306a36Sopenharmony_ci * Returns pci_dev or NULL. Caller is responsible for dropping a reference 7962306a36Sopenharmony_ci * on the returned pci_dev with pci_dev_put(). 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_cistatic struct pci_dev *acpi_dpc_port_get(struct pci_dev *pdev) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 8462306a36Sopenharmony_ci union acpi_object *obj; 8562306a36Sopenharmony_ci u16 port; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* 8862306a36Sopenharmony_ci * Behavior when calling unsupported _DSM functions is undefined, 8962306a36Sopenharmony_ci * so check whether EDR_PORT_DPC_ENABLE_DSM is supported. 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_ci if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 5, 9262306a36Sopenharmony_ci 1ULL << EDR_PORT_LOCATE_DSM)) 9362306a36Sopenharmony_ci return pci_dev_get(pdev); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 5, 9662306a36Sopenharmony_ci EDR_PORT_LOCATE_DSM, NULL); 9762306a36Sopenharmony_ci if (!obj) 9862306a36Sopenharmony_ci return pci_dev_get(pdev); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci if (obj->type != ACPI_TYPE_INTEGER) { 10162306a36Sopenharmony_ci ACPI_FREE(obj); 10262306a36Sopenharmony_ci pci_err(pdev, FW_BUG "Locate Port _DSM returned non integer\n"); 10362306a36Sopenharmony_ci return NULL; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* 10762306a36Sopenharmony_ci * Firmware returns DPC port BDF details in following format: 10862306a36Sopenharmony_ci * 15:8 = bus 10962306a36Sopenharmony_ci * 7:3 = device 11062306a36Sopenharmony_ci * 2:0 = function 11162306a36Sopenharmony_ci */ 11262306a36Sopenharmony_ci port = obj->integer.value; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci ACPI_FREE(obj); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci return pci_get_domain_bus_and_slot(pci_domain_nr(pdev->bus), 11762306a36Sopenharmony_ci PCI_BUS_NUM(port), port & 0xff); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci/* 12162306a36Sopenharmony_ci * _OST wrapper function to let firmware know the status of EDR event 12262306a36Sopenharmony_ci * @pdev : Device used to send _OST 12362306a36Sopenharmony_ci * @edev : Device which experienced EDR event 12462306a36Sopenharmony_ci * @status : Status of EDR event 12562306a36Sopenharmony_ci */ 12662306a36Sopenharmony_cistatic int acpi_send_edr_status(struct pci_dev *pdev, struct pci_dev *edev, 12762306a36Sopenharmony_ci u16 status) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 13062306a36Sopenharmony_ci u32 ost_status; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci pci_dbg(pdev, "Status for %s: %#x\n", pci_name(edev), status); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ost_status = PCI_DEVID(edev->bus->number, edev->devfn) << 16; 13562306a36Sopenharmony_ci ost_status |= status; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci status = acpi_evaluate_ost(adev->handle, ACPI_NOTIFY_DISCONNECT_RECOVER, 13862306a36Sopenharmony_ci ost_status, NULL); 13962306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 14062306a36Sopenharmony_ci return -EINVAL; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci return 0; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic void edr_handle_event(acpi_handle handle, u32 event, void *data) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct pci_dev *pdev = data, *edev; 14862306a36Sopenharmony_ci pci_ers_result_t estate = PCI_ERS_RESULT_DISCONNECT; 14962306a36Sopenharmony_ci u16 status; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (event != ACPI_NOTIFY_DISCONNECT_RECOVER) 15262306a36Sopenharmony_ci return; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* 15562306a36Sopenharmony_ci * pdev is a Root Port or Downstream Port that is still present and 15662306a36Sopenharmony_ci * has triggered a containment event, e.g., DPC, so its child 15762306a36Sopenharmony_ci * devices have been disconnected (ACPI r6.5, sec 5.6.6). 15862306a36Sopenharmony_ci */ 15962306a36Sopenharmony_ci pci_info(pdev, "EDR event received\n"); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci /* 16262306a36Sopenharmony_ci * Locate the port that experienced the containment event. pdev 16362306a36Sopenharmony_ci * may be that port or a parent of it (PCI Firmware r3.3, sec 16462306a36Sopenharmony_ci * 4.6.13). 16562306a36Sopenharmony_ci */ 16662306a36Sopenharmony_ci edev = acpi_dpc_port_get(pdev); 16762306a36Sopenharmony_ci if (!edev) { 16862306a36Sopenharmony_ci pci_err(pdev, "Firmware failed to locate DPC port\n"); 16962306a36Sopenharmony_ci return; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci pci_dbg(pdev, "Reported EDR dev: %s\n", pci_name(edev)); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* If port does not support DPC, just send the OST */ 17562306a36Sopenharmony_ci if (!edev->dpc_cap) { 17662306a36Sopenharmony_ci pci_err(edev, FW_BUG "This device doesn't support DPC\n"); 17762306a36Sopenharmony_ci goto send_ost; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci /* Check if there is a valid DPC trigger */ 18162306a36Sopenharmony_ci pci_read_config_word(edev, edev->dpc_cap + PCI_EXP_DPC_STATUS, &status); 18262306a36Sopenharmony_ci if (!(status & PCI_EXP_DPC_STATUS_TRIGGER)) { 18362306a36Sopenharmony_ci pci_err(edev, "Invalid DPC trigger %#010x\n", status); 18462306a36Sopenharmony_ci goto send_ost; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci dpc_process_error(edev); 18862306a36Sopenharmony_ci pci_aer_raw_clear_status(edev); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci /* 19162306a36Sopenharmony_ci * Irrespective of whether the DPC event is triggered by ERR_FATAL 19262306a36Sopenharmony_ci * or ERR_NONFATAL, since the link is already down, use the FATAL 19362306a36Sopenharmony_ci * error recovery path for both cases. 19462306a36Sopenharmony_ci */ 19562306a36Sopenharmony_ci estate = pcie_do_recovery(edev, pci_channel_io_frozen, dpc_reset_link); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cisend_ost: 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* 20062306a36Sopenharmony_ci * If recovery is successful, send _OST(0xF, BDF << 16 | 0x80) 20162306a36Sopenharmony_ci * to firmware. If not successful, send _OST(0xF, BDF << 16 | 0x81). 20262306a36Sopenharmony_ci */ 20362306a36Sopenharmony_ci if (estate == PCI_ERS_RESULT_RECOVERED) { 20462306a36Sopenharmony_ci pci_dbg(edev, "DPC port successfully recovered\n"); 20562306a36Sopenharmony_ci pcie_clear_device_status(edev); 20662306a36Sopenharmony_ci acpi_send_edr_status(pdev, edev, EDR_OST_SUCCESS); 20762306a36Sopenharmony_ci } else { 20862306a36Sopenharmony_ci pci_dbg(edev, "DPC port recovery failed\n"); 20962306a36Sopenharmony_ci acpi_send_edr_status(pdev, edev, EDR_OST_FAILED); 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci pci_dev_put(edev); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_civoid pci_acpi_add_edr_notifier(struct pci_dev *pdev) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 21862306a36Sopenharmony_ci acpi_status status; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci if (!adev) { 22162306a36Sopenharmony_ci pci_dbg(pdev, "No valid ACPI node, skipping EDR init\n"); 22262306a36Sopenharmony_ci return; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci status = acpi_install_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, 22662306a36Sopenharmony_ci edr_handle_event, pdev); 22762306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 22862306a36Sopenharmony_ci pci_err(pdev, "Failed to install notify handler\n"); 22962306a36Sopenharmony_ci return; 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (acpi_enable_dpc(pdev)) 23362306a36Sopenharmony_ci acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, 23462306a36Sopenharmony_ci edr_handle_event); 23562306a36Sopenharmony_ci else 23662306a36Sopenharmony_ci pci_dbg(pdev, "Notify handler installed\n"); 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_civoid pci_acpi_remove_edr_notifier(struct pci_dev *pdev) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (!adev) 24462306a36Sopenharmony_ci return; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, 24762306a36Sopenharmony_ci edr_handle_event); 24862306a36Sopenharmony_ci pci_dbg(pdev, "Notify handler removed\n"); 24962306a36Sopenharmony_ci} 250