mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-09 14:43:16 +00:00
PCI hotplug: acpiphp: Prevent deadlock on PCI-to-PCI bridge remove
I originally submitted a patch to workaround this by pushing all Ejection Requests and Device Checks onto the kacpi_hotplug queue. http://marc.info/?l=linux-acpi&m=131678270930105&w=2 The patch is still insufficient in that Bus Checks also need to be added. Rather than add all events, including non-PCI-hotplug events, to the hotplug queue, mjg suggested that a better approach would be to modify the acpiphp driver so only acpiphp events would be added to the kacpi_hotplug queue. It's a longer patch, but at least we maintain the benefit of having separate queues in ACPI. This, of course, is still only a workaround the problem. As Bjorn and mjg pointed out, we have to refactor a lot of this code to do the right thing but at this point it is a better to have this code working. The acpi core places all events on the kacpi_notify queue. When the acpiphp driver is loaded and a PCI card with a PCI-to-PCI bridge is removed the following call sequence occurs: cleanup_p2p_bridge() -> cleanup_bridge() -> acpi_remove_notify_handler() -> acpi_os_wait_events_complete() -> flush_workqueue(kacpi_notify_wq) which is the queue we are currently executing on and the process will hang. Move all hotplug acpiphp events onto the kacpi_hotplug workqueue. In handle_hotplug_event_bridge() and handle_hotplug_event_func() we can simply push the rest of the work onto the kacpi_hotplug queue and then avoid the deadlock. Signed-off-by: Prarit Bhargava <prarit@redhat.com> Cc: mjg@redhat.com Cc: bhelgaas@google.com Cc: linux-acpi@vger.kernel.org Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
This commit is contained in:
parent
379021d5c0
commit
6af8bef14d
@ -80,7 +80,8 @@ static acpi_osd_handler acpi_irq_handler;
|
|||||||
static void *acpi_irq_context;
|
static void *acpi_irq_context;
|
||||||
static struct workqueue_struct *kacpid_wq;
|
static struct workqueue_struct *kacpid_wq;
|
||||||
static struct workqueue_struct *kacpi_notify_wq;
|
static struct workqueue_struct *kacpi_notify_wq;
|
||||||
static struct workqueue_struct *kacpi_hotplug_wq;
|
struct workqueue_struct *kacpi_hotplug_wq;
|
||||||
|
EXPORT_SYMBOL(kacpi_hotplug_wq);
|
||||||
|
|
||||||
struct acpi_res_list {
|
struct acpi_res_list {
|
||||||
resource_size_t start;
|
resource_size_t start;
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
#include <linux/pci-acpi.h>
|
#include <linux/pci-acpi.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
|
||||||
#include "../pci.h"
|
#include "../pci.h"
|
||||||
#include "acpiphp.h"
|
#include "acpiphp.h"
|
||||||
@ -1149,15 +1150,35 @@ check_sub_bridges(acpi_handle handle, u32 lvl, void *context, void **rv)
|
|||||||
return AE_OK ;
|
return AE_OK ;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
struct acpiphp_hp_work {
|
||||||
* handle_hotplug_event_bridge - handle ACPI event on bridges
|
struct work_struct work;
|
||||||
* @handle: Notify()'ed acpi_handle
|
acpi_handle handle;
|
||||||
* @type: Notify code
|
u32 type;
|
||||||
* @context: pointer to acpiphp_bridge structure
|
void *context;
|
||||||
*
|
};
|
||||||
* Handles ACPI event notification on {host,p2p} bridges.
|
|
||||||
*/
|
static void alloc_acpiphp_hp_work(acpi_handle handle, u32 type,
|
||||||
static void handle_hotplug_event_bridge(acpi_handle handle, u32 type, void *context)
|
void *context,
|
||||||
|
void (*func)(struct work_struct *work))
|
||||||
|
{
|
||||||
|
struct acpiphp_hp_work *hp_work;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
hp_work = kmalloc(sizeof(*hp_work), GFP_KERNEL);
|
||||||
|
if (!hp_work)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hp_work->handle = handle;
|
||||||
|
hp_work->type = type;
|
||||||
|
hp_work->context = context;
|
||||||
|
|
||||||
|
INIT_WORK(&hp_work->work, func);
|
||||||
|
ret = queue_work(kacpi_hotplug_wq, &hp_work->work);
|
||||||
|
if (!ret)
|
||||||
|
kfree(hp_work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _handle_hotplug_event_bridge(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct acpiphp_bridge *bridge;
|
struct acpiphp_bridge *bridge;
|
||||||
char objname[64];
|
char objname[64];
|
||||||
@ -1165,11 +1186,18 @@ static void handle_hotplug_event_bridge(acpi_handle handle, u32 type, void *cont
|
|||||||
.pointer = objname };
|
.pointer = objname };
|
||||||
struct acpi_device *device;
|
struct acpi_device *device;
|
||||||
int num_sub_bridges = 0;
|
int num_sub_bridges = 0;
|
||||||
|
struct acpiphp_hp_work *hp_work;
|
||||||
|
acpi_handle handle;
|
||||||
|
u32 type;
|
||||||
|
|
||||||
|
hp_work = container_of(work, struct acpiphp_hp_work, work);
|
||||||
|
handle = hp_work->handle;
|
||||||
|
type = hp_work->type;
|
||||||
|
|
||||||
if (acpi_bus_get_device(handle, &device)) {
|
if (acpi_bus_get_device(handle, &device)) {
|
||||||
/* This bridge must have just been physically inserted */
|
/* This bridge must have just been physically inserted */
|
||||||
handle_bridge_insertion(handle, type);
|
handle_bridge_insertion(handle, type);
|
||||||
return;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge = acpiphp_handle_to_bridge(handle);
|
bridge = acpiphp_handle_to_bridge(handle);
|
||||||
@ -1180,7 +1208,7 @@ static void handle_hotplug_event_bridge(acpi_handle handle, u32 type, void *cont
|
|||||||
|
|
||||||
if (!bridge && !num_sub_bridges) {
|
if (!bridge && !num_sub_bridges) {
|
||||||
err("cannot get bridge info\n");
|
err("cannot get bridge info\n");
|
||||||
return;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
|
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
|
||||||
@ -1241,22 +1269,49 @@ static void handle_hotplug_event_bridge(acpi_handle handle, u32 type, void *cont
|
|||||||
warn("notify_handler: unknown event type 0x%x for %s\n", type, objname);
|
warn("notify_handler: unknown event type 0x%x for %s\n", type, objname);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
kfree(hp_work); /* allocated in handle_hotplug_event_bridge */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handle_hotplug_event_func - handle ACPI event on functions (i.e. slots)
|
* handle_hotplug_event_bridge - handle ACPI event on bridges
|
||||||
* @handle: Notify()'ed acpi_handle
|
* @handle: Notify()'ed acpi_handle
|
||||||
* @type: Notify code
|
* @type: Notify code
|
||||||
* @context: pointer to acpiphp_func structure
|
* @context: pointer to acpiphp_bridge structure
|
||||||
*
|
*
|
||||||
* Handles ACPI event notification on slots.
|
* Handles ACPI event notification on {host,p2p} bridges.
|
||||||
*/
|
*/
|
||||||
static void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context)
|
static void handle_hotplug_event_bridge(acpi_handle handle, u32 type,
|
||||||
|
void *context)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Currently the code adds all hotplug events to the kacpid_wq
|
||||||
|
* queue when it should add hotplug events to the kacpi_hotplug_wq.
|
||||||
|
* The proper way to fix this is to reorganize the code so that
|
||||||
|
* drivers (dock, etc.) do not call acpi_os_execute(), etc.
|
||||||
|
* For now just re-add this work to the kacpi_hotplug_wq so we
|
||||||
|
* don't deadlock on hotplug actions.
|
||||||
|
*/
|
||||||
|
alloc_acpiphp_hp_work(handle, type, context,
|
||||||
|
_handle_hotplug_event_bridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _handle_hotplug_event_func(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct acpiphp_func *func;
|
struct acpiphp_func *func;
|
||||||
char objname[64];
|
char objname[64];
|
||||||
struct acpi_buffer buffer = { .length = sizeof(objname),
|
struct acpi_buffer buffer = { .length = sizeof(objname),
|
||||||
.pointer = objname };
|
.pointer = objname };
|
||||||
|
struct acpiphp_hp_work *hp_work;
|
||||||
|
acpi_handle handle;
|
||||||
|
u32 type;
|
||||||
|
void *context;
|
||||||
|
|
||||||
|
hp_work = container_of(work, struct acpiphp_hp_work, work);
|
||||||
|
handle = hp_work->handle;
|
||||||
|
type = hp_work->type;
|
||||||
|
context = hp_work->context;
|
||||||
|
|
||||||
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
|
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
|
||||||
|
|
||||||
@ -1291,8 +1346,32 @@ static void handle_hotplug_event_func(acpi_handle handle, u32 type, void *contex
|
|||||||
warn("notify_handler: unknown event type 0x%x for %s\n", type, objname);
|
warn("notify_handler: unknown event type 0x%x for %s\n", type, objname);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kfree(hp_work); /* allocated in handle_hotplug_event_func */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handle_hotplug_event_func - handle ACPI event on functions (i.e. slots)
|
||||||
|
* @handle: Notify()'ed acpi_handle
|
||||||
|
* @type: Notify code
|
||||||
|
* @context: pointer to acpiphp_func structure
|
||||||
|
*
|
||||||
|
* Handles ACPI event notification on slots.
|
||||||
|
*/
|
||||||
|
static void handle_hotplug_event_func(acpi_handle handle, u32 type,
|
||||||
|
void *context)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Currently the code adds all hotplug events to the kacpid_wq
|
||||||
|
* queue when it should add hotplug events to the kacpi_hotplug_wq.
|
||||||
|
* The proper way to fix this is to reorganize the code so that
|
||||||
|
* drivers (dock, etc.) do not call acpi_os_execute(), etc.
|
||||||
|
* For now just re-add this work to the kacpi_hotplug_wq so we
|
||||||
|
* don't deadlock on hotplug actions.
|
||||||
|
*/
|
||||||
|
alloc_acpiphp_hp_work(handle, type, context,
|
||||||
|
_handle_hotplug_event_func);
|
||||||
|
}
|
||||||
|
|
||||||
static acpi_status
|
static acpi_status
|
||||||
find_root_bridges(acpi_handle handle, u32 lvl, void *context, void **rv)
|
find_root_bridges(acpi_handle handle, u32 lvl, void *context, void **rv)
|
||||||
|
@ -189,6 +189,8 @@ void acpi_os_fixed_event_count(u32 fixed_event_number);
|
|||||||
/*
|
/*
|
||||||
* Threads and Scheduling
|
* Threads and Scheduling
|
||||||
*/
|
*/
|
||||||
|
extern struct workqueue_struct *kacpi_hotplug_wq;
|
||||||
|
|
||||||
acpi_thread_id acpi_os_get_thread_id(void);
|
acpi_thread_id acpi_os_get_thread_id(void);
|
||||||
|
|
||||||
acpi_status
|
acpi_status
|
||||||
|
Loading…
x
Reference in New Issue
Block a user