diff options
author | Alexander Usyskin <alexander.usyskin@intel.com> | 2015-06-13 01:51:17 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-06-13 22:48:02 -0400 |
commit | 3dc196eae1db548f05e53e5875ff87b8ff79f249 (patch) | |
tree | c2727960ff823b0de49ccdc1af0fa939243864da | |
parent | c241e9b1d9ee7292d708d2298bc87f1455aad0c7 (diff) |
mei: me: wait for power gating exit confirmation
Fix the hbm power gating state machine so it will wait till it receives
confirmation interrupt for the PG_ISOLATION_EXIT message.
In process of the suspend flow the devices first have to exit from the
power gating state (runtime pm resume).
If we do not handle the confirmation interrupt after sending
PG_ISOLATION_EXIT message, we may receive it already after the suspend
flow has changed the device state and interrupt will be interpreted as a
spurious event, consequently link reset will be invoked which will
prevent the device from completing the suspend flow
kernel: [6603] mei_reset:136: mei_me 0000:00:16.0: powering down: end of reset
kernel: [476] mei_me_irq_thread_handler:643: mei_me 0000:00:16.0: function called after ISR to handle the interrupt processing.
kernel: mei_me 0000:00:16.0: FW not ready: resetting
Cc: <stable@vger.kernel.org> #3.18+
Cc: Gabriele Mazzotta <gabriele.mzt@gmail.com>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=86241
Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=770397
Tested-by: Gabriele Mazzotta <gabriele.mzt@gmail.com>
Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com>
Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/misc/mei/client.c | 2 | ||||
-rw-r--r-- | drivers/misc/mei/hw-me.c | 59 | ||||
-rw-r--r-- | drivers/misc/mei/hw-txe.c | 13 | ||||
-rw-r--r-- | drivers/misc/mei/mei_dev.h | 11 |
4 files changed, 80 insertions, 5 deletions
diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index d089b136f12c..6decbe136ea7 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c | |||
@@ -714,7 +714,7 @@ void mei_host_client_init(struct work_struct *work) | |||
714 | bool mei_hbuf_acquire(struct mei_device *dev) | 714 | bool mei_hbuf_acquire(struct mei_device *dev) |
715 | { | 715 | { |
716 | if (mei_pg_state(dev) == MEI_PG_ON || | 716 | if (mei_pg_state(dev) == MEI_PG_ON || |
717 | dev->pg_event == MEI_PG_EVENT_WAIT) { | 717 | mei_pg_in_transition(dev)) { |
718 | dev_dbg(dev->dev, "device is in pg\n"); | 718 | dev_dbg(dev->dev, "device is in pg\n"); |
719 | return false; | 719 | return false; |
720 | } | 720 | } |
diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 6fb75e62a764..43d7101ff993 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c | |||
@@ -663,11 +663,27 @@ int mei_me_pg_exit_sync(struct mei_device *dev) | |||
663 | mutex_lock(&dev->device_lock); | 663 | mutex_lock(&dev->device_lock); |
664 | 664 | ||
665 | reply: | 665 | reply: |
666 | if (dev->pg_event == MEI_PG_EVENT_RECEIVED) | 666 | if (dev->pg_event != MEI_PG_EVENT_RECEIVED) { |
667 | ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD); | 667 | ret = -ETIME; |
668 | goto out; | ||
669 | } | ||
670 | |||
671 | dev->pg_event = MEI_PG_EVENT_INTR_WAIT; | ||
672 | ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD); | ||
673 | if (ret) | ||
674 | return ret; | ||
675 | |||
676 | mutex_unlock(&dev->device_lock); | ||
677 | wait_event_timeout(dev->wait_pg, | ||
678 | dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, timeout); | ||
679 | mutex_lock(&dev->device_lock); | ||
680 | |||
681 | if (dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED) | ||
682 | ret = 0; | ||
668 | else | 683 | else |
669 | ret = -ETIME; | 684 | ret = -ETIME; |
670 | 685 | ||
686 | out: | ||
671 | dev->pg_event = MEI_PG_EVENT_IDLE; | 687 | dev->pg_event = MEI_PG_EVENT_IDLE; |
672 | hw->pg_state = MEI_PG_OFF; | 688 | hw->pg_state = MEI_PG_OFF; |
673 | 689 | ||
@@ -675,6 +691,19 @@ reply: | |||
675 | } | 691 | } |
676 | 692 | ||
677 | /** | 693 | /** |
694 | * mei_me_pg_in_transition - is device now in pg transition | ||
695 | * | ||
696 | * @dev: the device structure | ||
697 | * | ||
698 | * Return: true if in pg transition, false otherwise | ||
699 | */ | ||
700 | static bool mei_me_pg_in_transition(struct mei_device *dev) | ||
701 | { | ||
702 | return dev->pg_event >= MEI_PG_EVENT_WAIT && | ||
703 | dev->pg_event <= MEI_PG_EVENT_INTR_WAIT; | ||
704 | } | ||
705 | |||
706 | /** | ||
678 | * mei_me_pg_is_enabled - detect if PG is supported by HW | 707 | * mei_me_pg_is_enabled - detect if PG is supported by HW |
679 | * | 708 | * |
680 | * @dev: the device structure | 709 | * @dev: the device structure |
@@ -705,6 +734,24 @@ notsupported: | |||
705 | } | 734 | } |
706 | 735 | ||
707 | /** | 736 | /** |
737 | * mei_me_pg_intr - perform pg processing in interrupt thread handler | ||
738 | * | ||
739 | * @dev: the device structure | ||
740 | */ | ||
741 | static void mei_me_pg_intr(struct mei_device *dev) | ||
742 | { | ||
743 | struct mei_me_hw *hw = to_me_hw(dev); | ||
744 | |||
745 | if (dev->pg_event != MEI_PG_EVENT_INTR_WAIT) | ||
746 | return; | ||
747 | |||
748 | dev->pg_event = MEI_PG_EVENT_INTR_RECEIVED; | ||
749 | hw->pg_state = MEI_PG_OFF; | ||
750 | if (waitqueue_active(&dev->wait_pg)) | ||
751 | wake_up(&dev->wait_pg); | ||
752 | } | ||
753 | |||
754 | /** | ||
708 | * mei_me_irq_quick_handler - The ISR of the MEI device | 755 | * mei_me_irq_quick_handler - The ISR of the MEI device |
709 | * | 756 | * |
710 | * @irq: The irq number | 757 | * @irq: The irq number |
@@ -761,6 +808,8 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) | |||
761 | goto end; | 808 | goto end; |
762 | } | 809 | } |
763 | 810 | ||
811 | mei_me_pg_intr(dev); | ||
812 | |||
764 | /* check if we need to start the dev */ | 813 | /* check if we need to start the dev */ |
765 | if (!mei_host_is_ready(dev)) { | 814 | if (!mei_host_is_ready(dev)) { |
766 | if (mei_hw_is_ready(dev)) { | 815 | if (mei_hw_is_ready(dev)) { |
@@ -797,9 +846,10 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) | |||
797 | /* | 846 | /* |
798 | * During PG handshake only allowed write is the replay to the | 847 | * During PG handshake only allowed write is the replay to the |
799 | * PG exit message, so block calling write function | 848 | * PG exit message, so block calling write function |
800 | * if the pg state is not idle | 849 | * if the pg event is in PG handshake |
801 | */ | 850 | */ |
802 | if (dev->pg_event == MEI_PG_EVENT_IDLE) { | 851 | if (dev->pg_event != MEI_PG_EVENT_WAIT && |
852 | dev->pg_event != MEI_PG_EVENT_RECEIVED) { | ||
803 | rets = mei_irq_write_handler(dev, &complete_list); | 853 | rets = mei_irq_write_handler(dev, &complete_list); |
804 | dev->hbuf_is_ready = mei_hbuf_is_ready(dev); | 854 | dev->hbuf_is_ready = mei_hbuf_is_ready(dev); |
805 | } | 855 | } |
@@ -824,6 +874,7 @@ static const struct mei_hw_ops mei_me_hw_ops = { | |||
824 | .hw_config = mei_me_hw_config, | 874 | .hw_config = mei_me_hw_config, |
825 | .hw_start = mei_me_hw_start, | 875 | .hw_start = mei_me_hw_start, |
826 | 876 | ||
877 | .pg_in_transition = mei_me_pg_in_transition, | ||
827 | .pg_is_enabled = mei_me_pg_is_enabled, | 878 | .pg_is_enabled = mei_me_pg_is_enabled, |
828 | 879 | ||
829 | .intr_clear = mei_me_intr_clear, | 880 | .intr_clear = mei_me_intr_clear, |
diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 1cd223c96864..bae680c648ff 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c | |||
@@ -302,6 +302,18 @@ int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req) | |||
302 | } | 302 | } |
303 | 303 | ||
304 | /** | 304 | /** |
305 | * mei_txe_pg_in_transition - is device now in pg transition | ||
306 | * | ||
307 | * @dev: the device structure | ||
308 | * | ||
309 | * Return: true if in pg transition, false otherwise | ||
310 | */ | ||
311 | static bool mei_txe_pg_in_transition(struct mei_device *dev) | ||
312 | { | ||
313 | return dev->pg_event == MEI_PG_EVENT_WAIT; | ||
314 | } | ||
315 | |||
316 | /** | ||
305 | * mei_txe_pg_is_enabled - detect if PG is supported by HW | 317 | * mei_txe_pg_is_enabled - detect if PG is supported by HW |
306 | * | 318 | * |
307 | * @dev: the device structure | 319 | * @dev: the device structure |
@@ -1138,6 +1150,7 @@ static const struct mei_hw_ops mei_txe_hw_ops = { | |||
1138 | .hw_config = mei_txe_hw_config, | 1150 | .hw_config = mei_txe_hw_config, |
1139 | .hw_start = mei_txe_hw_start, | 1151 | .hw_start = mei_txe_hw_start, |
1140 | 1152 | ||
1153 | .pg_in_transition = mei_txe_pg_in_transition, | ||
1141 | .pg_is_enabled = mei_txe_pg_is_enabled, | 1154 | .pg_is_enabled = mei_txe_pg_is_enabled, |
1142 | 1155 | ||
1143 | .intr_clear = mei_txe_intr_clear, | 1156 | .intr_clear = mei_txe_intr_clear, |
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 70a644f688b0..453f6a333b42 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h | |||
@@ -276,6 +276,7 @@ struct mei_cl { | |||
276 | 276 | ||
277 | * @fw_status : get fw status registers | 277 | * @fw_status : get fw status registers |
278 | * @pg_state : power gating state of the device | 278 | * @pg_state : power gating state of the device |
279 | * @pg_in_transition : is device now in pg transition | ||
279 | * @pg_is_enabled : is power gating enabled | 280 | * @pg_is_enabled : is power gating enabled |
280 | 281 | ||
281 | * @intr_clear : clear pending interrupts | 282 | * @intr_clear : clear pending interrupts |
@@ -305,6 +306,7 @@ struct mei_hw_ops { | |||
305 | 306 | ||
306 | int (*fw_status)(struct mei_device *dev, struct mei_fw_status *fw_sts); | 307 | int (*fw_status)(struct mei_device *dev, struct mei_fw_status *fw_sts); |
307 | enum mei_pg_state (*pg_state)(struct mei_device *dev); | 308 | enum mei_pg_state (*pg_state)(struct mei_device *dev); |
309 | bool (*pg_in_transition)(struct mei_device *dev); | ||
308 | bool (*pg_is_enabled)(struct mei_device *dev); | 310 | bool (*pg_is_enabled)(struct mei_device *dev); |
309 | 311 | ||
310 | void (*intr_clear)(struct mei_device *dev); | 312 | void (*intr_clear)(struct mei_device *dev); |
@@ -349,11 +351,15 @@ struct mei_cl *mei_cl_bus_find_cl_by_uuid(struct mei_device *dev, uuid_le uuid); | |||
349 | * @MEI_PG_EVENT_IDLE: the driver is not in power gating transition | 351 | * @MEI_PG_EVENT_IDLE: the driver is not in power gating transition |
350 | * @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete | 352 | * @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete |
351 | * @MEI_PG_EVENT_RECEIVED: the driver received pg event | 353 | * @MEI_PG_EVENT_RECEIVED: the driver received pg event |
354 | * @MEI_PG_EVENT_INTR_WAIT: the driver is waiting for a pg event interrupt | ||
355 | * @MEI_PG_EVENT_INTR_RECEIVED: the driver received pg event interrupt | ||
352 | */ | 356 | */ |
353 | enum mei_pg_event { | 357 | enum mei_pg_event { |
354 | MEI_PG_EVENT_IDLE, | 358 | MEI_PG_EVENT_IDLE, |
355 | MEI_PG_EVENT_WAIT, | 359 | MEI_PG_EVENT_WAIT, |
356 | MEI_PG_EVENT_RECEIVED, | 360 | MEI_PG_EVENT_RECEIVED, |
361 | MEI_PG_EVENT_INTR_WAIT, | ||
362 | MEI_PG_EVENT_INTR_RECEIVED, | ||
357 | }; | 363 | }; |
358 | 364 | ||
359 | /** | 365 | /** |
@@ -670,6 +676,11 @@ static inline enum mei_pg_state mei_pg_state(struct mei_device *dev) | |||
670 | return dev->ops->pg_state(dev); | 676 | return dev->ops->pg_state(dev); |
671 | } | 677 | } |
672 | 678 | ||
679 | static inline bool mei_pg_in_transition(struct mei_device *dev) | ||
680 | { | ||
681 | return dev->ops->pg_in_transition(dev); | ||
682 | } | ||
683 | |||
673 | static inline bool mei_pg_is_enabled(struct mei_device *dev) | 684 | static inline bool mei_pg_is_enabled(struct mei_device *dev) |
674 | { | 685 | { |
675 | return dev->ops->pg_is_enabled(dev); | 686 | return dev->ops->pg_is_enabled(dev); |