usb: typec: tcpm: Respond Wait if VDM state machine is running

Port partner could send PR_SWAP/DR_SWAP/VCONN_SWAP/Request just after it
enters Ready states. This will cause conficts if the port is going to
send DISC_IDENT in the Ready states of TCPM. Set a flag indicating that
the state machine is processing VDM and respond Wait messages until the
VDM state machine stops.

Tested-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Kyle Tso <kyletso@google.com>
Link: https://lore.kernel.org/r/20210114145053.1952756-4-kyletso@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Kyle Tso 2021-01-14 22:50:53 +08:00 коммит произвёл Greg Kroah-Hartman
Родитель 8dea75e113
Коммит 8d3a0578ad
1 изменённых файлов: 73 добавлений и 7 удалений

Просмотреть файл

@ -352,6 +352,7 @@ struct tcpm_port {
struct hrtimer enable_frs_timer; struct hrtimer enable_frs_timer;
struct kthread_work enable_frs; struct kthread_work enable_frs;
bool state_machine_running; bool state_machine_running;
bool vdm_sm_running;
struct completion tx_complete; struct completion tx_complete;
enum tcpm_transmit_status tx_status; enum tcpm_transmit_status tx_status;
@ -1526,6 +1527,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
rlen = 1; rlen = 1;
} else { } else {
tcpm_register_partner_altmodes(port); tcpm_register_partner_altmodes(port);
port->vdm_sm_running = false;
} }
break; break;
case CMD_ENTER_MODE: case CMD_ENTER_MODE:
@ -1569,10 +1571,12 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
rlen = 1; rlen = 1;
break; break;
} }
port->vdm_sm_running = false;
break; break;
default: default:
response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK);
rlen = 1; rlen = 1;
port->vdm_sm_running = false;
break; break;
} }
@ -1739,6 +1743,8 @@ static void vdm_run_state_machine(struct tcpm_port *port)
switch (PD_VDO_CMD(vdo_hdr)) { switch (PD_VDO_CMD(vdo_hdr)) {
case CMD_DISCOVER_IDENT: case CMD_DISCOVER_IDENT:
res = tcpm_ams_start(port, DISCOVER_IDENTITY); res = tcpm_ams_start(port, DISCOVER_IDENTITY);
if (res == 0)
port->send_discover = false;
break; break;
case CMD_DISCOVER_SVID: case CMD_DISCOVER_SVID:
res = tcpm_ams_start(port, DISCOVER_SVIDS); res = tcpm_ams_start(port, DISCOVER_SVIDS);
@ -1763,9 +1769,11 @@ static void vdm_run_state_machine(struct tcpm_port *port)
break; break;
} }
if (res < 0) if (res < 0) {
port->vdm_sm_running = false;
return; return;
} }
}
port->vdm_state = VDM_STATE_SEND_MESSAGE; port->vdm_state = VDM_STATE_SEND_MESSAGE;
mod_vdm_delayed_work(port, (port->negotiated_rev >= PD_REV30 && mod_vdm_delayed_work(port, (port->negotiated_rev >= PD_REV30 &&
@ -1843,6 +1851,9 @@ static void vdm_state_machine_work(struct kthread_work *work)
port->vdm_state != VDM_STATE_BUSY && port->vdm_state != VDM_STATE_BUSY &&
port->vdm_state != VDM_STATE_SEND_MESSAGE); port->vdm_state != VDM_STATE_SEND_MESSAGE);
if (port->vdm_state == VDM_STATE_ERR_TMOUT)
port->vdm_sm_running = false;
mutex_unlock(&port->lock); mutex_unlock(&port->lock);
} }
@ -2226,6 +2237,12 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
} }
port->sink_request = le32_to_cpu(msg->payload[0]); port->sink_request = le32_to_cpu(msg->payload[0]);
if (port->vdm_sm_running && port->explicit_contract) {
tcpm_pd_handle_msg(port, PD_MSG_CTRL_WAIT, port->ams);
break;
}
if (port->state == SRC_SEND_CAPABILITIES) if (port->state == SRC_SEND_CAPABILITIES)
tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
else else
@ -2328,6 +2345,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
TYPEC_PWR_MODE_PD, TYPEC_PWR_MODE_PD,
port->pps_data.active, port->pps_data.active,
port->supply_voltage); port->supply_voltage);
/* Set VDM running flag ASAP */
if (port->data_role == TYPEC_HOST &&
port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state(port, SNK_READY, 0); tcpm_set_state(port, SNK_READY, 0);
} else { } else {
/* /*
@ -2365,10 +2386,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
switch (port->state) { switch (port->state) {
case SNK_NEGOTIATE_CAPABILITIES: case SNK_NEGOTIATE_CAPABILITIES:
/* USB PD specification, Figure 8-43 */ /* USB PD specification, Figure 8-43 */
if (port->explicit_contract) if (port->explicit_contract) {
next_state = SNK_READY; next_state = SNK_READY;
else if (port->data_role == TYPEC_HOST &&
port->send_discover)
port->vdm_sm_running = true;
} else {
next_state = SNK_WAIT_CAPABILITIES; next_state = SNK_WAIT_CAPABILITIES;
}
tcpm_set_state(port, next_state, 0); tcpm_set_state(port, next_state, 0);
break; break;
case SNK_NEGOTIATE_PPS_CAPABILITIES: case SNK_NEGOTIATE_PPS_CAPABILITIES:
@ -2377,6 +2402,11 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
port->pps_data.op_curr = port->current_limit; port->pps_data.op_curr = port->current_limit;
port->pps_status = (type == PD_CTRL_WAIT ? port->pps_status = (type == PD_CTRL_WAIT ?
-EAGAIN : -EOPNOTSUPP); -EAGAIN : -EOPNOTSUPP);
if (port->data_role == TYPEC_HOST &&
port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state(port, SNK_READY, 0); tcpm_set_state(port, SNK_READY, 0);
break; break;
case DR_SWAP_SEND: case DR_SWAP_SEND:
@ -2433,6 +2463,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
} }
break; break;
case DR_SWAP_SEND: case DR_SWAP_SEND:
if (port->data_role == TYPEC_DEVICE &&
port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0); tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0);
break; break;
case PR_SWAP_SEND: case PR_SWAP_SEND:
@ -2463,26 +2497,43 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
* 6.3.9: If an alternate mode is active, a request to swap * 6.3.9: If an alternate mode is active, a request to swap
* alternate modes shall trigger a port reset. * alternate modes shall trigger a port reset.
*/ */
if (port->typec_caps.data != TYPEC_PORT_DRD) if (port->typec_caps.data != TYPEC_PORT_DRD) {
tcpm_pd_handle_msg(port, tcpm_pd_handle_msg(port,
port->negotiated_rev < PD_REV30 ? port->negotiated_rev < PD_REV30 ?
PD_MSG_CTRL_REJECT : PD_MSG_CTRL_REJECT :
PD_MSG_CTRL_NOT_SUPP, PD_MSG_CTRL_NOT_SUPP,
NONE_AMS); NONE_AMS);
else } else {
if (port->vdm_sm_running) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
tcpm_pd_handle_state(port, DR_SWAP_ACCEPT, DATA_ROLE_SWAP, 0); tcpm_pd_handle_state(port, DR_SWAP_ACCEPT, DATA_ROLE_SWAP, 0);
}
break; break;
case PD_CTRL_PR_SWAP: case PD_CTRL_PR_SWAP:
if (port->port_type != TYPEC_PORT_DRP) if (port->port_type != TYPEC_PORT_DRP) {
tcpm_pd_handle_msg(port, tcpm_pd_handle_msg(port,
port->negotiated_rev < PD_REV30 ? port->negotiated_rev < PD_REV30 ?
PD_MSG_CTRL_REJECT : PD_MSG_CTRL_REJECT :
PD_MSG_CTRL_NOT_SUPP, PD_MSG_CTRL_NOT_SUPP,
NONE_AMS); NONE_AMS);
else } else {
if (port->vdm_sm_running) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
tcpm_pd_handle_state(port, PR_SWAP_ACCEPT, POWER_ROLE_SWAP, 0); tcpm_pd_handle_state(port, PR_SWAP_ACCEPT, POWER_ROLE_SWAP, 0);
}
break; break;
case PD_CTRL_VCONN_SWAP: case PD_CTRL_VCONN_SWAP:
if (port->vdm_sm_running) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
tcpm_pd_handle_state(port, VCONN_SWAP_ACCEPT, VCONN_SWAP, 0); tcpm_pd_handle_state(port, VCONN_SWAP_ACCEPT, VCONN_SWAP, 0);
break; break;
case PD_CTRL_GET_SOURCE_CAP_EXT: case PD_CTRL_GET_SOURCE_CAP_EXT:
@ -3346,6 +3397,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
} }
port->in_ams = false; port->in_ams = false;
port->ams = NONE_AMS; port->ams = NONE_AMS;
port->vdm_sm_running = false;
tcpm_unregister_altmodes(port); tcpm_unregister_altmodes(port);
tcpm_typec_disconnect(port); tcpm_typec_disconnect(port);
port->attached = false; port->attached = false;
@ -4144,6 +4196,9 @@ static void run_state_machine(struct tcpm_port *port)
break; break;
case DR_SWAP_ACCEPT: case DR_SWAP_ACCEPT:
tcpm_pd_send_control(port, PD_CTRL_ACCEPT); tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
/* Set VDM state machine running flag ASAP */
if (port->data_role == TYPEC_DEVICE && port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
break; break;
case DR_SWAP_SEND_TIMEOUT: case DR_SWAP_SEND_TIMEOUT:
@ -4299,6 +4354,8 @@ static void run_state_machine(struct tcpm_port *port)
break; break;
case VCONN_SWAP_SEND_TIMEOUT: case VCONN_SWAP_SEND_TIMEOUT:
tcpm_swap_complete(port, -ETIMEDOUT); tcpm_swap_complete(port, -ETIMEDOUT);
if (port->data_role == TYPEC_HOST && port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state(port, ready_state(port), 0); tcpm_set_state(port, ready_state(port), 0);
break; break;
case VCONN_SWAP_START: case VCONN_SWAP_START:
@ -4314,10 +4371,14 @@ static void run_state_machine(struct tcpm_port *port)
case VCONN_SWAP_TURN_ON_VCONN: case VCONN_SWAP_TURN_ON_VCONN:
tcpm_set_vconn(port, true); tcpm_set_vconn(port, true);
tcpm_pd_send_control(port, PD_CTRL_PS_RDY); tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
if (port->data_role == TYPEC_HOST && port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state(port, ready_state(port), 0); tcpm_set_state(port, ready_state(port), 0);
break; break;
case VCONN_SWAP_TURN_OFF_VCONN: case VCONN_SWAP_TURN_OFF_VCONN:
tcpm_set_vconn(port, false); tcpm_set_vconn(port, false);
if (port->data_role == TYPEC_HOST && port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state(port, ready_state(port), 0); tcpm_set_state(port, ready_state(port), 0);
break; break;
@ -4325,6 +4386,8 @@ static void run_state_machine(struct tcpm_port *port)
case PR_SWAP_CANCEL: case PR_SWAP_CANCEL:
case VCONN_SWAP_CANCEL: case VCONN_SWAP_CANCEL:
tcpm_swap_complete(port, port->swap_status); tcpm_swap_complete(port, port->swap_status);
if (port->data_role == TYPEC_HOST && port->send_discover)
port->vdm_sm_running = true;
if (port->pwr_role == TYPEC_SOURCE) if (port->pwr_role == TYPEC_SOURCE)
tcpm_set_state(port, SRC_READY, 0); tcpm_set_state(port, SRC_READY, 0);
else else
@ -4654,6 +4717,9 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port)
switch (port->state) { switch (port->state) {
case SNK_TRANSITION_SINK_VBUS: case SNK_TRANSITION_SINK_VBUS:
port->explicit_contract = true; port->explicit_contract = true;
/* Set the VDM flag ASAP */
if (port->data_role == TYPEC_HOST && port->send_discover)
port->vdm_sm_running = true;
tcpm_set_state(port, SNK_READY, 0); tcpm_set_state(port, SNK_READY, 0);
break; break;
case SNK_DISCOVERY: case SNK_DISCOVERY: