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

Commit 1873f86a authored by Mayank Rana's avatar Mayank Rana
Browse files

dwc3-msm: Fix race condition between USB PM suspend and cable connect



dwc3_msm_pm_suspend() is racing against USB cable connect as below:
- Device is power collapse. On connecting USB cable, APPS is resumed and
SMB charger driver is receiving interrupt while Linux power framework
calls each registered peripherals' driver PM resume, and it agains tries
to go into suspend as SMB charger doesn't probably hold wakelock or there
is more interrupt latency in terms of smb charger driver's ISR is called.
- dwc3_msm_pm_resume() schedules resume work. resume_work() is queued but
it is not running.
- Since autosleep is enabled and no wakeup source have yet been taken,
the system will immediately attempt to re-enter PM suspend. Hence USB PM
suspend routine dwc3_msm_pm_suspend() is called which checks USB LPM
status by calling dwc3_msm_suspend() and finds USB is already suspended,
and it marks pm_suspended flag to true.
- dwc3_resume_work() starts running and check pm_suspended flag. If it is
true, it doesn't call dwc3_ext_event_notify() which update vbus/id state
with mdwc->inputs, and schedules sm_work which brings USB out of LPM and
acquires wakeup source to prevent APPS power collapse if USB cable is
connected.

Fix above issue by taking wakeup source i.e. using pm_stay_awake() on
receiving VBUS/ID and power event related notification which makes sure
that system stays out of PM sleep until resume_work and sm_work have had
chance to run.

Also log important handling of important events with sm_work,
pm_stay_awake() and pm_relax() usage with USB buffer to improve debugging.

CRs-Fixed: 1074315
Change-Id: I94fb0e5ae5e48c8932cf9e3540bd080e67d44988
Signed-off-by: default avatarMayank Rana <mrana@codeaurora.org>
parent c4e05e15
Loading
Loading
Loading
Loading
+35 −12
Original line number Diff line number Diff line
@@ -1961,6 +1961,7 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)

	if (atomic_read(&dwc->in_lpm)) {
		dev_dbg(mdwc->dev, "%s: Already suspended\n", __func__);
		dbg_event(0xFF, "AlreadySUS", 0);
		return 0;
	}

@@ -2104,6 +2105,7 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc)
	}

	dev_info(mdwc->dev, "DWC3 in low power mode\n");
	dbg_event(0xFF, "SUSComplete", mdwc->lpm_to_suspend_delay);
	return 0;
}

@@ -2113,9 +2115,11 @@ static int dwc3_msm_resume(struct dwc3_msm *mdwc)
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);

	dev_dbg(mdwc->dev, "%s: exiting lpm\n", __func__);
	dbg_event(0xFF, "msmresume", atomic_read(&dwc->in_lpm));

	if (!atomic_read(&dwc->in_lpm)) {
		dev_dbg(mdwc->dev, "%s: Already resumed\n", __func__);
		dbg_event(0xFF, "AlreadyRES", 0);
		return 0;
	}

