Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit af7d3841 authored by Jack Pham's avatar Jack Pham
Browse files

usb: pd: Make select_pdo_store() wait until request goes through



When sending a request through the 'select_pdo' sysfs file,
make sure the request has gone through and is acknowledged
by the source before returning. This allows for checking if
the request was rejected so that an error can be returned.

Rename the 'swap_complete' completion variable to 'is_ready'
so it can be reused in this context. Also add a mutex so that
select_pdo will not contend with a dual_role swap.

Change-Id: Ie8b088faa15c67915e3bd96972b4f59f0cc66afe
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent ee1f9051
Loading
Loading
Loading
Loading
+58 −15
Original line number Diff line number Diff line
@@ -338,8 +338,10 @@ struct usbpd {
	enum power_role		current_pr;
	bool			in_pr_swap;
	bool			pd_phy_opened;
	struct completion	swap_complete;
	bool			send_request;
	struct completion	is_ready;

	struct mutex		swap_lock;
	struct dual_role_phy_instance	*dual_role;
	struct dual_role_phy_desc	dr_desc;
	bool			send_pr_swap;
@@ -463,6 +465,9 @@ static inline void pd_reset_protocol(struct usbpd *pd)
	 */
	pd->rx_msgid = -1;
	pd->tx_msgid = 0;
	pd->send_request = false;
	pd->send_pr_swap = false;
	pd->send_dr_swap = false;
}

static int pd_send_msg(struct usbpd *pd, u8 hdr_type, const u32 *data,
@@ -842,7 +847,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
		}

		kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		complete(&pd->swap_complete);
		complete(&pd->is_ready);
		dual_role_instance_changed(pd->dual_role);
		break;

@@ -977,7 +982,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
	case PE_SNK_READY:
		pd->in_explicit_contract = true;
		kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
		complete(&pd->swap_complete);
		complete(&pd->is_ready);
		dual_role_instance_changed(pd->dual_role);
		break;

