Message ID | 20210824113014.1420117-2-xu.yang_2@nxp.com |
---|---|
State | New |
Headers | show |
Series | [1/2] usb: typec: tcpm: Fix up tcpm set delayed state which may not delay | expand |
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
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.
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>