linux-sg2042/drivers/pci/hotplug/pciehp.h

184 lines
6.0 KiB
C
Raw Normal View History

/*
* PCI Express Hot Plug Controller Driver
*
* Copyright (C) 1995,2001 Compaq Computer Corporation
* Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
* Copyright (C) 2001 IBM Corp.
* Copyright (C) 2003-2004 Intel Corporation
*
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com>
*
*/
#ifndef _PCIEHP_H
#define _PCIEHP_H
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/delay.h>
#include <linux/sched.h> /* signal_pending() */
#include <linux/pcieport_if.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#define MY_NAME "pciehp"
extern bool pciehp_poll_mode;
extern int pciehp_poll_time;
extern bool pciehp_debug;
extern bool pciehp_force;
#define dbg(format, arg...) \
do { \
if (pciehp_debug) \
printk(KERN_DEBUG "%s: " format, MY_NAME , ## arg); \
} while (0)
#define err(format, arg...) \
printk(KERN_ERR "%s: " format, MY_NAME , ## arg)
#define info(format, arg...) \
printk(KERN_INFO "%s: " format, MY_NAME , ## arg)
#define warn(format, arg...) \
printk(KERN_WARNING "%s: " format, MY_NAME , ## arg)
#define ctrl_dbg(ctrl, format, arg...) \
do { \
if (pciehp_debug) \
dev_printk(KERN_DEBUG, &ctrl->pcie->device, \
format, ## arg); \
} while (0)
#define ctrl_err(ctrl, format, arg...) \
dev_err(&ctrl->pcie->device, format, ## arg)
#define ctrl_info(ctrl, format, arg...) \
dev_info(&ctrl->pcie->device, format, ## arg)
#define ctrl_warn(ctrl, format, arg...) \
dev_warn(&ctrl->pcie->device, format, ## arg)
#define SLOT_NAME_SIZE 10
struct slot {
u8 state;
struct controller *ctrl;
struct hotplug_slot *hotplug_slot;
struct delayed_work work; /* work for button event */
struct mutex lock;
PCI: pciehp: Use per-slot workqueues to avoid deadlock When we have a hotplug-capable PCIe port with a second hotplug-capable PCIe port below it, removing the device below the upstream port causes a deadlock. The deadlock happens because we use the pciehp_wq workqueue to run pciehp_power_thread(), which uses pciehp_disable_slot() to remove devices below the upstream port. When we remove the downstream PCIe port, we call pciehp_remove(), the pciehp driver's .remove() method. That calls flush_workqueue(pciehp_wq), which deadlocks because the pciehp_power_thread() work item is still running. This patch avoids the deadlock by creating a workqueue for every PCIe port and removing the single shared workqueue. Here's the call path that leads to the deadlock: pciehp_queue_pushbutton_work queue_work(pciehp_wq) # queue pciehp_power_thread ... pciehp_power_thread pciehp_disable_slot remove_board pciehp_unconfigure_device pci_stop_and_remove_bus_device ... pciehp_remove # pciehp driver .remove method pciehp_release_ctrl pcie_cleanup_slot flush_workqueue(pciehp_wq) This is fairly urgent because it can be caused by simply unplugging a Thunderbolt adapter, as reported by Daniel below. [bhelgaas: changelog] Reference: http://lkml.kernel.org/r/CAMVG2ssiRgcTD1bej2tkUUfsWmpL5eNtPcNif9va2-Gzb2u8nQ@mail.gmail.com Reported-and-tested-by: Daniel J Blueman <daniel@quora.org> Reviewed-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com> Signed-off-by: Yijing Wang <wangyijing@huawei.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> CC: stable@vger.kernel.org
2013-01-11 10:15:54 +08:00
struct workqueue_struct *wq;
};
struct event_info {
u32 event_type;
struct slot *p_slot;
struct work_struct work;
};
struct controller {
struct mutex ctrl_lock; /* controller lock */
struct pcie_device *pcie; /* PCI Express port service */
struct slot *slot;
wait_queue_head_t queue; /* sleep & wake process */
u32 slot_cap;
struct timer_list poll_timer;
unsigned int cmd_busy:1;
pciehp: fix slow probing Fix the "pciehp probing slow" problem reported from Jan C. Nordholz in http://bugzilla.kernel.org/show_bug.cgi?id=10751. The command completed bit in Slot Status register applies only to commands issued to control the attention indicator, power indicator, power controller, or electromechanical interlock. However, writes to other parts of the Slot Control register would end up writing to the control fields. Hence, any write to Slot Control register is considered as a command. However, if the controller doesn't support any of attention indicator, power indicator, power controller and electromechanical interlock, command completed bit would not set in writing to Slot Control register. In this case, we should not wait for command completed bit set, otherwise all commands would be considered not completed in timeout seconds (1 sec.). The cause of the problem is pciehp driver didn't take this situation into account. This patch changes pciehp to take it into account. This patch also add the check for "No Command Completed Support" bit in Slot Capability register. If it is set, we should not wait for command completed bit set as well. This problem seems to be revealed by the commit c27fb883dffe11aa4cb35ecea1fa1832ba45d4da that fixed the bug that pciehp did not wait for command completed properly (pciehp just ignored the command completion event). Signed-off-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com> Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
2008-05-27 18:04:30 +08:00
unsigned int no_cmd_complete:1;
unsigned int link_active_reporting:1;
unsigned int notification_enabled:1;
unsigned int power_fault_detected;
};
#define INT_BUTTON_IGNORE 0
#define INT_PRESENCE_ON 1
#define INT_PRESENCE_OFF 2
#define INT_SWITCH_CLOSE 3
#define INT_SWITCH_OPEN 4
#define INT_POWER_FAULT 5
#define INT_POWER_FAULT_CLEAR 6
#define INT_BUTTON_PRESS 7
#define INT_BUTTON_RELEASE 8
#define INT_BUTTON_CANCEL 9
#define STATIC_STATE 0
#define BLINKINGON_STATE 1
#define BLINKINGOFF_STATE 2
#define POWERON_STATE 3
#define POWEROFF_STATE 4
#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP)
#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP)
#define MRL_SENS(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_MRLSP)
#define ATTN_LED(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_AIP)
#define PWR_LED(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PIP)
#define HP_SUPR_RM(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_HPS)
#define EMI(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_EIP)
#define NO_CMD_CMPL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_NCCS)
#define PSN(ctrl) ((ctrl)->slot_cap >> 19)
extern int pciehp_sysfs_enable_slot(struct slot *slot);
extern int pciehp_sysfs_disable_slot(struct slot *slot);
pciehp: fix NULL dereference in interrupt handler Fix the following NULL dereference problem reported from Pierre Ossman and Ingo Molnar. pciehp: HPC vendor_id 8086 device_id 27d0 ss_vid 0 ss_did 0 pciehp: pciehp_find_slot: slot (device=0x0) not found BUG: unable to handle kernel NULL pointer dereference at 0000000000000070 IP: [<ffffffff80494a8b>] pciehp_handle_presence_change+0x7e/0x113 PGD 0 Oops: 0000 [1] CPU 0 Modules linked in: Pid: 1, comm: swapper Tainted: G W 2.6.26-rc3-sched-devel.git-00001-g2b99b26-dirty #170 RIP: 0010:[<ffffffff80494a8b>] [<ffffffff80494a8b>] pciehp_handle_presence_change+0x7e/0x113 RSP: 0000:ffff81003f83fbb0 EFLAGS: 00010046 RAX: 0000000000000039 RBX: 0000000000000000 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000046 RBP: ffff81003f83fbd0 R08: 0000000000000001 R09: ffffffff80245103 R10: 0000000000000020 R11: 0000000000000000 R12: ffff81003ea53a30 R13: 0000000000000000 R14: 0000000000000011 R15: ffffffff80495926 FS: 0000000000000000(0000) GS:ffffffff80be7400(0000) knlGS:0000000000000000 CS: 0010 DS: 0018 ES: 0018 CR0: 000000008005003b CR2: 0000000000000070 CR3: 0000000000201000 CR4: 00000000000006a0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Process swapper (pid: 1, threadinfo ffff81003f83e000, task ffff81003f840000) Stack: 0000000000000008 ffff81003f83fbf6 ffff81003ea53a30 0000000000000008 ffff81003f83fc10 ffffffff80495ab4 0000000000000011 0000000000000002 0000000000000202 0000000000000202 00000000fffffff4 ffff81003ea53a30 Call Trace: [<ffffffff80495ab4>] pcie_isr+0x18e/0x1bc [<ffffffff80260831>] request_irq+0x106/0x12f [<ffffffff80495fb6>] pcie_init+0x15e/0x6cc [<ffffffff804933a3>] pciehp_probe+0x64/0x541 [<ffffffff8048f4e7>] pcie_port_probe_service+0x4c/0x76 [<ffffffff8054af70>] driver_probe_device+0xd4/0x1f0 [<ffffffff8054b108>] __driver_attach+0x7c/0x7e [<ffffffff8054b08c>] ? __driver_attach+0x0/0x7e [<ffffffff8054a4b6>] bus_for_each_dev+0x53/0x7d [<ffffffff8054ad3c>] driver_attach+0x1c/0x1e [<ffffffff8054a9c2>] bus_add_driver+0xdd/0x25b [<ffffffff80c09d3d>] ? pcied_init+0x0/0x8b [<ffffffff8054b288>] driver_register+0x5f/0x13e [<ffffffff80c09d3d>] ? pcied_init+0x0/0x8b [<ffffffff8048f441>] pcie_port_service_register+0x47/0x49 [<ffffffff80c09d52>] pcied_init+0x15/0x8b [<ffffffff80bf3938>] kernel_init+0x75/0x243 [<ffffffff808639d2>] ? _spin_unlock_irq+0x2b/0x3a [<ffffffff80228d1f>] ? finish_task_switch+0x57/0x9a [<ffffffff8020c258>] child_rip+0xa/0x12 [<ffffffff8020bcec>] ? restore_args+0x0/0x30 [<ffffffff80bf38c3>] ? kernel_init+0x0/0x243 [<ffffffff8020c24e>] ? child_rip+0x0/0x12 Code: 83 80 00 00 00 48 39 f0 75 e1 0f b6 c9 48 c7 c2 00 0e 8d 80 48 c7 c6 8a 60 a6 80 48 c7 c7 10 db a8 80 31 c0 e8 3f 8d d9 ff 31 db <48> 8b 43 70 48 8d 75 ef 48 89 df ff 50 30 80 7d ef 00 74 37 48 RIP [<ffffffff80494a8b>] pciehp_handle_presence_change+0x7e/0x113 RSP <ffff81003f83fbb0> CR2: 0000000000000070 Kernel panic - not syncing: Fatal exception The situation under which it occurs is hw and timing related: it appears to happen on a system that has PCI hotplug hardware but with no active hotplug cards, and another interrupt in the same (shared) IRQ line arrives too early, before the hotplug-slot entry has been set up - as triggered by CONFIG_DEBUG_SHIRQ=y: This patch contains the following two fixes. (1) Clear all events bits in Slot Status register to prevent the pciehp driver from detecting the spurious events that would have been occur before pciehp loading. (2) Add check whether slot initialization had been already done. This is short term fix. We need more structural fixes to install interrupt handler after slot initialization is done. Signed-off-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com> Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
2008-05-27 18:03:16 +08:00
extern u8 pciehp_handle_attention_button(struct slot *p_slot);
extern u8 pciehp_handle_switch_change(struct slot *p_slot);
pciehp: fix NULL dereference in interrupt handler Fix the following NULL dereference problem reported from Pierre Ossman and Ingo Molnar. pciehp: HPC vendor_id 8086 device_id 27d0 ss_vid 0 ss_did 0 pciehp: pciehp_find_slot: slot (device=0x0) not found BUG: unable to handle kernel NULL pointer dereference at 0000000000000070 IP: [<ffffffff80494a8b>] pciehp_handle_presence_change+0x7e/0x113 PGD 0 Oops: 0000 [1] CPU 0 Modules linked in: Pid: 1, comm: swapper Tainted: G W 2.6.26-rc3-sched-devel.git-00001-g2b99b26-dirty #170 RIP: 0010:[<ffffffff80494a8b>] [<ffffffff80494a8b>] pciehp_handle_presence_change+0x7e/0x113 RSP: 0000:ffff81003f83fbb0 EFLAGS: 00010046 RAX: 0000000000000039 RBX: 0000000000000000 RCX: 0000000000000000 RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000046 RBP: ffff81003f83fbd0 R08: 0000000000000001 R09: ffffffff80245103 R10: 0000000000000020 R11: 0000000000000000 R12: ffff81003ea53a30 R13: 0000000000000000 R14: 0000000000000011 R15: ffffffff80495926 FS: 0000000000000000(0000) GS:ffffffff80be7400(0000) knlGS:0000000000000000 CS: 0010 DS: 0018 ES: 0018 CR0: 000000008005003b CR2: 0000000000000070 CR3: 0000000000201000 CR4: 00000000000006a0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Process swapper (pid: 1, threadinfo ffff81003f83e000, task ffff81003f840000) Stack: 0000000000000008 ffff81003f83fbf6 ffff81003ea53a30 0000000000000008 ffff81003f83fc10 ffffffff80495ab4 0000000000000011 0000000000000002 0000000000000202 0000000000000202 00000000fffffff4 ffff81003ea53a30 Call Trace: [<ffffffff80495ab4>] pcie_isr+0x18e/0x1bc [<ffffffff80260831>] request_irq+0x106/0x12f [<ffffffff80495fb6>] pcie_init+0x15e/0x6cc [<ffffffff804933a3>] pciehp_probe+0x64/0x541 [<ffffffff8048f4e7>] pcie_port_probe_service+0x4c/0x76 [<ffffffff8054af70>] driver_probe_device+0xd4/0x1f0 [<ffffffff8054b108>] __driver_attach+0x7c/0x7e [<ffffffff8054b08c>] ? __driver_attach+0x0/0x7e [<ffffffff8054a4b6>] bus_for_each_dev+0x53/0x7d [<ffffffff8054ad3c>] driver_attach+0x1c/0x1e [<ffffffff8054a9c2>] bus_add_driver+0xdd/0x25b [<ffffffff80c09d3d>] ? pcied_init+0x0/0x8b [<ffffffff8054b288>] driver_register+0x5f/0x13e [<ffffffff80c09d3d>] ? pcied_init+0x0/0x8b [<ffffffff8048f441>] pcie_port_service_register+0x47/0x49 [<ffffffff80c09d52>] pcied_init+0x15/0x8b [<ffffffff80bf3938>] kernel_init+0x75/0x243 [<ffffffff808639d2>] ? _spin_unlock_irq+0x2b/0x3a [<ffffffff80228d1f>] ? finish_task_switch+0x57/0x9a [<ffffffff8020c258>] child_rip+0xa/0x12 [<ffffffff8020bcec>] ? restore_args+0x0/0x30 [<ffffffff80bf38c3>] ? kernel_init+0x0/0x243 [<ffffffff8020c24e>] ? child_rip+0x0/0x12 Code: 83 80 00 00 00 48 39 f0 75 e1 0f b6 c9 48 c7 c2 00 0e 8d 80 48 c7 c6 8a 60 a6 80 48 c7 c7 10 db a8 80 31 c0 e8 3f 8d d9 ff 31 db <48> 8b 43 70 48 8d 75 ef 48 89 df ff 50 30 80 7d ef 00 74 37 48 RIP [<ffffffff80494a8b>] pciehp_handle_presence_change+0x7e/0x113 RSP <ffff81003f83fbb0> CR2: 0000000000000070 Kernel panic - not syncing: Fatal exception The situation under which it occurs is hw and timing related: it appears to happen on a system that has PCI hotplug hardware but with no active hotplug cards, and another interrupt in the same (shared) IRQ line arrives too early, before the hotplug-slot entry has been set up - as triggered by CONFIG_DEBUG_SHIRQ=y: This patch contains the following two fixes. (1) Clear all events bits in Slot Status register to prevent the pciehp driver from detecting the spurious events that would have been occur before pciehp loading. (2) Add check whether slot initialization had been already done. This is short term fix. We need more structural fixes to install interrupt handler after slot initialization is done. Signed-off-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Kenji Kaneshige <kaneshige.kenji@jp.fujitsu.com> Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
2008-05-27 18:03:16 +08:00
extern u8 pciehp_handle_presence_change(struct slot *p_slot);
extern u8 pciehp_handle_power_fault(struct slot *p_slot);
extern int pciehp_configure_device(struct slot *p_slot);
extern int pciehp_unconfigure_device(struct slot *p_slot);
extern void pciehp_queue_pushbutton_work(struct work_struct *work);
struct controller *pcie_init(struct pcie_device *dev);
int pcie_init_notification(struct controller *ctrl);
int pciehp_enable_slot(struct slot *p_slot);
int pciehp_disable_slot(struct slot *p_slot);
int pcie_enable_notification(struct controller *ctrl);
int pciehp_power_on_slot(struct slot *slot);
int pciehp_power_off_slot(struct slot *slot);
int pciehp_get_power_status(struct slot *slot, u8 *status);
int pciehp_get_attention_status(struct slot *slot, u8 *status);
int pciehp_set_attention_status(struct slot *slot, u8 status);
int pciehp_get_latch_status(struct slot *slot, u8 *status);
int pciehp_get_adapter_status(struct slot *slot, u8 *status);
int pciehp_query_power_fault(struct slot *slot);
void pciehp_green_led_on(struct slot *slot);
void pciehp_green_led_off(struct slot *slot);
void pciehp_green_led_blink(struct slot *slot);
int pciehp_check_link_status(struct controller *ctrl);
void pciehp_release_ctrl(struct controller *ctrl);
static inline const char *slot_name(struct slot *slot)
{
return hotplug_slot_name(slot->hotplug_slot);
}
#ifdef CONFIG_ACPI
#include <acpi/acpi.h>
#include <acpi/acpi_bus.h>
#include <linux/pci-acpi.h>
extern void __init pciehp_acpi_slot_detection_init(void);
extern int pciehp_acpi_slot_detection_check(struct pci_dev *dev);
static inline void pciehp_firmware_init(void)
{
pciehp_acpi_slot_detection_init();
}
#else
#define pciehp_firmware_init() do {} while (0)
static inline int pciehp_acpi_slot_detection_check(struct pci_dev *dev)
{
return 0;
}
#endif /* CONFIG_ACPI */
#endif /* _PCIEHP_H */