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