diff options
author | Sreekanth Reddy <sreekanth.reddy@avagotech.com> | 2015-11-11 07:00:31 -0500 |
---|---|---|
committer | Martin K. Petersen <martin.petersen@oracle.com> | 2015-11-11 19:01:11 -0500 |
commit | 146b16c8071f5f6c67895d15beeee1163f5107c4 (patch) | |
tree | 468dd22608c91e18f91f5d81b382fedf50123715 | |
parent | d1cb5e495e5384f6d90a8573f1be9cc79b85c862 (diff) |
mpt3sas: Refcount fw_events and fix unsafe list usage
The fw_event_work struct is concurrently referenced at shutdown. Add a
refcount to protect it and refactor the code to use it.
Additionally, refactor _scsih_fw_event_cleanup_queue() such that it no
longer iterates over the list without holding the lock since
_firmware_event_work() concurrently deletes items from the list.
This patch is ported from commit 008549f6e8a1 ("mpt2sas: Refcount
fw_events and fix unsafe list usage"). These changes are also required
for mpt3sas.
Signed-off-by: Sreekanth Reddy <Sreekanth.Reddy@avagotech.com>
Acked-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
-rw-r--r-- | drivers/scsi/mpt3sas/mpt3sas_scsih.c | 116 |
1 files changed, 94 insertions, 22 deletions
diff --git a/drivers/scsi/mpt3sas/mpt3sas_scsih.c b/drivers/scsi/mpt3sas/mpt3sas_scsih.c index 5dbf214ecf6d..436e65e513ae 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_scsih.c +++ b/drivers/scsi/mpt3sas/mpt3sas_scsih.c | |||
@@ -200,9 +200,37 @@ struct fw_event_work { | |||
200 | u8 VP_ID; | 200 | u8 VP_ID; |
201 | u8 ignore; | 201 | u8 ignore; |
202 | u16 event; | 202 | u16 event; |
203 | struct kref refcount; | ||
203 | char event_data[0] __aligned(4); | 204 | char event_data[0] __aligned(4); |
204 | }; | 205 | }; |
205 | 206 | ||
207 | static void fw_event_work_free(struct kref *r) | ||
208 | { | ||
209 | kfree(container_of(r, struct fw_event_work, refcount)); | ||
210 | } | ||
211 | |||
212 | static void fw_event_work_get(struct fw_event_work *fw_work) | ||
213 | { | ||
214 | kref_get(&fw_work->refcount); | ||
215 | } | ||
216 | |||
217 | static void fw_event_work_put(struct fw_event_work *fw_work) | ||
218 | { | ||
219 | kref_put(&fw_work->refcount, fw_event_work_free); | ||
220 | } | ||
221 | |||
222 | static struct fw_event_work *alloc_fw_event_work(int len) | ||
223 | { | ||
224 | struct fw_event_work *fw_event; | ||
225 | |||
226 | fw_event = kzalloc(sizeof(*fw_event) + len, GFP_ATOMIC); | ||
227 | if (!fw_event) | ||
228 | return NULL; | ||
229 | |||
230 | kref_init(&fw_event->refcount); | ||
231 | return fw_event; | ||
232 | } | ||
233 | |||
206 | /** | 234 | /** |
207 | * struct _scsi_io_transfer - scsi io transfer | 235 | * struct _scsi_io_transfer - scsi io transfer |
208 | * @handle: sas device handle (assigned by firmware) | 236 | * @handle: sas device handle (assigned by firmware) |
@@ -2598,32 +2626,36 @@ _scsih_fw_event_add(struct MPT3SAS_ADAPTER *ioc, struct fw_event_work *fw_event) | |||
2598 | return; | 2626 | return; |
2599 | 2627 | ||
2600 | spin_lock_irqsave(&ioc->fw_event_lock, flags); | 2628 | spin_lock_irqsave(&ioc->fw_event_lock, flags); |
2629 | fw_event_work_get(fw_event); | ||
2601 | INIT_LIST_HEAD(&fw_event->list); | 2630 | INIT_LIST_HEAD(&fw_event->list); |
2602 | list_add_tail(&fw_event->list, &ioc->fw_event_list); | 2631 | list_add_tail(&fw_event->list, &ioc->fw_event_list); |
2603 | INIT_WORK(&fw_event->work, _firmware_event_work); | 2632 | INIT_WORK(&fw_event->work, _firmware_event_work); |
2633 | fw_event_work_get(fw_event); | ||
2604 | queue_work(ioc->firmware_event_thread, &fw_event->work); | 2634 | queue_work(ioc->firmware_event_thread, &fw_event->work); |
2605 | spin_unlock_irqrestore(&ioc->fw_event_lock, flags); | 2635 | spin_unlock_irqrestore(&ioc->fw_event_lock, flags); |
2606 | } | 2636 | } |
2607 | 2637 | ||
2608 | /** | 2638 | /** |
2609 | * _scsih_fw_event_free - delete fw_event | 2639 | * _scsih_fw_event_del_from_list - delete fw_event from the list |
2610 | * @ioc: per adapter object | 2640 | * @ioc: per adapter object |
2611 | * @fw_event: object describing the event | 2641 | * @fw_event: object describing the event |
2612 | * Context: This function will acquire ioc->fw_event_lock. | 2642 | * Context: This function will acquire ioc->fw_event_lock. |
2613 | * | 2643 | * |
2614 | * This removes firmware event object from link list, frees associated memory. | 2644 | * If the fw_event is on the fw_event_list, remove it and do a put. |
2615 | * | 2645 | * |
2616 | * Return nothing. | 2646 | * Return nothing. |
2617 | */ | 2647 | */ |
2618 | static void | 2648 | static void |
2619 | _scsih_fw_event_free(struct MPT3SAS_ADAPTER *ioc, struct fw_event_work | 2649 | _scsih_fw_event_del_from_list(struct MPT3SAS_ADAPTER *ioc, struct fw_event_work |
2620 | *fw_event) | 2650 | *fw_event) |
2621 | { | 2651 | { |
2622 | unsigned long flags; | 2652 | unsigned long flags; |
2623 | 2653 | ||
2624 | spin_lock_irqsave(&ioc->fw_event_lock, flags); | 2654 | spin_lock_irqsave(&ioc->fw_event_lock, flags); |
2625 | list_del(&fw_event->list); | 2655 | if (!list_empty(&fw_event->list)) { |
2626 | kfree(fw_event); | 2656 | list_del_init(&fw_event->list); |
2657 | fw_event_work_put(fw_event); | ||
2658 | } | ||
2627 | spin_unlock_irqrestore(&ioc->fw_event_lock, flags); | 2659 | spin_unlock_irqrestore(&ioc->fw_event_lock, flags); |
2628 | } | 2660 | } |
2629 | 2661 | ||
@@ -2640,17 +2672,19 @@ mpt3sas_send_trigger_data_event(struct MPT3SAS_ADAPTER *ioc, | |||
2640 | struct SL_WH_TRIGGERS_EVENT_DATA_T *event_data) | 2672 | struct SL_WH_TRIGGERS_EVENT_DATA_T *event_data) |
2641 | { | 2673 | { |
2642 | struct fw_event_work *fw_event; | 2674 | struct fw_event_work *fw_event; |
2675 | u16 sz; | ||
2643 | 2676 | ||
2644 | if (ioc->is_driver_loading) | 2677 | if (ioc->is_driver_loading) |
2645 | return; | 2678 | return; |
2646 | fw_event = kzalloc(sizeof(*fw_event) + sizeof(*event_data), | 2679 | sz = sizeof(*event_data); |
2647 | GFP_ATOMIC); | 2680 | fw_event = alloc_fw_event_work(sz); |
2648 | if (!fw_event) | 2681 | if (!fw_event) |
2649 | return; | 2682 | return; |
2650 | fw_event->event = MPT3SAS_PROCESS_TRIGGER_DIAG; | 2683 | fw_event->event = MPT3SAS_PROCESS_TRIGGER_DIAG; |
2651 | fw_event->ioc = ioc; | 2684 | fw_event->ioc = ioc; |
2652 | memcpy(fw_event->event_data, event_data, sizeof(*event_data)); | 2685 | memcpy(fw_event->event_data, event_data, sizeof(*event_data)); |
2653 | _scsih_fw_event_add(ioc, fw_event); | 2686 | _scsih_fw_event_add(ioc, fw_event); |
2687 | fw_event_work_put(fw_event); | ||
2654 | } | 2688 | } |
2655 | 2689 | ||
2656 | /** | 2690 | /** |
@@ -2666,12 +2700,13 @@ _scsih_error_recovery_delete_devices(struct MPT3SAS_ADAPTER *ioc) | |||
2666 | 2700 | ||
2667 | if (ioc->is_driver_loading) | 2701 | if (ioc->is_driver_loading) |
2668 | return; | 2702 | return; |
2669 | fw_event = kzalloc(sizeof(struct fw_event_work), GFP_ATOMIC); | 2703 | fw_event = alloc_fw_event_work(0); |
2670 | if (!fw_event) | 2704 | if (!fw_event) |
2671 | return; | 2705 | return; |
2672 | fw_event->event = MPT3SAS_REMOVE_UNRESPONDING_DEVICES; | 2706 | fw_event->event = MPT3SAS_REMOVE_UNRESPONDING_DEVICES; |
2673 | fw_event->ioc = ioc; | 2707 | fw_event->ioc = ioc; |
2674 | _scsih_fw_event_add(ioc, fw_event); | 2708 | _scsih_fw_event_add(ioc, fw_event); |
2709 | fw_event_work_put(fw_event); | ||
2675 | } | 2710 | } |
2676 | 2711 | ||
2677 | /** | 2712 | /** |
@@ -2685,12 +2720,29 @@ mpt3sas_port_enable_complete(struct MPT3SAS_ADAPTER *ioc) | |||
2685 | { | 2720 | { |
2686 | struct fw_event_work *fw_event; | 2721 | struct fw_event_work *fw_event; |
2687 | 2722 | ||
2688 | fw_event = kzalloc(sizeof(struct fw_event_work), GFP_ATOMIC); | 2723 | fw_event = alloc_fw_event_work(0); |
2689 | if (!fw_event) | 2724 | if (!fw_event) |
2690 | return; | 2725 | return; |
2691 | fw_event->event = MPT3SAS_PORT_ENABLE_COMPLETE; | 2726 | fw_event->event = MPT3SAS_PORT_ENABLE_COMPLETE; |
2692 | fw_event->ioc = ioc; | 2727 | fw_event->ioc = ioc; |
2693 | _scsih_fw_event_add(ioc, fw_event); | 2728 | _scsih_fw_event_add(ioc, fw_event); |
2729 | fw_event_work_put(fw_event); | ||
2730 | } | ||
2731 | |||
2732 | static struct fw_event_work *dequeue_next_fw_event(struct MPT3SAS_ADAPTER *ioc) | ||
2733 | { | ||
2734 | unsigned long flags; | ||
2735 | struct fw_event_work *fw_event = NULL; | ||
2736 | |||
2737 | spin_lock_irqsave(&ioc->fw_event_lock, flags); | ||
2738 | if (!list_empty(&ioc->fw_event_list)) { | ||
2739 | fw_event = list_first_entry(&ioc->fw_event_list, | ||
2740 | struct fw_event_work, list); | ||
2741 | list_del_init(&fw_event->list); | ||
2742 | } | ||
2743 | spin_unlock_irqrestore(&ioc->fw_event_lock, flags); | ||
2744 | |||
2745 | return fw_event; | ||
2694 | } | 2746 | } |
2695 | 2747 | ||
2696 | /** | 2748 | /** |
@@ -2705,17 +2757,25 @@ mpt3sas_port_enable_complete(struct MPT3SAS_ADAPTER *ioc) | |||
2705 | static void | 2757 | static void |
2706 | _scsih_fw_event_cleanup_queue(struct MPT3SAS_ADAPTER *ioc) | 2758 | _scsih_fw_event_cleanup_queue(struct MPT3SAS_ADAPTER *ioc) |
2707 | { | 2759 | { |
2708 | struct fw_event_work *fw_event, *next; | 2760 | struct fw_event_work *fw_event; |
2709 | 2761 | ||
2710 | if (list_empty(&ioc->fw_event_list) || | 2762 | if (list_empty(&ioc->fw_event_list) || |
2711 | !ioc->firmware_event_thread || in_interrupt()) | 2763 | !ioc->firmware_event_thread || in_interrupt()) |
2712 | return; | 2764 | return; |
2713 | 2765 | ||
2714 | list_for_each_entry_safe(fw_event, next, &ioc->fw_event_list, list) { | 2766 | while ((fw_event = dequeue_next_fw_event(ioc))) { |
2715 | if (cancel_delayed_work_sync(&fw_event->delayed_work)) { | 2767 | /* |
2716 | _scsih_fw_event_free(ioc, fw_event); | 2768 | * Wait on the fw_event to complete. If this returns 1, then |
2717 | continue; | 2769 | * the event was never executed, and we need a put for the |
2718 | } | 2770 | * reference the delayed_work had on the fw_event. |
2771 | * | ||
2772 | * If it did execute, we wait for it to finish, and the put will | ||
2773 | * happen from _firmware_event_work() | ||
2774 | */ | ||
2775 | if (cancel_delayed_work_sync(&fw_event->delayed_work)) | ||
2776 | fw_event_work_put(fw_event); | ||
2777 | |||
2778 | fw_event_work_put(fw_event); | ||
2719 | } | 2779 | } |
2720 | } | 2780 | } |
2721 | 2781 | ||
@@ -4242,13 +4302,14 @@ _scsih_send_event_to_turn_on_pfa_led(struct MPT3SAS_ADAPTER *ioc, u16 handle) | |||
4242 | { | 4302 | { |
4243 | struct fw_event_work *fw_event; | 4303 | struct fw_event_work *fw_event; |
4244 | 4304 | ||
4245 | fw_event = kzalloc(sizeof(struct fw_event_work), GFP_ATOMIC); | 4305 | fw_event = alloc_fw_event_work(0); |
4246 | if (!fw_event) | 4306 | if (!fw_event) |
4247 | return; | 4307 | return; |
4248 | fw_event->event = MPT3SAS_TURN_ON_PFA_LED; | 4308 | fw_event->event = MPT3SAS_TURN_ON_PFA_LED; |
4249 | fw_event->device_handle = handle; | 4309 | fw_event->device_handle = handle; |
4250 | fw_event->ioc = ioc; | 4310 | fw_event->ioc = ioc; |
4251 | _scsih_fw_event_add(ioc, fw_event); | 4311 | _scsih_fw_event_add(ioc, fw_event); |
4312 | fw_event_work_put(fw_event); | ||
4252 | } | 4313 | } |
4253 | 4314 | ||
4254 | /** | 4315 | /** |
@@ -7498,10 +7559,11 @@ mpt3sas_scsih_reset_handler(struct MPT3SAS_ADAPTER *ioc, int reset_phase) | |||
7498 | static void | 7559 | static void |
7499 | _mpt3sas_fw_work(struct MPT3SAS_ADAPTER *ioc, struct fw_event_work *fw_event) | 7560 | _mpt3sas_fw_work(struct MPT3SAS_ADAPTER *ioc, struct fw_event_work *fw_event) |
7500 | { | 7561 | { |
7562 | _scsih_fw_event_del_from_list(ioc, fw_event); | ||
7563 | |||
7501 | /* the queue is being flushed so ignore this event */ | 7564 | /* the queue is being flushed so ignore this event */ |
7502 | if (ioc->remove_host || | 7565 | if (ioc->remove_host || ioc->pci_error_recovery) { |
7503 | ioc->pci_error_recovery) { | 7566 | fw_event_work_put(fw_event); |
7504 | _scsih_fw_event_free(ioc, fw_event); | ||
7505 | return; | 7567 | return; |
7506 | } | 7568 | } |
7507 | 7569 | ||
@@ -7512,8 +7574,16 @@ _mpt3sas_fw_work(struct MPT3SAS_ADAPTER *ioc, struct fw_event_work *fw_event) | |||
7512 | fw_event->event_data); | 7574 | fw_event->event_data); |
7513 | break; | 7575 | break; |
7514 | case MPT3SAS_REMOVE_UNRESPONDING_DEVICES: | 7576 | case MPT3SAS_REMOVE_UNRESPONDING_DEVICES: |
7515 | while (scsi_host_in_recovery(ioc->shost) || ioc->shost_recovery) | 7577 | while (scsi_host_in_recovery(ioc->shost) || |
7578 | ioc->shost_recovery) { | ||
7579 | /* | ||
7580 | * If we're unloading, bail. Otherwise, this can become | ||
7581 | * an infinite loop. | ||
7582 | */ | ||
7583 | if (ioc->remove_host) | ||
7584 | goto out; | ||
7516 | ssleep(1); | 7585 | ssleep(1); |
7586 | } | ||
7517 | _scsih_remove_unresponding_sas_devices(ioc); | 7587 | _scsih_remove_unresponding_sas_devices(ioc); |
7518 | _scsih_scan_for_devices_after_reset(ioc); | 7588 | _scsih_scan_for_devices_after_reset(ioc); |
7519 | break; | 7589 | break; |
@@ -7558,7 +7628,8 @@ _mpt3sas_fw_work(struct MPT3SAS_ADAPTER *ioc, struct fw_event_work *fw_event) | |||
7558 | _scsih_sas_ir_operation_status_event(ioc, fw_event); | 7628 | _scsih_sas_ir_operation_status_event(ioc, fw_event); |
7559 | break; | 7629 | break; |
7560 | } | 7630 | } |
7561 | _scsih_fw_event_free(ioc, fw_event); | 7631 | out: |
7632 | fw_event_work_put(fw_event); | ||
7562 | } | 7633 | } |
7563 | 7634 | ||
7564 | /** | 7635 | /** |
@@ -7720,7 +7791,7 @@ mpt3sas_scsih_event_callback(struct MPT3SAS_ADAPTER *ioc, u8 msix_index, | |||
7720 | } | 7791 | } |
7721 | 7792 | ||
7722 | sz = le16_to_cpu(mpi_reply->EventDataLength) * 4; | 7793 | sz = le16_to_cpu(mpi_reply->EventDataLength) * 4; |
7723 | fw_event = kzalloc(sizeof(*fw_event) + sz, GFP_ATOMIC); | 7794 | fw_event = alloc_fw_event_work(sz); |
7724 | if (!fw_event) { | 7795 | if (!fw_event) { |
7725 | pr_err(MPT3SAS_FMT "failure at %s:%d/%s()!\n", | 7796 | pr_err(MPT3SAS_FMT "failure at %s:%d/%s()!\n", |
7726 | ioc->name, __FILE__, __LINE__, __func__); | 7797 | ioc->name, __FILE__, __LINE__, __func__); |
@@ -7733,6 +7804,7 @@ mpt3sas_scsih_event_callback(struct MPT3SAS_ADAPTER *ioc, u8 msix_index, | |||
7733 | fw_event->VP_ID = mpi_reply->VP_ID; | 7804 | fw_event->VP_ID = mpi_reply->VP_ID; |
7734 | fw_event->event = event; | 7805 | fw_event->event = event; |
7735 | _scsih_fw_event_add(ioc, fw_event); | 7806 | _scsih_fw_event_add(ioc, fw_event); |
7807 | fw_event_work_put(fw_event); | ||
7736 | return 1; | 7808 | return 1; |
7737 | } | 7809 | } |
7738 | 7810 | ||