1ac1c8e35SKuppuswamy Sathyanarayanan // SPDX-License-Identifier: GPL-2.0 2ac1c8e35SKuppuswamy Sathyanarayanan /* 3ac1c8e35SKuppuswamy Sathyanarayanan * PCI Error Disconnect Recover support 4ac1c8e35SKuppuswamy Sathyanarayanan * Author: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com> 5ac1c8e35SKuppuswamy Sathyanarayanan * 6ac1c8e35SKuppuswamy Sathyanarayanan * Copyright (C) 2020 Intel Corp. 7ac1c8e35SKuppuswamy Sathyanarayanan */ 8ac1c8e35SKuppuswamy Sathyanarayanan 9ac1c8e35SKuppuswamy Sathyanarayanan #define dev_fmt(fmt) "EDR: " fmt 10ac1c8e35SKuppuswamy Sathyanarayanan 11ac1c8e35SKuppuswamy Sathyanarayanan #include <linux/pci.h> 12ac1c8e35SKuppuswamy Sathyanarayanan #include <linux/pci-acpi.h> 13ac1c8e35SKuppuswamy Sathyanarayanan 14ac1c8e35SKuppuswamy Sathyanarayanan #include "portdrv.h" 15ac1c8e35SKuppuswamy Sathyanarayanan #include "../pci.h" 16ac1c8e35SKuppuswamy Sathyanarayanan 17ac1c8e35SKuppuswamy Sathyanarayanan #define EDR_PORT_DPC_ENABLE_DSM 0x0C 18ac1c8e35SKuppuswamy Sathyanarayanan #define EDR_PORT_LOCATE_DSM 0x0D 19ac1c8e35SKuppuswamy Sathyanarayanan #define EDR_OST_SUCCESS 0x80 20ac1c8e35SKuppuswamy Sathyanarayanan #define EDR_OST_FAILED 0x81 21ac1c8e35SKuppuswamy Sathyanarayanan 22ac1c8e35SKuppuswamy Sathyanarayanan /* 23ac1c8e35SKuppuswamy Sathyanarayanan * _DSM wrapper function to enable/disable DPC 24ac1c8e35SKuppuswamy Sathyanarayanan * @pdev : PCI device structure 25ac1c8e35SKuppuswamy Sathyanarayanan * 26ac1c8e35SKuppuswamy Sathyanarayanan * returns 0 on success or errno on failure. 27ac1c8e35SKuppuswamy Sathyanarayanan */ 28ac1c8e35SKuppuswamy Sathyanarayanan static int acpi_enable_dpc(struct pci_dev *pdev) 29ac1c8e35SKuppuswamy Sathyanarayanan { 30ac1c8e35SKuppuswamy Sathyanarayanan struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 31ac1c8e35SKuppuswamy Sathyanarayanan union acpi_object *obj, argv4, req; 32ac1c8e35SKuppuswamy Sathyanarayanan int status = 0; 33ac1c8e35SKuppuswamy Sathyanarayanan 34ac1c8e35SKuppuswamy Sathyanarayanan /* 35f24ba846SKuppuswamy Sathyanarayanan * Per PCI Firmware r3.3, sec 4.6.12, EDR_PORT_DPC_ENABLE_DSM is 36f24ba846SKuppuswamy Sathyanarayanan * optional. Return success if it's not implemented. 37ac1c8e35SKuppuswamy Sathyanarayanan */ 38f24ba846SKuppuswamy Sathyanarayanan if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 6, 39ac1c8e35SKuppuswamy Sathyanarayanan 1ULL << EDR_PORT_DPC_ENABLE_DSM)) 40ac1c8e35SKuppuswamy Sathyanarayanan return 0; 41ac1c8e35SKuppuswamy Sathyanarayanan 42ac1c8e35SKuppuswamy Sathyanarayanan req.type = ACPI_TYPE_INTEGER; 43ac1c8e35SKuppuswamy Sathyanarayanan req.integer.value = 1; 44ac1c8e35SKuppuswamy Sathyanarayanan 45ac1c8e35SKuppuswamy Sathyanarayanan argv4.type = ACPI_TYPE_PACKAGE; 46ac1c8e35SKuppuswamy Sathyanarayanan argv4.package.count = 1; 47ac1c8e35SKuppuswamy Sathyanarayanan argv4.package.elements = &req; 48ac1c8e35SKuppuswamy Sathyanarayanan 49f24ba846SKuppuswamy Sathyanarayanan obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 6, 50ac1c8e35SKuppuswamy Sathyanarayanan EDR_PORT_DPC_ENABLE_DSM, &argv4); 51ac1c8e35SKuppuswamy Sathyanarayanan if (!obj) 52ac1c8e35SKuppuswamy Sathyanarayanan return 0; 53ac1c8e35SKuppuswamy Sathyanarayanan 54ac1c8e35SKuppuswamy Sathyanarayanan if (obj->type != ACPI_TYPE_INTEGER) { 55ac1c8e35SKuppuswamy Sathyanarayanan pci_err(pdev, FW_BUG "Enable DPC _DSM returned non integer\n"); 56ac1c8e35SKuppuswamy Sathyanarayanan status = -EIO; 57ac1c8e35SKuppuswamy Sathyanarayanan } 58ac1c8e35SKuppuswamy Sathyanarayanan 59ac1c8e35SKuppuswamy Sathyanarayanan if (obj->integer.value != 1) { 60ac1c8e35SKuppuswamy Sathyanarayanan pci_err(pdev, "Enable DPC _DSM failed to enable DPC\n"); 61ac1c8e35SKuppuswamy Sathyanarayanan status = -EIO; 62ac1c8e35SKuppuswamy Sathyanarayanan } 63ac1c8e35SKuppuswamy Sathyanarayanan 64ac1c8e35SKuppuswamy Sathyanarayanan ACPI_FREE(obj); 65ac1c8e35SKuppuswamy Sathyanarayanan 66ac1c8e35SKuppuswamy Sathyanarayanan return status; 67ac1c8e35SKuppuswamy Sathyanarayanan } 68ac1c8e35SKuppuswamy Sathyanarayanan 69ac1c8e35SKuppuswamy Sathyanarayanan /* 70ac1c8e35SKuppuswamy Sathyanarayanan * _DSM wrapper function to locate DPC port 71ac1c8e35SKuppuswamy Sathyanarayanan * @pdev : Device which received EDR event 72ac1c8e35SKuppuswamy Sathyanarayanan * 73ac1c8e35SKuppuswamy Sathyanarayanan * Returns pci_dev or NULL. Caller is responsible for dropping a reference 74ac1c8e35SKuppuswamy Sathyanarayanan * on the returned pci_dev with pci_dev_put(). 75ac1c8e35SKuppuswamy Sathyanarayanan */ 76ac1c8e35SKuppuswamy Sathyanarayanan static struct pci_dev *acpi_dpc_port_get(struct pci_dev *pdev) 77ac1c8e35SKuppuswamy Sathyanarayanan { 78ac1c8e35SKuppuswamy Sathyanarayanan struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 79ac1c8e35SKuppuswamy Sathyanarayanan union acpi_object *obj; 80ac1c8e35SKuppuswamy Sathyanarayanan u16 port; 81ac1c8e35SKuppuswamy Sathyanarayanan 82ac1c8e35SKuppuswamy Sathyanarayanan /* 83*e2e78a29SKuppuswamy Sathyanarayanan * If EDR_PORT_LOCATE_DSM is not implemented under the target of 84*e2e78a29SKuppuswamy Sathyanarayanan * EDR, the target is the port that experienced the containment 85*e2e78a29SKuppuswamy Sathyanarayanan * event (PCI Firmware r3.3, sec 4.6.13). 86ac1c8e35SKuppuswamy Sathyanarayanan */ 87ac1c8e35SKuppuswamy Sathyanarayanan if (!acpi_check_dsm(adev->handle, &pci_acpi_dsm_guid, 5, 88ac1c8e35SKuppuswamy Sathyanarayanan 1ULL << EDR_PORT_LOCATE_DSM)) 89ac1c8e35SKuppuswamy Sathyanarayanan return pci_dev_get(pdev); 90ac1c8e35SKuppuswamy Sathyanarayanan 91ac1c8e35SKuppuswamy Sathyanarayanan obj = acpi_evaluate_dsm(adev->handle, &pci_acpi_dsm_guid, 5, 92ac1c8e35SKuppuswamy Sathyanarayanan EDR_PORT_LOCATE_DSM, NULL); 93ac1c8e35SKuppuswamy Sathyanarayanan if (!obj) 94ac1c8e35SKuppuswamy Sathyanarayanan return pci_dev_get(pdev); 95ac1c8e35SKuppuswamy Sathyanarayanan 96ac1c8e35SKuppuswamy Sathyanarayanan if (obj->type != ACPI_TYPE_INTEGER) { 97ac1c8e35SKuppuswamy Sathyanarayanan ACPI_FREE(obj); 98ac1c8e35SKuppuswamy Sathyanarayanan pci_err(pdev, FW_BUG "Locate Port _DSM returned non integer\n"); 99ac1c8e35SKuppuswamy Sathyanarayanan return NULL; 100ac1c8e35SKuppuswamy Sathyanarayanan } 101ac1c8e35SKuppuswamy Sathyanarayanan 102ac1c8e35SKuppuswamy Sathyanarayanan /* 103*e2e78a29SKuppuswamy Sathyanarayanan * Bit 31 represents the success/failure of the operation. If bit 104*e2e78a29SKuppuswamy Sathyanarayanan * 31 is set, the operation failed. 105*e2e78a29SKuppuswamy Sathyanarayanan */ 106*e2e78a29SKuppuswamy Sathyanarayanan if (obj->integer.value & BIT(31)) { 107*e2e78a29SKuppuswamy Sathyanarayanan ACPI_FREE(obj); 108*e2e78a29SKuppuswamy Sathyanarayanan pci_err(pdev, "Locate Port _DSM failed\n"); 109*e2e78a29SKuppuswamy Sathyanarayanan return NULL; 110*e2e78a29SKuppuswamy Sathyanarayanan } 111*e2e78a29SKuppuswamy Sathyanarayanan 112*e2e78a29SKuppuswamy Sathyanarayanan /* 113ac1c8e35SKuppuswamy Sathyanarayanan * Firmware returns DPC port BDF details in following format: 114ac1c8e35SKuppuswamy Sathyanarayanan * 15:8 = bus 115ac1c8e35SKuppuswamy Sathyanarayanan * 7:3 = device 116ac1c8e35SKuppuswamy Sathyanarayanan * 2:0 = function 117ac1c8e35SKuppuswamy Sathyanarayanan */ 118ac1c8e35SKuppuswamy Sathyanarayanan port = obj->integer.value; 119ac1c8e35SKuppuswamy Sathyanarayanan 120ac1c8e35SKuppuswamy Sathyanarayanan ACPI_FREE(obj); 121ac1c8e35SKuppuswamy Sathyanarayanan 122ac1c8e35SKuppuswamy Sathyanarayanan return pci_get_domain_bus_and_slot(pci_domain_nr(pdev->bus), 123ac1c8e35SKuppuswamy Sathyanarayanan PCI_BUS_NUM(port), port & 0xff); 124ac1c8e35SKuppuswamy Sathyanarayanan } 125ac1c8e35SKuppuswamy Sathyanarayanan 126ac1c8e35SKuppuswamy Sathyanarayanan /* 127ac1c8e35SKuppuswamy Sathyanarayanan * _OST wrapper function to let firmware know the status of EDR event 128ac1c8e35SKuppuswamy Sathyanarayanan * @pdev : Device used to send _OST 129ac1c8e35SKuppuswamy Sathyanarayanan * @edev : Device which experienced EDR event 130ac1c8e35SKuppuswamy Sathyanarayanan * @status : Status of EDR event 131ac1c8e35SKuppuswamy Sathyanarayanan */ 132ac1c8e35SKuppuswamy Sathyanarayanan static int acpi_send_edr_status(struct pci_dev *pdev, struct pci_dev *edev, 133ac1c8e35SKuppuswamy Sathyanarayanan u16 status) 134ac1c8e35SKuppuswamy Sathyanarayanan { 135ac1c8e35SKuppuswamy Sathyanarayanan struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 136ac1c8e35SKuppuswamy Sathyanarayanan u32 ost_status; 137ac1c8e35SKuppuswamy Sathyanarayanan 138ac1c8e35SKuppuswamy Sathyanarayanan pci_dbg(pdev, "Status for %s: %#x\n", pci_name(edev), status); 139ac1c8e35SKuppuswamy Sathyanarayanan 140ac1c8e35SKuppuswamy Sathyanarayanan ost_status = PCI_DEVID(edev->bus->number, edev->devfn) << 16; 141ac1c8e35SKuppuswamy Sathyanarayanan ost_status |= status; 142ac1c8e35SKuppuswamy Sathyanarayanan 143ac1c8e35SKuppuswamy Sathyanarayanan status = acpi_evaluate_ost(adev->handle, ACPI_NOTIFY_DISCONNECT_RECOVER, 144ac1c8e35SKuppuswamy Sathyanarayanan ost_status, NULL); 145ac1c8e35SKuppuswamy Sathyanarayanan if (ACPI_FAILURE(status)) 146ac1c8e35SKuppuswamy Sathyanarayanan return -EINVAL; 147ac1c8e35SKuppuswamy Sathyanarayanan 148ac1c8e35SKuppuswamy Sathyanarayanan return 0; 149ac1c8e35SKuppuswamy Sathyanarayanan } 150ac1c8e35SKuppuswamy Sathyanarayanan 151ac1c8e35SKuppuswamy Sathyanarayanan static void edr_handle_event(acpi_handle handle, u32 event, void *data) 152ac1c8e35SKuppuswamy Sathyanarayanan { 153ac1c8e35SKuppuswamy Sathyanarayanan struct pci_dev *pdev = data, *edev; 154ac1c8e35SKuppuswamy Sathyanarayanan pci_ers_result_t estate = PCI_ERS_RESULT_DISCONNECT; 155ac1c8e35SKuppuswamy Sathyanarayanan u16 status; 156ac1c8e35SKuppuswamy Sathyanarayanan 157ac1c8e35SKuppuswamy Sathyanarayanan if (event != ACPI_NOTIFY_DISCONNECT_RECOVER) 158ac1c8e35SKuppuswamy Sathyanarayanan return; 159ac1c8e35SKuppuswamy Sathyanarayanan 160774820b3SBjorn Helgaas /* 161774820b3SBjorn Helgaas * pdev is a Root Port or Downstream Port that is still present and 162774820b3SBjorn Helgaas * has triggered a containment event, e.g., DPC, so its child 163774820b3SBjorn Helgaas * devices have been disconnected (ACPI r6.5, sec 5.6.6). 164774820b3SBjorn Helgaas */ 165af03958dSKuppuswamy Sathyanarayanan pci_info(pdev, "EDR event received\n"); 166af03958dSKuppuswamy Sathyanarayanan 167774820b3SBjorn Helgaas /* 168774820b3SBjorn Helgaas * Locate the port that experienced the containment event. pdev 169774820b3SBjorn Helgaas * may be that port or a parent of it (PCI Firmware r3.3, sec 170774820b3SBjorn Helgaas * 4.6.13). 171774820b3SBjorn Helgaas */ 172ac1c8e35SKuppuswamy Sathyanarayanan edev = acpi_dpc_port_get(pdev); 173ac1c8e35SKuppuswamy Sathyanarayanan if (!edev) { 174ac1c8e35SKuppuswamy Sathyanarayanan pci_err(pdev, "Firmware failed to locate DPC port\n"); 175ac1c8e35SKuppuswamy Sathyanarayanan return; 176ac1c8e35SKuppuswamy Sathyanarayanan } 177ac1c8e35SKuppuswamy Sathyanarayanan 178ac1c8e35SKuppuswamy Sathyanarayanan pci_dbg(pdev, "Reported EDR dev: %s\n", pci_name(edev)); 179ac1c8e35SKuppuswamy Sathyanarayanan 180ac1c8e35SKuppuswamy Sathyanarayanan /* If port does not support DPC, just send the OST */ 181ac1c8e35SKuppuswamy Sathyanarayanan if (!edev->dpc_cap) { 182ac1c8e35SKuppuswamy Sathyanarayanan pci_err(edev, FW_BUG "This device doesn't support DPC\n"); 183ac1c8e35SKuppuswamy Sathyanarayanan goto send_ost; 184ac1c8e35SKuppuswamy Sathyanarayanan } 185ac1c8e35SKuppuswamy Sathyanarayanan 186ac1c8e35SKuppuswamy Sathyanarayanan /* Check if there is a valid DPC trigger */ 187ac1c8e35SKuppuswamy Sathyanarayanan pci_read_config_word(edev, edev->dpc_cap + PCI_EXP_DPC_STATUS, &status); 188ac1c8e35SKuppuswamy Sathyanarayanan if (!(status & PCI_EXP_DPC_STATUS_TRIGGER)) { 189ac1c8e35SKuppuswamy Sathyanarayanan pci_err(edev, "Invalid DPC trigger %#010x\n", status); 190ac1c8e35SKuppuswamy Sathyanarayanan goto send_ost; 191ac1c8e35SKuppuswamy Sathyanarayanan } 192ac1c8e35SKuppuswamy Sathyanarayanan 193ac1c8e35SKuppuswamy Sathyanarayanan dpc_process_error(edev); 194ac1c8e35SKuppuswamy Sathyanarayanan pci_aer_raw_clear_status(edev); 195ac1c8e35SKuppuswamy Sathyanarayanan 196ac1c8e35SKuppuswamy Sathyanarayanan /* 197ac1c8e35SKuppuswamy Sathyanarayanan * Irrespective of whether the DPC event is triggered by ERR_FATAL 198ac1c8e35SKuppuswamy Sathyanarayanan * or ERR_NONFATAL, since the link is already down, use the FATAL 199ac1c8e35SKuppuswamy Sathyanarayanan * error recovery path for both cases. 200ac1c8e35SKuppuswamy Sathyanarayanan */ 201ac1c8e35SKuppuswamy Sathyanarayanan estate = pcie_do_recovery(edev, pci_channel_io_frozen, dpc_reset_link); 202ac1c8e35SKuppuswamy Sathyanarayanan 203ac1c8e35SKuppuswamy Sathyanarayanan send_ost: 204ac1c8e35SKuppuswamy Sathyanarayanan 205ac1c8e35SKuppuswamy Sathyanarayanan /* 206ac1c8e35SKuppuswamy Sathyanarayanan * If recovery is successful, send _OST(0xF, BDF << 16 | 0x80) 207ac1c8e35SKuppuswamy Sathyanarayanan * to firmware. If not successful, send _OST(0xF, BDF << 16 | 0x81). 208ac1c8e35SKuppuswamy Sathyanarayanan */ 209ac1c8e35SKuppuswamy Sathyanarayanan if (estate == PCI_ERS_RESULT_RECOVERED) { 210ac1c8e35SKuppuswamy Sathyanarayanan pci_dbg(edev, "DPC port successfully recovered\n"); 211c441b1e0SKuppuswamy Sathyanarayanan pcie_clear_device_status(edev); 212ac1c8e35SKuppuswamy Sathyanarayanan acpi_send_edr_status(pdev, edev, EDR_OST_SUCCESS); 213ac1c8e35SKuppuswamy Sathyanarayanan } else { 214ac1c8e35SKuppuswamy Sathyanarayanan pci_dbg(edev, "DPC port recovery failed\n"); 215ac1c8e35SKuppuswamy Sathyanarayanan acpi_send_edr_status(pdev, edev, EDR_OST_FAILED); 216ac1c8e35SKuppuswamy Sathyanarayanan } 217ac1c8e35SKuppuswamy Sathyanarayanan 218ac1c8e35SKuppuswamy Sathyanarayanan pci_dev_put(edev); 219ac1c8e35SKuppuswamy Sathyanarayanan } 220ac1c8e35SKuppuswamy Sathyanarayanan 221ac1c8e35SKuppuswamy Sathyanarayanan void pci_acpi_add_edr_notifier(struct pci_dev *pdev) 222ac1c8e35SKuppuswamy Sathyanarayanan { 223ac1c8e35SKuppuswamy Sathyanarayanan struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 224ac1c8e35SKuppuswamy Sathyanarayanan acpi_status status; 225ac1c8e35SKuppuswamy Sathyanarayanan 226ac1c8e35SKuppuswamy Sathyanarayanan if (!adev) { 227ac1c8e35SKuppuswamy Sathyanarayanan pci_dbg(pdev, "No valid ACPI node, skipping EDR init\n"); 228ac1c8e35SKuppuswamy Sathyanarayanan return; 229ac1c8e35SKuppuswamy Sathyanarayanan } 230ac1c8e35SKuppuswamy Sathyanarayanan 231ac1c8e35SKuppuswamy Sathyanarayanan status = acpi_install_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, 232ac1c8e35SKuppuswamy Sathyanarayanan edr_handle_event, pdev); 233ac1c8e35SKuppuswamy Sathyanarayanan if (ACPI_FAILURE(status)) { 234ac1c8e35SKuppuswamy Sathyanarayanan pci_err(pdev, "Failed to install notify handler\n"); 235ac1c8e35SKuppuswamy Sathyanarayanan return; 236ac1c8e35SKuppuswamy Sathyanarayanan } 237ac1c8e35SKuppuswamy Sathyanarayanan 238ac1c8e35SKuppuswamy Sathyanarayanan if (acpi_enable_dpc(pdev)) 239ac1c8e35SKuppuswamy Sathyanarayanan acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, 240ac1c8e35SKuppuswamy Sathyanarayanan edr_handle_event); 241ac1c8e35SKuppuswamy Sathyanarayanan else 242ac1c8e35SKuppuswamy Sathyanarayanan pci_dbg(pdev, "Notify handler installed\n"); 243ac1c8e35SKuppuswamy Sathyanarayanan } 244ac1c8e35SKuppuswamy Sathyanarayanan 245ac1c8e35SKuppuswamy Sathyanarayanan void pci_acpi_remove_edr_notifier(struct pci_dev *pdev) 246ac1c8e35SKuppuswamy Sathyanarayanan { 247ac1c8e35SKuppuswamy Sathyanarayanan struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 248ac1c8e35SKuppuswamy Sathyanarayanan 249ac1c8e35SKuppuswamy Sathyanarayanan if (!adev) 250ac1c8e35SKuppuswamy Sathyanarayanan return; 251ac1c8e35SKuppuswamy Sathyanarayanan 252ac1c8e35SKuppuswamy Sathyanarayanan acpi_remove_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, 253ac1c8e35SKuppuswamy Sathyanarayanan edr_handle_event); 254ac1c8e35SKuppuswamy Sathyanarayanan pci_dbg(pdev, "Notify handler removed\n"); 255ac1c8e35SKuppuswamy Sathyanarayanan } 256