All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay
@ 2021-08-24 11:30 Xu Yang
  2021-08-24 11:30 ` [PATCH 2/2] usb: typec: tcpm: Don't handle two continuous same state Xu Yang
  2021-08-24 16:56 ` [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay Guenter Roeck
  0 siblings, 2 replies; 4+ messages in thread
From: Xu Yang @ 2021-08-24 11:30 UTC (permalink / raw)
  To: linux; +Cc: jun.li, heikki.krogerus, gregkh, linux-usb, linux-imx

Setting a delayed state by tcpm_set_state may enter the delayed state
instantly without delay for the specified time.

[   65.424458] CC1: 0 -> 0, CC2: 0 -> 2 [state TOGGLING, polarity 0, connected]
[   65.424475] state change TOGGLING -> SRC_ATTACH_WAIT [rev1 NONE_AMS]
[   65.427233] VBUS off
[   65.427238] VBUS VSAFE0V
[   65.427243] pending state change SRC_ATTACH_WAIT -> SNK_TRY @ 200 ms [rev1 NONE_AMS]
[   65.427252] state change SRC_ATTACH_WAIT -> SNK_TRY [delayed 200 ms]
[   65.427258] cc:=2

In this log, tcpm should change to SNK_TRY state after 200 ms.
The following sequence may trigger this abnormal result:

          [tcpm_pd_event_handler]      [tcpm_state_machine_work]

1       tcpm_set_state(A, 0)
2           port->state = A
3           port->delayed_state = INVALID_STATE
4           queue work to worker_list
5       tcpm_set_state(B, ms)
6           port->delayed_state = B
7           start timer
8                                   dequeue work from worker_list
9                                   tcpm_state_machine_work
10                                  port->delayed_state != INVALID_STATE
11                                      port->state = B
12                                      port->delayed_state = INVALID_STATE
13                                  handle B state

In step 9, tcpm_state_machine_work gets scheduled because it has
been queued in step 4. At this point, however, both port->state and
port->delayed_state are non INVALID_STATE which causes the pending state
to be handled in step 13 without delay.

If a non-delayed state and a delayed state are orderly set in some works
except tcpm_state_machine_work, this bug will certainly occur. Also, if
set in a thread different from tcpm worker thread, this bug may occur.

Therefore, when port->delayed_state is a valid state but the
state_machine_timer is still running, tcpm_state_machine_work should
keep the delayed state pending until the state_machine_timer timeout.

Fixes: 4b4e02c83167 ("typec: tcpm: Move out of staging")
cc: <stable@vger.kernel.org>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index c40e0513873d..4bdf119b1306 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -4815,7 +4815,8 @@ static void tcpm_state_machine_work(struct kthread_work *work)
 		goto done;
 
 	/* If we were queued due to a delayed state change, update it now */
-	if (port->delayed_state) {
+	if (port->delayed_state != INVALID_STATE &&
+	    ktime_after(ktime_get(), port->delayed_runtime)) {
 		tcpm_log(port, "state change %s -> %s [delayed %ld ms]",
 			 tcpm_states[port->state],
 			 tcpm_states[port->delayed_state], port->delay_ms);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] usb: typec: tcpm: Don't handle two continuous same state
  2021-08-24 11:30 [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay Xu Yang
@ 2021-08-24 11:30 ` Xu Yang
  2021-08-24 16:41   ` Guenter Roeck
  2021-08-24 16:56 ` [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay Guenter Roeck
  1 sibling, 1 reply; 4+ messages in thread
From: Xu Yang @ 2021-08-24 11:30 UTC (permalink / raw)
  To: linux; +Cc: jun.li, heikki.krogerus, gregkh, linux-usb, linux-imx

When two tcpm_set_state are successively executed in non-worker context,
such as tcpm_init, an abnormal sequence may be exist which causes two
continuous same state to be handled.

tcpm_init:
[    1.686293] CC1: 0 -> 0, CC2: 0 -> 0 [state SNK_UNATTACHED, polarity 0, disconnected]
[    1.686300] state change SNK_UNATTACHED -> PORT_RESET [rev1 NONE_AMS]
[    1.686309] 1-0050: registered
[    1.686315] Setting usb_comm capable false
[    1.687417] Setting voltage/current limit 0 mV 0 mA
[    1.687425] polarity 0
[    1.690709] Requesting mux state 0, usb-role 0, orientation 0
[    1.691599] cc:=0
[    1.691871] pending state change PORT_RESET -> PORT_RESET_WAIT_OFF @ 100 ms [rev1 NONE_AMS]
[    1.691880] Setting usb_comm capable false
[    1.692973] Setting voltage/current limit 0 mV 0 mA
[    1.692980] polarity 0
[    1.696843] Requesting mux state 0, usb-role 0, orientation 0
[    1.697729] cc:=0
[    1.697994] pending state change PORT_RESET -> PORT_RESET_WAIT_OFF @ 100 ms [rev1 NONE_AMS]

abnormal sequence:
            [tcpm_init]                 [tcpm_state_machine_work]
1       tcpm_set_state(A)
2           port->state = A
3           kthread_queue_work
4           queue work to worker_list
5                                       dequeue work from worker_list
6       tcpm_set_state(B)
7           port->state = B
8           kthread_queue_work
9           queue work to worker_list
10                                      tcpm_state_machine_work(B)
11                                      port->state not change
12                                      dequeue work from worker_list
13                                      tcpm_state_machine_work(B)

state B is handled twice.

Fixes: 4b4e02c83167 ("typec: tcpm: Move out of staging")
cc: <stable@vger.kernel.org>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>

diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 4bdf119b1306..e0a319e00b12 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -4825,6 +4825,10 @@ static void tcpm_state_machine_work(struct kthread_work *work)
 		port->delayed_state = INVALID_STATE;
 	}
 
+	/* If target state is the same as the last entered state, skip it */
+	if (port->enter_state == port->state)
+		goto done;
+
 	/*
 	 * Continue running as long as we have (non-delayed) state changes
 	 * to make.
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/2] usb: typec: tcpm: Don't handle two continuous same state
  2021-08-24 11:30 ` [PATCH 2/2] usb: typec: tcpm: Don't handle two continuous same state Xu Yang
@ 2021-08-24 16:41   ` Guenter Roeck
  0 siblings, 0 replies; 4+ messages in thread
From: Guenter Roeck @ 2021-08-24 16:41 UTC (permalink / raw)
  To: Xu Yang; +Cc: jun.li, heikki.krogerus, gregkh, linux-usb, linux-imx

On 8/24/21 4:30 AM, Xu Yang wrote:
> When two tcpm_set_state are successively executed in non-worker context,
> such as tcpm_init, an abnormal sequence may be exist which causes two
> continuous same state to be handled.
> 
> tcpm_init:
> [    1.686293] CC1: 0 -> 0, CC2: 0 -> 0 [state SNK_UNATTACHED, polarity 0, disconnected]
> [    1.686300] state change SNK_UNATTACHED -> PORT_RESET [rev1 NONE_AMS]
> [    1.686309] 1-0050: registered
> [    1.686315] Setting usb_comm capable false
> [    1.687417] Setting voltage/current limit 0 mV 0 mA
> [    1.687425] polarity 0
> [    1.690709] Requesting mux state 0, usb-role 0, orientation 0
> [    1.691599] cc:=0
> [    1.691871] pending state change PORT_RESET -> PORT_RESET_WAIT_OFF @ 100 ms [rev1 NONE_AMS]
> [    1.691880] Setting usb_comm capable false
> [    1.692973] Setting voltage/current limit 0 mV 0 mA
> [    1.692980] polarity 0
> [    1.696843] Requesting mux state 0, usb-role 0, orientation 0
> [    1.697729] cc:=0
> [    1.697994] pending state change PORT_RESET -> PORT_RESET_WAIT_OFF @ 100 ms [rev1 NONE_AMS]
> 
> abnormal sequence:
>              [tcpm_init]                 [tcpm_state_machine_work]
> 1       tcpm_set_state(A)
> 2           port->state = A
> 3           kthread_queue_work
> 4           queue work to worker_list
> 5                                       dequeue work from worker_list
> 6       tcpm_set_state(B)
> 7           port->state = B
> 8           kthread_queue_work
> 9           queue work to worker_list
> 10                                      tcpm_state_machine_work(B)
> 11                                      port->state not change
> 12                                      dequeue work from worker_list
> 13                                      tcpm_state_machine_work(B)
> 
> state B is handled twice.
> 
> Fixes: 4b4e02c83167 ("typec: tcpm: Move out of staging")
> cc: <stable@vger.kernel.org>
> Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
> 
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index 4bdf119b1306..e0a319e00b12 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -4825,6 +4825,10 @@ static void tcpm_state_machine_work(struct kthread_work *work)
>   		port->delayed_state = INVALID_STATE;
>   	}
>   
> +	/* If target state is the same as the last entered state, skip it */
> +	if (port->enter_state == port->state)
> +		goto done;
> +

I don't think that really solves the problem; it looks more like a bandage around
a race condition between setting a state in one context and executing the state
machine in another. I think it would be better to solve the underlying race condition.

The problem is ultimately that tcpm_init() ends up calling tcpm_set_state() several
times, the last time being PORT_RESET. The worker starts running and waits for the
port lock to be released. Since the worker is already running, but state_machine_running
is not yet set, tcpm_set_state() triggers it again. Maybe we need a second flag to detect
and handle that situation, or a better means to determine if the worker is running.

Guenter

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay
  2021-08-24 11:30 [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay Xu Yang
  2021-08-24 11:30 ` [PATCH 2/2] usb: typec: tcpm: Don't handle two continuous same state Xu Yang
@ 2021-08-24 16:56 ` Guenter Roeck
  1 sibling, 0 replies; 4+ messages in thread
From: Guenter Roeck @ 2021-08-24 16:56 UTC (permalink / raw)
  To: Xu Yang; +Cc: jun.li, heikki.krogerus, gregkh, linux-usb, linux-imx

On 8/24/21 4:30 AM, Xu Yang wrote:
> Setting a delayed state by tcpm_set_state may enter the delayed state
> instantly without delay for the specified time.
> 
> [   65.424458] CC1: 0 -> 0, CC2: 0 -> 2 [state TOGGLING, polarity 0, connected]
> [   65.424475] state change TOGGLING -> SRC_ATTACH_WAIT [rev1 NONE_AMS]
> [   65.427233] VBUS off
> [   65.427238] VBUS VSAFE0V
> [   65.427243] pending state change SRC_ATTACH_WAIT -> SNK_TRY @ 200 ms [rev1 NONE_AMS]
> [   65.427252] state change SRC_ATTACH_WAIT -> SNK_TRY [delayed 200 ms]
> [   65.427258] cc:=2
> 
> In this log, tcpm should change to SNK_TRY state after 200 ms.
> The following sequence may trigger this abnormal result:
> 
>            [tcpm_pd_event_handler]      [tcpm_state_machine_work]
> 
> 1       tcpm_set_state(A, 0)
> 2           port->state = A
> 3           port->delayed_state = INVALID_STATE
> 4           queue work to worker_list
> 5       tcpm_set_state(B, ms)
> 6           port->delayed_state = B
> 7           start timer
> 8                                   dequeue work from worker_list
> 9                                   tcpm_state_machine_work
> 10                                  port->delayed_state != INVALID_STATE
> 11                                      port->state = B
> 12                                      port->delayed_state = INVALID_STATE
> 13                                  handle B state
> 
> In step 9, tcpm_state_machine_work gets scheduled because it has
> been queued in step 4. At this point, however, both port->state and
> port->delayed_state are non INVALID_STATE which causes the pending state
> to be handled in step 13 without delay.
> 
> If a non-delayed state and a delayed state are orderly set in some works
> except tcpm_state_machine_work, this bug will certainly occur. Also, if
> set in a thread different from tcpm worker thread, this bug may occur.
> 
> Therefore, when port->delayed_state is a valid state but the
> state_machine_timer is still running, tcpm_state_machine_work should
> keep the delayed state pending until the state_machine_timer timeout.
> 
> Fixes: 4b4e02c83167 ("typec: tcpm: Move out of staging")
> cc: <stable@vger.kernel.org>
> Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
> 
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index c40e0513873d..4bdf119b1306 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -4815,7 +4815,8 @@ static void tcpm_state_machine_work(struct kthread_work *work)
>   		goto done;
>   
>   	/* If we were queued due to a delayed state change, update it now */
> -	if (port->delayed_state) {
> +	if (port->delayed_state != INVALID_STATE &&
> +	    ktime_after(ktime_get(), port->delayed_runtime)) {
>   		tcpm_log(port, "state change %s -> %s [delayed %ld ms]",
>   			 tcpm_states[port->state],
>   			 tcpm_states[port->delayed_state], port->delay_ms);
> 
Unless I am missing something, this doesn't really match what the description says.
It will ignore the pending state change and execute the state change to SRC_ATTACH_WAIT.
This will then likely call tcpm_set_state() again. In other words, the state change to A
is executed even though it was superseded with a state change to (B, ms). That doesn't
look correct to me.

I think the problem may be similar to the problem in patch 2: The worker is already
running by the time tcpm_set_state(B, ms) is called, because tcpm_set_state(A, 0)
triggered it. This means that "dequeue work from worker_list" already happened
before tcpm_set_state(B, ms) was called. The only difference to patch 2 is that the
multiple state changes are not triggered from tcpm_init() but by some external event
outside the state machine. We should try to find a solution which covers both
situations and makes sure that the worker only handles the most recent state change.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2021-08-24 16:57 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-24 11:30 [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay Xu Yang
2021-08-24 11:30 ` [PATCH 2/2] usb: typec: tcpm: Don't handle two continuous same state Xu Yang
2021-08-24 16:41   ` Guenter Roeck
2021-08-24 16:56 ` [PATCH 1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay Guenter Roeck

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.