1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * PCI Express Hot Plug Controller Driver |
4 | * |
5 | * Copyright (C) 1995,2001 Compaq Computer Corporation |
6 | * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) |
7 | * Copyright (C) 2001 IBM Corp. |
8 | * Copyright (C) 2003-2004 Intel Corporation |
9 | * |
10 | * All rights reserved. |
11 | * |
12 | * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> |
13 | * |
14 | */ |
15 | |
16 | #define dev_fmt(fmt) "pciehp: " fmt |
17 | |
18 | #include <linux/kernel.h> |
19 | #include <linux/types.h> |
20 | #include <linux/pm_runtime.h> |
21 | #include <linux/pci.h> |
22 | #include "pciehp.h" |
23 | |
24 | /* The following routines constitute the bulk of the |
25 | hotplug controller logic |
26 | */ |
27 | |
28 | #define SAFE_REMOVAL true |
29 | #define SURPRISE_REMOVAL false |
30 | |
31 | static void set_slot_off(struct controller *ctrl) |
32 | { |
33 | /* |
34 | * Turn off slot, turn on attention indicator, turn off power |
35 | * indicator |
36 | */ |
37 | if (POWER_CTRL(ctrl)) { |
38 | pciehp_power_off_slot(ctrl); |
39 | |
40 | /* |
41 | * After turning power off, we must wait for at least 1 second |
42 | * before taking any action that relies on power having been |
43 | * removed from the slot/adapter. |
44 | */ |
45 | msleep(msecs: 1000); |
46 | } |
47 | |
48 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, |
49 | PCI_EXP_SLTCTL_ATTN_IND_ON); |
50 | } |
51 | |
52 | /** |
53 | * board_added - Called after a board has been added to the system. |
54 | * @ctrl: PCIe hotplug controller where board is added |
55 | * |
56 | * Turns power on for the board. |
57 | * Configures board. |
58 | */ |
59 | static int board_added(struct controller *ctrl) |
60 | { |
61 | int retval = 0; |
62 | struct pci_bus *parent = ctrl->pcie->port->subordinate; |
63 | |
64 | if (POWER_CTRL(ctrl)) { |
65 | /* Power on slot */ |
66 | retval = pciehp_power_on_slot(ctrl); |
67 | if (retval) |
68 | return retval; |
69 | } |
70 | |
71 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_BLINK, |
72 | INDICATOR_NOOP); |
73 | |
74 | /* Check link training status */ |
75 | retval = pciehp_check_link_status(ctrl); |
76 | if (retval) |
77 | goto err_exit; |
78 | |
79 | /* Check for a power fault */ |
80 | if (ctrl->power_fault_detected || pciehp_query_power_fault(ctrl)) { |
81 | ctrl_err(ctrl, "Slot(%s): Power fault\n" , slot_name(ctrl)); |
82 | retval = -EIO; |
83 | goto err_exit; |
84 | } |
85 | |
86 | retval = pciehp_configure_device(ctrl); |
87 | if (retval) { |
88 | if (retval != -EEXIST) { |
89 | ctrl_err(ctrl, "Cannot add device at %04x:%02x:00\n" , |
90 | pci_domain_nr(parent), parent->number); |
91 | goto err_exit; |
92 | } |
93 | } |
94 | |
95 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_ON, |
96 | PCI_EXP_SLTCTL_ATTN_IND_OFF); |
97 | return 0; |
98 | |
99 | err_exit: |
100 | set_slot_off(ctrl); |
101 | return retval; |
102 | } |
103 | |
104 | /** |
105 | * remove_board - Turn off slot and Power Indicator |
106 | * @ctrl: PCIe hotplug controller where board is being removed |
107 | * @safe_removal: whether the board is safely removed (versus surprise removed) |
108 | */ |
109 | static void remove_board(struct controller *ctrl, bool safe_removal) |
110 | { |
111 | pciehp_unconfigure_device(ctrl, presence: safe_removal); |
112 | |
113 | if (POWER_CTRL(ctrl)) { |
114 | pciehp_power_off_slot(ctrl); |
115 | |
116 | /* |
117 | * After turning power off, we must wait for at least 1 second |
118 | * before taking any action that relies on power having been |
119 | * removed from the slot/adapter. |
120 | */ |
121 | msleep(msecs: 1000); |
122 | |
123 | /* Ignore link or presence changes caused by power off */ |
124 | atomic_and(i: ~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC), |
125 | v: &ctrl->pending_events); |
126 | } |
127 | |
128 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, |
129 | INDICATOR_NOOP); |
130 | } |
131 | |
132 | static int pciehp_enable_slot(struct controller *ctrl); |
133 | static int pciehp_disable_slot(struct controller *ctrl, bool safe_removal); |
134 | |
135 | void pciehp_request(struct controller *ctrl, int action) |
136 | { |
137 | atomic_or(i: action, v: &ctrl->pending_events); |
138 | if (!pciehp_poll_mode) |
139 | irq_wake_thread(irq: ctrl->pcie->irq, dev_id: ctrl); |
140 | } |
141 | |
142 | void pciehp_queue_pushbutton_work(struct work_struct *work) |
143 | { |
144 | struct controller *ctrl = container_of(work, struct controller, |
145 | button_work.work); |
146 | |
147 | mutex_lock(&ctrl->state_lock); |
148 | switch (ctrl->state) { |
149 | case BLINKINGOFF_STATE: |
150 | pciehp_request(ctrl, DISABLE_SLOT); |
151 | break; |
152 | case BLINKINGON_STATE: |
153 | pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); |
154 | break; |
155 | default: |
156 | break; |
157 | } |
158 | mutex_unlock(lock: &ctrl->state_lock); |
159 | } |
160 | |
161 | void pciehp_handle_button_press(struct controller *ctrl) |
162 | { |
163 | mutex_lock(&ctrl->state_lock); |
164 | switch (ctrl->state) { |
165 | case OFF_STATE: |
166 | case ON_STATE: |
167 | if (ctrl->state == ON_STATE) { |
168 | ctrl->state = BLINKINGOFF_STATE; |
169 | ctrl_info(ctrl, "Slot(%s): Button press: will power off in 5 sec\n" , |
170 | slot_name(ctrl)); |
171 | } else { |
172 | ctrl->state = BLINKINGON_STATE; |
173 | ctrl_info(ctrl, "Slot(%s): Button press: will power on in 5 sec\n" , |
174 | slot_name(ctrl)); |
175 | } |
176 | /* blink power indicator and turn off attention */ |
177 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_BLINK, |
178 | PCI_EXP_SLTCTL_ATTN_IND_OFF); |
179 | schedule_delayed_work(dwork: &ctrl->button_work, delay: 5 * HZ); |
180 | break; |
181 | case BLINKINGOFF_STATE: |
182 | case BLINKINGON_STATE: |
183 | /* |
184 | * Cancel if we are still blinking; this means that we |
185 | * press the attention again before the 5 sec. limit |
186 | * expires to cancel hot-add or hot-remove |
187 | */ |
188 | cancel_delayed_work(dwork: &ctrl->button_work); |
189 | if (ctrl->state == BLINKINGOFF_STATE) { |
190 | ctrl->state = ON_STATE; |
191 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_ON, |
192 | PCI_EXP_SLTCTL_ATTN_IND_OFF); |
193 | ctrl_info(ctrl, "Slot(%s): Button press: canceling request to power off\n" , |
194 | slot_name(ctrl)); |
195 | } else { |
196 | ctrl->state = OFF_STATE; |
197 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, |
198 | PCI_EXP_SLTCTL_ATTN_IND_OFF); |
199 | ctrl_info(ctrl, "Slot(%s): Button press: canceling request to power on\n" , |
200 | slot_name(ctrl)); |
201 | } |
202 | break; |
203 | default: |
204 | ctrl_err(ctrl, "Slot(%s): Button press: ignoring invalid state %#x\n" , |
205 | slot_name(ctrl), ctrl->state); |
206 | break; |
207 | } |
208 | mutex_unlock(lock: &ctrl->state_lock); |
209 | } |
210 | |
211 | void pciehp_handle_disable_request(struct controller *ctrl) |
212 | { |
213 | mutex_lock(&ctrl->state_lock); |
214 | switch (ctrl->state) { |
215 | case BLINKINGON_STATE: |
216 | case BLINKINGOFF_STATE: |
217 | cancel_delayed_work(dwork: &ctrl->button_work); |
218 | break; |
219 | } |
220 | ctrl->state = POWEROFF_STATE; |
221 | mutex_unlock(lock: &ctrl->state_lock); |
222 | |
223 | ctrl->request_result = pciehp_disable_slot(ctrl, SAFE_REMOVAL); |
224 | } |
225 | |
226 | void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) |
227 | { |
228 | int present, link_active; |
229 | |
230 | /* |
231 | * If the slot is on and presence or link has changed, turn it off. |
232 | * Even if it's occupied again, we cannot assume the card is the same. |
233 | */ |
234 | mutex_lock(&ctrl->state_lock); |
235 | switch (ctrl->state) { |
236 | case BLINKINGOFF_STATE: |
237 | cancel_delayed_work(dwork: &ctrl->button_work); |
238 | fallthrough; |
239 | case ON_STATE: |
240 | ctrl->state = POWEROFF_STATE; |
241 | mutex_unlock(lock: &ctrl->state_lock); |
242 | if (events & PCI_EXP_SLTSTA_DLLSC) |
243 | ctrl_info(ctrl, "Slot(%s): Link Down\n" , |
244 | slot_name(ctrl)); |
245 | if (events & PCI_EXP_SLTSTA_PDC) |
246 | ctrl_info(ctrl, "Slot(%s): Card not present\n" , |
247 | slot_name(ctrl)); |
248 | pciehp_disable_slot(ctrl, SURPRISE_REMOVAL); |
249 | break; |
250 | default: |
251 | mutex_unlock(lock: &ctrl->state_lock); |
252 | break; |
253 | } |
254 | |
255 | /* Turn the slot on if it's occupied or link is up */ |
256 | mutex_lock(&ctrl->state_lock); |
257 | present = pciehp_card_present(ctrl); |
258 | link_active = pciehp_check_link_active(ctrl); |
259 | if (present <= 0 && link_active <= 0) { |
260 | if (ctrl->state == BLINKINGON_STATE) { |
261 | ctrl->state = OFF_STATE; |
262 | cancel_delayed_work(dwork: &ctrl->button_work); |
263 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, |
264 | INDICATOR_NOOP); |
265 | ctrl_info(ctrl, "Slot(%s): Card not present\n" , |
266 | slot_name(ctrl)); |
267 | } |
268 | mutex_unlock(lock: &ctrl->state_lock); |
269 | return; |
270 | } |
271 | |
272 | switch (ctrl->state) { |
273 | case BLINKINGON_STATE: |
274 | cancel_delayed_work(dwork: &ctrl->button_work); |
275 | fallthrough; |
276 | case OFF_STATE: |
277 | ctrl->state = POWERON_STATE; |
278 | mutex_unlock(lock: &ctrl->state_lock); |
279 | if (present) |
280 | ctrl_info(ctrl, "Slot(%s): Card present\n" , |
281 | slot_name(ctrl)); |
282 | if (link_active) |
283 | ctrl_info(ctrl, "Slot(%s): Link Up\n" , |
284 | slot_name(ctrl)); |
285 | ctrl->request_result = pciehp_enable_slot(ctrl); |
286 | break; |
287 | default: |
288 | mutex_unlock(lock: &ctrl->state_lock); |
289 | break; |
290 | } |
291 | } |
292 | |
293 | static int __pciehp_enable_slot(struct controller *ctrl) |
294 | { |
295 | u8 getstatus = 0; |
296 | |
297 | if (MRL_SENS(ctrl)) { |
298 | pciehp_get_latch_status(ctrl, status: &getstatus); |
299 | if (getstatus) { |
300 | ctrl_info(ctrl, "Slot(%s): Latch open\n" , |
301 | slot_name(ctrl)); |
302 | return -ENODEV; |
303 | } |
304 | } |
305 | |
306 | if (POWER_CTRL(ctrl)) { |
307 | pciehp_get_power_status(ctrl, status: &getstatus); |
308 | if (getstatus) { |
309 | ctrl_info(ctrl, "Slot(%s): Already enabled\n" , |
310 | slot_name(ctrl)); |
311 | return 0; |
312 | } |
313 | } |
314 | |
315 | return board_added(ctrl); |
316 | } |
317 | |
318 | static int pciehp_enable_slot(struct controller *ctrl) |
319 | { |
320 | int ret; |
321 | |
322 | pm_runtime_get_sync(dev: &ctrl->pcie->port->dev); |
323 | ret = __pciehp_enable_slot(ctrl); |
324 | if (ret && ATTN_BUTTN(ctrl)) |
325 | /* may be blinking */ |
326 | pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, |
327 | INDICATOR_NOOP); |
328 | pm_runtime_put(dev: &ctrl->pcie->port->dev); |
329 | |
330 | mutex_lock(&ctrl->state_lock); |
331 | ctrl->state = ret ? OFF_STATE : ON_STATE; |
332 | mutex_unlock(lock: &ctrl->state_lock); |
333 | |
334 | return ret; |
335 | } |
336 | |
337 | static int __pciehp_disable_slot(struct controller *ctrl, bool safe_removal) |
338 | { |
339 | u8 getstatus = 0; |
340 | |
341 | if (POWER_CTRL(ctrl)) { |
342 | pciehp_get_power_status(ctrl, status: &getstatus); |
343 | if (!getstatus) { |
344 | ctrl_info(ctrl, "Slot(%s): Already disabled\n" , |
345 | slot_name(ctrl)); |
346 | return -EINVAL; |
347 | } |
348 | } |
349 | |
350 | remove_board(ctrl, safe_removal); |
351 | return 0; |
352 | } |
353 | |
354 | static int pciehp_disable_slot(struct controller *ctrl, bool safe_removal) |
355 | { |
356 | int ret; |
357 | |
358 | pm_runtime_get_sync(dev: &ctrl->pcie->port->dev); |
359 | ret = __pciehp_disable_slot(ctrl, safe_removal); |
360 | pm_runtime_put(dev: &ctrl->pcie->port->dev); |
361 | |
362 | mutex_lock(&ctrl->state_lock); |
363 | ctrl->state = OFF_STATE; |
364 | mutex_unlock(lock: &ctrl->state_lock); |
365 | |
366 | return ret; |
367 | } |
368 | |
369 | int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) |
370 | { |
371 | struct controller *ctrl = to_ctrl(hotplug_slot); |
372 | |
373 | mutex_lock(&ctrl->state_lock); |
374 | switch (ctrl->state) { |
375 | case BLINKINGON_STATE: |
376 | case OFF_STATE: |
377 | mutex_unlock(lock: &ctrl->state_lock); |
378 | /* |
379 | * The IRQ thread becomes a no-op if the user pulls out the |
380 | * card before the thread wakes up, so initialize to -ENODEV. |
381 | */ |
382 | ctrl->request_result = -ENODEV; |
383 | pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); |
384 | wait_event(ctrl->requester, |
385 | !atomic_read(&ctrl->pending_events) && |
386 | !ctrl->ist_running); |
387 | return ctrl->request_result; |
388 | case POWERON_STATE: |
389 | ctrl_info(ctrl, "Slot(%s): Already in powering on state\n" , |
390 | slot_name(ctrl)); |
391 | break; |
392 | case BLINKINGOFF_STATE: |
393 | case ON_STATE: |
394 | case POWEROFF_STATE: |
395 | ctrl_info(ctrl, "Slot(%s): Already enabled\n" , |
396 | slot_name(ctrl)); |
397 | break; |
398 | default: |
399 | ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n" , |
400 | slot_name(ctrl), ctrl->state); |
401 | break; |
402 | } |
403 | mutex_unlock(lock: &ctrl->state_lock); |
404 | |
405 | return -ENODEV; |
406 | } |
407 | |
408 | int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) |
409 | { |
410 | struct controller *ctrl = to_ctrl(hotplug_slot); |
411 | |
412 | mutex_lock(&ctrl->state_lock); |
413 | switch (ctrl->state) { |
414 | case BLINKINGOFF_STATE: |
415 | case ON_STATE: |
416 | mutex_unlock(lock: &ctrl->state_lock); |
417 | pciehp_request(ctrl, DISABLE_SLOT); |
418 | wait_event(ctrl->requester, |
419 | !atomic_read(&ctrl->pending_events) && |
420 | !ctrl->ist_running); |
421 | return ctrl->request_result; |
422 | case POWEROFF_STATE: |
423 | ctrl_info(ctrl, "Slot(%s): Already in powering off state\n" , |
424 | slot_name(ctrl)); |
425 | break; |
426 | case BLINKINGON_STATE: |
427 | case OFF_STATE: |
428 | case POWERON_STATE: |
429 | ctrl_info(ctrl, "Slot(%s): Already disabled\n" , |
430 | slot_name(ctrl)); |
431 | break; |
432 | default: |
433 | ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n" , |
434 | slot_name(ctrl), ctrl->state); |
435 | break; |
436 | } |
437 | mutex_unlock(lock: &ctrl->state_lock); |
438 | |
439 | return -ENODEV; |
440 | } |
441 | |