Unverified Commit 98eec07b authored by openeuler-ci-bot's avatar openeuler-ci-bot Committed by Gitee
Browse files

!4346 v2 pciehp: fix a race between pciehp and removing operations by sysfs

parents 370d9c06 6bab6f66
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -199,6 +199,11 @@ static inline const char *slot_name(struct controller *ctrl)
	return hotplug_slot_name(&ctrl->hotplug_slot);
}

static inline struct pci_dev *ctrl_dev(struct controller *ctrl)
{
	return ctrl->pcie->port;
}

static inline struct controller *to_ctrl(struct hotplug_slot *hotplug_slot)
{
	return container_of(hotplug_slot, struct controller, hotplug_slot);
+40 −0
Original line number Diff line number Diff line
@@ -143,6 +143,8 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)
{
	struct controller *ctrl = container_of(work, struct controller,
					       button_work.work);
	int events = ctrl->button_work.data;
	struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;

	mutex_lock(&ctrl->state_lock);
	switch (ctrl->state) {
@@ -153,6 +155,15 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)
		pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
		break;
	default:
		if (events) {
			atomic_or(events, &ctrl->pending_events);
			if (!pciehp_poll_mode)
				irq_wake_thread(ctrl->pcie->irq, ctrl);
		} else {
			if (rpdev)
				clear_bit(0,
					  &rpdev->slot_being_removed_rescanned);
		}
		break;
	}
	mutex_unlock(&ctrl->state_lock);
@@ -160,6 +171,8 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)

void pciehp_handle_button_press(struct controller *ctrl)
{
	struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;

	mutex_lock(&ctrl->state_lock);
	switch (ctrl->state) {
	case OFF_STATE:
@@ -176,6 +189,7 @@ void pciehp_handle_button_press(struct controller *ctrl)
		/* blink power indicator and turn off attention */
		pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_BLINK,
				      PCI_EXP_SLTCTL_ATTN_IND_OFF);
		ctrl->button_work.data = 0;
		schedule_delayed_work(&ctrl->button_work, 5 * HZ);
		break;
	case BLINKINGOFF_STATE:
@@ -199,10 +213,14 @@ void pciehp_handle_button_press(struct controller *ctrl)
			ctrl_info(ctrl, "Slot(%s): Button press: canceling request to power on\n",
				  slot_name(ctrl));
		}
		if (rpdev)
			clear_bit(0, &rpdev->slot_being_removed_rescanned);
		break;
	default:
		ctrl_err(ctrl, "Slot(%s): Button press: ignoring invalid state %#x\n",
			 slot_name(ctrl), ctrl->state);
		if (rpdev)
			clear_bit(0, &rpdev->slot_being_removed_rescanned);
		break;
	}
	mutex_unlock(&ctrl->state_lock);
@@ -210,6 +228,8 @@ void pciehp_handle_button_press(struct controller *ctrl)

