diff options
-rw-r--r-- | drivers/pci/hotplug/pciehp.h | 16 | ||||
-rw-r--r-- | drivers/pci/hotplug/pciehp_core.c | 44 | ||||
-rw-r--r-- | drivers/pci/hotplug/pciehp_ctrl.c | 616 | ||||
-rw-r--r-- | drivers/pci/hotplug/pciehp_hpc.c | 34 |
4 files changed, 321 insertions, 389 deletions
diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h index d19fcae8a7c..c98e27128cc 100644 --- a/drivers/pci/hotplug/pciehp.h +++ b/drivers/pci/hotplug/pciehp.h @@ -43,6 +43,7 @@ extern int pciehp_poll_mode; extern int pciehp_poll_time; extern int pciehp_debug; extern int pciehp_force; +extern struct workqueue_struct *pciehp_wq; #define dbg(format, arg...) \ do { \ @@ -70,14 +71,16 @@ struct slot { struct list_head slot_list; char name[SLOT_NAME_SIZE]; unsigned long last_emi_toggle; + struct delayed_work work; /* work for button event */ + struct mutex lock; }; struct event_info { u32 event_type; - u8 hp_slot; + struct slot *p_slot; + struct work_struct work; }; -#define MAX_EVENTS 10 struct controller { struct controller *next; struct mutex crit_sect; /* critical section mutex */ @@ -86,11 +89,9 @@ struct controller { int slot_num_inc; /* 1 or -1 */ struct pci_dev *pci_dev; struct list_head slot_list; - struct event_info event_queue[MAX_EVENTS]; struct slot *slot; struct hpc_ops *hpc_ops; wait_queue_head_t queue; /* sleep & wake process */ - u8 next_event; u8 bus; u8 device; u8 function; @@ -149,16 +150,15 @@ struct controller { #define HP_SUPR_RM(cap) (cap & HP_SUPR_RM_SUP) #define EMI(cap) (cap & EMI_PRSN) -extern int pciehp_event_start_thread(void); -extern void pciehp_event_stop_thread(void); -extern int pciehp_enable_slot(struct slot *slot); -extern int pciehp_disable_slot(struct slot *slot); +extern int pciehp_sysfs_enable_slot(struct slot *slot); +extern int pciehp_sysfs_disable_slot(struct slot *slot); extern u8 pciehp_handle_attention_button(u8 hp_slot, struct controller *ctrl); extern u8 pciehp_handle_switch_change(u8 hp_slot, struct controller *ctrl); extern u8 pciehp_handle_presence_change(u8 hp_slot, struct controller *ctrl); extern u8 pciehp_handle_power_fault(u8 hp_slot, struct controller *ctrl); extern int pciehp_configure_device(struct slot *p_slot); extern int pciehp_unconfigure_device(struct slot *p_slot); +extern void queue_pushbutton_work(struct work_struct *work); int pcie_init(struct controller *ctrl, struct pcie_device *dev); /* Global variables */ diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index a92eda6e02f..749227ecdb0 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -41,6 +41,7 @@ int pciehp_debug; int pciehp_poll_mode; int pciehp_poll_time; int pciehp_force; +struct workqueue_struct *pciehp_wq; struct controller *pciehp_ctrl_list; #define DRIVER_VERSION "0.4" @@ -62,7 +63,6 @@ MODULE_PARM_DESC(pciehp_force, "Force pciehp, even if _OSC and OSHP are missing" #define PCIE_MODULE_NAME "pciehp" -static int pcie_start_thread (void); static int set_attention_status (struct hotplug_slot *slot, u8 value); static int enable_slot (struct hotplug_slot *slot); static int disable_slot (struct hotplug_slot *slot); @@ -229,6 +229,8 @@ static int init_slots(struct controller *ctrl) 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, queue_pushbutton_work); /* register this slot with the hotplug pci core */ hotplug_slot->private = slot; @@ -286,6 +288,9 @@ static void cleanup_slots(struct controller *ctrl) if (EMI(ctrl->ctrlcap)) sysfs_remove_file(&slot->hotplug_slot->kobj, &hotplug_slot_attr_lock.attr); + cancel_delayed_work(&slot->work); + flush_scheduled_work(); + flush_workqueue(pciehp_wq); pci_hp_deregister(slot->hotplug_slot); } } @@ -314,7 +319,7 @@ static int enable_slot(struct hotplug_slot *hotplug_slot) dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); - return pciehp_enable_slot(slot); + return pciehp_sysfs_enable_slot(slot); } @@ -324,7 +329,7 @@ static int disable_slot(struct hotplug_slot *hotplug_slot) dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name); - return pciehp_disable_slot(slot); + return pciehp_sysfs_disable_slot(slot); } static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) @@ -466,9 +471,6 @@ static int pciehp_probe(struct pcie_device *dev, const struct pcie_port_service_ t_slot = pciehp_find_slot(ctrl, ctrl->slot_device_offset); - /* Finish setting up the hot plug ctrl device */ - ctrl->next_event = 0; - if (!pciehp_ctrl_list) { pciehp_ctrl_list = ctrl; ctrl->next = NULL; @@ -496,22 +498,6 @@ err_out_none: return -ENODEV; } - -static int pcie_start_thread(void) -{ - int retval = 0; - - dbg("Initialize + Start the notification/polling mechanism \n"); - - retval = pciehp_event_start_thread(); - if (retval) { - dbg("pciehp_event_start_thread() failed\n"); - return retval; - } - - return retval; -} - static void __exit unload_pciehpd(void) { struct controller *ctrl; @@ -529,10 +515,6 @@ static void __exit unload_pciehpd(void) kfree(tctrl); } - - /* Stop the notification mechanism */ - pciehp_event_stop_thread(); - } static void pciehp_remove (struct pcie_device *device) @@ -585,21 +567,11 @@ static int __init pcied_init(void) pciehp_poll_mode = 1; #endif - retval = pcie_start_thread(); - if (retval) - goto error_hpc_init; - retval = pcie_port_service_register(&hpdriver_portdrv); dbg("pcie_port_service_register = %d\n", retval); info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); if (retval) dbg("%s: Failure to register service\n", __FUNCTION__); - -error_hpc_init: - if (retval) { - pciehp_event_stop_thread(); - }; - return retval; } diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index 4283ef56dbd..91441e5ae63 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -32,92 +32,61 @@ #include <linux/types.h> #include <linux/smp_lock.h> #include <linux/pci.h> +#include <linux/workqueue.h> #include "../pci.h" #include "pciehp.h" -static void interrupt_event_handler(struct controller *ctrl); +static void interrupt_event_handler(struct work_struct *work); +static int pciehp_enable_slot(struct slot *p_slot); +static int pciehp_disable_slot(struct slot *p_slot); -static struct semaphore event_semaphore; /* mutex for process loop (up if something to process) */ -static struct semaphore event_exit; /* guard ensure thread has exited before calling it quits */ -static int event_finished; -static unsigned long pushbutton_pending; /* = 0 */ -static unsigned long surprise_rm_pending; /* = 0 */ - -static inline char *slot_name(struct slot *p_slot) +static int queue_interrupt_event(struct slot *p_slot, u32 event_type) { - return p_slot->hotplug_slot->name; + struct event_info *info; + + info = kmalloc(sizeof(*info), GFP_ATOMIC); + if (!info) + return -ENOMEM; + + info->event_type = event_type; + info->p_slot = p_slot; + INIT_WORK(&info->work, interrupt_event_handler); + + schedule_work(&info->work); + + return 0; } u8 pciehp_handle_attention_button(u8 hp_slot, struct controller *ctrl) { struct slot *p_slot; - u8 rc = 0; - u8 getstatus; - struct event_info *taskInfo; + u32 event_type; /* Attention Button Change */ dbg("pciehp: Attention button interrupt received.\n"); - - /* This is the structure that tells the worker thread what to do */ - taskInfo = &(ctrl->event_queue[ctrl->next_event]); - p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); - - p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); - - ctrl->next_event = (ctrl->next_event + 1) % MAX_EVENTS; - taskInfo->hp_slot = hp_slot; - rc++; + p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); /* * Button pressed - See if need to TAKE ACTION!!! */ - info("Button pressed on Slot(%s)\n", slot_name(p_slot)); - taskInfo->event_type = INT_BUTTON_PRESS; - - if ((p_slot->state == BLINKINGON_STATE) - || (p_slot->state == BLINKINGOFF_STATE)) { - /* Cancel if we are still blinking; this means that we press the - * attention again before the 5 sec. limit expires to cancel hot-add - * or hot-remove - */ - taskInfo->event_type = INT_BUTTON_CANCEL; - info("Button cancel on Slot(%s)\n", slot_name(p_slot)); - } else if ((p_slot->state == POWERON_STATE) - || (p_slot->state == POWEROFF_STATE)) { - /* Ignore if the slot is on power-on or power-off state; this - * means that the previous attention button action to hot-add or - * hot-remove is undergoing - */ - taskInfo->event_type = INT_BUTTON_IGNORE; - info("Button ignore on Slot(%s)\n", slot_name(p_slot)); - } + info("Button pressed on Slot(%s)\n", p_slot->name); + event_type = INT_BUTTON_PRESS; - if (rc) - up(&event_semaphore); /* signal event thread that new event is posted */ + queue_interrupt_event(p_slot, event_type); return 0; - } u8 pciehp_handle_switch_change(u8 hp_slot, struct controller *ctrl) { struct slot *p_slot; - u8 rc = 0; u8 getstatus; - struct event_info *taskInfo; + u32 event_type; /* Switch Change */ dbg("pciehp: Switch interrupt received.\n"); - /* This is the structure that tells the worker thread - * what to do - */ - taskInfo = &(ctrl->event_queue[ctrl->next_event]); - ctrl->next_event = (ctrl->next_event + 1) % MAX_EVENTS; - taskInfo->hp_slot = hp_slot; - - rc++; p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); @@ -125,39 +94,30 @@ u8 pciehp_handle_switch_change(u8 hp_slot, struct controller *ctrl) /* * Switch opened */ - info("Latch open on Slot(%s)\n", slot_name(p_slot)); - taskInfo->event_type = INT_SWITCH_OPEN; + info("Latch open on Slot(%s)\n", p_slot->name); + event_type = INT_SWITCH_OPEN; } else { /* * Switch closed */ - info("Latch close on Slot(%s)\n", slot_name(p_slot)); - taskInfo->event_type = INT_SWITCH_CLOSE; + info("Latch close on Slot(%s)\n", p_slot->name); + event_type = INT_SWITCH_CLOSE; } - if (rc) - up(&event_semaphore); /* signal event thread that new event is posted */ + queue_interrupt_event(p_slot, event_type); - return rc; + return 1; } u8 pciehp_handle_presence_change(u8 hp_slot, struct controller *ctrl) { struct slot *p_slot; - u8 presence_save, rc = 0; - struct event_info *taskInfo; + u32 event_type; + u8 presence_save; /* Presence Change */ dbg("pciehp: Presence/Notify input change.\n"); - /* This is the structure that tells the worker thread - * what to do - */ - taskInfo = &(ctrl->event_queue[ctrl->next_event]); - ctrl->next_event = (ctrl->next_event + 1) % MAX_EVENTS; - taskInfo->hp_slot = hp_slot; - - rc++; p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); /* Switch is open, assume a presence change @@ -168,59 +128,49 @@ u8 pciehp_handle_presence_change(u8 hp_slot, struct controller *ctrl) /* * Card Present */ - info("Card present on Slot(%s)\n", slot_name(p_slot)); - taskInfo->event_type = INT_PRESENCE_ON; + info("Card present on Slot(%s)\n", p_slot->name); + event_type = INT_PRESENCE_ON; } else { /* * Not Present */ - info("Card not present on Slot(%s)\n", slot_name(p_slot)); - taskInfo->event_type = INT_PRESENCE_OFF; + info("Card not present on Slot(%s)\n", p_slot->name); + event_type = INT_PRESENCE_OFF; } - if (rc) - up(&event_semaphore); /* signal event thread that new event is posted */ + queue_interrupt_event(p_slot, event_type); - return rc; + return 1; } u8 pciehp_handle_power_fault(u8 hp_slot, struct controller *ctrl) { struct slot *p_slot; - u8 rc = 0; - struct event_info *taskInfo; + u32 event_type; /* power fault */ dbg("pciehp: Power fault interrupt received.\n"); - /* this is the structure that tells the worker thread - * what to do - */ - taskInfo = &(ctrl->event_queue[ctrl->next_event]); - ctrl->next_event = (ctrl->next_event + 1) % MAX_EVENTS; - taskInfo->hp_slot = hp_slot; - - rc++; p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); if ( !(p_slot->hpc_ops->query_power_fault(p_slot))) { /* * power fault Cleared */ - info("Power fault cleared on Slot(%s)\n", slot_name(p_slot)); - taskInfo->event_type = INT_POWER_FAULT_CLEAR; + info("Power fault cleared on Slot(%s)\n", p_slot->name); + event_type = INT_POWER_FAULT_CLEAR; } else { /* * power fault */ - info("Power fault on Slot(%s)\n", slot_name(p_slot)); - taskInfo->event_type = INT_POWER_FAULT; + info("Power fault on Slot(%s)\n", p_slot->name); + event_type = INT_POWER_FAULT; info("power fault bit %x set\n", hp_slot); } - if (rc) - up(&event_semaphore); /* signal event thread that new event is posted */ - return rc; + queue_interrupt_event(p_slot, event_type); + + return 1; } /* The following routines constitute the bulk of the @@ -357,13 +307,10 @@ static int remove_board(struct slot *p_slot) return 0; } - -static void pushbutton_helper_thread(unsigned long data) -{ - pushbutton_pending = data; - - up(&event_semaphore); -} +struct power_work_info { + struct slot *p_slot; + struct work_struct work; +}; /** * pciehp_pushbutton_thread @@ -372,276 +319,214 @@ static void pushbutton_helper_thread(unsigned long data) * Handles all pending events and exits. * */ -static void pciehp_pushbutton_thread(unsigned long slot) +static void pciehp_power_thread(struct work_struct *work) { - struct slot *p_slot = (struct slot *) slot; - u8 getstatus; - - pushbutton_pending = 0; - - if (!p_slot) { - dbg("%s: Error! slot NULL\n", __FUNCTION__); - return; - } - - p_slot->hpc_ops->get_power_status(p_slot, &getstatus); - if (getstatus) { - p_slot->state = POWEROFF_STATE; - dbg("%s: disabling bus:device(%x:%x)\n", __FUNCTION__, - p_slot->bus, p_slot->device); - + struct power_work_info *info = + container_of(work, struct power_work_info, work); + struct slot *p_slot = info->p_slot; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case POWEROFF_STATE: + mutex_unlock(&p_slot->lock); + dbg("%s: disabling bus:device(%x:%x)\n", + __FUNCTION__, p_slot->bus, p_slot->device); pciehp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); p_slot->state = STATIC_STATE; - } else { - p_slot->state = POWERON_STATE; - dbg("%s: adding bus:device(%x:%x)\n", __FUNCTION__, - p_slot->bus, p_slot->device); - + break; + case POWERON_STATE: + mutex_unlock(&p_slot->lock); if (pciehp_enable_slot(p_slot) && PWR_LED(p_slot->ctrl->ctrlcap)) p_slot->hpc_ops->green_led_off(p_slot); - + mutex_lock(&p_slot->lock); p_slot->state = STATIC_STATE; + break; + default: + break; } + mutex_unlock(&p_slot->lock); - return; + kfree(info); } -/** - * pciehp_surprise_rm_thread - * - * Scheduled procedure to handle blocking stuff for the surprise removal - * Handles all pending events and exits. - * - */ -static void pciehp_surprise_rm_thread(unsigned long slot) +void queue_pushbutton_work(struct work_struct *work) { - struct slot *p_slot = (struct slot *) slot; - u8 getstatus; - - surprise_rm_pending = 0; + struct slot *p_slot = container_of(work, struct slot, work.work); + struct power_work_info *info; - if (!p_slot) { - dbg("%s: Error! slot NULL\n", __FUNCTION__); + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + err("%s: Cannot allocate memory\n", __FUNCTION__); return; } + info->p_slot = p_slot; + INIT_WORK(&info->work, pciehp_power_thread); - p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); - if (!getstatus) { + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: p_slot->state = POWEROFF_STATE; - dbg("%s: removing bus:device(%x:%x)\n", - __FUNCTION__, p_slot->bus, p_slot->device); - - pciehp_disable_slot(p_slot); - p_slot->state = STATIC_STATE; - } else { + break; + case BLINKINGON_STATE: p_slot->state = POWERON_STATE; - dbg("%s: adding bus:device(%x:%x)\n", - __FUNCTION__, p_slot->bus, p_slot->device); - - if (pciehp_enable_slot(p_slot) && - PWR_LED(p_slot->ctrl->ctrlcap)) - p_slot->hpc_ops->green_led_off(p_slot); - - p_slot->state = STATIC_STATE; + break; + default: + goto out; } - - return; + queue_work(pciehp_wq, &info->work); + out: + mutex_unlock(&p_slot->lock); } - - -/* this is the main worker thread */ -static int event_thread(void* data) -{ - struct controller *ctrl; - lock_kernel(); - daemonize("pciehpd_event"); - - unlock_kernel(); - - while (1) { - dbg("!!!!event_thread sleeping\n"); - down_interruptible (&event_semaphore); - dbg("event_thread woken finished = %d\n", event_finished); - if (event_finished || signal_pending(current)) - break; - /* Do stuff here */ - if (pushbutton_pending) - pciehp_pushbutton_thread(pushbutton_pending); - else if (surprise_rm_pending) - pciehp_surprise_rm_thread(surprise_rm_pending); - else - for (ctrl = pciehp_ctrl_list; ctrl; ctrl=ctrl->next) - interrupt_event_handler(ctrl); - } - dbg("event_thread signals exit\n"); - up(&event_exit); - return 0; -} - -int pciehp_event_start_thread(void) -{ - int pid; - - /* initialize our semaphores */ - init_MUTEX_LOCKED(&event_exit); - event_finished=0; - - init_MUTEX_LOCKED(&event_semaphore); - pid = kernel_thread(event_thread, NULL, 0); - - if (pid < 0) { - err ("Can't start up our event thread\n"); - return -1; - } - return 0; -} - - -void pciehp_event_stop_thread(void) -{ - event_finished = 1; - up(&event_semaphore); - down(&event_exit); -} - - static int update_slot_info(struct slot *slot) { struct hotplug_slot_info *info; - /* char buffer[SLOT_NAME_SIZE]; */ int result; - info = kmalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL); + info = kmalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; - /* make_slot_name (&buffer[0], SLOT_NAME_SIZE, slot); */ - slot->hpc_ops->get_power_status(slot, &(info->power_status)); slot->hpc_ops->get_attention_status(slot, &(info->attention_status)); slot->hpc_ops->get_latch_status(slot, &(info->latch_status)); slot->hpc_ops->get_adapter_status(slot, &(info->adapter_status)); - /* result = pci_hp_change_slot_info(buffer, info); */ result = pci_hp_change_slot_info(slot->hotplug_slot, info); kfree (info); return result; } -static void interrupt_event_handler(struct controller *ctrl) +/* + * Note: This function must be called with slot->lock held + */ +static void handle_button_press_event(struct slot *p_slot) { - int loop = 0; - int change = 1; - u8 hp_slot; + struct controller *ctrl = p_slot->ctrl; u8 getstatus; - struct slot *p_slot; - while (change) { - change = 0; - - for (loop = 0; loop < MAX_EVENTS; loop++) { - if (ctrl->event_queue[loop].event_type != 0) { - hp_slot = ctrl->event_queue[loop].hp_slot; - - p_slot = pciehp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); - - if (ctrl->event_queue[loop].event_type == INT_BUTTON_CANCEL) { - dbg("button cancel\n"); - del_timer(&p_slot->task_event); - - switch (p_slot->state) { - case BLINKINGOFF_STATE: - if (PWR_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->green_led_on(p_slot); - - if (ATTN_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->set_attention_status(p_slot, 0); - break; - case BLINKINGON_STATE: - if (PWR_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->green_led_off(p_slot); - - if (ATTN_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->set_attention_status(p_slot, 0); - break; - default: - warn("Not a valid state\n"); - return; - } - info("PCI slot #%s - action canceled due to button press.\n", slot_name(p_slot)); - p_slot->state = STATIC_STATE; - } - /* ***********Button Pressed (No action on 1st press...) */ - else if (ctrl->event_queue[loop].event_type == INT_BUTTON_PRESS) { - - if (ATTN_BUTTN(ctrl->ctrlcap)) { - dbg("Button pressed\n"); - p_slot->hpc_ops->get_power_status(p_slot, &getstatus); - if (getstatus) { - /* slot is on */ - dbg("slot is on\n"); - p_slot->state = BLINKINGOFF_STATE; - info("PCI slot #%s - powering off due to button press.\n", slot_name(p_slot)); - } else { - /* slot is off */ - dbg("slot is off\n"); - p_slot->state = BLINKINGON_STATE; - info("PCI slot #%s - powering on due to button press.\n", slot_name(p_slot)); - } - - /* blink green LED and turn off amber */ - if (PWR_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->green_led_blink(p_slot); - - if (ATTN_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->set_attention_status(p_slot, 0); - - init_timer(&p_slot->task_event); - p_slot->task_event.expires = jiffies + 5 * HZ; /* 5 second delay */ - p_slot->task_event.function = (void (*)(unsigned long)) pushbutton_helper_thread; - p_slot->task_event.data = (unsigned long) p_slot; - - add_timer(&p_slot->task_event); - } - } - /***********POWER FAULT********************/ - else if (ctrl->event_queue[loop].event_type == INT_POWER_FAULT) { - if (POWER_CTRL(ctrl->ctrlcap)) { - dbg("power fault\n"); - if (ATTN_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->set_attention_status(p_slot, 1); - - if (PWR_LED(ctrl->ctrlcap)) - p_slot->hpc_ops->green_led_off(p_slot); - } - } - /***********SURPRISE REMOVAL********************/ - else if ((ctrl->event_queue[loop].event_type == INT_PRESENCE_ON) || - (ctrl->event_queue[loop].event_type == INT_PRESENCE_OFF)) { - if (HP_SUPR_RM(ctrl->ctrlcap)) { - dbg("Surprise Removal\n"); - if (p_slot) { - surprise_rm_pending = (unsigned long) p_slot; - up(&event_semaphore); - update_slot_info(p_slot); - } - } - } else { - /* refresh notification */ - if (p_slot) - update_slot_info(p_slot); - } - - ctrl->event_queue[loop].event_type = 0; - - change = 1; - } - } /* End of FOR loop */ + switch (p_slot->state) { + case STATIC_STATE: + p_slot->hpc_ops->get_power_status(p_slot, &getstatus); + if (getstatus) { + p_slot->state = BLINKINGOFF_STATE; + info("PCI slot #%s - powering off due to button " + "press.\n", p_slot->name); + } else { + p_slot->state = BLINKINGON_STATE; + info("PCI slot #%s - powering on due to button " + "press.\n", p_slot->name); + } + /* blink green LED and turn off amber */ + if (PWR_LED(ctrl->ctrlcap)) + p_slot->hpc_ops->green_led_blink(p_slot); + if (ATTN_LED(ctrl->ctrlcap)) + p_slot->hpc_ops->set_attention_status(p_slot, 0); + + schedule_delayed_work(&p_slot->work, 5*HZ); + break; + case BLINKINGOFF_STATE: + case BLINKINGON_STATE: + /* + * Cancel if we are still blinking; this means that we + * press the attention again before the 5 sec. limit + * expires to cancel hot-add or hot-remove + */ + info("Button cancel on Slot(%s)\n", p_slot->name); + dbg("%s: button cancel\n", __FUNCTION__); + cancel_delayed_work(&p_slot->work); + if (p_slot->state == BLINKINGOFF_STATE) { + if (PWR_LED(ctrl->ctrlcap)) + p_slot->hpc_ops->green_led_on(p_slot); + } else { + if (PWR_LED(ctrl->ctrlcap)) + p_slot->hpc_ops->green_led_off(p_slot); + } + if (ATTN_LED(ctrl->ctrlcap)) + p_slot->hpc_ops->set_attention_status(p_slot, 0); + info("PCI slot #%s - action canceled due to button press\n", + p_slot->name); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + case POWERON_STATE: + /* + * Ignore if the slot is on power-on or power-off state; + * this means that the previous attention button action + * to hot-add or hot-remove is undergoing + */ + info("Button ignore on Slot(%s)\n", p_slot->name); + update_slot_info(p_slot); + break; + default: + warn("Not a valid state\n"); + break; } } +/* + * Note: This function must be called with slot->lock held + */ +static void handle_surprise_event(struct slot *p_slot) +{ + u8 getstatus; + struct power_work_info *info; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + err("%s: Cannot allocate memory\n", __FUNCTION__); + return; + } + info->p_slot = p_slot; + INIT_WORK(&info->work, pciehp_power_thread); + + p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); + if (!getstatus) + p_slot->state = POWEROFF_STATE; + else + p_slot->state = POWERON_STATE; + + queue_work(pciehp_wq, &info->work); +} + +static void interrupt_event_handler(struct work_struct *work) +{ + struct event_info *info = container_of(work, struct event_info, work); + struct slot *p_slot = info->p_slot; + struct controller *ctrl = p_slot->ctrl; + + mutex_lock(&p_slot->lock); + switch (info->event_type) { + case INT_BUTTON_PRESS: + handle_button_press_event(p_slot); + break; + case INT_POWER_FAULT: + if (!POWER_CTRL(ctrl->ctrlcap)) + break; + if (ATTN_LED(ctrl->ctrlcap)) + p_slot->hpc_ops->set_attention_status(p_slot, 1); + if (PWR_LED(ctrl->ctrlcap)) + p_slot->hpc_ops->green_led_off(p_slot); + break; + case INT_PRESENCE_ON: + case INT_PRESENCE_OFF: + if (!HP_SUPR_RM(ctrl->ctrlcap)) + break; + dbg("Surprise Removal\n"); + update_slot_info(p_slot); + handle_surprise_event(p_slot); + break; + default: + update_slot_info(p_slot); + break; + } + mutex_unlock(&p_slot->lock); + + kfree(info); +} + int pciehp_enable_slot(struct slot *p_slot) { u8 getstatus = 0; @@ -653,7 +538,7 @@ int pciehp_enable_slot(struct slot *p_slot) rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); if (rc || !getstatus) { info("%s: no adapter on slot(%s)\n", __FUNCTION__, - slot_name(p_slot)); + p_slot->name); mutex_unlock(&p_slot->ctrl->crit_sect); return -ENODEV; } @@ -661,7 +546,7 @@ int pciehp_enable_slot(struct slot *p_slot) rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); if (rc || getstatus) { info("%s: latch open on slot(%s)\n", __FUNCTION__, - slot_name(p_slot)); + p_slot->name); mutex_unlock(&p_slot->ctrl->crit_sect); return -ENODEV; } @@ -671,7 +556,7 @@ int pciehp_enable_slot(struct slot *p_slot) rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); if (rc || getstatus) { info("%s: already enabled on slot(%s)\n", __FUNCTION__, - slot_name(p_slot)); + p_slot->name); mutex_unlock(&p_slot->ctrl->crit_sect); return -EINVAL; } @@ -706,7 +591,7 @@ int pciehp_disable_slot(struct slot *p_slot) ret = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); if (ret || !getstatus) { info("%s: no adapter on slot(%s)\n", __FUNCTION__, - slot_name(p_slot)); + p_slot->name); mutex_unlock(&p_slot->ctrl->crit_sect); return -ENODEV; } @@ -716,7 +601,7 @@ int pciehp_disable_slot(struct slot *p_slot) ret = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); if (ret || getstatus) { info("%s: latch open on slot(%s)\n", __FUNCTION__, - slot_name(p_slot)); + p_slot->name); mutex_unlock(&p_slot->ctrl->crit_sect); return -ENODEV; } @@ -726,7 +611,7 @@ int pciehp_disable_slot(struct slot *p_slot) ret = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); if (ret || !getstatus) { info("%s: already disabled slot(%s)\n", __FUNCTION__, - slot_name(p_slot)); + p_slot->name); mutex_unlock(&p_slot->ctrl->crit_sect); return -EINVAL; } @@ -739,3 +624,66 @@ int pciehp_disable_slot(struct slot *p_slot) return ret; } +int pciehp_sysfs_enable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGON_STATE: + cancel_delayed_work(&p_slot->work); + case STATIC_STATE: + p_slot->state = POWERON_STATE; + mutex_unlock(&p_slot->lock); + retval = pciehp_enable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWERON_STATE: + info("Slot %s is already in powering on state\n", + p_slot->name); + break; + case BLINKINGOFF_STATE: + case POWEROFF_STATE: + info("Already enabled on slot %s\n", p_slot->name); + break; + default: + err("Not a valid state on slot %s\n", p_slot->name); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} + +int pciehp_sysfs_disable_slot(struct slot *p_slot) +{ + int retval = -ENODEV; + + mutex_lock(&p_slot->lock); + switch (p_slot->state) { + case BLINKINGOFF_STATE: + cancel_delayed_work(&p_slot->work); + case STATIC_STATE: + p_slot->state = POWEROFF_STATE; + mutex_unlock(&p_slot->lock); + retval = pciehp_disable_slot(p_slot); + mutex_lock(&p_slot->lock); + p_slot->state = STATIC_STATE; + break; + case POWEROFF_STATE: + info("Slot %s is already in powering off state\n", + p_slot->name); + break; + case BLINKINGON_STATE: + case POWERON_STATE: + info("Already disabled on slot %s\n", p_slot->name); + break; + default: + err("Not a valid state on slot %s\n", p_slot->name); + break; + } + mutex_unlock(&p_slot->lock); + + return retval; +} diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index fbc64aa2dd6..9aac6a87eb5 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -71,6 +71,8 @@ #define DBG_LEAVE_ROUTINE #endif /* DEBUG */ +static atomic_t pciehp_num_controllers = ATOMIC_INIT(0); + struct ctrl_reg { u8 cap_id; u8 nxt_ptr; @@ -219,10 +221,7 @@ static inline int pciehp_writel(struct controller *ctrl, int reg, u32 value) #define EMI_STATE 0x0080 #define EMI_STATUS_BIT 7 -static spinlock_t hpc_event_lock; - DEFINE_DBG_BUFFER /* Debug string buffer for entire HPC defined here */ -static int ctlr_seq_num = 0; /* Controller sequence # */ static irqreturn_t pcie_isr(int irq, void *dev_id); static void start_int_poll_timer(struct controller *ctrl, int sec); @@ -656,6 +655,13 @@ static void hpc_release_ctlr(struct controller *ctrl) else free_irq(ctrl->pci_dev->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); + DBG_LEAVE_ROUTINE } @@ -1152,7 +1158,6 @@ int pciehp_acpi_get_hp_hw_control_from_firmware(struct pci_dev *dev) int pcie_init(struct controller * ctrl, struct pcie_device *dev) { int rc; - static int first = 1; u16 temp_word; u16 cap_reg; u16 intr_enable = 0; @@ -1221,11 +1226,6 @@ int pcie_init(struct controller * ctrl, struct pcie_device *dev) dbg("%s: SLOTCTRL offset %x slot_ctrl %x\n", __FUNCTION__, ctrl->cap_base + SLOTCTRL, slot_ctrl); - if (first) { - spin_lock_init(&hpc_event_lock); - first = 0; - } - for ( rc = 0; rc < DEVICE_COUNT_RESOURCE; rc++) if (pci_resource_len(pdev, rc) > 0) dbg("pci resource[%d] start=0x%llx(len=0x%llx)\n", rc, @@ -1286,7 +1286,8 @@ int pcie_init(struct controller * ctrl, struct pcie_device *dev) rc = request_irq(ctrl->pci_dev->irq, pcie_isr, IRQF_SHARED, MY_NAME, (void *)ctrl); dbg("%s: request_irq %d for hpc%d (returns %d)\n", - __FUNCTION__, ctrl->pci_dev->irq, ctlr_seq_num, rc); + __FUNCTION__, ctrl->pci_dev->irq, + atomic_read(&pciehp_num_controllers), rc); if (rc) { err("Can't get irq %d for the hotplug controller\n", ctrl->pci_dev->irq); @@ -1296,6 +1297,18 @@ int pcie_init(struct controller * ctrl, struct pcie_device *dev) dbg("pciehp ctrl b:d:f:irq=0x%x:%x:%x:%x\n", pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), dev->irq); + /* + * If this is the first controller to be initialized, + * initialize the pciehp work queue + */ + if (atomic_add_return(1, &pciehp_num_controllers) == 1) { + pciehp_wq = create_singlethread_workqueue("pciehpd"); + if (!pciehp_wq) { + rc = -ENOMEM; + goto abort_free_irq; + } + } + rc = pciehp_readw(ctrl, SLOTCTRL, &temp_word); if (rc) { err("%s: Cannot read SLOTCTRL register\n", __FUNCTION__); @@ -1349,7 +1362,6 @@ int pcie_init(struct controller * ctrl, struct pcie_device *dev) goto abort_disable_intr; } - ctlr_seq_num++; ctrl->hpc_ops = &pciehp_hpc_ops; DBG_LEAVE_ROUTINE |