@@ -2075,6 +2080,9 @@ static void usbpd_sm(struct work_struct *w)
			vconn_swap(pd);
		} else if (IS_DATA(rx_msg, MSG_VDM)) {
			handle_vdm_rx(pd, rx_msg);
		} else if (pd->send_request) {
			pd->send_request = false;
			usbpd_set_state(pd, PE_SNK_SELECT_CAPABILITY);
		} else if (pd->send_pr_swap && is_sink_tx_ok(pd)) {
			pd->send_pr_swap = false;
			ret = pd_send_msg(pd, MSG_PR_SWAP, NULL, 0, SOP_MSG);
@@ -2540,17 +2548,21 @@ static int usbpd_dr_set_property(struct dual_role_phy_instance *dual_role,
				return -EAGAIN;
			}

			reinit_completion(&pd->swap_complete);
			mutex_lock(&pd->swap_lock);
			reinit_completion(&pd->is_ready);
			pd->send_dr_swap = true;
			kick_sm(pd, 0);

			/* wait for operation to complete */
			if (!wait_for_completion_timeout(&pd->swap_complete,
			if (!wait_for_completion_timeout(&pd->is_ready,
					msecs_to_jiffies(100))) {
				usbpd_err(&pd->dev, "data_role swap timed out\n");
				mutex_unlock(&pd->swap_lock);
				return -ETIMEDOUT;
			}

			mutex_unlock(&pd->swap_lock);

			if ((*val == DUAL_ROLE_PROP_DR_HOST &&
					pd->current_dr != DR_DFP) ||
				(*val == DUAL_ROLE_PROP_DR_DEVICE &&
@@ -2591,17 +2603,21 @@ static int usbpd_dr_set_property(struct dual_role_phy_instance *dual_role,
				return -EAGAIN;
			}

			reinit_completion(&pd->swap_complete);
			mutex_lock(&pd->swap_lock);
			reinit_completion(&pd->is_ready);
			pd->send_pr_swap = true;
			kick_sm(pd, 0);

			/* wait for operation to complete */
			if (!wait_for_completion_timeout(&pd->swap_complete,
			if (!wait_for_completion_timeout(&pd->is_ready,
					msecs_to_jiffies(2000))) {
				usbpd_err(&pd->dev, "power_role swap timed out\n");
				mutex_unlock(&pd->swap_lock);
				return -ETIMEDOUT;
			}

			mutex_unlock(&pd->swap_lock);

			if ((*val == DUAL_ROLE_PROP_PR_SRC &&
					pd->current_pr != PR_SRC) ||
				(*val == DUAL_ROLE_PROP_PR_SNK &&
@@ -2864,36 +2880,62 @@ static ssize_t select_pdo_store(struct device *dev,
	int pdo, uv = 0, ua = 0;
	int ret;

	mutex_lock(&pd->swap_lock);

	/* Only allowed if we are already in explicit sink contract */
	if (pd->current_state != PE_SNK_READY || !is_sink_tx_ok(pd)) {
		usbpd_err(&pd->dev, "select_pdo: Cannot select new PDO yet\n");
		return -EBUSY;
		ret = -EBUSY;
		goto out;
	}

	ret = sscanf(buf, "%d %d %d %d", &src_cap_id, &pdo, &uv, &ua);
	if (ret != 2 && ret != 4) {
		usbpd_err(&pd->dev, "select_pdo: Must specify <src cap id> <PDO> [<uV> <uA>]\n");
		return -EINVAL;
		ret = -EINVAL;
		goto out;
	}

	if (src_cap_id != pd->src_cap_id) {
		usbpd_err(&pd->dev, "select_pdo: src_cap_id mismatch.  Requested:%d, current:%d\n",
				src_cap_id, pd->src_cap_id);
		return -EINVAL;
		ret = -EINVAL;
		goto out;
	}

	if (pdo < 1 || pdo > 7) {
		usbpd_err(&pd->dev, "select_pdo: invalid PDO:%d\n", pdo);
		return -EINVAL;
		ret = -EINVAL;
		goto out;
	}

	ret = pd_select_pdo(pd, pdo, uv, ua);
	if (ret)
		return ret;
		goto out;

	usbpd_set_state(pd, PE_SNK_SELECT_CAPABILITY);
	reinit_completion(&pd->is_ready);
	pd->send_request = true;
	kick_sm(pd, 0);

	return size;
	/* wait for operation to complete */
	if (!wait_for_completion_timeout(&pd->is_ready,
			msecs_to_jiffies(1000))) {
		usbpd_err(&pd->dev, "select_pdo: request timed out\n");
		ret = -ETIMEDOUT;
		goto out;
	}

	/* determine if request was accepted/rejected */
	if (pd->selected_pdo != pd->requested_pdo ||
			pd->current_voltage != pd->requested_voltage) {
		usbpd_err(&pd->dev, "select_pdo: request rejected\n");
		ret = -EINVAL;
	}

out:
	pd->send_request = false;
	mutex_unlock(&pd->swap_lock);
	return ret ? ret : size;
}

static ssize_t select_pdo_show(struct device *dev,
@@ -3123,6 +3165,7 @@ struct usbpd *usbpd_create(struct device *parent)
	INIT_WORK(&pd->sm_work, usbpd_sm);
	hrtimer_init(&pd->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	pd->timer.function = pd_timeout;
	mutex_init(&pd->swap_lock);

	pd->usb_psy = power_supply_get_by_name("usb");
	if (!pd->usb_psy) {
@@ -3233,7 +3276,7 @@ struct usbpd *usbpd_create(struct device *parent)
	spin_lock_init(&pd->rx_lock);
	INIT_LIST_HEAD(&pd->rx_q);
	INIT_LIST_HEAD(&pd->svid_handlers);
	init_completion(&pd->swap_complete);
	init_completion(&pd->is_ready);

	pd->psy_nb.notifier_call = psy_changed;
	ret = power_supply_reg_notifier(&pd->psy_nb);