@@ -2370,7 +2374,7 @@ static irqreturn_t msm_dwc3_pwr_irq_thread(int irq, void *_mdwc)
	else
		dwc3_pwr_event_handler(mdwc);

	dbg_event(0xFF, "PWR IRQ", atomic_read(&dwc->in_lpm));
	dbg_event(0xFF, "PIRQThread", atomic_read(&dwc->in_lpm));

	return IRQ_HANDLED;
}
@@ -2390,6 +2394,7 @@ static irqreturn_t msm_dwc3_pwr_irq(int irq, void *data)
	 * all other power events.
	 */
	if (atomic_read(&dwc->in_lpm)) {
		pm_stay_awake(mdwc->dev);
		/* set this to call dwc3_msm_resume() */
		mdwc->resume_pending = true;
		return IRQ_WAKE_THREAD;
@@ -2461,9 +2466,12 @@ static int dwc3_msm_power_set_property_usb(struct power_supply *psy,
		/* Let OTG know about ID detection */
		mdwc->id_state = id;
		dbg_event(0xFF, "id_state", mdwc->id_state);
		if (dwc->is_drd)
		if (dwc->is_drd) {
			dbg_event(0xFF, "stayID", 0);
			pm_stay_awake(mdwc->dev);
			queue_delayed_work(mdwc->dwc3_wq,
					&mdwc->resume_work, 0);
		}
		break;
	/* PMIC notification for DP_DM state */
	case POWER_SUPPLY_PROP_DP_DM:
@@ -2496,6 +2504,8 @@ static int dwc3_msm_power_set_property_usb(struct power_supply *psy,
		mdwc->vbus_active = val->intval;
		if (dwc->is_drd && !mdwc->in_restart) {
			dbg_event(0xFF, "Q RW (vbus)", val->intval);
			dbg_event(0xFF, "stayVbus", 0);
			pm_stay_awake(mdwc->dev);
			queue_delayed_work(mdwc->dwc3_wq,
					&mdwc->resume_work, 0);
		}
@@ -2600,13 +2610,16 @@ static enum power_supply_property dwc3_msm_pm_power_props_usb[] = {
static irqreturn_t dwc3_pmic_id_irq(int irq, void *data)
{
	struct dwc3_msm *mdwc = data;
	struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3);
	enum dwc3_id_state id;

	/* If we can't read ID line state for some reason, treat it as float */
	id = !!irq_read_line(irq);
	if (mdwc->id_state != id) {
		mdwc->id_state = id;
		schedule_work(&mdwc->resume_work.work);
		dbg_event(0xFF, "stayIDIRQ", 0);
		pm_stay_awake(mdwc->dev);
		queue_delayed_work(mdwc->dwc3_wq, &mdwc->resume_work, 0);
	}

	return IRQ_HANDLED;
@@ -3569,6 +3582,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
				dwc3_msm_gadget_vbus_draw(mdwc,
						dcp_max_current);
				atomic_set(&dwc->in_lpm, 1);
				dbg_event(0xFF, "RelaxDCP", 0);
				pm_relax(mdwc->dev);
				break;
			case DWC3_CDP_CHARGER:
@@ -3612,24 +3626,28 @@ static void dwc3_otg_sm_work(struct work_struct *w)

	case OTG_STATE_B_IDLE:
		if (!test_bit(ID, &mdwc->inputs)) {
			dev_dbg(mdwc->dev, "!id\n");
			dbg_event(0xFF, "!id", 0);
			mdwc->otg_state = OTG_STATE_A_IDLE;
			work = 1;
			mdwc->chg_type = DWC3_INVALID_CHARGER;
		} else if (test_bit(B_SESS_VLD, &mdwc->inputs)) {
			dev_dbg(mdwc->dev, "b_sess_vld\n");
			dbg_event(0xFF, "b_sess_vld", 0);
			switch (mdwc->chg_type) {
			case DWC3_DCP_CHARGER:
			case DWC3_PROPRIETARY_CHARGER:
				dev_dbg(mdwc->dev, "lpm, DCP charger\n");
				dbg_event(0xFF, "DCPCharger", 0);
				dwc3_msm_gadget_vbus_draw(mdwc,
						dcp_max_current);
				dbg_event(0xFF, "RelDCPBIDLE", 0);
				pm_relax(mdwc->dev);
				break;
			case DWC3_CDP_CHARGER:
				dbg_event(0xFF, "CDPCharger", 0);
				dwc3_msm_gadget_vbus_draw(mdwc,
						DWC3_IDEV_CHG_MAX);
				/* fall through */
			case DWC3_SDP_CHARGER:
				dbg_event(0xFF, "SDPCharger", 0);
				/*
				 * Increment pm usage count upon cable
				 * connect. Count is decremented in
@@ -3659,13 +3677,15 @@ static void dwc3_otg_sm_work(struct work_struct *w)
			mdwc->typec_current_max = 0;
			dwc3_msm_gadget_vbus_draw(mdwc, 0);
			dev_dbg(mdwc->dev, "No device, allowing suspend\n");
			dbg_event(0xFF, "RelNodev", 0);
			pm_relax(mdwc->dev);
		}
		break;

	case OTG_STATE_B_PERIPHERAL:
		if (!test_bit(B_SESS_VLD, &mdwc->inputs) ||
				!test_bit(ID, &mdwc->inputs)) {
			dev_dbg(mdwc->dev, "!id || !bsv\n");
			dbg_event(0xFF, "!id || !bsv", 0);
			mdwc->otg_state = OTG_STATE_B_IDLE;
			dwc3_otg_start_peripheral(mdwc, 0);
			/*
@@ -3680,7 +3700,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
			work = 1;
		} else if (test_bit(B_SUSPEND, &mdwc->inputs) &&
			test_bit(B_SESS_VLD, &mdwc->inputs)) {
			dev_dbg(mdwc->dev, "BPER bsv && susp\n");
			dbg_event(0xFF, "BPER susp", 0);
			mdwc->otg_state = OTG_STATE_B_SUSPEND;
			/*
			 * Decrement pm usage count upon bus suspend.
@@ -3698,11 +3718,11 @@ static void dwc3_otg_sm_work(struct work_struct *w)

	case OTG_STATE_B_SUSPEND:
		if (!test_bit(B_SESS_VLD, &mdwc->inputs)) {
			dev_dbg(mdwc->dev, "BSUSP: !bsv\n");
			dbg_event(0xFF, "BSUSP: !bsv", 0);
			mdwc->otg_state = OTG_STATE_B_IDLE;
			dwc3_otg_start_peripheral(mdwc, 0);
		} else if (!test_bit(B_SUSPEND, &mdwc->inputs)) {
			dev_dbg(mdwc->dev, "BSUSP !susp\n");
			dbg_event(0xFF, "BSUSP: !susp", 0);
			mdwc->otg_state = OTG_STATE_B_PERIPHERAL;
			/*
			 * Increment pm usage count upon host
@@ -3719,7 +3739,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)
	case OTG_STATE_A_IDLE:
		/* Switch to A-Device*/
		if (test_bit(ID, &mdwc->inputs)) {
			dev_dbg(mdwc->dev, "id\n");
			dbg_event(0xFF, "id", 0);
			mdwc->otg_state = OTG_STATE_B_IDLE;
			mdwc->vbus_retry_count = 0;
			work = 1;
@@ -3747,7 +3767,7 @@ static void dwc3_otg_sm_work(struct work_struct *w)

	case OTG_STATE_A_HOST:
		if (test_bit(ID, &mdwc->inputs) || mdwc->hc_died) {
			dev_dbg(mdwc->dev, "id || hc_died\n");
			dbg_event(0xFF, "id || hc_died", 0);
			dwc3_otg_start_host(mdwc, 0);
			mdwc->otg_state = OTG_STATE_B_IDLE;
			mdwc->vbus_retry_count = 0;
@@ -3793,6 +3813,8 @@ static int dwc3_msm_pm_suspend(struct device *dev)
	if (!ret)
		atomic_set(&mdwc->pm_suspended, 1);

	dbg_event(0xFF, "vbus_active", mdwc->vbus_active);
	dbg_event(0xFF, "otg_state", mdwc->otg_state);
	return ret;
}

@@ -3812,6 +3834,7 @@ static int dwc3_msm_pm_resume(struct device *dev)
	/* kick in otg state machine */
	queue_delayed_work(mdwc->dwc3_wq, &mdwc->resume_work, 0);

	dbg_event(0xFF, "PMResComplete", 0);
	return 0;
}
#endif