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