void pciehp_handle_disable_request(struct controller *ctrl)
{
	struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;

	mutex_lock(&ctrl->state_lock);
	switch (ctrl->state) {
	case BLINKINGON_STATE:
@@ -221,11 +241,14 @@ void pciehp_handle_disable_request(struct controller *ctrl)
	mutex_unlock(&ctrl->state_lock);

	ctrl->request_result = pciehp_disable_slot(ctrl, SAFE_REMOVAL);
	if (rpdev)
		clear_bit(0, &rpdev->slot_being_removed_rescanned);
}

void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events)
{
	int present, link_active;
	struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;

	/*
	 * If the slot is on and presence or link has changed, turn it off.
@@ -266,6 +289,8 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events)
				  slot_name(ctrl));
		}
		mutex_unlock(&ctrl->state_lock);
		if (rpdev)
			clear_bit(0, &rpdev->slot_being_removed_rescanned);
		return;
	}

@@ -288,6 +313,8 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events)
		mutex_unlock(&ctrl->state_lock);
		break;
	}
	if (rpdev)
		clear_bit(0, &rpdev->slot_being_removed_rescanned);
}

static int __pciehp_enable_slot(struct controller *ctrl)
@@ -408,6 +435,14 @@ int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot)
int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot)
{
	struct controller *ctrl = to_ctrl(hotplug_slot);
	struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;

	if (rpdev && test_and_set_bit(0,
				&rpdev->slot_being_removed_rescanned)) {
		ctrl_info(ctrl, "Slot(%s): Slot is being removed or rescanned, please try later!\n",
			  slot_name(ctrl));
		return -EINVAL;
	}

	mutex_lock(&ctrl->state_lock);
	switch (ctrl->state) {
@@ -418,6 +453,8 @@ int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot)
		wait_event(ctrl->requester,
			   !atomic_read(&ctrl->pending_events) &&
			   !ctrl->ist_running);
		if (rpdev)
			clear_bit(0, &rpdev->slot_being_removed_rescanned);
		return ctrl->request_result;
	case POWEROFF_STATE:
		ctrl_info(ctrl, "Slot(%s): Already in powering off state\n",
@@ -436,5 +473,8 @@ int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot)
	}
	mutex_unlock(&ctrl->state_lock);

	if (rpdev)
		clear_bit(0, &rpdev->slot_being_removed_rescanned);

	return -ENODEV;
}
+68 −11
Original line number Diff line number Diff line
@@ -45,11 +45,6 @@ static const struct dmi_system_id inband_presence_disabled_dmi_table[] = {
	{}
};

static inline struct pci_dev *ctrl_dev(struct controller *ctrl)
{
	return ctrl->pcie->port;
}

static irqreturn_t pciehp_isr(int irq, void *dev_id);
static irqreturn_t pciehp_ist(int irq, void *dev_id);
static int pciehp_poll(void *data);
@@ -694,6 +689,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
{
	struct controller *ctrl = (struct controller *)dev_id;
	struct pci_dev *pdev = ctrl_dev(ctrl);
	struct pci_dev *rpdev = pdev->rpdev;
	irqreturn_t ret;
	u32 events;

@@ -716,8 +712,20 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
	}

	/* Check Attention Button Pressed */
	if (events & PCI_EXP_SLTSTA_ABP)
	if (events & PCI_EXP_SLTSTA_ABP) {
		if (!rpdev || (rpdev && !test_and_set_bit(0,
					&rpdev->slot_being_removed_rescanned)))
			pciehp_handle_button_press(ctrl);
		else {
			if (ctrl->state == BLINKINGOFF_STATE ||
					ctrl->state == BLINKINGON_STATE)
				pciehp_handle_button_press(ctrl);
			else
				ctrl_info(ctrl, "Slot(%s): Slot operation failed because a remove or"
					  " rescan operation is under processing, please try later!\n",
					  slot_name(ctrl));
		}
	}

	/* Check Power Fault Detected */
	if (events & PCI_EXP_SLTSTA_PFD) {
@@ -741,10 +749,59 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
	 * or Data Link Layer State Changed events.
	 */
	down_read_nested(&ctrl->reset_lock, ctrl->depth);
	if (events & DISABLE_SLOT)
	if (events & DISABLE_SLOT) {
		if (!rpdev || (rpdev && !test_and_set_bit(0,
					&rpdev->slot_being_removed_rescanned)))
			pciehp_handle_disable_request(ctrl);
		else {
			if (ctrl->state == BLINKINGOFF_STATE ||
					ctrl->state == BLINKINGON_STATE)
				pciehp_handle_disable_request(ctrl);
	else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC))
			else {
				ctrl_info(ctrl, "Slot(%s): DISABLE_SLOT event in remove or rescan process!\n",
						slot_name(ctrl));
				/*
				 * we use the work_struct private data to store
				 * the event type
				 */
				ctrl->button_work.data = DISABLE_SLOT;
				/*
				 * If 'work.timer' is pending, schedule the work will
				 * cause BUG_ON().
				 */
				if (!timer_pending(&ctrl->button_work.timer))
					schedule_delayed_work(&ctrl->button_work, 3 * HZ);
				else
					ctrl_info(ctrl, "Slot(%s): Didn't schedule delayed_work because timer is pending!\n",
							slot_name(ctrl));
			}
		}
	} else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) {
		if (!rpdev || (rpdev && !test_and_set_bit(0,
					&rpdev->slot_being_removed_rescanned)))
			pciehp_handle_presence_or_link_change(ctrl, events);
		else {
			if (ctrl->state == BLINKINGOFF_STATE ||
					ctrl->state == BLINKINGON_STATE)
				pciehp_handle_presence_or_link_change(ctrl,
						events);
			else {
				/*
				 * When we are removing or rescanning through
				 * sysfs, suprise link down/up happens. So we
				 * will handle this event 3 seconds later.
				 */
				ctrl_info(ctrl, "Slot(%s): Surprise link down/up in remove or rescan process!\n",
						slot_name(ctrl));
				ctrl->button_work.data = events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
				if (!timer_pending(&ctrl->button_work.timer))
					schedule_delayed_work(&ctrl->button_work, 3 * HZ);
				else
					ctrl_info(ctrl, "Slot(%s): Didn't schedule delayed_work because timer is pending!\n",
							slot_name(ctrl));
			}
		}
	}
	up_read(&ctrl->reset_lock);

	ret = IRQ_HANDLED;
+22 −0
Original line number Diff line number Diff line
@@ -484,16 +484,38 @@ static ssize_t remove_store(struct device *dev, struct device_attribute *attr,
{
	unsigned long val;
	struct pci_dev *pdev = to_pci_dev(dev);
	struct pci_dev *rpdev = pdev->rpdev;

	if (kstrtoul(buf, 0, &val) < 0)
		return -EINVAL;

	if (rpdev && test_and_set_bit(0,
				&rpdev->slot_being_removed_rescanned)) {
		pr_info("Slot is being removed or rescanned, please try later!\n");
		return -EINVAL;
	}

	/*
	 * if 'dev' is root port itself, 'pci_stop_and_remove_bus_device()' may
	 * free the 'rpdev', but we need to clear
	 * 'rpdev->slot_being_removed_rescanned' in the end. So get 'rpdev' to
	 * avoid possible 'use-after-free'.
	 */
	if (rpdev)
		pci_dev_get(rpdev);

	if (val) {
		pci_dev_get(pdev);
		if (device_remove_file_self(dev, attr))
			pci_stop_and_remove_bus_device_locked(pdev);
		pci_dev_put(pdev);
	}

	if (rpdev) {
		clear_bit(0, &rpdev->slot_being_removed_rescanned);
		pci_dev_put(rpdev);
	}

	return count;
}
static DEVICE_ATTR_IGNORE_LOCKDEP(remove, 0220, NULL,
+5 −0
Original line number Diff line number Diff line
@@ -2584,6 +2584,11 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
	/* Set up MSI IRQ domain */
	pci_set_msi_domain(dev);

	if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT)
		dev->rpdev = dev;
	else
		dev->rpdev = pcie_find_root_port(dev);

	/* Notifier could use PCI capabilities */
	dev->match_driver = false;
	ret = device_add(&dev->dev);
Loading