Hi heikki, > -----Original Message----- > From: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> > Sent: Monday, October 11, 2021 2:31 PM > To: Xu Yang <xu.yang_2@xxxxxxx>; linux@xxxxxxxxxxxx > Cc: Jun Li <jun.li@xxxxxxx>; gregkh@xxxxxxxxxxxxxxxxxxx; linux- > usb@xxxxxxxxxxxxxxx; dl-linux-imx <linux-imx@xxxxxxx> > Subject: [EXT] Re: [PATCH v2] usb: typec: tcpm: fix issue of multiple > tcpm_set_state > > Caution: EXT Email > > Guenter, can you check this? > > On Fri, Aug 27, 2021 at 07:48:09PM +0800, Xu Yang wrote: > > There are potential problems when states are set as following: > > > > tcpm_set_state(A, 0) > > tcpm_set_state(B, X) > > > > As long as the state A is set and the state_machine work is queued > > successfully, state_machine work will be scheduled soon after. Before > > running into tcpm_state_machine_work(), there is a chance to set state > > B again. If it does occur: > > > > either (X = 0) > > port->state = B and state_machine work is queued again, then work > > will be executed twice. > > or (X != 0) > > port->state = A and port->delayed_state = B, then work will be > > executed once but timer is still running. > > > > For this situation, tcpm should only handle the most recent state > > change as if only one state is set just now. Therefore, if the > > state_machine work has already been queued, it can't be queued again > > before running into tcpm_state_machine_work(). > > > > The state_machine_running flag already prevents from queuing the work, > > so we can make it contain the pending stage (after work be queued and > > before running into tcpm_state_machine_work). The > > state_machine_pending_or_running flag can be used to indicate that a > > state can be handled without queuing the work again. > > > > Because the state_machine work has been queued for state A, there is > > no way to cancel as it may be already dequeued later, and then will > > run into > > tcpm_state_machine_work() certainly. To handle the delayed state B, > > such an abnormal work should be skiped. If port->delayed_state != > > INVALID_STATE and timer is still running, it's time to skip. > > > > Fixes: 4b4e02c83167 ("typec: tcpm: Move out of staging") > > cc: <stable@xxxxxxxxxxxxxxx> > > Signed-off-by: Xu Yang <xu.yang_2@xxxxxxx> > > What changed since v1? > > thanks, In patch v1, I committed two patches to fix two problems. However, the two problems are resulted by the same reason. And I try to address the issues after it occurs. In patch v2, according to Guenter's advice, I try to avoid such problems occurring from the source. So I set a state_machine_pending_or_running flag to indicate that a state can be handled without queuing the work again. Meanwhile, one patch is enough to address the problems. Xu Yang > > > diff --git a/drivers/usb/typec/tcpm/tcpm.c > > b/drivers/usb/typec/tcpm/tcpm.c index 049f4c61ee82..a913bc620e88 > > 100644 > > --- a/drivers/usb/typec/tcpm/tcpm.c > > +++ b/drivers/usb/typec/tcpm/tcpm.c > > @@ -371,7 +371,7 @@ struct tcpm_port { > > struct kthread_work enable_frs; > > struct hrtimer send_discover_timer; > > struct kthread_work send_discover_work; > > - bool state_machine_running; > > + bool state_machine_pending_or_running; > > bool vdm_sm_running; > > > > struct completion tx_complete; > > @@ -1192,6 +1192,7 @@ static void mod_tcpm_delayed_work(struct > tcpm_port *port, unsigned int delay_ms) > > } else { > > hrtimer_cancel(&port->state_machine_timer); > > kthread_queue_work(port->wq, &port->state_machine); > > + port->state_machine_pending_or_running = true; > > } > > } > > > > @@ -1250,7 +1251,7 @@ static void tcpm_set_state(struct tcpm_port > *port, enum tcpm_state state, > > * tcpm_state_machine_work() will continue running the state > > * machine. > > */ > > - if (!port->state_machine_running) > > + if (!port->state_machine_pending_or_running) > > mod_tcpm_delayed_work(port, 0); > > } > > } > > @@ -4810,13 +4811,15 @@ static void tcpm_state_machine_work(struct > kthread_work *work) > > enum tcpm_state prev_state; > > > > mutex_lock(&port->lock); > > - port->state_machine_running = true; > > > > if (port->queued_message && tcpm_send_queued_message(port)) > > goto done; > > > > /* If we were queued due to a delayed state change, update it now */ > > if (port->delayed_state) { > > + if (ktime_before(ktime_get(), port->delayed_runtime)) > > + goto done; > > + > > tcpm_log(port, "state change %s -> %s [delayed %ld ms]", > > tcpm_states[port->state], > > tcpm_states[port->delayed_state], > > port->delay_ms); @@ -4837,7 +4840,7 @@ static void > tcpm_state_machine_work(struct kthread_work *work) > > } while (port->state != prev_state && !port->delayed_state); > > > > done: > > - port->state_machine_running = false; > > + port->state_machine_pending_or_running = false; > > mutex_unlock(&port->lock); > > } > > > > @@ -6300,6 +6303,7 @@ static enum hrtimer_restart > state_machine_timer_handler(struct hrtimer *timer) > > struct tcpm_port *port = container_of(timer, struct tcpm_port, > > state_machine_timer); > > > > kthread_queue_work(port->wq, &port->state_machine); > > + port->state_machine_pending_or_running = true; > > return HRTIMER_NORESTART; > > } > > > > -- > > 2.25.1 > > -- > heikki