1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Standard 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 | #include <linux/module.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/types.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/pci.h> |
21 | #include "../pci.h" |
22 | #include "shpchp.h" |
23 | |
24 | static void interrupt_event_handler(struct work_struct *work); |
25 | static int shpchp_enable_slot(struct slot *p_slot); |
26 | static int shpchp_disable_slot(struct slot *p_slot); |
27 | |
28 | static int queue_interrupt_event(struct slot *p_slot, u32 event_type) |
29 | { |
30 | struct event_info *info; |
31 | |
32 | info = kmalloc(size: sizeof(*info), GFP_ATOMIC); |
33 | if (!info) |
34 | return -ENOMEM; |
35 | |
36 | info->event_type = event_type; |
37 | info->p_slot = p_slot; |
38 | INIT_WORK(&info->work, interrupt_event_handler); |
39 | |
40 | queue_work(wq: p_slot->wq, work: &info->work); |
41 | |
42 | return 0; |
43 | } |
44 | |
45 | u8 shpchp_handle_attention_button(u8 hp_slot, struct controller *ctrl) |
46 | { |
47 | struct slot *p_slot; |
48 | u32 event_type; |
49 | |
50 | /* Attention Button Change */ |
51 | ctrl_dbg(ctrl, "Attention button interrupt received\n" ); |
52 | |
53 | p_slot = shpchp_find_slot(ctrl, device: hp_slot + ctrl->slot_device_offset); |
54 | p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); |
55 | |
56 | /* |
57 | * Button pressed - See if need to TAKE ACTION!!! |
58 | */ |
59 | ctrl_info(ctrl, "Button pressed on Slot(%s)\n" , slot_name(p_slot)); |
60 | event_type = INT_BUTTON_PRESS; |
61 | |
62 | queue_interrupt_event(p_slot, event_type); |
63 | |
64 | return 0; |
65 | |
66 | } |
67 | |
68 | u8 shpchp_handle_switch_change(u8 hp_slot, struct controller *ctrl) |
69 | { |
70 | struct slot *p_slot; |
71 | u8 getstatus; |
72 | u32 event_type; |
73 | |
74 | /* Switch Change */ |
75 | ctrl_dbg(ctrl, "Switch interrupt received\n" ); |
76 | |
77 | p_slot = shpchp_find_slot(ctrl, device: hp_slot + ctrl->slot_device_offset); |
78 | p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); |
79 | p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); |
80 | ctrl_dbg(ctrl, "Card present %x Power status %x\n" , |
81 | p_slot->presence_save, p_slot->pwr_save); |
82 | |
83 | if (getstatus) { |
84 | /* |
85 | * Switch opened |
86 | */ |
87 | ctrl_info(ctrl, "Latch open on Slot(%s)\n" , slot_name(p_slot)); |
88 | event_type = INT_SWITCH_OPEN; |
89 | if (p_slot->pwr_save && p_slot->presence_save) { |
90 | event_type = INT_POWER_FAULT; |
91 | ctrl_err(ctrl, "Surprise Removal of card\n" ); |
92 | } |
93 | } else { |
94 | /* |
95 | * Switch closed |
96 | */ |
97 | ctrl_info(ctrl, "Latch close on Slot(%s)\n" , slot_name(p_slot)); |
98 | event_type = INT_SWITCH_CLOSE; |
99 | } |
100 | |
101 | queue_interrupt_event(p_slot, event_type); |
102 | |
103 | return 1; |
104 | } |
105 | |
106 | u8 shpchp_handle_presence_change(u8 hp_slot, struct controller *ctrl) |
107 | { |
108 | struct slot *p_slot; |
109 | u32 event_type; |
110 | |
111 | /* Presence Change */ |
112 | ctrl_dbg(ctrl, "Presence/Notify input change\n" ); |
113 | |
114 | p_slot = shpchp_find_slot(ctrl, device: hp_slot + ctrl->slot_device_offset); |
115 | |
116 | /* |
117 | * Save the presence state |
118 | */ |
119 | p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); |
120 | if (p_slot->presence_save) { |
121 | /* |
122 | * Card Present |
123 | */ |
124 | ctrl_info(ctrl, "Card present on Slot(%s)\n" , |
125 | slot_name(p_slot)); |
126 | event_type = INT_PRESENCE_ON; |
127 | } else { |
128 | /* |
129 | * Not Present |
130 | */ |
131 | ctrl_info(ctrl, "Card not present on Slot(%s)\n" , |
132 | slot_name(p_slot)); |
133 | event_type = INT_PRESENCE_OFF; |
134 | } |
135 | |
136 | queue_interrupt_event(p_slot, event_type); |
137 | |
138 | return 1; |
139 | } |
140 | |
141 | u8 shpchp_handle_power_fault(u8 hp_slot, struct controller *ctrl) |
142 | { |
143 | struct slot *p_slot; |
144 | u32 event_type; |
145 | |
146 | /* Power fault */ |
147 | ctrl_dbg(ctrl, "Power fault interrupt received\n" ); |
148 | |
149 | p_slot = shpchp_find_slot(ctrl, device: hp_slot + ctrl->slot_device_offset); |
150 | |
151 | if (!(p_slot->hpc_ops->query_power_fault(p_slot))) { |
152 | /* |
153 | * Power fault Cleared |
154 | */ |
155 | ctrl_info(ctrl, "Power fault cleared on Slot(%s)\n" , |
156 | slot_name(p_slot)); |
157 | p_slot->status = 0x00; |
158 | event_type = INT_POWER_FAULT_CLEAR; |
159 | } else { |
160 | /* |
161 | * Power fault |
162 | */ |
163 | ctrl_info(ctrl, "Power fault on Slot(%s)\n" , slot_name(p_slot)); |
164 | event_type = INT_POWER_FAULT; |
165 | /* set power fault status for this board */ |
166 | p_slot->status = 0xFF; |
167 | ctrl_info(ctrl, "Power fault bit %x set\n" , hp_slot); |
168 | } |
169 | |
170 | queue_interrupt_event(p_slot, event_type); |
171 | |
172 | return 1; |
173 | } |
174 | |
175 | /* The following routines constitute the bulk of the |
176 | hotplug controller logic |
177 | */ |
178 | static int change_bus_speed(struct controller *ctrl, struct slot *p_slot, |
179 | enum pci_bus_speed speed) |
180 | { |
181 | int rc = 0; |
182 | |
183 | ctrl_dbg(ctrl, "Change speed to %d\n" , speed); |
184 | rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, speed); |
185 | if (rc) { |
186 | ctrl_err(ctrl, "%s: Issue of set bus speed mode command failed\n" , |
187 | __func__); |
188 | return WRONG_BUS_FREQUENCY; |
189 | } |
190 | return rc; |
191 | } |
192 | |
193 | static int fix_bus_speed(struct controller *ctrl, struct slot *pslot, |
194 | u8 flag, enum pci_bus_speed asp, enum pci_bus_speed bsp, |
195 | enum pci_bus_speed msp) |
196 | { |
197 | int rc = 0; |
198 | |
199 | /* |
200 | * If other slots on the same bus are occupied, we cannot |
201 | * change the bus speed. |
202 | */ |
203 | if (flag) { |
204 | if (asp < bsp) { |
205 | ctrl_err(ctrl, "Speed of bus %x and adapter %x mismatch\n" , |
206 | bsp, asp); |
207 | rc = WRONG_BUS_FREQUENCY; |
208 | } |
209 | return rc; |
210 | } |
211 | |
212 | if (asp < msp) { |
213 | if (bsp != asp) |
214 | rc = change_bus_speed(ctrl, p_slot: pslot, speed: asp); |
215 | } else { |
216 | if (bsp != msp) |
217 | rc = change_bus_speed(ctrl, p_slot: pslot, speed: msp); |
218 | } |
219 | return rc; |
220 | } |
221 | |
222 | /** |
223 | * board_added - Called after a board has been added to the system. |
224 | * @p_slot: target &slot |
225 | * |
226 | * Turns power on for the board. |
227 | * Configures board. |
228 | */ |
229 | static int board_added(struct slot *p_slot) |
230 | { |
231 | u8 hp_slot; |
232 | u8 slots_not_empty = 0; |
233 | int rc = 0; |
234 | enum pci_bus_speed asp, bsp, msp; |
235 | struct controller *ctrl = p_slot->ctrl; |
236 | struct pci_bus *parent = ctrl->pci_dev->subordinate; |
237 | |
238 | hp_slot = p_slot->device - ctrl->slot_device_offset; |
239 | |
240 | ctrl_dbg(ctrl, "%s: p_slot->device, slot_offset, hp_slot = %d, %d ,%d\n" , |
241 | __func__, p_slot->device, ctrl->slot_device_offset, hp_slot); |
242 | |
243 | /* Power on slot without connecting to bus */ |
244 | rc = p_slot->hpc_ops->power_on_slot(p_slot); |
245 | if (rc) { |
246 | ctrl_err(ctrl, "Failed to power on slot\n" ); |
247 | return -1; |
248 | } |
249 | |
250 | if ((ctrl->pci_dev->vendor == 0x8086) && (ctrl->pci_dev->device == 0x0332)) { |
251 | rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, PCI_SPEED_33MHz); |
252 | if (rc) { |
253 | ctrl_err(ctrl, "%s: Issue of set bus speed mode command failed\n" , |
254 | __func__); |
255 | return WRONG_BUS_FREQUENCY; |
256 | } |
257 | |
258 | /* turn on board, blink green LED, turn off Amber LED */ |
259 | rc = p_slot->hpc_ops->slot_enable(p_slot); |
260 | if (rc) { |
261 | ctrl_err(ctrl, "Issue of Slot Enable command failed\n" ); |
262 | return rc; |
263 | } |
264 | } |
265 | |
266 | rc = p_slot->hpc_ops->get_adapter_speed(p_slot, &asp); |
267 | if (rc) { |
268 | ctrl_err(ctrl, "Can't get adapter speed or bus mode mismatch\n" ); |
269 | return WRONG_BUS_FREQUENCY; |
270 | } |
271 | |
272 | bsp = ctrl->pci_dev->subordinate->cur_bus_speed; |
273 | msp = ctrl->pci_dev->subordinate->max_bus_speed; |
274 | |
275 | /* Check if there are other slots or devices on the same bus */ |
276 | if (!list_empty(head: &ctrl->pci_dev->subordinate->devices)) |
277 | slots_not_empty = 1; |
278 | |
279 | ctrl_dbg(ctrl, "%s: slots_not_empty %d, adapter_speed %d, bus_speed %d, max_bus_speed %d\n" , |
280 | __func__, slots_not_empty, asp, |
281 | bsp, msp); |
282 | |
283 | rc = fix_bus_speed(ctrl, pslot: p_slot, flag: slots_not_empty, asp, bsp, msp); |
284 | if (rc) |
285 | return rc; |
286 | |
287 | /* turn on board, blink green LED, turn off Amber LED */ |
288 | rc = p_slot->hpc_ops->slot_enable(p_slot); |
289 | if (rc) { |
290 | ctrl_err(ctrl, "Issue of Slot Enable command failed\n" ); |
291 | return rc; |
292 | } |
293 | |
294 | /* Wait for ~1 second */ |
295 | msleep(msecs: 1000); |
296 | |
297 | ctrl_dbg(ctrl, "%s: slot status = %x\n" , __func__, p_slot->status); |
298 | /* Check for a power fault */ |
299 | if (p_slot->status == 0xFF) { |
300 | /* power fault occurred, but it was benign */ |
301 | ctrl_dbg(ctrl, "%s: Power fault\n" , __func__); |
302 | p_slot->status = 0; |
303 | goto err_exit; |
304 | } |
305 | |
306 | if (shpchp_configure_device(p_slot)) { |
307 | ctrl_err(ctrl, "Cannot add device at %04x:%02x:%02x\n" , |
308 | pci_domain_nr(parent), p_slot->bus, p_slot->device); |
309 | goto err_exit; |
310 | } |
311 | |
312 | p_slot->status = 0; |
313 | p_slot->is_a_board = 0x01; |
314 | p_slot->pwr_save = 1; |
315 | |
316 | p_slot->hpc_ops->green_led_on(p_slot); |
317 | |
318 | return 0; |
319 | |
320 | err_exit: |
321 | /* turn off slot, turn on Amber LED, turn off Green LED */ |
322 | rc = p_slot->hpc_ops->slot_disable(p_slot); |
323 | if (rc) { |
324 | ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n" , |
325 | __func__); |
326 | return rc; |
327 | } |
328 | |
329 | return(rc); |
330 | } |
331 | |
332 | |
333 | /** |
334 | * remove_board - Turns off slot and LEDs |
335 | * @p_slot: target &slot |
336 | */ |
337 | static int remove_board(struct slot *p_slot) |
338 | { |
339 | struct controller *ctrl = p_slot->ctrl; |
340 | u8 hp_slot; |
341 | int rc; |
342 | |
343 | shpchp_unconfigure_device(p_slot); |
344 | |
345 | hp_slot = p_slot->device - ctrl->slot_device_offset; |
346 | p_slot = shpchp_find_slot(ctrl, device: hp_slot + ctrl->slot_device_offset); |
347 | |
348 | ctrl_dbg(ctrl, "%s: hp_slot = %d\n" , __func__, hp_slot); |
349 | |
350 | /* Change status to shutdown */ |
351 | if (p_slot->is_a_board) |
352 | p_slot->status = 0x01; |
353 | |
354 | /* turn off slot, turn on Amber LED, turn off Green LED */ |
355 | rc = p_slot->hpc_ops->slot_disable(p_slot); |
356 | if (rc) { |
357 | ctrl_err(ctrl, "%s: Issue of Slot Disable command failed\n" , |
358 | __func__); |
359 | return rc; |
360 | } |
361 | |
362 | rc = p_slot->hpc_ops->set_attention_status(p_slot, 0); |
363 | if (rc) { |
364 | ctrl_err(ctrl, "Issue of Set Attention command failed\n" ); |
365 | return rc; |
366 | } |
367 | |
368 | p_slot->pwr_save = 0; |
369 | p_slot->is_a_board = 0; |
370 | |
371 | return 0; |
372 | } |
373 | |
374 | |
375 | struct pushbutton_work_info { |
376 | struct slot *p_slot; |
377 | struct work_struct work; |
378 | }; |
379 | |
380 | /** |
381 | * shpchp_pushbutton_thread - handle pushbutton events |
382 | * @work: &struct work_struct to be handled |
383 | * |
384 | * Scheduled procedure to handle blocking stuff for the pushbuttons. |
385 | * Handles all pending events and exits. |
386 | */ |
387 | static void shpchp_pushbutton_thread(struct work_struct *work) |
388 | { |
389 | struct pushbutton_work_info *info = |
390 | container_of(work, struct pushbutton_work_info, work); |
391 | struct slot *p_slot = info->p_slot; |
392 | |
393 | mutex_lock(&p_slot->lock); |
394 | switch (p_slot->state) { |
395 | case POWEROFF_STATE: |
396 | mutex_unlock(lock: &p_slot->lock); |
397 | shpchp_disable_slot(p_slot); |
398 | mutex_lock(&p_slot->lock); |
399 | p_slot->state = STATIC_STATE; |
400 | break; |
401 | case POWERON_STATE: |
402 | mutex_unlock(lock: &p_slot->lock); |
403 | if (shpchp_enable_slot(p_slot)) |
404 | p_slot->hpc_ops->green_led_off(p_slot); |
405 | mutex_lock(&p_slot->lock); |
406 | p_slot->state = STATIC_STATE; |
407 | break; |
408 | default: |
409 | break; |
410 | } |
411 | mutex_unlock(lock: &p_slot->lock); |
412 | |
413 | kfree(objp: info); |
414 | } |
415 | |
416 | void shpchp_queue_pushbutton_work(struct work_struct *work) |
417 | { |
418 | struct slot *p_slot = container_of(work, struct slot, work.work); |
419 | struct pushbutton_work_info *info; |
420 | |
421 | info = kmalloc(size: sizeof(*info), GFP_KERNEL); |
422 | if (!info) { |
423 | ctrl_err(p_slot->ctrl, "%s: Cannot allocate memory\n" , |
424 | __func__); |
425 | return; |
426 | } |
427 | info->p_slot = p_slot; |
428 | INIT_WORK(&info->work, shpchp_pushbutton_thread); |
429 | |
430 | mutex_lock(&p_slot->lock); |
431 | switch (p_slot->state) { |
432 | case BLINKINGOFF_STATE: |
433 | p_slot->state = POWEROFF_STATE; |
434 | break; |
435 | case BLINKINGON_STATE: |
436 | p_slot->state = POWERON_STATE; |
437 | break; |
438 | default: |
439 | kfree(objp: info); |
440 | goto out; |
441 | } |
442 | queue_work(wq: p_slot->wq, work: &info->work); |
443 | out: |
444 | mutex_unlock(lock: &p_slot->lock); |
445 | } |
446 | |
447 | static void update_slot_info(struct slot *slot) |
448 | { |
449 | slot->hpc_ops->get_power_status(slot, &slot->pwr_save); |
450 | slot->hpc_ops->get_attention_status(slot, &slot->attention_save); |
451 | slot->hpc_ops->get_latch_status(slot, &slot->latch_save); |
452 | slot->hpc_ops->get_adapter_status(slot, &slot->presence_save); |
453 | } |
454 | |
455 | /* |
456 | * Note: This function must be called with slot->lock held |
457 | */ |
458 | static void handle_button_press_event(struct slot *p_slot) |
459 | { |
460 | u8 getstatus; |
461 | struct controller *ctrl = p_slot->ctrl; |
462 | |
463 | switch (p_slot->state) { |
464 | case STATIC_STATE: |
465 | p_slot->hpc_ops->get_power_status(p_slot, &getstatus); |
466 | if (getstatus) { |
467 | p_slot->state = BLINKINGOFF_STATE; |
468 | ctrl_info(ctrl, "PCI slot #%s - powering off due to button press\n" , |
469 | slot_name(p_slot)); |
470 | } else { |
471 | p_slot->state = BLINKINGON_STATE; |
472 | ctrl_info(ctrl, "PCI slot #%s - powering on due to button press\n" , |
473 | slot_name(p_slot)); |
474 | } |
475 | /* blink green LED and turn off amber */ |
476 | p_slot->hpc_ops->green_led_blink(p_slot); |
477 | p_slot->hpc_ops->set_attention_status(p_slot, 0); |
478 | |
479 | queue_delayed_work(wq: p_slot->wq, dwork: &p_slot->work, delay: 5*HZ); |
480 | break; |
481 | case BLINKINGOFF_STATE: |
482 | case BLINKINGON_STATE: |
483 | /* |
484 | * Cancel if we are still blinking; this means that we |
485 | * press the attention again before the 5 sec. limit |
486 | * expires to cancel hot-add or hot-remove |
487 | */ |
488 | ctrl_info(ctrl, "Button cancel on Slot(%s)\n" , |
489 | slot_name(p_slot)); |
490 | cancel_delayed_work(dwork: &p_slot->work); |
491 | if (p_slot->state == BLINKINGOFF_STATE) |
492 | p_slot->hpc_ops->green_led_on(p_slot); |
493 | else |
494 | p_slot->hpc_ops->green_led_off(p_slot); |
495 | p_slot->hpc_ops->set_attention_status(p_slot, 0); |
496 | ctrl_info(ctrl, "PCI slot #%s - action canceled due to button press\n" , |
497 | slot_name(p_slot)); |
498 | p_slot->state = STATIC_STATE; |
499 | break; |
500 | case POWEROFF_STATE: |
501 | case POWERON_STATE: |
502 | /* |
503 | * Ignore if the slot is on power-on or power-off state; |
504 | * this means that the previous attention button action |
505 | * to hot-add or hot-remove is undergoing |
506 | */ |
507 | ctrl_info(ctrl, "Button ignore on Slot(%s)\n" , |
508 | slot_name(p_slot)); |
509 | update_slot_info(slot: p_slot); |
510 | break; |
511 | default: |
512 | ctrl_warn(ctrl, "Not a valid state\n" ); |
513 | break; |
514 | } |
515 | } |
516 | |
517 | static void interrupt_event_handler(struct work_struct *work) |
518 | { |
519 | struct event_info *info = container_of(work, struct event_info, work); |
520 | struct slot *p_slot = info->p_slot; |
521 | |
522 | mutex_lock(&p_slot->lock); |
523 | switch (info->event_type) { |
524 | case INT_BUTTON_PRESS: |
525 | handle_button_press_event(p_slot); |
526 | break; |
527 | case INT_POWER_FAULT: |
528 | ctrl_dbg(p_slot->ctrl, "%s: Power fault\n" , __func__); |
529 | p_slot->hpc_ops->set_attention_status(p_slot, 1); |
530 | p_slot->hpc_ops->green_led_off(p_slot); |
531 | break; |
532 | default: |
533 | update_slot_info(slot: p_slot); |
534 | break; |
535 | } |
536 | mutex_unlock(lock: &p_slot->lock); |
537 | |
538 | kfree(objp: info); |
539 | } |
540 | |
541 | |
542 | static int shpchp_enable_slot (struct slot *p_slot) |
543 | { |
544 | u8 getstatus = 0; |
545 | int rc, retval = -ENODEV; |
546 | struct controller *ctrl = p_slot->ctrl; |
547 | |
548 | /* Check to see if (latch closed, card present, power off) */ |
549 | mutex_lock(&p_slot->ctrl->crit_sect); |
550 | rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); |
551 | if (rc || !getstatus) { |
552 | ctrl_info(ctrl, "No adapter on slot(%s)\n" , slot_name(p_slot)); |
553 | goto out; |
554 | } |
555 | rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); |
556 | if (rc || getstatus) { |
557 | ctrl_info(ctrl, "Latch open on slot(%s)\n" , slot_name(p_slot)); |
558 | goto out; |
559 | } |
560 | rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); |
561 | if (rc || getstatus) { |
562 | ctrl_info(ctrl, "Already enabled on slot(%s)\n" , |
563 | slot_name(p_slot)); |
564 | goto out; |
565 | } |
566 | |
567 | p_slot->is_a_board = 1; |
568 | |
569 | /* We have to save the presence info for these slots */ |
570 | p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); |
571 | p_slot->hpc_ops->get_power_status(p_slot, &(p_slot->pwr_save)); |
572 | ctrl_dbg(ctrl, "%s: p_slot->pwr_save %x\n" , __func__, p_slot->pwr_save); |
573 | p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); |
574 | |
575 | if ((p_slot->ctrl->pci_dev->vendor == PCI_VENDOR_ID_AMD && |
576 | p_slot->ctrl->pci_dev->device == PCI_DEVICE_ID_AMD_POGO_7458) |
577 | && p_slot->ctrl->num_slots == 1) { |
578 | /* handle AMD POGO errata; this must be done before enable */ |
579 | amd_pogo_errata_save_misc_reg(p_slot); |
580 | retval = board_added(p_slot); |
581 | /* handle AMD POGO errata; this must be done after enable */ |
582 | amd_pogo_errata_restore_misc_reg(p_slot); |
583 | } else |
584 | retval = board_added(p_slot); |
585 | |
586 | if (retval) { |
587 | p_slot->hpc_ops->get_adapter_status(p_slot, |
588 | &(p_slot->presence_save)); |
589 | p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); |
590 | } |
591 | |
592 | update_slot_info(slot: p_slot); |
593 | out: |
594 | mutex_unlock(lock: &p_slot->ctrl->crit_sect); |
595 | return retval; |
596 | } |
597 | |
598 | |
599 | static int shpchp_disable_slot (struct slot *p_slot) |
600 | { |
601 | u8 getstatus = 0; |
602 | int rc, retval = -ENODEV; |
603 | struct controller *ctrl = p_slot->ctrl; |
604 | |
605 | if (!p_slot->ctrl) |
606 | return -ENODEV; |
607 | |
608 | /* Check to see if (latch closed, card present, power on) */ |
609 | mutex_lock(&p_slot->ctrl->crit_sect); |
610 | |
611 | rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); |
612 | if (rc || !getstatus) { |
613 | ctrl_info(ctrl, "No adapter on slot(%s)\n" , slot_name(p_slot)); |
614 | goto out; |
615 | } |
616 | rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); |
617 | if (rc || getstatus) { |
618 | ctrl_info(ctrl, "Latch open on slot(%s)\n" , slot_name(p_slot)); |
619 | goto out; |
620 | } |
621 | rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); |
622 | if (rc || !getstatus) { |
623 | ctrl_info(ctrl, "Already disabled on slot(%s)\n" , |
624 | slot_name(p_slot)); |
625 | goto out; |
626 | } |
627 | |
628 | retval = remove_board(p_slot); |
629 | update_slot_info(slot: p_slot); |
630 | out: |
631 | mutex_unlock(lock: &p_slot->ctrl->crit_sect); |
632 | return retval; |
633 | } |
634 | |
635 | int shpchp_sysfs_enable_slot(struct slot *p_slot) |
636 | { |
637 | int retval = -ENODEV; |
638 | struct controller *ctrl = p_slot->ctrl; |
639 | |
640 | mutex_lock(&p_slot->lock); |
641 | switch (p_slot->state) { |
642 | case BLINKINGON_STATE: |
643 | cancel_delayed_work(dwork: &p_slot->work); |
644 | fallthrough; |
645 | case STATIC_STATE: |
646 | p_slot->state = POWERON_STATE; |
647 | mutex_unlock(lock: &p_slot->lock); |
648 | retval = shpchp_enable_slot(p_slot); |
649 | mutex_lock(&p_slot->lock); |
650 | p_slot->state = STATIC_STATE; |
651 | break; |
652 | case POWERON_STATE: |
653 | ctrl_info(ctrl, "Slot %s is already in powering on state\n" , |
654 | slot_name(p_slot)); |
655 | break; |
656 | case BLINKINGOFF_STATE: |
657 | case POWEROFF_STATE: |
658 | ctrl_info(ctrl, "Already enabled on slot %s\n" , |
659 | slot_name(p_slot)); |
660 | break; |
661 | default: |
662 | ctrl_err(ctrl, "Not a valid state on slot %s\n" , |
663 | slot_name(p_slot)); |
664 | break; |
665 | } |
666 | mutex_unlock(lock: &p_slot->lock); |
667 | |
668 | return retval; |
669 | } |
670 | |
671 | int shpchp_sysfs_disable_slot(struct slot *p_slot) |
672 | { |
673 | int retval = -ENODEV; |
674 | struct controller *ctrl = p_slot->ctrl; |
675 | |
676 | mutex_lock(&p_slot->lock); |
677 | switch (p_slot->state) { |
678 | case BLINKINGOFF_STATE: |
679 | cancel_delayed_work(dwork: &p_slot->work); |
680 | fallthrough; |
681 | case STATIC_STATE: |
682 | p_slot->state = POWEROFF_STATE; |
683 | mutex_unlock(lock: &p_slot->lock); |
684 | retval = shpchp_disable_slot(p_slot); |
685 | mutex_lock(&p_slot->lock); |
686 | p_slot->state = STATIC_STATE; |
687 | break; |
688 | case POWEROFF_STATE: |
689 | ctrl_info(ctrl, "Slot %s is already in powering off state\n" , |
690 | slot_name(p_slot)); |
691 | break; |
692 | case BLINKINGON_STATE: |
693 | case POWERON_STATE: |
694 | ctrl_info(ctrl, "Already disabled on slot %s\n" , |
695 | slot_name(p_slot)); |
696 | break; |
697 | default: |
698 | ctrl_err(ctrl, "Not a valid state on slot %s\n" , |
699 | slot_name(p_slot)); |
700 | break; |
701 | } |
702 | mutex_unlock(lock: &p_slot->lock); |
703 | |
704 | return retval; |
705 | } |
706 | |