提交 834faefb 编写于 作者: X Xiongfeng Wang 提交者: Xie XiuQi

pciehp: make 'slot_being_removed_rescanned' per root port

hulk inclusion
category: bugfix
bugzilla: 20881
CVE: NA
---------------------------

We use a global variable 'slot_being_removed_rescaned' to mark whether a
slot is being removed or rescaned. This will cause a slot hotplug
operation is delayed if another slot is being remove or rescaned. But
if these two slots are under different root ports, they should not
influence each other. This patch make the flag
'slot_being_removed_rescanned' per root port so that one slot hotplug
operation doesn't influence slots below another root port.

We record the root port in struct pci_dev when the pci device is
initialized and added into the system instead of using
'pcie_find_root_port()' to find the root port when we need it. Because
iterating the pci tree needs the protection of
'pci_lock_rescan_remove()'. This will make the problem more complexed
because the lock is very coarse-grained. We don't need to worry about
'use-after-free' because child pci devices are always removed before the
root port device is removed.

Fixes: 764cafd9875e ("pciehp: fix a race between pciehp and removing
operations by sysfs")
Signed-off-by: NXiongfeng Wang <wangxiongfeng2@huawei.com>
Reviewed-by: NHanjun Guo <guohanjun@huawei.com>
Signed-off-by: NYang Yingliang <yangyingliang@huawei.com>
上级 77a2fc07
...@@ -213,4 +213,9 @@ static inline const char *slot_name(struct slot *slot) ...@@ -213,4 +213,9 @@ static inline const char *slot_name(struct slot *slot)
return hotplug_slot_name(slot->hotplug_slot); return hotplug_slot_name(slot->hotplug_slot);
} }
static inline struct pci_dev *ctrl_dev(struct controller *ctrl)
{
return ctrl->pcie->port;
}
#endif /* _PCIEHP_H */ #endif /* _PCIEHP_H */
...@@ -146,6 +146,7 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) ...@@ -146,6 +146,7 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)
struct slot *p_slot = container_of(work, struct slot, work.work); struct slot *p_slot = container_of(work, struct slot, work.work);
struct controller *ctrl = p_slot->ctrl; struct controller *ctrl = p_slot->ctrl;
int events = p_slot->work.data; int events = p_slot->work.data;
struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;
mutex_lock(&p_slot->lock); mutex_lock(&p_slot->lock);
switch (p_slot->state) { switch (p_slot->state) {
...@@ -160,8 +161,11 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) ...@@ -160,8 +161,11 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)
atomic_or(events, &ctrl->pending_events); atomic_or(events, &ctrl->pending_events);
if (!pciehp_poll_mode) if (!pciehp_poll_mode)
irq_wake_thread(ctrl->pcie->irq, ctrl); irq_wake_thread(ctrl->pcie->irq, ctrl);
} else } else {
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0,
&rpdev->slot_being_removed_rescanned);
}
break; break;
} }
mutex_unlock(&p_slot->lock); mutex_unlock(&p_slot->lock);
...@@ -170,6 +174,7 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) ...@@ -170,6 +174,7 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)
void pciehp_handle_button_press(struct slot *p_slot) void pciehp_handle_button_press(struct slot *p_slot)
{ {
struct controller *ctrl = p_slot->ctrl; struct controller *ctrl = p_slot->ctrl;
struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;
mutex_lock(&p_slot->lock); mutex_lock(&p_slot->lock);
switch (p_slot->state) { switch (p_slot->state) {
...@@ -209,12 +214,14 @@ void pciehp_handle_button_press(struct slot *p_slot) ...@@ -209,12 +214,14 @@ void pciehp_handle_button_press(struct slot *p_slot)
pciehp_set_attention_status(p_slot, 0); pciehp_set_attention_status(p_slot, 0);
ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n",
slot_name(p_slot)); slot_name(p_slot));
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0, &rpdev->slot_being_removed_rescanned);
break; break;
default: default:
ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n",
slot_name(p_slot), p_slot->state); slot_name(p_slot), p_slot->state);
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0, &rpdev->slot_being_removed_rescanned);
break; break;
} }
mutex_unlock(&p_slot->lock); mutex_unlock(&p_slot->lock);
...@@ -223,6 +230,7 @@ void pciehp_handle_button_press(struct slot *p_slot) ...@@ -223,6 +230,7 @@ void pciehp_handle_button_press(struct slot *p_slot)
void pciehp_handle_disable_request(struct slot *slot) void pciehp_handle_disable_request(struct slot *slot)
{ {
struct controller *ctrl = slot->ctrl; struct controller *ctrl = slot->ctrl;
struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;
mutex_lock(&slot->lock); mutex_lock(&slot->lock);
switch (slot->state) { switch (slot->state) {
...@@ -235,7 +243,8 @@ void pciehp_handle_disable_request(struct slot *slot) ...@@ -235,7 +243,8 @@ void pciehp_handle_disable_request(struct slot *slot)
mutex_unlock(&slot->lock); mutex_unlock(&slot->lock);
ctrl->request_result = pciehp_disable_slot(slot, SAFE_REMOVAL); ctrl->request_result = pciehp_disable_slot(slot, SAFE_REMOVAL);
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0, &rpdev->slot_being_removed_rescanned);
} }
void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events)
...@@ -243,6 +252,7 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) ...@@ -243,6 +252,7 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events)
struct controller *ctrl = slot->ctrl; struct controller *ctrl = slot->ctrl;
bool present, link_active; bool present, link_active;
bool removal = SAFE_REMOVAL; bool removal = SAFE_REMOVAL;
struct pci_dev *rpdev = ctrl_dev(ctrl)->rpdev;
/* /*
* If the slot is on and presence or link has changed, turn it off. * If the slot is on and presence or link has changed, turn it off.
...@@ -285,7 +295,8 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) ...@@ -285,7 +295,8 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events)
link_active = pciehp_check_link_active(ctrl); link_active = pciehp_check_link_active(ctrl);
if (!present && !link_active) { if (!present && !link_active) {
mutex_unlock(&slot->lock); mutex_unlock(&slot->lock);
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0, &rpdev->slot_being_removed_rescanned);
return; return;
} }
...@@ -308,7 +319,8 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) ...@@ -308,7 +319,8 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events)
mutex_unlock(&slot->lock); mutex_unlock(&slot->lock);
break; break;
} }
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0, &rpdev->slot_being_removed_rescanned);
} }
static int __pciehp_enable_slot(struct slot *p_slot) static int __pciehp_enable_slot(struct slot *p_slot)
...@@ -435,8 +447,10 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) ...@@ -435,8 +447,10 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot)
{ {
struct controller *ctrl = p_slot->ctrl; struct controller *ctrl = p_slot->ctrl;
struct pci_dev *pdev = ctrl->pcie->port; struct pci_dev *pdev = ctrl->pcie->port;
struct pci_dev *rpdev = pdev->rpdev;
if (test_and_set_bit(0, &slot_being_removed_rescanned)) { 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", ctrl_info(ctrl, "Slot(%s): Slot is being removed or rescanned, please try later!\n",
slot_name(p_slot)); slot_name(p_slot));
return -EINVAL; return -EINVAL;
...@@ -452,7 +466,8 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) ...@@ -452,7 +466,8 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot)
pciehp_handle_disable_request(p_slot); pciehp_handle_disable_request(p_slot);
up_read(&ctrl->reset_lock); up_read(&ctrl->reset_lock);
pci_config_pm_runtime_put(pdev); pci_config_pm_runtime_put(pdev);
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0, &rpdev->slot_being_removed_rescanned);
return ctrl->request_result; return ctrl->request_result;
case POWEROFF_STATE: case POWEROFF_STATE:
ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", ctrl_info(ctrl, "Slot(%s): Already in powering off state\n",
...@@ -471,7 +486,8 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) ...@@ -471,7 +486,8 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot)
} }
mutex_unlock(&p_slot->lock); mutex_unlock(&p_slot->lock);
slot_being_removed_rescanned = 0; if (rpdev)
clear_bit(0, &rpdev->slot_being_removed_rescanned);
return -ENODEV; return -ENODEV;
} }
...@@ -27,11 +27,6 @@ ...@@ -27,11 +27,6 @@
#include "../pci.h" #include "../pci.h"
#include "pciehp.h" #include "pciehp.h"
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_isr(int irq, void *dev_id);
static irqreturn_t pciehp_ist(int irq, void *dev_id); static irqreturn_t pciehp_ist(int irq, void *dev_id);
static int pciehp_poll(void *data); static int pciehp_poll(void *data);
...@@ -632,6 +627,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) ...@@ -632,6 +627,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
{ {
struct controller *ctrl = (struct controller *)dev_id; struct controller *ctrl = (struct controller *)dev_id;
struct pci_dev *pdev = ctrl_dev(ctrl); struct pci_dev *pdev = ctrl_dev(ctrl);
struct pci_dev *rpdev = pdev->rpdev;
struct slot *slot = ctrl->slot; struct slot *slot = ctrl->slot;
irqreturn_t ret; irqreturn_t ret;
u32 events; u32 events;
...@@ -659,7 +655,8 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) ...@@ -659,7 +655,8 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
if (events & PCI_EXP_SLTSTA_ABP) { if (events & PCI_EXP_SLTSTA_ABP) {
ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", ctrl_info(ctrl, "Slot(%s): Attention button pressed\n",
slot_name(slot)); slot_name(slot));
if (!test_and_set_bit(0, &slot_being_removed_rescanned)) if (!rpdev || (rpdev && !test_and_set_bit(0,
&rpdev->slot_being_removed_rescanned)))
pciehp_handle_button_press(slot); pciehp_handle_button_press(slot);
else { else {
if (slot->state == BLINKINGOFF_STATE || if (slot->state == BLINKINGOFF_STATE ||
...@@ -686,7 +683,8 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) ...@@ -686,7 +683,8 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
*/ */
down_read(&ctrl->reset_lock); down_read(&ctrl->reset_lock);
if (events & DISABLE_SLOT) { if (events & DISABLE_SLOT) {
if (!test_and_set_bit(0, &slot_being_removed_rescanned)) if (!rpdev || (rpdev && !test_and_set_bit(0,
&rpdev->slot_being_removed_rescanned)))
pciehp_handle_disable_request(slot); pciehp_handle_disable_request(slot);
else { else {
if (slot->state == BLINKINGOFF_STATE || if (slot->state == BLINKINGOFF_STATE ||
...@@ -712,7 +710,8 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) ...@@ -712,7 +710,8 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
} }
} }
} else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) { } else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) {
if (!test_and_set_bit(0, &slot_being_removed_rescanned)) if (!rpdev || (rpdev && !test_and_set_bit(0,
&rpdev->slot_being_removed_rescanned)))
pciehp_handle_presence_or_link_change(slot, events); pciehp_handle_presence_or_link_change(slot, events);
else { else {
if (slot->state == BLINKINGOFF_STATE || if (slot->state == BLINKINGOFF_STATE ||
......
...@@ -470,19 +470,33 @@ static ssize_t remove_store(struct device *dev, struct device_attribute *attr, ...@@ -470,19 +470,33 @@ static ssize_t remove_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
{ {
unsigned long val; unsigned long val;
struct pci_dev *rpdev = to_pci_dev(dev)->rpdev;
if (kstrtoul(buf, 0, &val) < 0) if (kstrtoul(buf, 0, &val) < 0)
return -EINVAL; return -EINVAL;
if (test_and_set_bit(0, &slot_being_removed_rescanned)) { if (rpdev && test_and_set_bit(0,
&rpdev->slot_being_removed_rescanned)) {
pr_info("Slot is being removed or rescanned, please try later!\n"); pr_info("Slot is being removed or rescanned, please try later!\n");
return -EINVAL; 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 && device_remove_file_self(dev, attr)) if (val && device_remove_file_self(dev, attr))
pci_stop_and_remove_bus_device_locked(to_pci_dev(dev)); pci_stop_and_remove_bus_device_locked(to_pci_dev(dev));
slot_being_removed_rescanned = 0; if (rpdev) {
clear_bit(0, &rpdev->slot_being_removed_rescanned);
pci_dev_put(rpdev);
}
return count; return count;
} }
......
...@@ -2928,6 +2928,11 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus) ...@@ -2928,6 +2928,11 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
/* Set up MSI IRQ domain */ /* Set up MSI IRQ domain */
pci_set_msi_domain(dev); 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 */ /* Notifier could use PCI capabilities */
dev->match_driver = false; dev->match_driver = false;
ret = device_add(&dev->dev); ret = device_add(&dev->dev);
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
#include <linux/module.h> #include <linux/module.h>
#include "pci.h" #include "pci.h"
/*
* When a slot is being removed/rescanned, this flag is set.
*/
unsigned long slot_being_removed_rescanned;
static void pci_free_resources(struct pci_dev *dev) static void pci_free_resources(struct pci_dev *dev)
{ {
int i; int i;
......
...@@ -450,6 +450,12 @@ struct pci_dev { ...@@ -450,6 +450,12 @@ struct pci_dev {
char *driver_override; /* Driver name to force a match */ char *driver_override; /* Driver name to force a match */
unsigned long priv_flags; /* Private flags for the PCI driver */ unsigned long priv_flags; /* Private flags for the PCI driver */
/*
* This flag is only set on root ports. When a slot below a root port
* is being removed or rescanned, this flag is set.
*/
unsigned long slot_being_removed_rescanned;
struct pci_dev *rpdev; /* root port pci_dev */
KABI_RESERVE(1) KABI_RESERVE(1)
KABI_RESERVE(2) KABI_RESERVE(2)
...@@ -916,8 +922,6 @@ extern struct bus_type pci_bus_type; ...@@ -916,8 +922,6 @@ extern struct bus_type pci_bus_type;
* code, or PCI core code. */ * code, or PCI core code. */
extern struct list_head pci_root_buses; /* List of all known PCI buses */ extern struct list_head pci_root_buses; /* List of all known PCI buses */
extern unsigned long slot_being_removed_rescanned;
/* Some device drivers need know if PCI is initiated */ /* Some device drivers need know if PCI is initiated */
int no_pci_devices(void); int no_pci_devices(void);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册