Commit c4635eb0 authored by Kenji Kaneshige's avatar Kenji Kaneshige Committed by Jesse Barnes

pciehp: fix interrupt initialization

Current pciehp driver's intialization sequence is as follows:

(1) initialize controller data structure
(2) install interrupt handler
(3) enable software notification
(4) initialize controller specific slot data structure
(5) initialize generic slot data structure and register it to pci hotplug core

The interrupt handler of pciehp assumes that controller specific slot
data structure is already initialized. However, it is installed at (2)
before initializing controller specific slot data structure at
(4). Because of this, pciehp driver cannot handle the following cases
properly.

- If devices that shares IRQ with pciehp raise interrupts between (2) and (4).
- If hotplug events (e.g. MRL open) happen between (3) and (4).

We already have a workaround for this problem ("pciehp: fix NULL
dereference in interrupt handler: dbd79aed).
But we still need fundamental fix.

This patch fix the problem by changing the initilization sequence as follows:

(1) initialize controller data structure
(2) initialize controller specific slot data structure
(3) install interrupt handler
(4) enable software notification
(5) initialize generic slot data structure and register it to pci hotplug core
Signed-off-by: default avatarKenji Kaneshige <kaneshige.kenji@jp.fujitsu.com>
Acked-by: default avatarAlex Chiang <achiang@hp.com>
Signed-off-by: default avatarJesse Barnes <jbarnes@virtuousgeek.org>
parent e4ec7a00
...@@ -43,6 +43,7 @@ extern int pciehp_poll_mode; ...@@ -43,6 +43,7 @@ extern int pciehp_poll_mode;
extern int pciehp_poll_time; extern int pciehp_poll_time;
extern int pciehp_debug; extern int pciehp_debug;
extern int pciehp_force; extern int pciehp_force;
extern int pciehp_slot_with_bus;
extern struct workqueue_struct *pciehp_wq; extern struct workqueue_struct *pciehp_wq;
#define dbg(format, arg...) \ #define dbg(format, arg...) \
...@@ -156,10 +157,10 @@ extern u8 pciehp_handle_power_fault(struct slot *p_slot); ...@@ -156,10 +157,10 @@ extern u8 pciehp_handle_power_fault(struct slot *p_slot);
extern int pciehp_configure_device(struct slot *p_slot); extern int pciehp_configure_device(struct slot *p_slot);
extern int pciehp_unconfigure_device(struct slot *p_slot); extern int pciehp_unconfigure_device(struct slot *p_slot);
extern void pciehp_queue_pushbutton_work(struct work_struct *work); extern void pciehp_queue_pushbutton_work(struct work_struct *work);
int pcie_init(struct controller *ctrl, struct pcie_device *dev); struct controller *pcie_init(struct pcie_device *dev);
int pciehp_enable_slot(struct slot *p_slot); int pciehp_enable_slot(struct slot *p_slot);
int pciehp_disable_slot(struct slot *p_slot); int pciehp_disable_slot(struct slot *p_slot);
int pcie_init_hardware_part2(struct controller *ctrl, struct pcie_device *dev); int pcie_enable_notification(struct controller *ctrl);
static inline struct slot *pciehp_find_slot(struct controller *ctrl, u8 device) static inline struct slot *pciehp_find_slot(struct controller *ctrl, u8 device)
{ {
......
...@@ -41,7 +41,7 @@ int pciehp_debug; ...@@ -41,7 +41,7 @@ int pciehp_debug;
int pciehp_poll_mode; int pciehp_poll_mode;
int pciehp_poll_time; int pciehp_poll_time;
int pciehp_force; int pciehp_force;
static int pciehp_slot_with_bus; int pciehp_slot_with_bus;
struct workqueue_struct *pciehp_wq; struct workqueue_struct *pciehp_wq;
#define DRIVER_VERSION "0.4" #define DRIVER_VERSION "0.4"
...@@ -183,23 +183,10 @@ static struct hotplug_slot_attribute hotplug_slot_attr_lock = { ...@@ -183,23 +183,10 @@ static struct hotplug_slot_attribute hotplug_slot_attr_lock = {
*/ */
static void release_slot(struct hotplug_slot *hotplug_slot) static void release_slot(struct hotplug_slot *hotplug_slot)
{ {
struct slot *slot = hotplug_slot->private;
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
kfree(slot->hotplug_slot->info); kfree(hotplug_slot->info);
kfree(slot->hotplug_slot); kfree(hotplug_slot);
kfree(slot);
}
static void make_slot_name(struct slot *slot)
{
if (pciehp_slot_with_bus)
snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%04d_%04d",
slot->bus, slot->number);
else
snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%d",
slot->number);
} }
static int init_slots(struct controller *ctrl) static int init_slots(struct controller *ctrl)
...@@ -208,44 +195,27 @@ static int init_slots(struct controller *ctrl) ...@@ -208,44 +195,27 @@ static int init_slots(struct controller *ctrl)
struct hotplug_slot *hotplug_slot; struct hotplug_slot *hotplug_slot;
struct hotplug_slot_info *info; struct hotplug_slot_info *info;
int retval = -ENOMEM; int retval = -ENOMEM;
int i;
for (i = 0; i < ctrl->num_slots; i++) {
slot = kzalloc(sizeof(*slot), GFP_KERNEL);
if (!slot)
goto error;
list_for_each_entry(slot, &ctrl->slot_list, slot_list) {
hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL); hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL);
if (!hotplug_slot) if (!hotplug_slot)
goto error_slot; goto error;
slot->hotplug_slot = hotplug_slot;
info = kzalloc(sizeof(*info), GFP_KERNEL); info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) if (!info)
goto error_hpslot; goto error_hpslot;
hotplug_slot->info = info;
hotplug_slot->name = slot->name;
slot->hp_slot = i;
slot->ctrl = ctrl;
slot->bus = ctrl->pci_dev->subordinate->number;
slot->device = ctrl->slot_device_offset + i;
slot->hpc_ops = ctrl->hpc_ops;
slot->number = ctrl->first_slot;
mutex_init(&slot->lock);
INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
/* register this slot with the hotplug pci core */ /* register this slot with the hotplug pci core */
hotplug_slot->info = info;
hotplug_slot->name = slot->name;
hotplug_slot->private = slot; hotplug_slot->private = slot;
hotplug_slot->release = &release_slot; hotplug_slot->release = &release_slot;
make_slot_name(slot);
hotplug_slot->ops = &pciehp_hotplug_slot_ops; hotplug_slot->ops = &pciehp_hotplug_slot_ops;
get_power_status(hotplug_slot, &info->power_status); get_power_status(hotplug_slot, &info->power_status);
get_attention_status(hotplug_slot, &info->attention_status); get_attention_status(hotplug_slot, &info->attention_status);
get_latch_status(hotplug_slot, &info->latch_status); get_latch_status(hotplug_slot, &info->latch_status);
get_adapter_status(hotplug_slot, &info->adapter_status); get_adapter_status(hotplug_slot, &info->adapter_status);
slot->hotplug_slot = hotplug_slot;
dbg("Registering bus=%x dev=%x hp_slot=%x sun=%x " dbg("Registering bus=%x dev=%x hp_slot=%x sun=%x "
"slot_device_offset=%x\n", slot->bus, slot->device, "slot_device_offset=%x\n", slot->bus, slot->device,
...@@ -271,8 +241,6 @@ static int init_slots(struct controller *ctrl) ...@@ -271,8 +241,6 @@ static int init_slots(struct controller *ctrl)
goto error_info; goto error_info;
} }
} }
list_add(&slot->slot_list, &ctrl->slot_list);
} }
return 0; return 0;
...@@ -280,27 +248,18 @@ error_info: ...@@ -280,27 +248,18 @@ error_info:
kfree(info); kfree(info);
error_hpslot: error_hpslot:
kfree(hotplug_slot); kfree(hotplug_slot);
error_slot:
kfree(slot);
error: error:
return retval; return retval;
} }
static void cleanup_slots(struct controller *ctrl) static void cleanup_slots(struct controller *ctrl)
{ {
struct list_head *tmp;
struct list_head *next;
struct slot *slot; struct slot *slot;
list_for_each_safe(tmp, next, &ctrl->slot_list) { list_for_each_entry(slot, &ctrl->slot_list, slot_list) {
slot = list_entry(tmp, struct slot, slot_list);
list_del(&slot->slot_list);
if (EMI(ctrl)) if (EMI(ctrl))
sysfs_remove_file(&slot->hotplug_slot->pci_slot->kobj, sysfs_remove_file(&slot->hotplug_slot->pci_slot->kobj,
&hotplug_slot_attr_lock.attr); &hotplug_slot_attr_lock.attr);
cancel_delayed_work(&slot->work);
flush_scheduled_work();
flush_workqueue(pciehp_wq);
pci_hp_deregister(slot->hotplug_slot); pci_hp_deregister(slot->hotplug_slot);
} }
} }
...@@ -441,25 +400,13 @@ static int pciehp_probe(struct pcie_device *dev, const struct pcie_port_service_ ...@@ -441,25 +400,13 @@ static int pciehp_probe(struct pcie_device *dev, const struct pcie_port_service_
else if (pciehp_get_hp_hw_control_from_firmware(pdev)) else if (pciehp_get_hp_hw_control_from_firmware(pdev))
goto err_out_none; goto err_out_none;
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); ctrl = pcie_init(dev);
if (!ctrl) { if (!ctrl) {
err("%s : out of memory\n", __func__);
goto err_out_none;
}
INIT_LIST_HEAD(&ctrl->slot_list);
rc = pcie_init(ctrl, dev);
if (rc) {
dbg("%s: controller initialization failed\n", PCIE_MODULE_NAME); dbg("%s: controller initialization failed\n", PCIE_MODULE_NAME);
goto err_out_free_ctrl; goto err_out_none;
} }
pci_set_drvdata(pdev, ctrl); pci_set_drvdata(pdev, ctrl);
dbg("%s: ctrl bus=0x%x, device=%x, function=%x, irq=%x\n",
__func__, pdev->bus->number, PCI_SLOT(pdev->devfn),
PCI_FUNC(pdev->devfn), pdev->irq);
/* Setup the slot information structures */ /* Setup the slot information structures */
rc = init_slots(ctrl); rc = init_slots(ctrl);
if (rc) { if (rc) {
...@@ -492,8 +439,6 @@ err_out_free_ctrl_slot: ...@@ -492,8 +439,6 @@ err_out_free_ctrl_slot:
cleanup_slots(ctrl); cleanup_slots(ctrl);
err_out_release_ctlr: err_out_release_ctlr:
ctrl->hpc_ops->release_ctlr(ctrl); ctrl->hpc_ops->release_ctlr(ctrl);
err_out_free_ctrl:
kfree(ctrl);
err_out_none: err_out_none:
return -ENODEV; return -ENODEV;
} }
...@@ -505,7 +450,6 @@ static void pciehp_remove (struct pcie_device *dev) ...@@ -505,7 +450,6 @@ static void pciehp_remove (struct pcie_device *dev)
cleanup_slots(ctrl); cleanup_slots(ctrl);
ctrl->hpc_ops->release_ctlr(ctrl); ctrl->hpc_ops->release_ctlr(ctrl);
kfree(ctrl);
} }
#ifdef CONFIG_PM #ifdef CONFIG_PM
...@@ -525,7 +469,7 @@ static int pciehp_resume (struct pcie_device *dev) ...@@ -525,7 +469,7 @@ static int pciehp_resume (struct pcie_device *dev)
u8 status; u8 status;
/* reinitialize the chipset's event detection logic */ /* reinitialize the chipset's event detection logic */
pcie_init_hardware_part2(ctrl, dev); pcie_enable_notification(ctrl);
t_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset); t_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset);
......
...@@ -609,23 +609,6 @@ static void hpc_set_green_led_blink(struct slot *slot) ...@@ -609,23 +609,6 @@ static void hpc_set_green_led_blink(struct slot *slot)
__func__, ctrl->cap_base + SLOTCTRL, slot_cmd); __func__, ctrl->cap_base + SLOTCTRL, slot_cmd);
} }
static void hpc_release_ctlr(struct controller *ctrl)
{
/* Mask Hot-plug Interrupt Enable */
if (pcie_write_cmd(ctrl, 0, HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE))
err("%s: Cannot mask hotplug interrupt enable\n", __func__);
/* Free interrupt handler or interrupt polling timer */
pciehp_free_irq(ctrl);
/*
* If this is the last controller to be released, destroy the
* pciehp work queue
*/
if (atomic_dec_and_test(&pciehp_num_controllers))
destroy_workqueue(pciehp_wq);
}
static int hpc_power_on_slot(struct slot * slot) static int hpc_power_on_slot(struct slot * slot)
{ {
struct controller *ctrl = slot->ctrl; struct controller *ctrl = slot->ctrl;
...@@ -798,19 +781,7 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) ...@@ -798,19 +781,7 @@ static irqreturn_t pcie_isr(int irq, void *dev_id)
if (!(intr_loc & ~CMD_COMPLETED)) if (!(intr_loc & ~CMD_COMPLETED))
return IRQ_HANDLED; return IRQ_HANDLED;
/*
* Return without handling events if this handler routine is
* called before controller initialization is done. This may
* happen if hotplug event or another interrupt that shares
* the IRQ with pciehp arrives before slot initialization is
* done after interrupt handler is registered.
*
* FIXME - Need more structural fixes. We need to be ready to
* handle the event before installing interrupt handler.
*/
p_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset); p_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset);
if (!p_slot || !p_slot->hpc_ops)
return IRQ_HANDLED;
/* Check MRL Sensor Changed */ /* Check MRL Sensor Changed */
if (intr_loc & MRL_SENS_CHANGED) if (intr_loc & MRL_SENS_CHANGED)
...@@ -987,6 +958,7 @@ static int hpc_get_cur_lnk_width(struct slot *slot, ...@@ -987,6 +958,7 @@ static int hpc_get_cur_lnk_width(struct slot *slot,
return retval; return retval;
} }
static void pcie_release_ctrl(struct controller *ctrl);
static struct hpc_ops pciehp_hpc_ops = { static struct hpc_ops pciehp_hpc_ops = {
.power_on_slot = hpc_power_on_slot, .power_on_slot = hpc_power_on_slot,
.power_off_slot = hpc_power_off_slot, .power_off_slot = hpc_power_off_slot,
...@@ -1008,28 +980,11 @@ static struct hpc_ops pciehp_hpc_ops = { ...@@ -1008,28 +980,11 @@ static struct hpc_ops pciehp_hpc_ops = {
.green_led_off = hpc_set_green_led_off, .green_led_off = hpc_set_green_led_off,
.green_led_blink = hpc_set_green_led_blink, .green_led_blink = hpc_set_green_led_blink,
.release_ctlr = hpc_release_ctlr, .release_ctlr = pcie_release_ctrl,
.check_lnk_status = hpc_check_lnk_status, .check_lnk_status = hpc_check_lnk_status,
}; };
static int pcie_init_hardware_part1(struct controller *ctrl, int pcie_enable_notification(struct controller *ctrl)
struct pcie_device *dev)
{
/* Clear all remaining event bits in Slot Status register */
if (pciehp_writew(ctrl, SLOTSTATUS, 0x1f)) {
err("%s: Cannot write to SLOTSTATUS register\n", __func__);
return -1;
}
/* Mask Hot-plug Interrupt Enable */
if (pcie_write_cmd(ctrl, 0, HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE)) {
err("%s: Cannot mask hotplug interrupt enable\n", __func__);
return -1;
}
return 0;
}
int pcie_init_hardware_part2(struct controller *ctrl, struct pcie_device *dev)
{ {
u16 cmd, mask; u16 cmd, mask;
...@@ -1050,10 +1005,76 @@ int pcie_init_hardware_part2(struct controller *ctrl, struct pcie_device *dev) ...@@ -1050,10 +1005,76 @@ int pcie_init_hardware_part2(struct controller *ctrl, struct pcie_device *dev)
err("%s: Cannot enable software notification\n", __func__); err("%s: Cannot enable software notification\n", __func__);
return -1; return -1;
} }
return 0;
}
static void pcie_disable_notification(struct controller *ctrl)
{
u16 mask;
mask = PRSN_DETECT_ENABLE | ATTN_BUTTN_ENABLE | MRL_DETECT_ENABLE |
PWR_FAULT_DETECT_ENABLE | HP_INTR_ENABLE | CMD_CMPL_INTR_ENABLE;
if (pcie_write_cmd(ctrl, 0, mask))
warn("%s: Cannot disable software notification\n", __func__);
}
static int pcie_init_notification(struct controller *ctrl)
{
if (pciehp_request_irq(ctrl))
return -1;
if (pcie_enable_notification(ctrl)) {
pciehp_free_irq(ctrl);
return -1;
}
return 0;
}
static void pcie_shutdown_notification(struct controller *ctrl)
{
pcie_disable_notification(ctrl);
pciehp_free_irq(ctrl);
}
static void make_slot_name(struct slot *slot)
{
if (pciehp_slot_with_bus)
snprintf(slot->name, SLOT_NAME_SIZE, "%04d_%04d",
slot->bus, slot->number);
else
snprintf(slot->name, SLOT_NAME_SIZE, "%d", slot->number);
}
static int pcie_init_slot(struct controller *ctrl)
{
struct slot *slot;
slot = kzalloc(sizeof(*slot), GFP_KERNEL);
if (!slot)
return -ENOMEM;
slot->hp_slot = 0;
slot->ctrl = ctrl;
slot->bus = ctrl->pci_dev->subordinate->number;
slot->device = ctrl->slot_device_offset + slot->hp_slot;
slot->hpc_ops = ctrl->hpc_ops;
slot->number = ctrl->first_slot;
make_slot_name(slot);
mutex_init(&slot->lock);
INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
list_add(&slot->slot_list, &ctrl->slot_list);
return 0; return 0;
} }
static void pcie_cleanup_slot(struct controller *ctrl)
{
struct slot *slot;
slot = list_first_entry(&ctrl->slot_list, struct slot, slot_list);
list_del(&slot->slot_list);
cancel_delayed_work(&slot->work);
flush_scheduled_work();
flush_workqueue(pciehp_wq);
kfree(slot);
}
static inline void dbg_ctrl(struct controller *ctrl) static inline void dbg_ctrl(struct controller *ctrl)
{ {
int i; int i;
...@@ -1093,11 +1114,19 @@ static inline void dbg_ctrl(struct controller *ctrl) ...@@ -1093,11 +1114,19 @@ static inline void dbg_ctrl(struct controller *ctrl)
dbg("Slot Control : 0x%04x\n", reg16); dbg("Slot Control : 0x%04x\n", reg16);
} }
int pcie_init(struct controller *ctrl, struct pcie_device *dev) struct controller *pcie_init(struct pcie_device *dev)
{ {
struct controller *ctrl;
u32 slot_cap; u32 slot_cap;
struct pci_dev *pdev = dev->port; struct pci_dev *pdev = dev->port;
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
if (!ctrl) {
err("%s : out of memory\n", __func__);
goto abort;
}
INIT_LIST_HEAD(&ctrl->slot_list);
ctrl->pci_dev = pdev; ctrl->pci_dev = pdev;
ctrl->cap_base = pci_find_capability(pdev, PCI_CAP_ID_EXP); ctrl->cap_base = pci_find_capability(pdev, PCI_CAP_ID_EXP);
if (!ctrl->cap_base) { if (!ctrl->cap_base) {
...@@ -1128,15 +1157,12 @@ int pcie_init(struct controller *ctrl, struct pcie_device *dev) ...@@ -1128,15 +1157,12 @@ int pcie_init(struct controller *ctrl, struct pcie_device *dev)
!(POWER_CTRL(ctrl) | ATTN_LED(ctrl) | PWR_LED(ctrl) | EMI(ctrl))) !(POWER_CTRL(ctrl) | ATTN_LED(ctrl) | PWR_LED(ctrl) | EMI(ctrl)))
ctrl->no_cmd_complete = 1; ctrl->no_cmd_complete = 1;
info("HPC vendor_id %x device_id %x ss_vid %x ss_did %x\n", /* Clear all remaining event bits in Slot Status register */
pdev->vendor, pdev->device, if (pciehp_writew(ctrl, SLOTSTATUS, 0x1f))
pdev->subsystem_vendor, pdev->subsystem_device); goto abort_ctrl;
if (pcie_init_hardware_part1(ctrl, dev)) /* Disable sotfware notification */
goto abort; pcie_disable_notification(ctrl);
if (pciehp_request_irq(ctrl))
goto abort;
/* /*
* If this is the first controller to be initialized, * If this is the first controller to be initialized,
...@@ -1144,18 +1170,39 @@ int pcie_init(struct controller *ctrl, struct pcie_device *dev) ...@@ -1144,18 +1170,39 @@ int pcie_init(struct controller *ctrl, struct pcie_device *dev)
*/ */
if (atomic_add_return(1, &pciehp_num_controllers) == 1) { if (atomic_add_return(1, &pciehp_num_controllers) == 1) {
pciehp_wq = create_singlethread_workqueue("pciehpd"); pciehp_wq = create_singlethread_workqueue("pciehpd");
if (!pciehp_wq) { if (!pciehp_wq)
goto abort_free_irq; goto abort_ctrl;
}
} }
if (pcie_init_hardware_part2(ctrl, dev)) info("HPC vendor_id %x device_id %x ss_vid %x ss_did %x\n",
goto abort_free_irq; pdev->vendor, pdev->device,
pdev->subsystem_vendor, pdev->subsystem_device);
if (pcie_init_slot(ctrl))
goto abort_ctrl;
return 0; if (pcie_init_notification(ctrl))
goto abort_slot;
abort_free_irq: return ctrl;
pciehp_free_irq(ctrl);
abort_slot:
pcie_cleanup_slot(ctrl);
abort_ctrl:
kfree(ctrl);
abort: abort:
return -1; return NULL;
}
void pcie_release_ctrl(struct controller *ctrl)
{
pcie_shutdown_notification(ctrl);
pcie_cleanup_slot(ctrl);
/*
* If this is the last controller to be released, destroy the
* pciehp work queue
*/
if (atomic_dec_and_test(&pciehp_num_controllers))
destroy_workqueue(pciehp_wq);
kfree(ctrl);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment