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