Commit 0f32114e authored by Bjorn Helgaas's avatar Bjorn Helgaas
Browse files

Merge branch 'pci/aspm'

- Disable ASPM on MFD function removal to avoid use-after-free (Ding Hui)

- Tighten up pci_enable_link_state() and pci_disable_link_state()
  interfaces so they don't enable/disable states the driver didn't specify
  (Ajay Agarwal)

- Avoid link retraining race that can happen if ASPM sets link control
  parameters while the link is in the midst of training for some other
  reason (Ilpo Järvinen)

* pci/aspm:
  PCI/ASPM: Avoid link retraining race
  PCI/ASPM: Factor out pcie_wait_for_retrain()
  PCI/ASPM: Return 0 or -ETIMEDOUT from  pcie_retrain_link()
  PCI/ASPM: Remove unnecessary ASPM_STATE_L1SS check
  PCI/ASPM: Rename L1.2-specific functions from 'l1ss' to 'l12'
  PCI/ASPM: Set ASPM_STATE_L1 when driver enables L1.1 or L1.2
  PCI/ASPM: Set only ASPM_STATE_L1 when driver enables L1
  PCI/ASPM: Disable only ASPM_STATE_L1 when driver disables L1
  PCI/ASPM: Disable ASPM on MFD function removal to avoid use-after-free
parents a274a4e6 e7e39756
Loading
Loading
Loading
Loading
+64 −46
Original line number Diff line number Diff line
@@ -193,12 +193,39 @@ static void pcie_clkpm_cap_init(struct pcie_link_state *link, int blacklist)
	link->clkpm_disable = blacklist ? 1 : 0;
}

static bool pcie_retrain_link(struct pcie_link_state *link)
static int pcie_wait_for_retrain(struct pci_dev *pdev)
{
	struct pci_dev *parent = link->pdev;
	unsigned long end_jiffies;
	u16 reg16;

	/* Wait for Link Training to be cleared by hardware */
	end_jiffies = jiffies + LINK_RETRAIN_TIMEOUT;
	do {
		pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &reg16);
		if (!(reg16 & PCI_EXP_LNKSTA_LT))
			return 0;
		msleep(1);
	} while (time_before(jiffies, end_jiffies));

	return -ETIMEDOUT;
}

static int pcie_retrain_link(struct pcie_link_state *link)
{
	struct pci_dev *parent = link->pdev;
	int rc;
	u16 reg16;

	/*
	 * Ensure the updated LNKCTL parameters are used during link
	 * training by checking that there is no ongoing link training to
	 * avoid LTSSM race as recommended in Implementation Note at the
	 * end of PCIe r6.0.1 sec 7.5.3.7.
	 */
	rc = pcie_wait_for_retrain(parent);
	if (rc)
		return rc;

	pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &reg16);
	reg16 |= PCI_EXP_LNKCTL_RL;
	pcie_capability_write_word(parent, PCI_EXP_LNKCTL, reg16);
@@ -212,15 +239,7 @@ static bool pcie_retrain_link(struct pcie_link_state *link)
		pcie_capability_write_word(parent, PCI_EXP_LNKCTL, reg16);
	}

	/* Wait for link training end. Break out after waiting for timeout */
	end_jiffies = jiffies + LINK_RETRAIN_TIMEOUT;
	do {
		pcie_capability_read_word(parent, PCI_EXP_LNKSTA, &reg16);
		if (!(reg16 & PCI_EXP_LNKSTA_LT))
			break;
		msleep(1);
	} while (time_before(jiffies, end_jiffies));
	return !(reg16 & PCI_EXP_LNKSTA_LT);
	return pcie_wait_for_retrain(parent);
}

/*
@@ -289,8 +308,7 @@ static void pcie_aspm_configure_common_clock(struct pcie_link_state *link)
		reg16 &= ~PCI_EXP_LNKCTL_CCC;
	pcie_capability_write_word(parent, PCI_EXP_LNKCTL, reg16);

	if (pcie_retrain_link(link))
		return;
	if (pcie_retrain_link(link)) {

		/* Training failed. Restore common clock configurations */
		pci_err(parent, "ASPM: Could not configure common clock\n");
@@ -299,6 +317,7 @@ static void pcie_aspm_configure_common_clock(struct pcie_link_state *link)
					   child_reg[PCI_FUNC(child->devfn)]);
		pcie_capability_write_word(parent, PCI_EXP_LNKCTL, parent_reg);
	}
}

/* Convert L0s latency encoding to ns */
static u32 calc_l0s_latency(u32 lnkcap)
@@ -337,7 +356,7 @@ static u32 calc_l1_acceptable(u32 encoding)
}

