Re: [PATCH 2/2] usb: typec: tcpm: Don't handle two continuous same state
From: Guenter Roeck <linux@roeck-us.net>
Date: 2021-08-24 16:41:31
On 8/24/21 4:30 AM, Xu Yang wrote:
quoted hunk ↗ jump to hunk
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: <redacted> 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