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 | */ |
28 | static 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(handle: adev->handle, guid: &pci_acpi_dsm_guid, rev: 5, |
39 | funcs: 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(handle: adev->handle, guid: &pci_acpi_dsm_guid, rev: 5, |
55 | EDR_PORT_DPC_ENABLE_DSM, argv4: &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 | */ |
81 | static 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(handle: adev->handle, guid: &pci_acpi_dsm_guid, rev: 5, |
92 | funcs: 1ULL << EDR_PORT_LOCATE_DSM)) |
93 | return pci_dev_get(dev: pdev); |
94 | |
95 | obj = acpi_evaluate_dsm(handle: adev->handle, guid: &pci_acpi_dsm_guid, rev: 5, |
96 | EDR_PORT_LOCATE_DSM, NULL); |
97 | if (!obj) |
98 | return pci_dev_get(dev: 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(domain: pci_domain_nr(bus: pdev->bus), |
117 | PCI_BUS_NUM(port), devfn: 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 | */ |
126 | static 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(handle: adev->handle, ACPI_NOTIFY_DISCONNECT_RECOVER, |
138 | status_code: ost_status, NULL); |
139 | if (ACPI_FAILURE(status)) |
140 | return -EINVAL; |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | static 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 | /* |
155 | * pdev is a Root Port or Downstream Port that is still present and |
156 | * has triggered a containment event, e.g., DPC, so its child |
157 | * devices have been disconnected (ACPI r6.5, sec 5.6.6). |
158 | */ |
159 | pci_info(pdev, "EDR event received\n" ); |
160 | |
161 | /* |
162 | * Locate the port that experienced the containment event. pdev |
163 | * may be that port or a parent of it (PCI Firmware r3.3, sec |
164 | * 4.6.13). |
165 | */ |
166 | edev = acpi_dpc_port_get(pdev); |
167 | if (!edev) { |
168 | pci_err(pdev, "Firmware failed to locate DPC port\n" ); |
169 | return; |
170 | } |
171 | |
172 | pci_dbg(pdev, "Reported EDR dev: %s\n" , pci_name(edev)); |
173 | |
174 | /* If port does not support DPC, just send the OST */ |
175 | if (!edev->dpc_cap) { |
176 | pci_err(edev, FW_BUG "This device doesn't support DPC\n" ); |
177 | goto send_ost; |
178 | } |
179 | |
180 | /* Check if there is a valid DPC trigger */ |
181 | pci_read_config_word(dev: edev, where: edev->dpc_cap + PCI_EXP_DPC_STATUS, val: &status); |
182 | if (!(status & PCI_EXP_DPC_STATUS_TRIGGER)) { |
183 | pci_err(edev, "Invalid DPC trigger %#010x\n" , status); |
184 | goto send_ost; |
185 | } |
186 | |
187 | dpc_process_error(pdev: edev); |
188 | pci_aer_raw_clear_status(dev: edev); |
189 | |
190 | /* |
191 | * Irrespective of whether the DPC event is triggered by ERR_FATAL |
192 | * or ERR_NONFATAL, since the link is already down, use the FATAL |
193 | * error recovery path for both cases. |
194 | */ |
195 | estate = pcie_do_recovery(dev: edev, state: pci_channel_io_frozen, reset_subordinates: dpc_reset_link); |
196 | |
197 | send_ost: |
198 | |
199 | /* |
200 | * If recovery is successful, send _OST(0xF, BDF << 16 | 0x80) |
201 | * to firmware. If not successful, send _OST(0xF, BDF << 16 | 0x81). |
202 | */ |
203 | if (estate == PCI_ERS_RESULT_RECOVERED) { |
204 | pci_dbg(edev, "DPC port successfully recovered\n" ); |
205 | pcie_clear_device_status(dev: edev); |
206 | acpi_send_edr_status(pdev, edev, EDR_OST_SUCCESS); |
207 | } else { |
208 | pci_dbg(edev, "DPC port recovery failed\n" ); |
209 | acpi_send_edr_status(pdev, edev, EDR_OST_FAILED); |
210 | } |
211 | |
212 | pci_dev_put(dev: edev); |
213 | } |
214 | |
215 | void pci_acpi_add_edr_notifier(struct pci_dev *pdev) |
216 | { |
217 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); |
218 | acpi_status status; |
219 | |
220 | if (!adev) { |
221 | pci_dbg(pdev, "No valid ACPI node, skipping EDR init\n" ); |
222 | return; |
223 | } |
224 | |
225 | status = acpi_install_notify_handler(device: adev->handle, ACPI_SYSTEM_NOTIFY, |
226 | handler: edr_handle_event, context: pdev); |
227 | if (ACPI_FAILURE(status)) { |
228 | pci_err(pdev, "Failed to install notify handler\n" ); |
229 | return; |
230 | } |
231 | |
232 | if (acpi_enable_dpc(pdev)) |
233 | acpi_remove_notify_handler(device: adev->handle, ACPI_SYSTEM_NOTIFY, |
234 | handler: edr_handle_event); |
235 | else |
236 | pci_dbg(pdev, "Notify handler installed\n" ); |
237 | } |
238 | |
239 | void pci_acpi_remove_edr_notifier(struct pci_dev *pdev) |
240 | { |
241 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); |
242 | |
243 | if (!adev) |
244 | return; |
245 | |
246 | acpi_remove_notify_handler(device: adev->handle, ACPI_SYSTEM_NOTIFY, |
247 | handler: edr_handle_event); |
248 | pci_dbg(pdev, "Notify handler removed\n" ); |
249 | } |
250 | |