/* Convert L1SS T_pwr encoding to usec */
static u32 calc_l1ss_pwron(struct pci_dev *pdev, u32 scale, u32 val)
static u32 calc_l12_pwron(struct pci_dev *pdev, u32 scale, u32 val)
{
	switch (scale) {
	case 0:
@@ -471,7 +490,7 @@ static void pci_clear_and_set_dword(struct pci_dev *pdev, int pos,
}

/* Calculate L1.2 PM substate timing parameters */
static void aspm_calc_l1ss_info(struct pcie_link_state *link,
static void aspm_calc_l12_info(struct pcie_link_state *link,
				u32 parent_l1ss_cap, u32 child_l1ss_cap)
{
	struct pci_dev *child = link->downstream, *parent = link->pdev;
@@ -481,9 +500,6 @@ static void aspm_calc_l1ss_info(struct pcie_link_state *link,
	u32 pctl1, pctl2, cctl1, cctl2;
	u32 pl1_2_enables, cl1_2_enables;

	if (!(link->aspm_support & ASPM_STATE_L1_2_MASK))
		return;

	/* Choose the greater of the two Port Common_Mode_Restore_Times */
	val1 = (parent_l1ss_cap & PCI_L1SS_CAP_CM_RESTORE_TIME) >> 8;
	val2 = (child_l1ss_cap & PCI_L1SS_CAP_CM_RESTORE_TIME) >> 8;
@@ -495,13 +511,13 @@ static void aspm_calc_l1ss_info(struct pcie_link_state *link,
	val2   = (child_l1ss_cap & PCI_L1SS_CAP_P_PWR_ON_VALUE) >> 19;
	scale2 = (child_l1ss_cap & PCI_L1SS_CAP_P_PWR_ON_SCALE) >> 16;

	if (calc_l1ss_pwron(parent, scale1, val1) >
	    calc_l1ss_pwron(child, scale2, val2)) {
	if (calc_l12_pwron(parent, scale1, val1) >
	    calc_l12_pwron(child, scale2, val2)) {
		ctl2 |= scale1 | (val1 << 3);
		t_power_on = calc_l1ss_pwron(parent, scale1, val1);
		t_power_on = calc_l12_pwron(parent, scale1, val1);
	} else {
		ctl2 |= scale2 | (val2 << 3);
		t_power_on = calc_l1ss_pwron(child, scale2, val2);
		t_power_on = calc_l12_pwron(child, scale2, val2);
	}

	/*
@@ -616,8 +632,8 @@ static void aspm_l1ss_init(struct pcie_link_state *link)
	if (parent_l1ss_ctl1 & child_l1ss_ctl1 & PCI_L1SS_CTL1_PCIPM_L1_2)
		link->aspm_enabled |= ASPM_STATE_L1_2_PCIPM;

	if (link->aspm_support & ASPM_STATE_L1SS)
		aspm_calc_l1ss_info(link, parent_l1ss_cap, child_l1ss_cap);
	if (link->aspm_support & ASPM_STATE_L1_2_MASK)
		aspm_calc_l12_info(link, parent_l1ss_cap, child_l1ss_cap);
}

static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
@@ -1010,21 +1026,24 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev)

	down_read(&pci_bus_sem);
	mutex_lock(&aspm_lock);
	/*
	 * All PCIe functions are in one slot, remove one function will remove
	 * the whole slot, so just wait until we are the last function left.
	 */
	if (!list_empty(&parent->subordinate->devices))
		goto out;

	link = parent->link_state;
	root = link->root;
	parent_link = link->parent;

	/* All functions are removed, so just disable ASPM for the link */
	/*
	 * link->downstream is a pointer to the pci_dev of function 0.  If
	 * we remove that function, the pci_dev is about to be deallocated,
	 * so we can't use link->downstream again.  Free the link state to
	 * avoid this.
	 *
	 * If we're removing a non-0 function, it's possible we could
	 * retain the link state, but PCIe r6.0, sec 7.5.3.7, recommends
	 * programming the same ASPM Control value for all functions of
	 * multi-function devices, so disable ASPM for all of them.
	 */
	pcie_config_aspm_link(link, 0);
	list_del(&link->sibling);
	/* Clock PM is for endpoint device */
	free_link_state(link);

	/* Recheck latencies and configure upstream links */
@@ -1032,7 +1051,7 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev)
		pcie_update_aspm_capable(root);
		pcie_config_aspm_path(parent_link);
	}
out:

	mutex_unlock(&aspm_lock);
	up_read(&pci_bus_sem);
}
@@ -1095,8 +1114,7 @@ static int __pci_disable_link_state(struct pci_dev *pdev, int state, bool sem)
	if (state & PCIE_LINK_STATE_L0S)
		link->aspm_disable |= ASPM_STATE_L0S;
	if (state & PCIE_LINK_STATE_L1)
		/* L1 PM substates require L1 */
		link->aspm_disable |= ASPM_STATE_L1 | ASPM_STATE_L1SS;
		link->aspm_disable |= ASPM_STATE_L1;
	if (state & PCIE_LINK_STATE_L1_1)
		link->aspm_disable |= ASPM_STATE_L1_1;
	if (state & PCIE_LINK_STATE_L1_2)
@@ -1171,16 +1189,16 @@ int pci_enable_link_state(struct pci_dev *pdev, int state)
	if (state & PCIE_LINK_STATE_L0S)
		link->aspm_default |= ASPM_STATE_L0S;
	if (state & PCIE_LINK_STATE_L1)
		link->aspm_default |= ASPM_STATE_L1;
	/* L1 PM substates require L1 */
		link->aspm_default |= ASPM_STATE_L1 | ASPM_STATE_L1SS;
	if (state & PCIE_LINK_STATE_L1_1)
		link->aspm_default |= ASPM_STATE_L1_1;
		link->aspm_default |= ASPM_STATE_L1_1 | ASPM_STATE_L1;
	if (state & PCIE_LINK_STATE_L1_2)
		link->aspm_default |= ASPM_STATE_L1_2;
		link->aspm_default |= ASPM_STATE_L1_2 | ASPM_STATE_L1;
	if (state & PCIE_LINK_STATE_L1_1_PCIPM)
		link->aspm_default |= ASPM_STATE_L1_1_PCIPM;
		link->aspm_default |= ASPM_STATE_L1_1_PCIPM | ASPM_STATE_L1;
	if (state & PCIE_LINK_STATE_L1_2_PCIPM)
		link->aspm_default |= ASPM_STATE_L1_2_PCIPM;
		link->aspm_default |= ASPM_STATE_L1_2_PCIPM | ASPM_STATE_L1;
	pcie_config_aspm_link(link, policy_to_aspm_state(link));

	link->clkpm_default = (state & PCIE_LINK_STATE_CLKPM) ? 1 : 0;