mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 16:54:21 +00:00 
			
		
		
		
	Merge branch 'pci/hotplug'
- Simplify SHPC existence/permission checks (Bjorn Helgaas)
  - Remove hotplug sample skeleton driver (Lukas Wunner)
  - Convert pciehp to threaded IRQ handling (Lukas Wunner)
  - Improve pciehp tolerance of missed events and initially unstable links
    (Lukas Wunner)
  - Clear spurious pciehp events on resume (Lukas Wunner)
  - Add pciehp runtime PM support, including for Thunderbolt controllers
    (Lukas Wunner)
  - Support interrupts from pciehp bridges in D3hot (Lukas Wunner)
* pci/hotplug:
  PCI: pciehp: Deduplicate presence check on probe & resume
  PCI: pciehp: Avoid implicit fallthroughs in switch statements
  PCI: Whitelist Thunderbolt ports for runtime D3
  PCI: Whitelist native hotplug ports for runtime D3
  PCI: sysfs: Resume to D0 on function reset
  PCI: pciehp: Resume parent to D0 on config space access
  PCI: pciehp: Resume to D0 on enable/disable
  PCI: pciehp: Support interrupts sent from D3hot
  PCI: pciehp: Obey compulsory command delay after resume
  PCI: pciehp: Clear spurious events earlier on resume
  PCI: portdrv: Deduplicate PM callback iterator
  PCI: pciehp: Avoid slot access during reset
  PCI: pciehp: Always enable occupied slot on probe
  PCI: pciehp: Become resilient to missed events
  PCI: pciehp: Tolerate initially unstable link
  PCI: pciehp: Declare pciehp_enable/disable_slot() static
  PCI: pciehp: Drop enable/disable lock
  PCI: pciehp: Enable/disable exclusively from IRQ thread
  PCI: pciehp: Track enable/disable status
  PCI: pciehp: Publish to user space last on probe
  PCI: hotplug: Demidlayer registration with the core
  PCI: pciehp: Drop slot workqueue
  PCI: pciehp: Handle events synchronously
  PCI: pciehp: Stop blinking on slot enable failure
  PCI: pciehp: Convert to threaded polling
  PCI: pciehp: Convert to threaded IRQ
  PCI: pciehp: Document struct slot and struct controller
  PCI: pciehp: Declare pciehp_unconfigure_device() void
  PCI: pciehp: Drop unnecessary NULL pointer check
  PCI: pciehp: Fix unprotected list iteration in IRQ handler
  PCI: pciehp: Fix use-after-free on unplug
  PCI: hotplug: Don't leak pci_slot on registration failure
  PCI: hotplug: Delete skeleton driver
  PCI: shpchp: Separate existence of SHPC and permission to use it
			
			
This commit is contained in:
		
						commit
						c0638a4553
					
				
					 29 changed files with 732 additions and 981 deletions
				
			
		|  | @ -73,20 +73,6 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev) | |||
| 	acpi_handle chandle, handle; | ||||
| 	struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Per PCI firmware specification, we should run the ACPI _OSC | ||||
| 	 * method to get control of hotplug hardware before using it. If | ||||
| 	 * an _OSC is missing, we look for an OSHP to do the same thing. | ||||
| 	 * To handle different BIOS behavior, we look for _OSC on a root | ||||
| 	 * bridge preferentially (according to PCI fw spec). Later for | ||||
| 	 * OSHP within the scope of the hotplug controller and its parents, | ||||
| 	 * up to the host bridge under which this controller exists. | ||||
| 	 */ | ||||
| 	if (shpchp_is_native(pdev)) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	/* If _OSC exists, we should not evaluate OSHP */ | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If there's no ACPI host bridge (i.e., ACPI support is compiled | ||||
| 	 * into the kernel but the hardware platform doesn't support ACPI), | ||||
|  | @ -97,9 +83,25 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev) | |||
| 	if (!root) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	if (root->osc_support_set) | ||||
| 		goto no_control; | ||||
| 	/*
 | ||||
| 	 * If _OSC exists, it determines whether we're allowed to manage | ||||
| 	 * the SHPC.  We executed it while enumerating the host bridge. | ||||
| 	 */ | ||||
| 	if (root->osc_support_set) { | ||||
| 		if (host->native_shpc_hotplug) | ||||
| 			return 0; | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * In the absence of _OSC, we're always allowed to manage the SHPC. | ||||
| 	 * However, if an OSHP method is present, we must execute it so the | ||||
| 	 * firmware can transfer control to the OS, e.g., direct interrupts | ||||
| 	 * to the OS instead of to the firmware. | ||||
| 	 * | ||||
| 	 * N.B. The PCI Firmware Spec (r3.2, sec 4.8) does not endorse | ||||
| 	 * searching up the ACPI hierarchy, so the loops below are suspect. | ||||
| 	 */ | ||||
| 	handle = ACPI_HANDLE(&pdev->dev); | ||||
| 	if (!handle) { | ||||
| 		/*
 | ||||
|  | @ -128,7 +130,7 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev) | |||
| 		if (ACPI_FAILURE(status)) | ||||
| 			break; | ||||
| 	} | ||||
| no_control: | ||||
| 
 | ||||
| 	pci_info(pdev, "Cannot get control of SHPC hotplug\n"); | ||||
| 	kfree(string.pointer); | ||||
| 	return -ENODEV; | ||||
|  |  | |||
|  | @ -254,20 +254,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * release_slot - free up the memory used by a slot | ||||
|  * @hotplug_slot: slot to free | ||||
|  */ | ||||
| static void release_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 
 | ||||
| 	pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); | ||||
| 
 | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
| /* callback routine to initialize 'struct slot' for each slot */ | ||||
| int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot, | ||||
| 				  unsigned int sun) | ||||
|  | @ -287,7 +273,6 @@ int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot, | |||
| 	slot->hotplug_slot->info = &slot->info; | ||||
| 
 | ||||
| 	slot->hotplug_slot->private = slot; | ||||
| 	slot->hotplug_slot->release = &release_slot; | ||||
| 	slot->hotplug_slot->ops = &acpi_hotplug_slot_ops; | ||||
| 
 | ||||
| 	slot->acpi_slot = acpiphp_slot; | ||||
|  | @ -324,13 +309,12 @@ error: | |||
| void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *acpiphp_slot) | ||||
| { | ||||
| 	struct slot *slot = acpiphp_slot->slot; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	pr_info("Slot [%s] unregistered\n", slot_name(slot)); | ||||
| 
 | ||||
| 	retval = pci_hp_deregister(slot->hotplug_slot); | ||||
| 	if (retval) | ||||
| 		pr_err("pci_hp_deregister failed with error %d\n", retval); | ||||
| 	pci_hp_deregister(slot->hotplug_slot); | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -195,10 +195,8 @@ get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void release_slot(struct hotplug_slot *hotplug_slot) | ||||
| static void release_slot(struct slot *slot) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 
 | ||||
| 	kfree(slot->hotplug_slot->info); | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	pci_dev_put(slot->dev); | ||||
|  | @ -253,7 +251,6 @@ cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last) | |||
| 		snprintf(name, SLOT_NAME_SIZE, "%02x:%02x", bus->number, i); | ||||
| 
 | ||||
| 		hotplug_slot->private = slot; | ||||
| 		hotplug_slot->release = &release_slot; | ||||
| 		hotplug_slot->ops = &cpci_hotplug_slot_ops; | ||||
| 
 | ||||
| 		/*
 | ||||
|  | @ -308,12 +305,8 @@ cpci_hp_unregister_bus(struct pci_bus *bus) | |||
| 			slots--; | ||||
| 
 | ||||
| 			dbg("deregistering slot %s", slot_name(slot)); | ||||
| 			status = pci_hp_deregister(slot->hotplug_slot); | ||||
| 			if (status) { | ||||
| 				err("pci_hp_deregister failed with error %d", | ||||
| 				    status); | ||||
| 				break; | ||||
| 			} | ||||
| 			pci_hp_deregister(slot->hotplug_slot); | ||||
| 			release_slot(slot); | ||||
| 		} | ||||
| 	} | ||||
| 	up_write(&list_rwsem); | ||||
|  | @ -623,6 +616,7 @@ cleanup_slots(void) | |||
| 	list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) { | ||||
| 		list_del(&slot->slot_list); | ||||
| 		pci_hp_deregister(slot->hotplug_slot); | ||||
| 		release_slot(slot); | ||||
| 	} | ||||
| cleanup_null: | ||||
| 	up_write(&list_rwsem); | ||||
|  |  | |||
|  | @ -266,17 +266,6 @@ static void __iomem *get_SMBIOS_entry(void __iomem *smbios_start, | |||
| 	return previous; | ||||
| } | ||||
| 
 | ||||
| static void release_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, slot_name(slot)); | ||||
| 
 | ||||
| 	kfree(slot->hotplug_slot->info); | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
| static int ctrl_slot_cleanup(struct controller *ctrl) | ||||
| { | ||||
| 	struct slot *old_slot, *next_slot; | ||||
|  | @ -285,9 +274,11 @@ static int ctrl_slot_cleanup(struct controller *ctrl) | |||
| 	ctrl->slot = NULL; | ||||
| 
 | ||||
| 	while (old_slot) { | ||||
| 		/* memory will be freed by the release_slot callback */ | ||||
| 		next_slot = old_slot->next; | ||||
| 		pci_hp_deregister(old_slot->hotplug_slot); | ||||
| 		kfree(old_slot->hotplug_slot->info); | ||||
| 		kfree(old_slot->hotplug_slot); | ||||
| 		kfree(old_slot); | ||||
| 		old_slot = next_slot; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -678,7 +669,6 @@ static int ctrl_slot_setup(struct controller *ctrl, | |||
| 			((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04; | ||||
| 
 | ||||
| 		/* register this slot with the hotplug pci core */ | ||||
| 		hotplug_slot->release = &release_slot; | ||||
| 		hotplug_slot->private = slot; | ||||
| 		snprintf(name, SLOT_NAME_SIZE, "%u", slot->number); | ||||
| 		hotplug_slot->ops = &cpqphp_hotplug_slot_ops; | ||||
|  |  | |||
|  | @ -673,7 +673,20 @@ static void free_slots(void) | |||
| 
 | ||||
| 	list_for_each_entry_safe(slot_cur, next, &ibmphp_slot_head, | ||||
| 				 ibm_slot_list) { | ||||
| 		pci_hp_deregister(slot_cur->hotplug_slot); | ||||
| 		pci_hp_del(slot_cur->hotplug_slot); | ||||
| 		slot_cur->ctrl = NULL; | ||||
| 		slot_cur->bus_on = NULL; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * We don't want to actually remove the resources, | ||||
| 		 * since ibmphp_free_resources() will do just that. | ||||
| 		 */ | ||||
| 		ibmphp_unconfigure_card(&slot_cur, -1); | ||||
| 
 | ||||
| 		pci_hp_destroy(slot_cur->hotplug_slot); | ||||
| 		kfree(slot_cur->hotplug_slot->info); | ||||
| 		kfree(slot_cur->hotplug_slot); | ||||
| 		kfree(slot_cur); | ||||
| 	} | ||||
| 	debug("%s -- exit\n", __func__); | ||||
| } | ||||
|  |  | |||
|  | @ -699,25 +699,6 @@ static int fillslotinfo(struct hotplug_slot *hotplug_slot) | |||
| 	return rc; | ||||
| } | ||||
| 
 | ||||
| static void release_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot; | ||||
| 
 | ||||
| 	if (!hotplug_slot || !hotplug_slot->private) | ||||
| 		return; | ||||
| 
 | ||||
| 	slot = hotplug_slot->private; | ||||
| 	kfree(slot->hotplug_slot->info); | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	slot->ctrl = NULL; | ||||
| 	slot->bus_on = NULL; | ||||
| 
 | ||||
| 	/* we don't want to actually remove the resources, since free_resources will do just that */ | ||||
| 	ibmphp_unconfigure_card(&slot, -1); | ||||
| 
 | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
| static struct pci_driver ibmphp_driver; | ||||
| 
 | ||||
| /*
 | ||||
|  | @ -941,7 +922,6 @@ static int __init ebda_rsrc_controller(void) | |||
| 			tmp_slot->hotplug_slot = hp_slot_ptr; | ||||
| 
 | ||||
| 			hp_slot_ptr->private = tmp_slot; | ||||
| 			hp_slot_ptr->release = release_slot; | ||||
| 
 | ||||
| 			rc = fillslotinfo(hp_slot_ptr); | ||||
| 			if (rc) | ||||
|  |  | |||
|  | @ -396,8 +396,9 @@ static struct hotplug_slot *get_slot_from_name(const char *name) | |||
|  * @owner: caller module owner | ||||
|  * @mod_name: caller module name | ||||
|  * | ||||
|  * Registers a hotplug slot with the pci hotplug subsystem, which will allow | ||||
|  * userspace interaction to the slot. | ||||
|  * Prepares a hotplug slot for in-kernel use and immediately publishes it to | ||||
|  * user space in one go.  Drivers may alternatively carry out the two steps | ||||
|  * separately by invoking pci_hp_initialize() and pci_hp_add(). | ||||
|  * | ||||
|  * Returns 0 if successful, anything else for an error. | ||||
|  */ | ||||
|  | @ -406,45 +407,91 @@ int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus, | |||
| 		      struct module *owner, const char *mod_name) | ||||
| { | ||||
| 	int result; | ||||
| 
 | ||||
| 	result = __pci_hp_initialize(slot, bus, devnr, name, owner, mod_name); | ||||
| 	if (result) | ||||
| 		return result; | ||||
| 
 | ||||
| 	result = pci_hp_add(slot); | ||||
| 	if (result) | ||||
| 		pci_hp_destroy(slot); | ||||
| 
 | ||||
| 	return result; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(__pci_hp_register); | ||||
| 
 | ||||
| /**
 | ||||
|  * __pci_hp_initialize - prepare hotplug slot for in-kernel use | ||||
|  * @slot: pointer to the &struct hotplug_slot to initialize | ||||
|  * @bus: bus this slot is on | ||||
|  * @devnr: slot number | ||||
|  * @name: name registered with kobject core | ||||
|  * @owner: caller module owner | ||||
|  * @mod_name: caller module name | ||||
|  * | ||||
|  * Allocate and fill in a PCI slot for use by a hotplug driver.  Once this has | ||||
|  * been called, the driver may invoke hotplug_slot_name() to get the slot's | ||||
|  * unique name.  The driver must be prepared to handle a ->reset_slot callback | ||||
|  * from this point on. | ||||
|  * | ||||
|  * Returns 0 on success or a negative int on error. | ||||
|  */ | ||||
| int __pci_hp_initialize(struct hotplug_slot *slot, struct pci_bus *bus, | ||||
| 			int devnr, const char *name, struct module *owner, | ||||
| 			const char *mod_name) | ||||
| { | ||||
| 	struct pci_slot *pci_slot; | ||||
| 
 | ||||
| 	if (slot == NULL) | ||||
| 		return -ENODEV; | ||||
| 	if ((slot->info == NULL) || (slot->ops == NULL)) | ||||
| 		return -EINVAL; | ||||
| 	if (slot->release == NULL) { | ||||
| 		dbg("Why are you trying to register a hotplug slot without a proper release function?\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	slot->ops->owner = owner; | ||||
| 	slot->ops->mod_name = mod_name; | ||||
| 
 | ||||
| 	mutex_lock(&pci_hp_mutex); | ||||
| 	/*
 | ||||
| 	 * No problems if we call this interface from both ACPI_PCI_SLOT | ||||
| 	 * driver and call it here again. If we've already created the | ||||
| 	 * pci_slot, the interface will simply bump the refcount. | ||||
| 	 */ | ||||
| 	pci_slot = pci_create_slot(bus, devnr, name, slot); | ||||
| 	if (IS_ERR(pci_slot)) { | ||||
| 		result = PTR_ERR(pci_slot); | ||||
| 		goto out; | ||||
| 	} | ||||
| 	if (IS_ERR(pci_slot)) | ||||
| 		return PTR_ERR(pci_slot); | ||||
| 
 | ||||
| 	slot->pci_slot = pci_slot; | ||||
| 	pci_slot->hotplug = slot; | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(__pci_hp_initialize); | ||||
| 
 | ||||
| 	list_add(&slot->slot_list, &pci_hotplug_slot_list); | ||||
| /**
 | ||||
|  * pci_hp_add - publish hotplug slot to user space | ||||
|  * @slot: pointer to the &struct hotplug_slot to publish | ||||
|  * | ||||
|  * Make a hotplug slot's sysfs interface available and inform user space of its | ||||
|  * addition by sending a uevent.  The hotplug driver must be prepared to handle | ||||
|  * all &struct hotplug_slot_ops callbacks from this point on. | ||||
|  * | ||||
|  * Returns 0 on success or a negative int on error. | ||||
|  */ | ||||
| int pci_hp_add(struct hotplug_slot *slot) | ||||
| { | ||||
| 	struct pci_slot *pci_slot = slot->pci_slot; | ||||
| 	int result; | ||||
| 
 | ||||
| 	result = fs_add_slot(pci_slot); | ||||
| 	kobject_uevent(&pci_slot->kobj, KOBJ_ADD); | ||||
| 	dbg("Added slot %s to the list\n", name); | ||||
| out: | ||||
| 	mutex_unlock(&pci_hp_mutex); | ||||
| 	if (result) | ||||
| 		return result; | ||||
| 
 | ||||
| 	kobject_uevent(&pci_slot->kobj, KOBJ_ADD); | ||||
| 	mutex_lock(&pci_hp_mutex); | ||||
| 	list_add(&slot->slot_list, &pci_hotplug_slot_list); | ||||
| 	mutex_unlock(&pci_hp_mutex); | ||||
| 	dbg("Added slot %s to the list\n", hotplug_slot_name(slot)); | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(__pci_hp_register); | ||||
| EXPORT_SYMBOL_GPL(pci_hp_add); | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem | ||||
|  | @ -455,35 +502,62 @@ EXPORT_SYMBOL_GPL(__pci_hp_register); | |||
|  * | ||||
|  * Returns 0 if successful, anything else for an error. | ||||
|  */ | ||||
| int pci_hp_deregister(struct hotplug_slot *slot) | ||||
| void pci_hp_deregister(struct hotplug_slot *slot) | ||||
| { | ||||
| 	pci_hp_del(slot); | ||||
| 	pci_hp_destroy(slot); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(pci_hp_deregister); | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_hp_del - unpublish hotplug slot from user space | ||||
|  * @slot: pointer to the &struct hotplug_slot to unpublish | ||||
|  * | ||||
|  * Remove a hotplug slot's sysfs interface. | ||||
|  * | ||||
|  * Returns 0 on success or a negative int on error. | ||||
|  */ | ||||
| void pci_hp_del(struct hotplug_slot *slot) | ||||
| { | ||||
| 	struct hotplug_slot *temp; | ||||
| 	struct pci_slot *pci_slot; | ||||
| 
 | ||||
| 	if (!slot) | ||||
| 		return -ENODEV; | ||||
| 	if (WARN_ON(!slot)) | ||||
| 		return; | ||||
| 
 | ||||
| 	mutex_lock(&pci_hp_mutex); | ||||
| 	temp = get_slot_from_name(hotplug_slot_name(slot)); | ||||
| 	if (temp != slot) { | ||||
| 	if (WARN_ON(temp != slot)) { | ||||
| 		mutex_unlock(&pci_hp_mutex); | ||||
| 		return -ENODEV; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	list_del(&slot->slot_list); | ||||
| 
 | ||||
| 	pci_slot = slot->pci_slot; | ||||
| 	fs_remove_slot(pci_slot); | ||||
| 	mutex_unlock(&pci_hp_mutex); | ||||
| 	dbg("Removed slot %s from the list\n", hotplug_slot_name(slot)); | ||||
| 	fs_remove_slot(slot->pci_slot); | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(pci_hp_del); | ||||
| 
 | ||||
| 	slot->release(slot); | ||||
| /**
 | ||||
|  * pci_hp_destroy - remove hotplug slot from in-kernel use | ||||
|  * @slot: pointer to the &struct hotplug_slot to destroy | ||||
|  * | ||||
|  * Destroy a PCI slot used by a hotplug driver.  Once this has been called, | ||||
|  * the driver may no longer invoke hotplug_slot_name() to get the slot's | ||||
|  * unique name.  The driver no longer needs to handle a ->reset_slot callback | ||||
|  * from this point on. | ||||
|  * | ||||
|  * Returns 0 on success or a negative int on error. | ||||
|  */ | ||||
| void pci_hp_destroy(struct hotplug_slot *slot) | ||||
| { | ||||
| 	struct pci_slot *pci_slot = slot->pci_slot; | ||||
| 
 | ||||
| 	slot->pci_slot = NULL; | ||||
| 	pci_slot->hotplug = NULL; | ||||
| 	pci_destroy_slot(pci_slot); | ||||
| 	mutex_unlock(&pci_hp_mutex); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(pci_hp_deregister); | ||||
| EXPORT_SYMBOL_GPL(pci_hp_destroy); | ||||
| 
 | ||||
| /**
 | ||||
|  * pci_hp_change_slot_info - changes the slot's information structure in the core | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ | |||
| #include <linux/delay.h> | ||||
| #include <linux/sched/signal.h>		/* signal_pending() */ | ||||
| #include <linux/mutex.h> | ||||
| #include <linux/rwsem.h> | ||||
| #include <linux/workqueue.h> | ||||
| 
 | ||||
| #include "../pcie/portdrv.h" | ||||
|  | @ -57,49 +58,111 @@ do {									\ | |||
| 	dev_warn(&ctrl->pcie->device, format, ## arg) | ||||
| 
 | ||||
| #define SLOT_NAME_SIZE 10 | ||||
| 
 | ||||
| /**
 | ||||
|  * struct slot - PCIe hotplug slot | ||||
|  * @state: current state machine position | ||||
|  * @ctrl: pointer to the slot's controller structure | ||||
|  * @hotplug_slot: pointer to the structure registered with the PCI hotplug core | ||||
|  * @work: work item to turn the slot on or off after 5 seconds in response to | ||||
|  *	an Attention Button press | ||||
|  * @lock: protects reads and writes of @state; | ||||
|  *	protects scheduling, execution and cancellation of @work | ||||
|  */ | ||||
| struct slot { | ||||
| 	u8 state; | ||||
| 	struct controller *ctrl; | ||||
| 	struct hotplug_slot *hotplug_slot; | ||||
| 	struct delayed_work work;	/* work for button event */ | ||||
| 	struct delayed_work work; | ||||
| 	struct mutex lock; | ||||
| 	struct mutex hotplug_lock; | ||||
| 	struct workqueue_struct *wq; | ||||
| }; | ||||
| 
 | ||||
| struct event_info { | ||||
| 	u32 event_type; | ||||
| 	struct slot *p_slot; | ||||
| 	struct work_struct work; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct controller - PCIe hotplug controller | ||||
|  * @ctrl_lock: serializes writes to the Slot Control register | ||||
|  * @pcie: pointer to the controller's PCIe port service device | ||||
|  * @reset_lock: prevents access to the Data Link Layer Link Active bit in the | ||||
|  *	Link Status register and to the Presence Detect State bit in the Slot | ||||
|  *	Status register during a slot reset which may cause them to flap | ||||
|  * @slot: pointer to the controller's slot structure | ||||
|  * @queue: wait queue to wake up on reception of a Command Completed event, | ||||
|  *	used for synchronous writes to the Slot Control register | ||||
|  * @slot_cap: cached copy of the Slot Capabilities register | ||||
|  * @slot_ctrl: cached copy of the Slot Control register | ||||
|  * @poll_thread: thread to poll for slot events if no IRQ is available, | ||||
|  *	enabled with pciehp_poll_mode module parameter | ||||
|  * @cmd_started: jiffies when the Slot Control register was last written; | ||||
|  *	the next write is allowed 1 second later, absent a Command Completed | ||||
|  *	interrupt (PCIe r4.0, sec 6.7.3.2) | ||||
|  * @cmd_busy: flag set on Slot Control register write, cleared by IRQ handler | ||||
|  *	on reception of a Command Completed event | ||||
|  * @link_active_reporting: cached copy of Data Link Layer Link Active Reporting | ||||
|  *	Capable bit in Link Capabilities register; if this bit is zero, the | ||||
|  *	Data Link Layer Link Active bit in the Link Status register will never | ||||
|  *	be set and the driver is thus confined to wait 1 second before assuming | ||||
|  *	the link to a hotplugged device is up and accessing it | ||||
|  * @notification_enabled: whether the IRQ was requested successfully | ||||
|  * @power_fault_detected: whether a power fault was detected by the hardware | ||||
|  *	that has not yet been cleared by the user | ||||
|  * @pending_events: used by the IRQ handler to save events retrieved from the | ||||
|  *	Slot Status register for later consumption by the IRQ thread | ||||
|  * @request_result: result of last user request submitted to the IRQ thread | ||||
|  * @requester: wait queue to wake up on completion of user request, | ||||
|  *	used for synchronous slot enable/disable request via sysfs | ||||
|  */ | ||||
| struct controller { | ||||
| 	struct mutex ctrl_lock;		/* controller lock */ | ||||
| 	struct pcie_device *pcie;	/* PCI Express port service */ | ||||
| 	struct mutex ctrl_lock; | ||||
| 	struct pcie_device *pcie; | ||||
| 	struct rw_semaphore reset_lock; | ||||
| 	struct slot *slot; | ||||
| 	wait_queue_head_t queue;	/* sleep & wake process */ | ||||
| 	wait_queue_head_t queue; | ||||
| 	u32 slot_cap; | ||||
| 	u16 slot_ctrl; | ||||
| 	struct timer_list poll_timer; | ||||
| 	struct task_struct *poll_thread; | ||||
| 	unsigned long cmd_started;	/* jiffies */ | ||||
| 	unsigned int cmd_busy:1; | ||||
| 	unsigned int link_active_reporting:1; | ||||
| 	unsigned int notification_enabled:1; | ||||
| 	unsigned int power_fault_detected; | ||||
| 	atomic_t pending_events; | ||||
| 	int request_result; | ||||
| 	wait_queue_head_t requester; | ||||
| }; | ||||
| 
 | ||||
| #define INT_PRESENCE_ON			1 | ||||
| #define INT_PRESENCE_OFF		2 | ||||
| #define INT_POWER_FAULT			3 | ||||
| #define INT_BUTTON_PRESS		4 | ||||
| #define INT_LINK_UP			5 | ||||
| #define INT_LINK_DOWN			6 | ||||
| 
 | ||||
| #define STATIC_STATE			0 | ||||
| /**
 | ||||
|  * DOC: Slot state | ||||
|  * | ||||
|  * @OFF_STATE: slot is powered off, no subordinate devices are enumerated | ||||
|  * @BLINKINGON_STATE: slot will be powered on after the 5 second delay, | ||||
|  *	green led is blinking | ||||
|  * @BLINKINGOFF_STATE: slot will be powered off after the 5 second delay, | ||||
|  *	green led is blinking | ||||
|  * @POWERON_STATE: slot is currently powering on | ||||
|  * @POWEROFF_STATE: slot is currently powering off | ||||
|  * @ON_STATE: slot is powered on, subordinate devices have been enumerated | ||||
|  */ | ||||
| #define OFF_STATE			0 | ||||
| #define BLINKINGON_STATE		1 | ||||
| #define BLINKINGOFF_STATE		2 | ||||
| #define POWERON_STATE			3 | ||||
| #define POWEROFF_STATE			4 | ||||
| #define ON_STATE			5 | ||||
| 
 | ||||
| /**
 | ||||
|  * DOC: Flags to request an action from the IRQ thread | ||||
|  * | ||||
|  * These are stored together with events read from the Slot Status register, | ||||
|  * hence must be greater than its 16-bit width. | ||||
|  * | ||||
|  * %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or | ||||
|  *	an Attention Button press after the 5 second delay | ||||
|  * %RERUN_ISR: Used by the IRQ handler to inform the IRQ thread that the | ||||
|  *	hotplug port was inaccessible when the interrupt occurred, requiring | ||||
|  *	that the IRQ handler is rerun by the IRQ thread after it has made the | ||||
|  *	hotplug port accessible by runtime resuming its parents to D0 | ||||
|  */ | ||||
| #define DISABLE_SLOT		(1 << 16) | ||||
| #define RERUN_ISR		(1 << 17) | ||||
| 
 | ||||
| #define ATTN_BUTTN(ctrl)	((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP) | ||||
| #define POWER_CTRL(ctrl)	((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP) | ||||
|  | @ -113,15 +176,17 @@ struct controller { | |||
| 
 | ||||
| int pciehp_sysfs_enable_slot(struct slot *slot); | ||||
| int pciehp_sysfs_disable_slot(struct slot *slot); | ||||
| void pciehp_queue_interrupt_event(struct slot *slot, u32 event_type); | ||||
| void pciehp_request(struct controller *ctrl, int action); | ||||
| void pciehp_handle_button_press(struct slot *slot); | ||||
| void pciehp_handle_disable_request(struct slot *slot); | ||||
| void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events); | ||||
| int pciehp_configure_device(struct slot *p_slot); | ||||
| int pciehp_unconfigure_device(struct slot *p_slot); | ||||
| void pciehp_unconfigure_device(struct slot *p_slot); | ||||
| 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); | ||||
| void pcie_reenable_notification(struct controller *ctrl); | ||||
| void pcie_shutdown_notification(struct controller *ctrl); | ||||
| void pcie_clear_hotplug_events(struct controller *ctrl); | ||||
| int pciehp_power_on_slot(struct slot *slot); | ||||
| void pciehp_power_off_slot(struct slot *slot); | ||||
| void pciehp_get_power_status(struct slot *slot, u8 *status); | ||||
|  |  | |||
|  | @ -26,11 +26,12 @@ | |||
| #include <linux/interrupt.h> | ||||
| #include <linux/time.h> | ||||
| 
 | ||||
| #include "../pci.h" | ||||
| 
 | ||||
| /* Global variables */ | ||||
| bool pciehp_debug; | ||||
| bool pciehp_poll_mode; | ||||
| int pciehp_poll_time; | ||||
| static bool pciehp_force; | ||||
| 
 | ||||
| /*
 | ||||
|  * not really modular, but the easiest way to keep compat with existing | ||||
|  | @ -39,11 +40,9 @@ static bool pciehp_force; | |||
| module_param(pciehp_debug, bool, 0644); | ||||
| module_param(pciehp_poll_mode, bool, 0644); | ||||
| module_param(pciehp_poll_time, int, 0644); | ||||
| module_param(pciehp_force, bool, 0644); | ||||
| MODULE_PARM_DESC(pciehp_debug, "Debugging mode enabled or not"); | ||||
| MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not"); | ||||
| MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds"); | ||||
| MODULE_PARM_DESC(pciehp_force, "Force pciehp, even if OSHP is missing"); | ||||
| 
 | ||||
| #define PCIE_MODULE_NAME "pciehp" | ||||
| 
 | ||||
|  | @ -56,17 +55,6 @@ static int get_latch_status(struct hotplug_slot *slot, u8 *value); | |||
| static int get_adapter_status(struct hotplug_slot *slot, u8 *value); | ||||
| static int reset_slot(struct hotplug_slot *slot, int probe); | ||||
| 
 | ||||
| /**
 | ||||
|  * release_slot - free up the memory used by a slot | ||||
|  * @hotplug_slot: slot to free | ||||
|  */ | ||||
| static void release_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	kfree(hotplug_slot->ops); | ||||
| 	kfree(hotplug_slot->info); | ||||
| 	kfree(hotplug_slot); | ||||
| } | ||||
| 
 | ||||
| static int init_slot(struct controller *ctrl) | ||||
| { | ||||
| 	struct slot *slot = ctrl->slot; | ||||
|  | @ -107,15 +95,14 @@ static int init_slot(struct controller *ctrl) | |||
| 	/* register this slot with the hotplug pci core */ | ||||
| 	hotplug->info = info; | ||||
| 	hotplug->private = slot; | ||||
| 	hotplug->release = &release_slot; | ||||
| 	hotplug->ops = ops; | ||||
| 	slot->hotplug_slot = hotplug; | ||||
| 	snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl)); | ||||
| 
 | ||||
| 	retval = pci_hp_register(hotplug, | ||||
| 	retval = pci_hp_initialize(hotplug, | ||||
| 				   ctrl->pcie->port->subordinate, 0, name); | ||||
| 	if (retval) | ||||
| 		ctrl_err(ctrl, "pci_hp_register failed: error %d\n", retval); | ||||
| 		ctrl_err(ctrl, "pci_hp_initialize failed: error %d\n", retval); | ||||
| out: | ||||
| 	if (retval) { | ||||
| 		kfree(ops); | ||||
|  | @ -127,7 +114,12 @@ out: | |||
| 
 | ||||
| static void cleanup_slot(struct controller *ctrl) | ||||
| { | ||||
| 	pci_hp_deregister(ctrl->slot->hotplug_slot); | ||||
| 	struct hotplug_slot *hotplug_slot = ctrl->slot->hotplug_slot; | ||||
| 
 | ||||
| 	pci_hp_destroy(hotplug_slot); | ||||
| 	kfree(hotplug_slot->ops); | ||||
| 	kfree(hotplug_slot->info); | ||||
| 	kfree(hotplug_slot); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  | @ -136,8 +128,11 @@ static void cleanup_slot(struct controller *ctrl) | |||
| static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	struct pci_dev *pdev = slot->ctrl->pcie->port; | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 	pciehp_set_attention_status(slot, status); | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -160,8 +155,11 @@ static int disable_slot(struct hotplug_slot *hotplug_slot) | |||
| static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	struct pci_dev *pdev = slot->ctrl->pcie->port; | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 	pciehp_get_power_status(slot, value); | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -176,16 +174,22 @@ static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) | |||
| static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	struct pci_dev *pdev = slot->ctrl->pcie->port; | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 	pciehp_get_latch_status(slot, value); | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	struct pci_dev *pdev = slot->ctrl->pcie->port; | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 	pciehp_get_adapter_status(slot, value); | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -196,12 +200,40 @@ static int reset_slot(struct hotplug_slot *hotplug_slot, int probe) | |||
| 	return pciehp_reset_slot(slot, probe); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * pciehp_check_presence() - synthesize event if presence has changed | ||||
|  * | ||||
|  * On probe and resume, an explicit presence check is necessary to bring up an | ||||
|  * occupied slot or bring down an unoccupied slot.  This can't be triggered by | ||||
|  * events in the Slot Status register, they may be stale and are therefore | ||||
|  * cleared.  Secondly, sending an interrupt for "events that occur while | ||||
|  * interrupt generation is disabled [when] interrupt generation is subsequently | ||||
|  * enabled" is optional per PCIe r4.0, sec 6.7.3.4. | ||||
|  */ | ||||
| static void pciehp_check_presence(struct controller *ctrl) | ||||
| { | ||||
| 	struct slot *slot = ctrl->slot; | ||||
| 	u8 occupied; | ||||
| 
 | ||||
| 	down_read(&ctrl->reset_lock); | ||||
| 	mutex_lock(&slot->lock); | ||||
| 
 | ||||
| 	pciehp_get_adapter_status(slot, &occupied); | ||||
| 	if ((occupied && (slot->state == OFF_STATE || | ||||
| 			  slot->state == BLINKINGON_STATE)) || | ||||
| 	    (!occupied && (slot->state == ON_STATE || | ||||
| 			   slot->state == BLINKINGOFF_STATE))) | ||||
| 		pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); | ||||
| 
 | ||||
| 	mutex_unlock(&slot->lock); | ||||
| 	up_read(&ctrl->reset_lock); | ||||
| } | ||||
| 
 | ||||
| static int pciehp_probe(struct pcie_device *dev) | ||||
| { | ||||
| 	int rc; | ||||
| 	struct controller *ctrl; | ||||
| 	struct slot *slot; | ||||
| 	u8 occupied, poweron; | ||||
| 
 | ||||
| 	/* If this is not a "hotplug" service, we have no business here. */ | ||||
| 	if (dev->service != PCIE_PORT_SERVICE_HP) | ||||
|  | @ -238,21 +270,20 @@ static int pciehp_probe(struct pcie_device *dev) | |||
| 		goto err_out_free_ctrl_slot; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Check if slot is occupied */ | ||||
| 	/* Publish to user space */ | ||||
| 	slot = ctrl->slot; | ||||
| 	pciehp_get_adapter_status(slot, &occupied); | ||||
| 	pciehp_get_power_status(slot, &poweron); | ||||
| 	if (occupied && pciehp_force) { | ||||
| 		mutex_lock(&slot->hotplug_lock); | ||||
| 		pciehp_enable_slot(slot); | ||||
| 		mutex_unlock(&slot->hotplug_lock); | ||||
| 	rc = pci_hp_add(slot->hotplug_slot); | ||||
| 	if (rc) { | ||||
| 		ctrl_err(ctrl, "Publication to user space failed (%d)\n", rc); | ||||
| 		goto err_out_shutdown_notification; | ||||
| 	} | ||||
| 	/* If empty slot's power status is on, turn power off */ | ||||
| 	if (!occupied && poweron && POWER_CTRL(ctrl)) | ||||
| 		pciehp_power_off_slot(slot); | ||||
| 
 | ||||
| 	pciehp_check_presence(ctrl); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err_out_shutdown_notification: | ||||
| 	pcie_shutdown_notification(ctrl); | ||||
| err_out_free_ctrl_slot: | ||||
| 	cleanup_slot(ctrl); | ||||
| err_out_release_ctlr: | ||||
|  | @ -264,6 +295,8 @@ static void pciehp_remove(struct pcie_device *dev) | |||
| { | ||||
| 	struct controller *ctrl = get_service_data(dev); | ||||
| 
 | ||||
| 	pci_hp_del(ctrl->slot->hotplug_slot); | ||||
| 	pcie_shutdown_notification(ctrl); | ||||
| 	cleanup_slot(ctrl); | ||||
| 	pciehp_release_ctrl(ctrl); | ||||
| } | ||||
|  | @ -274,27 +307,28 @@ static int pciehp_suspend(struct pcie_device *dev) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int pciehp_resume_noirq(struct pcie_device *dev) | ||||
| { | ||||
| 	struct controller *ctrl = get_service_data(dev); | ||||
| 	struct slot *slot = ctrl->slot; | ||||
| 
 | ||||
| 	/* pci_restore_state() just wrote to the Slot Control register */ | ||||
| 	ctrl->cmd_started = jiffies; | ||||
| 	ctrl->cmd_busy = true; | ||||
| 
 | ||||
| 	/* clear spurious events from rediscovery of inserted card */ | ||||
| 	if (slot->state == ON_STATE || slot->state == BLINKINGOFF_STATE) | ||||
| 		pcie_clear_hotplug_events(ctrl); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int pciehp_resume(struct pcie_device *dev) | ||||
| { | ||||
| 	struct controller *ctrl; | ||||
| 	struct slot *slot; | ||||
| 	u8 status; | ||||
| 	struct controller *ctrl = get_service_data(dev); | ||||
| 
 | ||||
| 	ctrl = get_service_data(dev); | ||||
| 	pciehp_check_presence(ctrl); | ||||
| 
 | ||||
| 	/* reinitialize the chipset's event detection logic */ | ||||
| 	pcie_reenable_notification(ctrl); | ||||
| 
 | ||||
| 	slot = ctrl->slot; | ||||
| 
 | ||||
| 	/* Check if slot is occupied */ | ||||
| 	pciehp_get_adapter_status(slot, &status); | ||||
| 	mutex_lock(&slot->hotplug_lock); | ||||
| 	if (status) | ||||
| 		pciehp_enable_slot(slot); | ||||
| 	else | ||||
| 		pciehp_disable_slot(slot); | ||||
| 	mutex_unlock(&slot->hotplug_lock); | ||||
| 	return 0; | ||||
| } | ||||
| #endif /* PM */ | ||||
|  | @ -309,6 +343,7 @@ static struct pcie_port_service_driver hpdriver_portdrv = { | |||
| 
 | ||||
| #ifdef	CONFIG_PM | ||||
| 	.suspend	= pciehp_suspend, | ||||
| 	.resume_noirq	= pciehp_resume_noirq, | ||||
| 	.resume		= pciehp_resume, | ||||
| #endif	/* PM */ | ||||
| }; | ||||
|  |  | |||
|  | @ -17,28 +17,11 @@ | |||
| #include <linux/kernel.h> | ||||
| #include <linux/types.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| #include <linux/pci.h> | ||||
| #include "../pci.h" | ||||
| #include "pciehp.h" | ||||
| 
 | ||||
| static void interrupt_event_handler(struct work_struct *work); | ||||
| 
 | ||||
| void pciehp_queue_interrupt_event(struct slot *p_slot, u32 event_type) | ||||
| { | ||||
| 	struct event_info *info; | ||||
| 
 | ||||
| 	info = kmalloc(sizeof(*info), GFP_ATOMIC); | ||||
| 	if (!info) { | ||||
| 		ctrl_err(p_slot->ctrl, "dropped event %d (ENOMEM)\n", event_type); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	INIT_WORK(&info->work, interrupt_event_handler); | ||||
| 	info->event_type = event_type; | ||||
| 	info->p_slot = p_slot; | ||||
| 	queue_work(p_slot->wq, &info->work); | ||||
| } | ||||
| 
 | ||||
| /* The following routines constitute the bulk of the
 | ||||
|    hotplug controller logic | ||||
|  */ | ||||
|  | @ -119,14 +102,11 @@ err_exit: | |||
|  * remove_board - Turns off slot and LEDs | ||||
|  * @p_slot: slot where board is being removed | ||||
|  */ | ||||
| static int remove_board(struct slot *p_slot) | ||||
| static void remove_board(struct slot *p_slot) | ||||
| { | ||||
| 	int retval; | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
| 
 | ||||
| 	retval = pciehp_unconfigure_device(p_slot); | ||||
| 	if (retval) | ||||
| 		return retval; | ||||
| 	pciehp_unconfigure_device(p_slot); | ||||
| 
 | ||||
| 	if (POWER_CTRL(ctrl)) { | ||||
| 		pciehp_power_off_slot(p_slot); | ||||
|  | @ -141,86 +121,30 @@ static int remove_board(struct slot *p_slot) | |||
| 
 | ||||
| 	/* turn off Green LED */ | ||||
| 	pciehp_green_led_off(p_slot); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| struct power_work_info { | ||||
| 	struct slot *p_slot; | ||||
| 	struct work_struct work; | ||||
| 	unsigned int req; | ||||
| #define DISABLE_REQ 0 | ||||
| #define ENABLE_REQ  1 | ||||
| }; | ||||
| static int pciehp_enable_slot(struct slot *slot); | ||||
| static int pciehp_disable_slot(struct slot *slot); | ||||
| 
 | ||||
| /**
 | ||||
|  * pciehp_power_thread - handle pushbutton events | ||||
|  * @work: &struct work_struct describing work to be done | ||||
|  * | ||||
|  * Scheduled procedure to handle blocking stuff for the pushbuttons. | ||||
|  * Handles all pending events and exits. | ||||
|  */ | ||||
| static void pciehp_power_thread(struct work_struct *work) | ||||
| void pciehp_request(struct controller *ctrl, int action) | ||||
| { | ||||
| 	struct power_work_info *info = | ||||
| 		container_of(work, struct power_work_info, work); | ||||
| 	struct slot *p_slot = info->p_slot; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	switch (info->req) { | ||||
| 	case DISABLE_REQ: | ||||
| 		mutex_lock(&p_slot->hotplug_lock); | ||||
| 		pciehp_disable_slot(p_slot); | ||||
| 		mutex_unlock(&p_slot->hotplug_lock); | ||||
| 		mutex_lock(&p_slot->lock); | ||||
| 		p_slot->state = STATIC_STATE; | ||||
| 		mutex_unlock(&p_slot->lock); | ||||
| 		break; | ||||
| 	case ENABLE_REQ: | ||||
| 		mutex_lock(&p_slot->hotplug_lock); | ||||
| 		ret = pciehp_enable_slot(p_slot); | ||||
| 		mutex_unlock(&p_slot->hotplug_lock); | ||||
| 		if (ret) | ||||
| 			pciehp_green_led_off(p_slot); | ||||
| 		mutex_lock(&p_slot->lock); | ||||
| 		p_slot->state = STATIC_STATE; | ||||
| 		mutex_unlock(&p_slot->lock); | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	kfree(info); | ||||
| } | ||||
| 
 | ||||
| static void pciehp_queue_power_work(struct slot *p_slot, int req) | ||||
| { | ||||
| 	struct power_work_info *info; | ||||
| 
 | ||||
| 	p_slot->state = (req == ENABLE_REQ) ? POWERON_STATE : POWEROFF_STATE; | ||||
| 
 | ||||
| 	info = kmalloc(sizeof(*info), GFP_KERNEL); | ||||
| 	if (!info) { | ||||
| 		ctrl_err(p_slot->ctrl, "no memory to queue %s request\n", | ||||
| 			 (req == ENABLE_REQ) ? "poweron" : "poweroff"); | ||||
| 		return; | ||||
| 	} | ||||
| 	info->p_slot = p_slot; | ||||
| 	INIT_WORK(&info->work, pciehp_power_thread); | ||||
| 	info->req = req; | ||||
| 	queue_work(p_slot->wq, &info->work); | ||||
| 	atomic_or(action, &ctrl->pending_events); | ||||
| 	if (!pciehp_poll_mode) | ||||
| 		irq_wake_thread(ctrl->pcie->irq, ctrl); | ||||
| } | ||||
| 
 | ||||
| void pciehp_queue_pushbutton_work(struct work_struct *work) | ||||
| { | ||||
| 	struct slot *p_slot = container_of(work, struct slot, work.work); | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
| 
 | ||||
| 	mutex_lock(&p_slot->lock); | ||||
| 	switch (p_slot->state) { | ||||
| 	case BLINKINGOFF_STATE: | ||||
| 		pciehp_queue_power_work(p_slot, DISABLE_REQ); | ||||
| 		pciehp_request(ctrl, DISABLE_SLOT); | ||||
| 		break; | ||||
| 	case BLINKINGON_STATE: | ||||
| 		pciehp_queue_power_work(p_slot, ENABLE_REQ); | ||||
| 		pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
|  | @ -228,18 +152,15 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) | |||
| 	mutex_unlock(&p_slot->lock); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Note: This function must be called with slot->lock held | ||||
|  */ | ||||
| static void handle_button_press_event(struct slot *p_slot) | ||||
| void pciehp_handle_button_press(struct slot *p_slot) | ||||
| { | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
| 	u8 getstatus; | ||||
| 
 | ||||
| 	mutex_lock(&p_slot->lock); | ||||
| 	switch (p_slot->state) { | ||||
| 	case STATIC_STATE: | ||||
| 		pciehp_get_power_status(p_slot, &getstatus); | ||||
| 		if (getstatus) { | ||||
| 	case OFF_STATE: | ||||
| 	case ON_STATE: | ||||
| 		if (p_slot->state == ON_STATE) { | ||||
| 			p_slot->state = BLINKINGOFF_STATE; | ||||
| 			ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", | ||||
| 				  slot_name(p_slot)); | ||||
|  | @ -251,7 +172,7 @@ static void handle_button_press_event(struct slot *p_slot) | |||
| 		/* blink green LED and turn off amber */ | ||||
| 		pciehp_green_led_blink(p_slot); | ||||
| 		pciehp_set_attention_status(p_slot, 0); | ||||
| 		queue_delayed_work(p_slot->wq, &p_slot->work, 5*HZ); | ||||
| 		schedule_delayed_work(&p_slot->work, 5 * HZ); | ||||
| 		break; | ||||
| 	case BLINKINGOFF_STATE: | ||||
| 	case BLINKINGON_STATE: | ||||
|  | @ -262,118 +183,104 @@ static void handle_button_press_event(struct slot *p_slot) | |||
| 		 */ | ||||
| 		ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(p_slot)); | ||||
| 		cancel_delayed_work(&p_slot->work); | ||||
| 		if (p_slot->state == BLINKINGOFF_STATE) | ||||
| 		if (p_slot->state == BLINKINGOFF_STATE) { | ||||
| 			p_slot->state = ON_STATE; | ||||
| 			pciehp_green_led_on(p_slot); | ||||
| 		else | ||||
| 		} else { | ||||
| 			p_slot->state = OFF_STATE; | ||||
| 			pciehp_green_led_off(p_slot); | ||||
| 		} | ||||
| 		pciehp_set_attention_status(p_slot, 0); | ||||
| 		ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", | ||||
| 			  slot_name(p_slot)); | ||||
| 		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 | ||||
| 		 */ | ||||
| 		ctrl_info(ctrl, "Slot(%s): Button ignored\n", | ||||
| 			  slot_name(p_slot)); | ||||
| 		break; | ||||
| 	default: | ||||
| 		ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", | ||||
| 			 slot_name(p_slot), p_slot->state); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Note: This function must be called with slot->lock held | ||||
|  */ | ||||
| static void handle_link_event(struct slot *p_slot, u32 event) | ||||
| { | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
| 
 | ||||
| 	switch (p_slot->state) { | ||||
| 	case BLINKINGON_STATE: | ||||
| 	case BLINKINGOFF_STATE: | ||||
| 		cancel_delayed_work(&p_slot->work); | ||||
| 		/* Fall through */ | ||||
| 	case STATIC_STATE: | ||||
| 		pciehp_queue_power_work(p_slot, event == INT_LINK_UP ? | ||||
| 					ENABLE_REQ : DISABLE_REQ); | ||||
| 		break; | ||||
| 	case POWERON_STATE: | ||||
| 		if (event == INT_LINK_UP) { | ||||
| 			ctrl_info(ctrl, "Slot(%s): Link Up event ignored; already powering on\n", | ||||
| 				  slot_name(p_slot)); | ||||
| 		} else { | ||||
| 			ctrl_info(ctrl, "Slot(%s): Link Down event queued; currently getting powered on\n", | ||||
| 				  slot_name(p_slot)); | ||||
| 			pciehp_queue_power_work(p_slot, DISABLE_REQ); | ||||
| 		} | ||||
| 		break; | ||||
| 	case POWEROFF_STATE: | ||||
| 		if (event == INT_LINK_UP) { | ||||
| 			ctrl_info(ctrl, "Slot(%s): Link Up event queued; currently getting powered off\n", | ||||
| 				  slot_name(p_slot)); | ||||
| 			pciehp_queue_power_work(p_slot, ENABLE_REQ); | ||||
| 		} else { | ||||
| 			ctrl_info(ctrl, "Slot(%s): Link Down event ignored; already powering off\n", | ||||
| 				  slot_name(p_slot)); | ||||
| 		} | ||||
| 		break; | ||||
| 	default: | ||||
| 		ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", | ||||
| 			 slot_name(p_slot), p_slot->state); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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)) | ||||
| 			break; | ||||
| 		pciehp_set_attention_status(p_slot, 1); | ||||
| 		pciehp_green_led_off(p_slot); | ||||
| 		break; | ||||
| 	case INT_PRESENCE_ON: | ||||
| 		pciehp_queue_power_work(p_slot, ENABLE_REQ); | ||||
| 		break; | ||||
| 	case INT_PRESENCE_OFF: | ||||
| 		/*
 | ||||
| 		 * Regardless of surprise capability, we need to | ||||
| 		 * definitely remove a card that has been pulled out! | ||||
| 		 */ | ||||
| 		pciehp_queue_power_work(p_slot, DISABLE_REQ); | ||||
| 		break; | ||||
| 	case INT_LINK_UP: | ||||
| 	case INT_LINK_DOWN: | ||||
| 		handle_link_event(p_slot, info->event_type); | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 	mutex_unlock(&p_slot->lock); | ||||
| 
 | ||||
| 	kfree(info); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Note: This function must be called with slot->hotplug_lock held | ||||
| void pciehp_handle_disable_request(struct slot *slot) | ||||
| { | ||||
| 	struct controller *ctrl = slot->ctrl; | ||||
| 
 | ||||
| 	mutex_lock(&slot->lock); | ||||
| 	switch (slot->state) { | ||||
| 	case BLINKINGON_STATE: | ||||
| 	case BLINKINGOFF_STATE: | ||||
| 		cancel_delayed_work(&slot->work); | ||||
| 		break; | ||||
| 	} | ||||
| 	slot->state = POWEROFF_STATE; | ||||
| 	mutex_unlock(&slot->lock); | ||||
| 
 | ||||
| 	ctrl->request_result = pciehp_disable_slot(slot); | ||||
| } | ||||
| 
 | ||||
| void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) | ||||
| { | ||||
| 	struct controller *ctrl = slot->ctrl; | ||||
| 	bool link_active; | ||||
| 	u8 present; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If the slot is on and presence or link has changed, turn it off. | ||||
| 	 * Even if it's occupied again, we cannot assume the card is the same. | ||||
| 	 */ | ||||
| int pciehp_enable_slot(struct slot *p_slot) | ||||
| 	mutex_lock(&slot->lock); | ||||
| 	switch (slot->state) { | ||||
| 	case BLINKINGOFF_STATE: | ||||
| 		cancel_delayed_work(&slot->work); | ||||
| 		/* fall through */ | ||||
| 	case ON_STATE: | ||||
| 		slot->state = POWEROFF_STATE; | ||||
| 		mutex_unlock(&slot->lock); | ||||
| 		if (events & PCI_EXP_SLTSTA_DLLSC) | ||||
| 			ctrl_info(ctrl, "Slot(%s): Link Down\n", | ||||
| 				  slot_name(slot)); | ||||
| 		if (events & PCI_EXP_SLTSTA_PDC) | ||||
| 			ctrl_info(ctrl, "Slot(%s): Card not present\n", | ||||
| 				  slot_name(slot)); | ||||
| 		pciehp_disable_slot(slot); | ||||
| 		break; | ||||
| 	default: | ||||
| 		mutex_unlock(&slot->lock); | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Turn the slot on if it's occupied or link is up */ | ||||
| 	mutex_lock(&slot->lock); | ||||
| 	pciehp_get_adapter_status(slot, &present); | ||||
| 	link_active = pciehp_check_link_active(ctrl); | ||||
| 	if (!present && !link_active) { | ||||
| 		mutex_unlock(&slot->lock); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	switch (slot->state) { | ||||
| 	case BLINKINGON_STATE: | ||||
| 		cancel_delayed_work(&slot->work); | ||||
| 		/* fall through */ | ||||
| 	case OFF_STATE: | ||||
| 		slot->state = POWERON_STATE; | ||||
| 		mutex_unlock(&slot->lock); | ||||
| 		if (present) | ||||
| 			ctrl_info(ctrl, "Slot(%s): Card present\n", | ||||
| 				  slot_name(slot)); | ||||
| 		if (link_active) | ||||
| 			ctrl_info(ctrl, "Slot(%s): Link Up\n", | ||||
| 				  slot_name(slot)); | ||||
| 		ctrl->request_result = pciehp_enable_slot(slot); | ||||
| 		break; | ||||
| 	default: | ||||
| 		mutex_unlock(&slot->lock); | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int __pciehp_enable_slot(struct slot *p_slot) | ||||
| { | ||||
| 	u8 getstatus = 0; | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
|  | @ -404,17 +311,29 @@ int pciehp_enable_slot(struct slot *p_slot) | |||
| 	return board_added(p_slot); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Note: This function must be called with slot->hotplug_lock held | ||||
|  */ | ||||
| int pciehp_disable_slot(struct slot *p_slot) | ||||
| static int pciehp_enable_slot(struct slot *slot) | ||||
| { | ||||
| 	struct controller *ctrl = slot->ctrl; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	pm_runtime_get_sync(&ctrl->pcie->port->dev); | ||||
| 	ret = __pciehp_enable_slot(slot); | ||||
| 	if (ret && ATTN_BUTTN(ctrl)) | ||||
| 		pciehp_green_led_off(slot); /* may be blinking */ | ||||
| 	pm_runtime_put(&ctrl->pcie->port->dev); | ||||
| 
 | ||||
| 	mutex_lock(&slot->lock); | ||||
| 	slot->state = ret ? OFF_STATE : ON_STATE; | ||||
| 	mutex_unlock(&slot->lock); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int __pciehp_disable_slot(struct slot *p_slot) | ||||
| { | ||||
| 	u8 getstatus = 0; | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
| 
 | ||||
| 	if (!p_slot->ctrl) | ||||
| 		return 1; | ||||
| 
 | ||||
| 	if (POWER_CTRL(p_slot->ctrl)) { | ||||
| 		pciehp_get_power_status(p_slot, &getstatus); | ||||
| 		if (!getstatus) { | ||||
|  | @ -424,32 +343,50 @@ int pciehp_disable_slot(struct slot *p_slot) | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return remove_board(p_slot); | ||||
| 	remove_board(p_slot); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int pciehp_disable_slot(struct slot *slot) | ||||
| { | ||||
| 	struct controller *ctrl = slot->ctrl; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	pm_runtime_get_sync(&ctrl->pcie->port->dev); | ||||
| 	ret = __pciehp_disable_slot(slot); | ||||
| 	pm_runtime_put(&ctrl->pcie->port->dev); | ||||
| 
 | ||||
| 	mutex_lock(&slot->lock); | ||||
| 	slot->state = OFF_STATE; | ||||
| 	mutex_unlock(&slot->lock); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| int pciehp_sysfs_enable_slot(struct slot *p_slot) | ||||
| { | ||||
| 	int retval = -ENODEV; | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
| 
 | ||||
| 	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; | ||||
| 	case OFF_STATE: | ||||
| 		mutex_unlock(&p_slot->lock); | ||||
| 		mutex_lock(&p_slot->hotplug_lock); | ||||
| 		retval = pciehp_enable_slot(p_slot); | ||||
| 		mutex_unlock(&p_slot->hotplug_lock); | ||||
| 		mutex_lock(&p_slot->lock); | ||||
| 		p_slot->state = STATIC_STATE; | ||||
| 		break; | ||||
| 		/*
 | ||||
| 		 * The IRQ thread becomes a no-op if the user pulls out the | ||||
| 		 * card before the thread wakes up, so initialize to -ENODEV. | ||||
| 		 */ | ||||
| 		ctrl->request_result = -ENODEV; | ||||
| 		pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); | ||||
| 		wait_event(ctrl->requester, | ||||
| 			   !atomic_read(&ctrl->pending_events)); | ||||
| 		return ctrl->request_result; | ||||
| 	case POWERON_STATE: | ||||
| 		ctrl_info(ctrl, "Slot(%s): Already in powering on state\n", | ||||
| 			  slot_name(p_slot)); | ||||
| 		break; | ||||
| 	case BLINKINGOFF_STATE: | ||||
| 	case ON_STATE: | ||||
| 	case POWEROFF_STATE: | ||||
| 		ctrl_info(ctrl, "Slot(%s): Already enabled\n", | ||||
| 			  slot_name(p_slot)); | ||||
|  | @ -461,32 +398,28 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot) | |||
| 	} | ||||
| 	mutex_unlock(&p_slot->lock); | ||||
| 
 | ||||
| 	return retval; | ||||
| 	return -ENODEV; | ||||
| } | ||||
| 
 | ||||
| int pciehp_sysfs_disable_slot(struct slot *p_slot) | ||||
| { | ||||
| 	int retval = -ENODEV; | ||||
| 	struct controller *ctrl = p_slot->ctrl; | ||||
| 
 | ||||
| 	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; | ||||
| 	case ON_STATE: | ||||
| 		mutex_unlock(&p_slot->lock); | ||||
| 		mutex_lock(&p_slot->hotplug_lock); | ||||
| 		retval = pciehp_disable_slot(p_slot); | ||||
| 		mutex_unlock(&p_slot->hotplug_lock); | ||||
| 		mutex_lock(&p_slot->lock); | ||||
| 		p_slot->state = STATIC_STATE; | ||||
| 		break; | ||||
| 		pciehp_request(ctrl, DISABLE_SLOT); | ||||
| 		wait_event(ctrl->requester, | ||||
| 			   !atomic_read(&ctrl->pending_events)); | ||||
| 		return ctrl->request_result; | ||||
| 	case POWEROFF_STATE: | ||||
| 		ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", | ||||
| 			  slot_name(p_slot)); | ||||
| 		break; | ||||
| 	case BLINKINGON_STATE: | ||||
| 	case OFF_STATE: | ||||
| 	case POWERON_STATE: | ||||
| 		ctrl_info(ctrl, "Slot(%s): Already disabled\n", | ||||
| 			  slot_name(p_slot)); | ||||
|  | @ -498,5 +431,5 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) | |||
| 	} | ||||
| 	mutex_unlock(&p_slot->lock); | ||||
| 
 | ||||
| 	return retval; | ||||
| 	return -ENODEV; | ||||
| } | ||||
|  |  | |||
|  | @ -17,8 +17,9 @@ | |||
| #include <linux/types.h> | ||||
| #include <linux/signal.h> | ||||
| #include <linux/jiffies.h> | ||||
| #include <linux/timer.h> | ||||
| #include <linux/kthread.h> | ||||
| #include <linux/pci.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/time.h> | ||||
| #include <linux/slab.h> | ||||
|  | @ -31,47 +32,24 @@ static inline struct pci_dev *ctrl_dev(struct controller *ctrl) | |||
| 	return ctrl->pcie->port; | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t pcie_isr(int irq, void *dev_id); | ||||
| static void start_int_poll_timer(struct controller *ctrl, int sec); | ||||
| 
 | ||||
| /* This is the interrupt polling timeout function. */ | ||||
| static void int_poll_timeout(struct timer_list *t) | ||||
| { | ||||
| 	struct controller *ctrl = from_timer(ctrl, t, poll_timer); | ||||
| 
 | ||||
| 	/* Poll for interrupt events.  regs == NULL => polling */ | ||||
| 	pcie_isr(0, ctrl); | ||||
| 
 | ||||
| 	if (!pciehp_poll_time) | ||||
| 		pciehp_poll_time = 2; /* default polling interval is 2 sec */ | ||||
| 
 | ||||
| 	start_int_poll_timer(ctrl, pciehp_poll_time); | ||||
| } | ||||
| 
 | ||||
| /* This function starts the interrupt polling timer. */ | ||||
| static void start_int_poll_timer(struct controller *ctrl, int sec) | ||||
| { | ||||
| 	/* Clamp to sane value */ | ||||
| 	if ((sec <= 0) || (sec > 60)) | ||||
| 		sec = 2; | ||||
| 
 | ||||
| 	ctrl->poll_timer.expires = jiffies + sec * HZ; | ||||
| 	add_timer(&ctrl->poll_timer); | ||||
| } | ||||
| static irqreturn_t pciehp_isr(int irq, void *dev_id); | ||||
| static irqreturn_t pciehp_ist(int irq, void *dev_id); | ||||
| static int pciehp_poll(void *data); | ||||
| 
 | ||||
| static inline int pciehp_request_irq(struct controller *ctrl) | ||||
| { | ||||
| 	int retval, irq = ctrl->pcie->irq; | ||||
| 
 | ||||
| 	/* Install interrupt polling timer. Start with 10 sec delay */ | ||||
| 	if (pciehp_poll_mode) { | ||||
| 		timer_setup(&ctrl->poll_timer, int_poll_timeout, 0); | ||||
| 		start_int_poll_timer(ctrl, 10); | ||||
| 		return 0; | ||||
| 		ctrl->poll_thread = kthread_run(&pciehp_poll, ctrl, | ||||
| 						"pciehp_poll-%s", | ||||
| 						slot_name(ctrl->slot)); | ||||
| 		return PTR_ERR_OR_ZERO(ctrl->poll_thread); | ||||
| 	} | ||||
| 
 | ||||
| 	/* Installs the interrupt handler */ | ||||
| 	retval = request_irq(irq, pcie_isr, IRQF_SHARED, MY_NAME, ctrl); | ||||
| 	retval = request_threaded_irq(irq, pciehp_isr, pciehp_ist, | ||||
| 				      IRQF_SHARED, MY_NAME, ctrl); | ||||
| 	if (retval) | ||||
| 		ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n", | ||||
| 			 irq); | ||||
|  | @ -81,7 +59,7 @@ static inline int pciehp_request_irq(struct controller *ctrl) | |||
| static inline void pciehp_free_irq(struct controller *ctrl) | ||||
| { | ||||
| 	if (pciehp_poll_mode) | ||||
| 		del_timer_sync(&ctrl->poll_timer); | ||||
| 		kthread_stop(ctrl->poll_thread); | ||||
| 	else | ||||
| 		free_irq(ctrl->pcie->irq, ctrl); | ||||
| } | ||||
|  | @ -293,6 +271,11 @@ int pciehp_check_link_status(struct controller *ctrl) | |||
| 	found = pci_bus_check_dev(ctrl->pcie->port->subordinate, | ||||
| 					PCI_DEVFN(0, 0)); | ||||
| 
 | ||||
| 	/* ignore link or presence changes up to this point */ | ||||
| 	if (found) | ||||
| 		atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC), | ||||
| 			   &ctrl->pending_events); | ||||
| 
 | ||||
| 	pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); | ||||
| 	ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); | ||||
| 	if ((lnk_status & PCI_EXP_LNKSTA_LT) || | ||||
|  | @ -339,7 +322,9 @@ int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, | |||
| 	struct pci_dev *pdev = ctrl_dev(slot->ctrl); | ||||
| 	u16 slot_ctrl; | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 	pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	*status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6; | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -350,7 +335,9 @@ void pciehp_get_attention_status(struct slot *slot, u8 *status) | |||
| 	struct pci_dev *pdev = ctrl_dev(ctrl); | ||||
| 	u16 slot_ctrl; | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 	pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", __func__, | ||||
| 		 pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_ctrl); | ||||
| 
 | ||||
|  | @ -425,9 +412,12 @@ int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, | |||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	struct controller *ctrl = slot->ctrl; | ||||
| 	struct pci_dev *pdev = ctrl_dev(ctrl); | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 	pcie_write_cmd_nowait(ctrl, status << 6, | ||||
| 			      PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC); | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -539,20 +529,35 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) | |||
| { | ||||
| 	struct controller *ctrl = (struct controller *)dev_id; | ||||
| 	struct pci_dev *pdev = ctrl_dev(ctrl); | ||||
| 	struct pci_bus *subordinate = pdev->subordinate; | ||||
| 	struct pci_dev *dev; | ||||
| 	struct slot *slot = ctrl->slot; | ||||
| 	struct device *parent = pdev->dev.parent; | ||||
| 	u16 status, events; | ||||
| 	u8 present; | ||||
| 	bool link; | ||||
| 
 | ||||
| 	/* Interrupts cannot originate from a controller that's asleep */ | ||||
| 	/*
 | ||||
| 	 * Interrupts only occur in D3hot or shallower (PCIe r4.0, sec 6.7.3.4). | ||||
| 	 */ | ||||
| 	if (pdev->current_state == PCI_D3cold) | ||||
| 		return IRQ_NONE; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Keep the port accessible by holding a runtime PM ref on its parent. | ||||
| 	 * Defer resume of the parent to the IRQ thread if it's suspended. | ||||
| 	 * Mask the interrupt until then. | ||||
| 	 */ | ||||
| 	if (parent) { | ||||
| 		pm_runtime_get_noresume(parent); | ||||
| 		if (!pm_runtime_active(parent)) { | ||||
| 			pm_runtime_put(parent); | ||||
| 			disable_irq_nosync(irq); | ||||
| 			atomic_or(RERUN_ISR, &ctrl->pending_events); | ||||
| 			return IRQ_WAKE_THREAD; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); | ||||
| 	if (status == (u16) ~0) { | ||||
| 		ctrl_info(ctrl, "%s: no response from device\n", __func__); | ||||
| 		if (parent) | ||||
| 			pm_runtime_put(parent); | ||||
| 		return IRQ_NONE; | ||||
| 	} | ||||
| 
 | ||||
|  | @ -571,86 +576,119 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) | |||
| 	if (ctrl->power_fault_detected) | ||||
| 		events &= ~PCI_EXP_SLTSTA_PFD; | ||||
| 
 | ||||
| 	if (!events) | ||||
| 	if (!events) { | ||||
| 		if (parent) | ||||
| 			pm_runtime_put(parent); | ||||
| 		return IRQ_NONE; | ||||
| 
 | ||||
| 	/* Capture link status before clearing interrupts */ | ||||
| 	if (events & PCI_EXP_SLTSTA_DLLSC) | ||||
| 		link = pciehp_check_link_active(ctrl); | ||||
| 	} | ||||
| 
 | ||||
| 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events); | ||||
| 	ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); | ||||
| 	if (parent) | ||||
| 		pm_runtime_put(parent); | ||||
| 
 | ||||
| 	/* Check Command Complete Interrupt Pending */ | ||||
| 	/*
 | ||||
| 	 * Command Completed notifications are not deferred to the | ||||
| 	 * IRQ thread because it may be waiting for their arrival. | ||||
| 	 */ | ||||
| 	if (events & PCI_EXP_SLTSTA_CC) { | ||||
| 		ctrl->cmd_busy = 0; | ||||
| 		smp_mb(); | ||||
| 		wake_up(&ctrl->queue); | ||||
| 
 | ||||
| 		if (events == PCI_EXP_SLTSTA_CC) | ||||
| 			return IRQ_HANDLED; | ||||
| 
 | ||||
| 		events &= ~PCI_EXP_SLTSTA_CC; | ||||
| 	} | ||||
| 
 | ||||
| 	if (subordinate) { | ||||
| 		list_for_each_entry(dev, &subordinate->devices, bus_list) { | ||||
| 			if (dev->ignore_hotplug) { | ||||
| 				ctrl_dbg(ctrl, "ignoring hotplug event %#06x (%s requested no hotplug)\n", | ||||
| 					 events, pci_name(dev)); | ||||
| 	if (pdev->ignore_hotplug) { | ||||
| 		ctrl_dbg(ctrl, "ignoring hotplug event %#06x\n", events); | ||||
| 		return IRQ_HANDLED; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Save pending events for consumption by IRQ thread. */ | ||||
| 	atomic_or(events, &ctrl->pending_events); | ||||
| 	return IRQ_WAKE_THREAD; | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t pciehp_ist(int irq, void *dev_id) | ||||
| { | ||||
| 	struct controller *ctrl = (struct controller *)dev_id; | ||||
| 	struct pci_dev *pdev = ctrl_dev(ctrl); | ||||
| 	struct slot *slot = ctrl->slot; | ||||
| 	irqreturn_t ret; | ||||
| 	u32 events; | ||||
| 
 | ||||
| 	pci_config_pm_runtime_get(pdev); | ||||
| 
 | ||||
| 	/* rerun pciehp_isr() if the port was inaccessible on interrupt */ | ||||
| 	if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) { | ||||
| 		ret = pciehp_isr(irq, dev_id); | ||||
| 		enable_irq(irq); | ||||
| 		if (ret != IRQ_WAKE_THREAD) { | ||||
| 			pci_config_pm_runtime_put(pdev); | ||||
| 			return ret; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	synchronize_hardirq(irq); | ||||
| 	events = atomic_xchg(&ctrl->pending_events, 0); | ||||
| 	if (!events) { | ||||
| 		pci_config_pm_runtime_put(pdev); | ||||
| 		return IRQ_NONE; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Check Attention Button Pressed */ | ||||
| 	if (events & PCI_EXP_SLTSTA_ABP) { | ||||
| 		ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", | ||||
| 			  slot_name(slot)); | ||||
| 		pciehp_queue_interrupt_event(slot, INT_BUTTON_PRESS); | ||||
| 		pciehp_handle_button_press(slot); | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Check Link Status Changed at higher precedence than Presence | ||||
| 	 * Detect Changed.  The PDS value may be set to "card present" from | ||||
| 	 * out-of-band detection, which may be in conflict with a Link Down | ||||
| 	 * and cause the wrong event to queue. | ||||
| 	 * Disable requests have higher priority than Presence Detect Changed | ||||
| 	 * or Data Link Layer State Changed events. | ||||
| 	 */ | ||||
| 	if (events & PCI_EXP_SLTSTA_DLLSC) { | ||||
| 		ctrl_info(ctrl, "Slot(%s): Link %s\n", slot_name(slot), | ||||
| 			  link ? "Up" : "Down"); | ||||
| 		pciehp_queue_interrupt_event(slot, link ? INT_LINK_UP : | ||||
| 					     INT_LINK_DOWN); | ||||
| 	} else if (events & PCI_EXP_SLTSTA_PDC) { | ||||
| 		present = !!(status & PCI_EXP_SLTSTA_PDS); | ||||
| 		ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot), | ||||
| 			  present ? "" : "not "); | ||||
| 		pciehp_queue_interrupt_event(slot, present ? INT_PRESENCE_ON : | ||||
| 					     INT_PRESENCE_OFF); | ||||
| 	} | ||||
| 	down_read(&ctrl->reset_lock); | ||||
| 	if (events & DISABLE_SLOT) | ||||
| 		pciehp_handle_disable_request(slot); | ||||
| 	else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) | ||||
| 		pciehp_handle_presence_or_link_change(slot, events); | ||||
| 	up_read(&ctrl->reset_lock); | ||||
| 
 | ||||
| 	/* Check Power Fault Detected */ | ||||
| 	if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { | ||||
| 		ctrl->power_fault_detected = 1; | ||||
| 		ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot)); | ||||
| 		pciehp_queue_interrupt_event(slot, INT_POWER_FAULT); | ||||
| 		pciehp_set_attention_status(slot, 1); | ||||
| 		pciehp_green_led_off(slot); | ||||
| 	} | ||||
| 
 | ||||
| 	pci_config_pm_runtime_put(pdev); | ||||
| 	wake_up(&ctrl->requester); | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t pcie_isr(int irq, void *dev_id) | ||||
| static int pciehp_poll(void *data) | ||||
| { | ||||
| 	irqreturn_t rc, handled = IRQ_NONE; | ||||
| 	struct controller *ctrl = data; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * To guarantee that all interrupt events are serviced, we need to | ||||
| 	 * re-inspect Slot Status register after clearing what is presumed | ||||
| 	 * to be the last pending interrupt. | ||||
| 	 */ | ||||
| 	do { | ||||
| 		rc = pciehp_isr(irq, dev_id); | ||||
| 		if (rc == IRQ_HANDLED) | ||||
| 			handled = IRQ_HANDLED; | ||||
| 	} while (rc == IRQ_HANDLED); | ||||
| 	schedule_timeout_idle(10 * HZ); /* start with 10 sec delay */ | ||||
| 
 | ||||
| 	/* Return IRQ_HANDLED if we handled one or more events */ | ||||
| 	return handled; | ||||
| 	while (!kthread_should_stop()) { | ||||
| 		/* poll for interrupt events or user requests */ | ||||
| 		while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD || | ||||
| 		       atomic_read(&ctrl->pending_events)) | ||||
| 			pciehp_ist(IRQ_NOTCONNECTED, ctrl); | ||||
| 
 | ||||
| 		if (pciehp_poll_time <= 0 || pciehp_poll_time > 60) | ||||
| 			pciehp_poll_time = 2; /* clamp to sane value */ | ||||
| 
 | ||||
| 		schedule_timeout_idle(pciehp_poll_time * HZ); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void pcie_enable_notification(struct controller *ctrl) | ||||
|  | @ -691,17 +729,6 @@ static void pcie_enable_notification(struct controller *ctrl) | |||
| 		 pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, cmd); | ||||
| } | ||||
| 
 | ||||
| void pcie_reenable_notification(struct controller *ctrl) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * Clear both Presence and Data Link Layer Changed to make sure | ||||
| 	 * those events still fire after we have re-enabled them. | ||||
| 	 */ | ||||
| 	pcie_capability_write_word(ctrl->pcie->port, PCI_EXP_SLTSTA, | ||||
| 				   PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); | ||||
| 	pcie_enable_notification(ctrl); | ||||
| } | ||||
| 
 | ||||
| static void pcie_disable_notification(struct controller *ctrl) | ||||
| { | ||||
| 	u16 mask; | ||||
|  | @ -715,6 +742,12 @@ static void pcie_disable_notification(struct controller *ctrl) | |||
| 		 pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0); | ||||
| } | ||||
| 
 | ||||
| void pcie_clear_hotplug_events(struct controller *ctrl) | ||||
| { | ||||
| 	pcie_capability_write_word(ctrl_dev(ctrl), PCI_EXP_SLTSTA, | ||||
| 				   PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary | ||||
|  * bus reset of the bridge, but at the same time we want to ensure that it is | ||||
|  | @ -732,6 +765,8 @@ int pciehp_reset_slot(struct slot *slot, int probe) | |||
| 	if (probe) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	down_write(&ctrl->reset_lock); | ||||
| 
 | ||||
| 	if (!ATTN_BUTTN(ctrl)) { | ||||
| 		ctrl_mask |= PCI_EXP_SLTCTL_PDCE; | ||||
| 		stat_mask |= PCI_EXP_SLTSTA_PDC; | ||||
|  | @ -742,8 +777,6 @@ int pciehp_reset_slot(struct slot *slot, int probe) | |||
| 	pcie_write_cmd(ctrl, 0, ctrl_mask); | ||||
| 	ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, | ||||
| 		 pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0); | ||||
| 	if (pciehp_poll_mode) | ||||
| 		del_timer_sync(&ctrl->poll_timer); | ||||
| 
 | ||||
| 	pci_reset_bridge_secondary_bus(ctrl->pcie->port); | ||||
| 
 | ||||
|  | @ -751,8 +784,8 @@ int pciehp_reset_slot(struct slot *slot, int probe) | |||
| 	pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask); | ||||
| 	ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, | ||||
| 		 pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask); | ||||
| 	if (pciehp_poll_mode) | ||||
| 		int_poll_timeout(&ctrl->poll_timer); | ||||
| 
 | ||||
| 	up_write(&ctrl->reset_lock); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -765,7 +798,7 @@ int pcie_init_notification(struct controller *ctrl) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void pcie_shutdown_notification(struct controller *ctrl) | ||||
| void pcie_shutdown_notification(struct controller *ctrl) | ||||
| { | ||||
| 	if (ctrl->notification_enabled) { | ||||
| 		pcie_disable_notification(ctrl); | ||||
|  | @ -776,32 +809,29 @@ static void pcie_shutdown_notification(struct controller *ctrl) | |||
| 
 | ||||
| static int pcie_init_slot(struct controller *ctrl) | ||||
| { | ||||
| 	struct pci_bus *subordinate = ctrl_dev(ctrl)->subordinate; | ||||
| 	struct slot *slot; | ||||
| 
 | ||||
| 	slot = kzalloc(sizeof(*slot), GFP_KERNEL); | ||||
| 	if (!slot) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	slot->wq = alloc_ordered_workqueue("pciehp-%u", 0, PSN(ctrl)); | ||||
| 	if (!slot->wq) | ||||
| 		goto abort; | ||||
| 	down_read(&pci_bus_sem); | ||||
| 	slot->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE; | ||||
| 	up_read(&pci_bus_sem); | ||||
| 
 | ||||
| 	slot->ctrl = ctrl; | ||||
| 	mutex_init(&slot->lock); | ||||
| 	mutex_init(&slot->hotplug_lock); | ||||
| 	INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work); | ||||
| 	ctrl->slot = slot; | ||||
| 	return 0; | ||||
| abort: | ||||
| 	kfree(slot); | ||||
| 	return -ENOMEM; | ||||
| } | ||||
| 
 | ||||
| static void pcie_cleanup_slot(struct controller *ctrl) | ||||
| { | ||||
| 	struct slot *slot = ctrl->slot; | ||||
| 	cancel_delayed_work(&slot->work); | ||||
| 	destroy_workqueue(slot->wq); | ||||
| 
 | ||||
| 	cancel_delayed_work_sync(&slot->work); | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
|  | @ -826,6 +856,7 @@ struct controller *pcie_init(struct pcie_device *dev) | |||
| { | ||||
| 	struct controller *ctrl; | ||||
| 	u32 slot_cap, link_cap; | ||||
| 	u8 occupied, poweron; | ||||
| 	struct pci_dev *pdev = dev->port; | ||||
| 
 | ||||
| 	ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); | ||||
|  | @ -847,6 +878,8 @@ struct controller *pcie_init(struct pcie_device *dev) | |||
| 
 | ||||
| 	ctrl->slot_cap = slot_cap; | ||||
| 	mutex_init(&ctrl->ctrl_lock); | ||||
| 	init_rwsem(&ctrl->reset_lock); | ||||
| 	init_waitqueue_head(&ctrl->requester); | ||||
| 	init_waitqueue_head(&ctrl->queue); | ||||
| 	dbg_ctrl(ctrl); | ||||
| 
 | ||||
|  | @ -855,16 +888,11 @@ struct controller *pcie_init(struct pcie_device *dev) | |||
| 	if (link_cap & PCI_EXP_LNKCAP_DLLLARC) | ||||
| 		ctrl->link_active_reporting = 1; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Clear all remaining event bits in Slot Status register except | ||||
| 	 * Presence Detect Changed. We want to make sure possible | ||||
| 	 * hotplug event is triggered when the interrupt is unmasked so | ||||
| 	 * that we don't lose that event. | ||||
| 	 */ | ||||
| 	/* Clear all remaining event bits in Slot Status register. */ | ||||
| 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, | ||||
| 		PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | | ||||
| 		PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_CC | | ||||
| 		PCI_EXP_SLTSTA_DLLSC); | ||||
| 		PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC); | ||||
| 
 | ||||
| 	ctrl_info(ctrl, "Slot #%d AttnBtn%c PwrCtrl%c MRL%c AttnInd%c PwrInd%c HotPlug%c Surprise%c Interlock%c NoCompl%c LLActRep%c%s\n", | ||||
| 		(slot_cap & PCI_EXP_SLTCAP_PSN) >> 19, | ||||
|  | @ -883,6 +911,19 @@ struct controller *pcie_init(struct pcie_device *dev) | |||
| 	if (pcie_init_slot(ctrl)) | ||||
| 		goto abort_ctrl; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If empty slot's power status is on, turn power off.  The IRQ isn't | ||||
| 	 * requested yet, so avoid triggering a notification with this command. | ||||
| 	 */ | ||||
| 	if (POWER_CTRL(ctrl)) { | ||||
| 		pciehp_get_adapter_status(ctrl->slot, &occupied); | ||||
| 		pciehp_get_power_status(ctrl->slot, &poweron); | ||||
| 		if (!occupied && poweron) { | ||||
| 			pcie_disable_notification(ctrl); | ||||
| 			pciehp_power_off_slot(ctrl->slot); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ctrl; | ||||
| 
 | ||||
| abort_ctrl: | ||||
|  | @ -893,7 +934,6 @@ abort: | |||
| 
 | ||||
| void pciehp_release_ctrl(struct controller *ctrl) | ||||
| { | ||||
| 	pcie_shutdown_notification(ctrl); | ||||
| 	pcie_cleanup_slot(ctrl); | ||||
| 	kfree(ctrl); | ||||
| } | ||||
|  |  | |||
|  | @ -62,9 +62,8 @@ int pciehp_configure_device(struct slot *p_slot) | |||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| int pciehp_unconfigure_device(struct slot *p_slot) | ||||
| void pciehp_unconfigure_device(struct slot *p_slot) | ||||
| { | ||||
| 	int rc = 0; | ||||
| 	u8 presence = 0; | ||||
| 	struct pci_dev *dev, *temp; | ||||
| 	struct pci_bus *parent = p_slot->ctrl->pcie->port->subordinate; | ||||
|  | @ -107,5 +106,4 @@ int pciehp_unconfigure_device(struct slot *p_slot) | |||
| 	} | ||||
| 
 | ||||
| 	pci_unlock_rescan_remove(); | ||||
| 	return rc; | ||||
| } | ||||
|  |  | |||
|  | @ -1,348 +0,0 @@ | |||
| // SPDX-License-Identifier: GPL-2.0+
 | ||||
| /*
 | ||||
|  * PCI Hot Plug Controller Skeleton Driver - 0.3 | ||||
|  * | ||||
|  * Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com) | ||||
|  * Copyright (C) 2001,2003 IBM Corp. | ||||
|  * | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * This driver is to be used as a skeleton driver to show how to interface | ||||
|  * with the pci hotplug core easily. | ||||
|  * | ||||
|  * Send feedback to <greg@kroah.com> | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/module.h> | ||||
| #include <linux/moduleparam.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/pci.h> | ||||
| #include <linux/pci_hotplug.h> | ||||
| #include <linux/init.h> | ||||
| 
 | ||||
| #define SLOT_NAME_SIZE	10 | ||||
| struct slot { | ||||
| 	u8 number; | ||||
| 	struct hotplug_slot *hotplug_slot; | ||||
| 	struct list_head slot_list; | ||||
| 	char name[SLOT_NAME_SIZE]; | ||||
| }; | ||||
| 
 | ||||
| static LIST_HEAD(slot_list); | ||||
| 
 | ||||
| #define MY_NAME	"pcihp_skeleton" | ||||
| 
 | ||||
| #define dbg(format, arg...)					\ | ||||
| 	do {							\ | ||||
| 		if (debug)					\ | ||||
| 			printk(KERN_DEBUG "%s: " format "\n",	\ | ||||
| 				MY_NAME, ## arg);		\ | ||||
| 	} while (0) | ||||
| #define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) | ||||
| #define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) | ||||
| #define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) | ||||
| 
 | ||||
| /* local variables */ | ||||
| static bool debug; | ||||
| static int num_slots; | ||||
| 
 | ||||
| #define DRIVER_VERSION	"0.3" | ||||
| #define DRIVER_AUTHOR	"Greg Kroah-Hartman <greg@kroah.com>" | ||||
| #define DRIVER_DESC	"Hot Plug PCI Controller Skeleton Driver" | ||||
| 
 | ||||
| MODULE_AUTHOR(DRIVER_AUTHOR); | ||||
| MODULE_DESCRIPTION(DRIVER_DESC); | ||||
| MODULE_LICENSE("GPL"); | ||||
| module_param(debug, bool, 0644); | ||||
| MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); | ||||
| 
 | ||||
| static int enable_slot(struct hotplug_slot *slot); | ||||
| static int disable_slot(struct hotplug_slot *slot); | ||||
| static int set_attention_status(struct hotplug_slot *slot, u8 value); | ||||
| static int hardware_test(struct hotplug_slot *slot, u32 value); | ||||
| static int get_power_status(struct hotplug_slot *slot, u8 *value); | ||||
| static int get_attention_status(struct hotplug_slot *slot, u8 *value); | ||||
| static int get_latch_status(struct hotplug_slot *slot, u8 *value); | ||||
| static int get_adapter_status(struct hotplug_slot *slot, u8 *value); | ||||
| 
 | ||||
| static struct hotplug_slot_ops skel_hotplug_slot_ops = { | ||||
| 	.enable_slot =		enable_slot, | ||||
| 	.disable_slot =		disable_slot, | ||||
| 	.set_attention_status =	set_attention_status, | ||||
| 	.hardware_test =	hardware_test, | ||||
| 	.get_power_status =	get_power_status, | ||||
| 	.get_attention_status =	get_attention_status, | ||||
| 	.get_latch_status =	get_latch_status, | ||||
| 	.get_adapter_status =	get_adapter_status, | ||||
| }; | ||||
| 
 | ||||
| static int enable_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Fill in code here to enable the specified slot | ||||
| 	 */ | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static int disable_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Fill in code here to disable the specified slot | ||||
| 	 */ | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	switch (status) { | ||||
| 	case 0: | ||||
| 		/*
 | ||||
| 		 * Fill in code here to turn light off | ||||
| 		 */ | ||||
| 		break; | ||||
| 
 | ||||
| 	case 1: | ||||
| 	default: | ||||
| 		/*
 | ||||
| 		 * Fill in code here to turn light on | ||||
| 		 */ | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static int hardware_test(struct hotplug_slot *hotplug_slot, u32 value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	switch (value) { | ||||
| 	case 0: | ||||
| 		/* Specify a test here */ | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		/* Specify another test here */ | ||||
| 		break; | ||||
| 	} | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Fill in logic to get the current power status of the specific | ||||
| 	 * slot and store it in the *value location. | ||||
| 	 */ | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Fill in logic to get the current attention status of the specific | ||||
| 	 * slot and store it in the *value location. | ||||
| 	 */ | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Fill in logic to get the current latch status of the specific | ||||
| 	 * slot and store it in the *value location. | ||||
| 	 */ | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 	int retval = 0; | ||||
| 
 | ||||
| 	dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Fill in logic to get the current adapter status of the specific | ||||
| 	 * slot and store it in the *value location. | ||||
| 	 */ | ||||
| 
 | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| 	kfree(slot->hotplug_slot->info); | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
| static void make_slot_name(struct slot *slot) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * Stupid way to make a filename out of the slot name. | ||||
| 	 * replace this if your hardware provides a better way to name slots. | ||||
| 	 */ | ||||
| 	snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%d", slot->number); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * init_slots - initialize 'struct slot' structures for each slot | ||||
|  * | ||||
|  */ | ||||
| static int __init init_slots(void) | ||||
| { | ||||
| 	struct slot *slot; | ||||
| 	struct hotplug_slot *hotplug_slot; | ||||
| 	struct hotplug_slot_info *info; | ||||
| 	int retval; | ||||
| 	int i; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Create a structure for each slot, and register that slot | ||||
| 	 * with the pci_hotplug subsystem. | ||||
| 	 */ | ||||
| 	for (i = 0; i < num_slots; ++i) { | ||||
| 		slot = kzalloc(sizeof(*slot), GFP_KERNEL); | ||||
| 		if (!slot) { | ||||
| 			retval = -ENOMEM; | ||||
| 			goto error; | ||||
| 		} | ||||
| 
 | ||||
| 		hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL); | ||||
| 		if (!hotplug_slot) { | ||||
| 			retval = -ENOMEM; | ||||
| 			goto error_slot; | ||||
| 		} | ||||
| 		slot->hotplug_slot = hotplug_slot; | ||||
| 
 | ||||
| 		info = kzalloc(sizeof(*info), GFP_KERNEL); | ||||
| 		if (!info) { | ||||
| 			retval = -ENOMEM; | ||||
| 			goto error_hpslot; | ||||
| 		} | ||||
| 		hotplug_slot->info = info; | ||||
| 
 | ||||
| 		slot->number = i; | ||||
| 
 | ||||
| 		hotplug_slot->name = slot->name; | ||||
| 		hotplug_slot->private = slot; | ||||
| 		hotplug_slot->release = &release_slot; | ||||
| 		make_slot_name(slot); | ||||
| 		hotplug_slot->ops = &skel_hotplug_slot_ops; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Initialize the slot info structure with some known | ||||
| 		 * good values. | ||||
| 		 */ | ||||
| 		get_power_status(hotplug_slot, &info->power_status); | ||||
| 		get_attention_status(hotplug_slot, &info->attention_status); | ||||
| 		get_latch_status(hotplug_slot, &info->latch_status); | ||||
| 		get_adapter_status(hotplug_slot, &info->adapter_status); | ||||
| 
 | ||||
| 		dbg("registering slot %d\n", i); | ||||
| 		retval = pci_hp_register(slot->hotplug_slot); | ||||
| 		if (retval) { | ||||
| 			err("pci_hp_register failed with error %d\n", retval); | ||||
| 			goto error_info; | ||||
| 		} | ||||
| 
 | ||||
| 		/* add slot to our internal list */ | ||||
| 		list_add(&slot->slot_list, &slot_list); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| error_info: | ||||
| 	kfree(info); | ||||
| error_hpslot: | ||||
| 	kfree(hotplug_slot); | ||||
| error_slot: | ||||
| 	kfree(slot); | ||||
| error: | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| static void __exit cleanup_slots(void) | ||||
| { | ||||
| 	struct slot *slot, *next; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Unregister all of our slots with the pci_hotplug subsystem. | ||||
| 	 * Memory will be freed in release_slot() callback after slot's | ||||
| 	 * lifespan is finished. | ||||
| 	 */ | ||||
| 	list_for_each_entry_safe(slot, next, &slot_list, slot_list) { | ||||
| 		list_del(&slot->slot_list); | ||||
| 		pci_hp_deregister(slot->hotplug_slot); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static int __init pcihp_skel_init(void) | ||||
| { | ||||
| 	int retval; | ||||
| 
 | ||||
| 	info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); | ||||
| 	/*
 | ||||
| 	 * Do specific initialization stuff for your driver here | ||||
| 	 * like initializing your controller hardware (if any) and | ||||
| 	 * determining the number of slots you have in the system | ||||
| 	 * right now. | ||||
| 	 */ | ||||
| 	num_slots = 5; | ||||
| 
 | ||||
| 	return init_slots(); | ||||
| } | ||||
| 
 | ||||
| static void __exit pcihp_skel_exit(void) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * Clean everything up. | ||||
| 	 */ | ||||
| 	cleanup_slots(); | ||||
| } | ||||
| 
 | ||||
| module_init(pcihp_skel_init); | ||||
| module_exit(pcihp_skel_exit); | ||||
|  | @ -538,9 +538,8 @@ static struct hotplug_slot_ops php_slot_ops = { | |||
| 	.disable_slot		= pnv_php_disable_slot, | ||||
| }; | ||||
| 
 | ||||
| static void pnv_php_release(struct hotplug_slot *slot) | ||||
| static void pnv_php_release(struct pnv_php_slot *php_slot) | ||||
| { | ||||
| 	struct pnv_php_slot *php_slot = slot->private; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	/* Remove from global or child list */ | ||||
|  | @ -596,7 +595,6 @@ static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn) | |||
| 	php_slot->power_state_check     = false; | ||||
| 	php_slot->slot.ops              = &php_slot_ops; | ||||
| 	php_slot->slot.info             = &php_slot->slot_info; | ||||
| 	php_slot->slot.release          = pnv_php_release; | ||||
| 	php_slot->slot.private          = php_slot; | ||||
| 
 | ||||
| 	INIT_LIST_HEAD(&php_slot->children); | ||||
|  | @ -924,6 +922,7 @@ static void pnv_php_unregister_one(struct device_node *dn) | |||
| 
 | ||||
| 	php_slot->state = PNV_PHP_STATE_OFFLINE; | ||||
| 	pci_hp_deregister(&php_slot->slot); | ||||
| 	pnv_php_release(php_slot); | ||||
| 	pnv_php_put_slot(php_slot); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -404,13 +404,13 @@ static void __exit cleanup_slots(void) | |||
| 	/*
 | ||||
| 	 * Unregister all of our slots with the pci_hotplug subsystem, | ||||
| 	 * and free up all memory that we had allocated. | ||||
| 	 * memory will be freed in release_slot callback. | ||||
| 	 */ | ||||
| 
 | ||||
| 	list_for_each_entry_safe(slot, next, &rpaphp_slot_head, | ||||
| 				 rpaphp_slot_list) { | ||||
| 		list_del(&slot->rpaphp_slot_list); | ||||
| 		pci_hp_deregister(slot->hotplug_slot); | ||||
| 		dealloc_slot_struct(slot); | ||||
| 	} | ||||
| 	return; | ||||
| } | ||||
|  |  | |||
|  | @ -19,12 +19,6 @@ | |||
| #include "rpaphp.h" | ||||
| 
 | ||||
| /* free up the memory used by a slot */ | ||||
| static void rpaphp_release_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot = (struct slot *) hotplug_slot->private; | ||||
| 	dealloc_slot_struct(slot); | ||||
| } | ||||
| 
 | ||||
| void dealloc_slot_struct(struct slot *slot) | ||||
| { | ||||
| 	kfree(slot->hotplug_slot->info); | ||||
|  | @ -56,7 +50,6 @@ struct slot *alloc_slot_struct(struct device_node *dn, | |||
| 	slot->power_domain = power_domain; | ||||
| 	slot->hotplug_slot->private = slot; | ||||
| 	slot->hotplug_slot->ops = &rpaphp_hotplug_slot_ops; | ||||
| 	slot->hotplug_slot->release = &rpaphp_release_slot; | ||||
| 
 | ||||
| 	return (slot); | ||||
| 
 | ||||
|  | @ -90,10 +83,8 @@ int rpaphp_deregister_slot(struct slot *slot) | |||
| 		__func__, slot->name); | ||||
| 
 | ||||
| 	list_del(&slot->rpaphp_slot_list); | ||||
| 
 | ||||
| 	retval = pci_hp_deregister(php_slot); | ||||
| 	if (retval) | ||||
| 		err("Problem unregistering a slot %s\n", slot->name); | ||||
| 	pci_hp_deregister(php_slot); | ||||
| 	dealloc_slot_struct(slot); | ||||
| 
 | ||||
| 	dbg("%s - Exit: rc[%d]\n", __func__, retval); | ||||
| 	return retval; | ||||
|  |  | |||
|  | @ -130,15 +130,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void release_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 
 | ||||
| 	kfree(slot->hotplug_slot->info); | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
| static struct hotplug_slot_ops s390_hotplug_slot_ops = { | ||||
| 	.enable_slot =		enable_slot, | ||||
| 	.disable_slot =		disable_slot, | ||||
|  | @ -175,7 +166,6 @@ int zpci_init_slot(struct zpci_dev *zdev) | |||
| 	hotplug_slot->info = info; | ||||
| 
 | ||||
| 	hotplug_slot->ops = &s390_hotplug_slot_ops; | ||||
| 	hotplug_slot->release = &release_slot; | ||||
| 
 | ||||
| 	get_power_status(hotplug_slot, &info->power_status); | ||||
| 	get_adapter_status(hotplug_slot, &info->adapter_status); | ||||
|  | @ -209,5 +199,8 @@ void zpci_exit_slot(struct zpci_dev *zdev) | |||
| 			continue; | ||||
| 		list_del(&slot->slot_list); | ||||
| 		pci_hp_deregister(slot->hotplug_slot); | ||||
| 		kfree(slot->hotplug_slot->info); | ||||
| 		kfree(slot->hotplug_slot); | ||||
| 		kfree(slot); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -628,7 +628,6 @@ static int sn_hotplug_slot_register(struct pci_bus *pci_bus) | |||
| 			goto alloc_err; | ||||
| 		} | ||||
| 		bss_hotplug_slot->ops = &sn_hotplug_slot_ops; | ||||
| 		bss_hotplug_slot->release = &sn_release_slot; | ||||
| 
 | ||||
| 		rc = pci_hp_register(bss_hotplug_slot, pci_bus, device, name); | ||||
| 		if (rc) | ||||
|  | @ -656,8 +655,10 @@ alloc_err: | |||
| 		sn_release_slot(bss_hotplug_slot); | ||||
| 
 | ||||
| 	/* destroy anything else on the list */ | ||||
| 	while ((bss_hotplug_slot = sn_hp_destroy())) | ||||
| 	while ((bss_hotplug_slot = sn_hp_destroy())) { | ||||
| 		pci_hp_deregister(bss_hotplug_slot); | ||||
| 		sn_release_slot(bss_hotplug_slot); | ||||
| 	} | ||||
| 
 | ||||
| 	return rc; | ||||
| } | ||||
|  | @ -703,8 +704,10 @@ static void __exit sn_pci_hotplug_exit(void) | |||
| { | ||||
| 	struct hotplug_slot *bss_hotplug_slot; | ||||
| 
 | ||||
| 	while ((bss_hotplug_slot = sn_hp_destroy())) | ||||
| 	while ((bss_hotplug_slot = sn_hp_destroy())) { | ||||
| 		pci_hp_deregister(bss_hotplug_slot); | ||||
| 		sn_release_slot(bss_hotplug_slot); | ||||
| 	} | ||||
| 
 | ||||
| 	if (!list_empty(&sn_hp_list)) | ||||
| 		printk(KERN_ERR "%s: internal list is not empty\n", __FILE__); | ||||
|  |  | |||
|  | @ -61,22 +61,6 @@ static struct hotplug_slot_ops shpchp_hotplug_slot_ops = { | |||
| 	.get_adapter_status =	get_adapter_status, | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * release_slot - free up the memory used by a slot | ||||
|  * @hotplug_slot: slot to free | ||||
|  */ | ||||
| static void release_slot(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	struct slot *slot = hotplug_slot->private; | ||||
| 
 | ||||
| 	ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n", | ||||
| 		 __func__, slot_name(slot)); | ||||
| 
 | ||||
| 	kfree(slot->hotplug_slot->info); | ||||
| 	kfree(slot->hotplug_slot); | ||||
| 	kfree(slot); | ||||
| } | ||||
| 
 | ||||
| static int init_slots(struct controller *ctrl) | ||||
| { | ||||
| 	struct slot *slot; | ||||
|  | @ -125,7 +109,6 @@ static int init_slots(struct controller *ctrl) | |||
| 
 | ||||
| 		/* register this slot with the hotplug pci core */ | ||||
| 		hotplug_slot->private = slot; | ||||
| 		hotplug_slot->release = &release_slot; | ||||
| 		snprintf(name, SLOT_NAME_SIZE, "%d", slot->number); | ||||
| 		hotplug_slot->ops = &shpchp_hotplug_slot_ops; | ||||
| 
 | ||||
|  | @ -171,6 +154,9 @@ void cleanup_slots(struct controller *ctrl) | |||
| 		cancel_delayed_work(&slot->work); | ||||
| 		destroy_workqueue(slot->wq); | ||||
| 		pci_hp_deregister(slot->hotplug_slot); | ||||
| 		kfree(slot->hotplug_slot->info); | ||||
| 		kfree(slot->hotplug_slot); | ||||
| 		kfree(slot); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -270,11 +256,30 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static bool shpc_capable(struct pci_dev *bridge) | ||||
| { | ||||
| 	/*
 | ||||
| 	 * It is assumed that AMD GOLAM chips support SHPC but they do not | ||||
| 	 * have SHPC capability. | ||||
| 	 */ | ||||
| 	if (bridge->vendor == PCI_VENDOR_ID_AMD && | ||||
| 	    bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450) | ||||
| 		return true; | ||||
| 
 | ||||
| 	if (pci_find_capability(bridge, PCI_CAP_ID_SHPC)) | ||||
| 		return true; | ||||
| 
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | ||||
| { | ||||
| 	int rc; | ||||
| 	struct controller *ctrl; | ||||
| 
 | ||||
| 	if (!shpc_capable(pdev)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	if (acpi_get_hp_hw_control_from_firmware(pdev)) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
|  | @ -303,6 +308,7 @@ static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |||
| 	if (rc) | ||||
| 		goto err_cleanup_slots; | ||||
| 
 | ||||
| 	pdev->shpc_managed = 1; | ||||
| 	return 0; | ||||
| 
 | ||||
| err_cleanup_slots: | ||||
|  | @ -319,6 +325,7 @@ static void shpc_remove(struct pci_dev *dev) | |||
| { | ||||
| 	struct controller *ctrl = pci_get_drvdata(dev); | ||||
| 
 | ||||
| 	dev->shpc_managed = 0; | ||||
| 	shpchp_remove_ctrl_files(ctrl); | ||||
| 	ctrl->hpc_ops->release_ctlr(ctrl); | ||||
| 	kfree(ctrl); | ||||
|  |  | |||
|  | @ -403,24 +403,7 @@ bool pciehp_is_native(struct pci_dev *bridge) | |||
|  */ | ||||
| bool shpchp_is_native(struct pci_dev *bridge) | ||||
| { | ||||
| 	const struct pci_host_bridge *host; | ||||
| 
 | ||||
| 	if (!IS_ENABLED(CONFIG_HOTPLUG_PCI_SHPC)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * It is assumed that AMD GOLAM chips support SHPC but they do not | ||||
| 	 * have SHPC capability. | ||||
| 	 */ | ||||
| 	if (bridge->vendor == PCI_VENDOR_ID_AMD && | ||||
| 	    bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450) | ||||
| 		return true; | ||||
| 
 | ||||
| 	if (!pci_find_capability(bridge, PCI_CAP_ID_SHPC)) | ||||
| 		return false; | ||||
| 
 | ||||
| 	host = pci_find_host_bridge(bridge->bus); | ||||
| 	return host->native_shpc_hotplug; | ||||
| 	return bridge->shpc_managed; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  |  | |||
|  | @ -1448,7 +1448,9 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr, | |||
| 	if (val != 1) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	pm_runtime_get_sync(dev); | ||||
| 	result = pci_reset_function(pdev); | ||||
| 	pm_runtime_put(dev); | ||||
| 	if (result < 0) | ||||
| 		return result; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2289,7 +2289,7 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev) | |||
|  * @bridge: Bridge to check | ||||
|  * | ||||
|  * This function checks if it is possible to move the bridge to D3. | ||||
|  * Currently we only allow D3 for recent enough PCIe ports. | ||||
|  * Currently we only allow D3 for recent enough PCIe ports and Thunderbolt. | ||||
|  */ | ||||
| bool pci_bridge_d3_possible(struct pci_dev *bridge) | ||||
| { | ||||
|  | @ -2304,18 +2304,27 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) | |||
| 			return false; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Hotplug interrupts cannot be delivered if the link is down, | ||||
| 		 * so parents of a hotplug port must stay awake. In addition, | ||||
| 		 * hotplug ports handled by firmware in System Management Mode | ||||
| 		 * Hotplug ports handled by firmware in System Management Mode | ||||
| 		 * may not be put into D3 by the OS (Thunderbolt on non-Macs). | ||||
| 		 * For simplicity, disallow in general for now. | ||||
| 		 */ | ||||
| 		if (bridge->is_hotplug_bridge) | ||||
| 		if (bridge->is_hotplug_bridge && !pciehp_is_native(bridge)) | ||||
| 			return false; | ||||
| 
 | ||||
| 		if (pci_bridge_d3_force) | ||||
| 			return true; | ||||
| 
 | ||||
| 		/* Even the oldest 2010 Thunderbolt controller supports D3. */ | ||||
| 		if (bridge->is_thunderbolt) | ||||
| 			return true; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * Hotplug ports handled natively by the OS were not validated | ||||
| 		 * by vendors for runtime D3 at least until 2018 because there | ||||
| 		 * was no OS support. | ||||
| 		 */ | ||||
| 		if (bridge->is_hotplug_bridge) | ||||
| 			return false; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * It should be safe to put PCIe ports from 2015 or newer | ||||
| 		 * to D3. | ||||
|  |  | |||
|  | @ -50,6 +50,7 @@ struct pcie_port_service_driver { | |||
| 	int (*probe) (struct pcie_device *dev); | ||||
| 	void (*remove) (struct pcie_device *dev); | ||||
| 	int (*suspend) (struct pcie_device *dev); | ||||
| 	int (*resume_noirq) (struct pcie_device *dev); | ||||
| 	int (*resume) (struct pcie_device *dev); | ||||
| 
 | ||||
| 	/* Device driver may resume normal operations */ | ||||
|  | @ -82,6 +83,7 @@ extern struct bus_type pcie_port_bus_type; | |||
| int pcie_port_device_register(struct pci_dev *dev); | ||||
| #ifdef CONFIG_PM | ||||
| int pcie_port_device_suspend(struct device *dev); | ||||
| int pcie_port_device_resume_noirq(struct device *dev); | ||||
| int pcie_port_device_resume(struct device *dev); | ||||
| #endif | ||||
| void pcie_port_device_remove(struct pci_dev *dev); | ||||
|  |  | |||
|  | @ -353,14 +353,19 @@ error_disable: | |||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static int suspend_iter(struct device *dev, void *data) | ||||
| typedef int (*pcie_pm_callback_t)(struct pcie_device *); | ||||
| 
 | ||||
| static int pm_iter(struct device *dev, void *data) | ||||
| { | ||||
| 	struct pcie_port_service_driver *service_driver; | ||||
| 	size_t offset = *(size_t *)data; | ||||
| 	pcie_pm_callback_t cb; | ||||
| 
 | ||||
| 	if ((dev->bus == &pcie_port_bus_type) && dev->driver) { | ||||
| 		service_driver = to_service_driver(dev->driver); | ||||
| 		if (service_driver->suspend) | ||||
| 			service_driver->suspend(to_pcie_device(dev)); | ||||
| 		cb = *(pcie_pm_callback_t *)((void *)service_driver + offset); | ||||
| 		if (cb) | ||||
| 			return cb(to_pcie_device(dev)); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -371,20 +376,14 @@ static int suspend_iter(struct device *dev, void *data) | |||
|  */ | ||||
| int pcie_port_device_suspend(struct device *dev) | ||||
| { | ||||
| 	return device_for_each_child(dev, NULL, suspend_iter); | ||||
| 	size_t off = offsetof(struct pcie_port_service_driver, suspend); | ||||
| 	return device_for_each_child(dev, &off, pm_iter); | ||||
| } | ||||
| 
 | ||||
| static int resume_iter(struct device *dev, void *data) | ||||
| int pcie_port_device_resume_noirq(struct device *dev) | ||||
| { | ||||
| 	struct pcie_port_service_driver *service_driver; | ||||
| 
 | ||||
| 	if ((dev->bus == &pcie_port_bus_type) && | ||||
| 	    (dev->driver)) { | ||||
| 		service_driver = to_service_driver(dev->driver); | ||||
| 		if (service_driver->resume) | ||||
| 			service_driver->resume(to_pcie_device(dev)); | ||||
| 	} | ||||
| 	return 0; | ||||
| 	size_t off = offsetof(struct pcie_port_service_driver, resume_noirq); | ||||
| 	return device_for_each_child(dev, &off, pm_iter); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -393,7 +392,8 @@ static int resume_iter(struct device *dev, void *data) | |||
|  */ | ||||
| int pcie_port_device_resume(struct device *dev) | ||||
| { | ||||
| 	return device_for_each_child(dev, NULL, resume_iter); | ||||
| 	size_t off = offsetof(struct pcie_port_service_driver, resume); | ||||
| 	return device_for_each_child(dev, &off, pm_iter); | ||||
| } | ||||
| #endif /* PM */ | ||||
| 
 | ||||
|  |  | |||
|  | @ -65,10 +65,12 @@ static int pcie_port_runtime_idle(struct device *dev) | |||
| 
 | ||||
| static const struct dev_pm_ops pcie_portdrv_pm_ops = { | ||||
| 	.suspend	= pcie_port_device_suspend, | ||||
| 	.resume_noirq	= pcie_port_device_resume_noirq, | ||||
| 	.resume		= pcie_port_device_resume, | ||||
| 	.freeze		= pcie_port_device_suspend, | ||||
| 	.thaw		= pcie_port_device_resume, | ||||
| 	.poweroff	= pcie_port_device_suspend, | ||||
| 	.restore_noirq	= pcie_port_device_resume_noirq, | ||||
| 	.restore	= pcie_port_device_resume, | ||||
| 	.runtime_suspend = pcie_port_runtime_suspend, | ||||
| 	.runtime_resume	= pcie_port_runtime_resume, | ||||
|  |  | |||
|  | @ -858,12 +858,6 @@ static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void asus_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	kfree(hotplug_slot->info); | ||||
| 	kfree(hotplug_slot); | ||||
| } | ||||
| 
 | ||||
| static struct hotplug_slot_ops asus_hotplug_slot_ops = { | ||||
| 	.owner = THIS_MODULE, | ||||
| 	.get_adapter_status = asus_get_adapter_status, | ||||
|  | @ -905,7 +899,6 @@ static int asus_setup_pci_hotplug(struct asus_wmi *asus) | |||
| 		goto error_info; | ||||
| 
 | ||||
| 	asus->hotplug_slot->private = asus; | ||||
| 	asus->hotplug_slot->release = &asus_cleanup_pci_hotplug; | ||||
| 	asus->hotplug_slot->ops = &asus_hotplug_slot_ops; | ||||
| 	asus_get_adapter_status(asus->hotplug_slot, | ||||
| 				&asus->hotplug_slot->info->adapter_status); | ||||
|  | @ -1051,8 +1044,11 @@ static void asus_wmi_rfkill_exit(struct asus_wmi *asus) | |||
| 	 * asus_unregister_rfkill_notifier() | ||||
| 	 */ | ||||
| 	asus_rfkill_hotplug(asus); | ||||
| 	if (asus->hotplug_slot) | ||||
| 	if (asus->hotplug_slot) { | ||||
| 		pci_hp_deregister(asus->hotplug_slot); | ||||
| 		kfree(asus->hotplug_slot->info); | ||||
| 		kfree(asus->hotplug_slot); | ||||
| 	} | ||||
| 	if (asus->hotplug_workqueue) | ||||
| 		destroy_workqueue(asus->hotplug_workqueue); | ||||
| 
 | ||||
|  |  | |||
|  | @ -726,12 +726,6 @@ static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot) | ||||
| { | ||||
| 	kfree(hotplug_slot->info); | ||||
| 	kfree(hotplug_slot); | ||||
| } | ||||
| 
 | ||||
| static struct hotplug_slot_ops eeepc_hotplug_slot_ops = { | ||||
| 	.owner = THIS_MODULE, | ||||
| 	.get_adapter_status = eeepc_get_adapter_status, | ||||
|  | @ -758,7 +752,6 @@ static int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc) | |||
| 		goto error_info; | ||||
| 
 | ||||
| 	eeepc->hotplug_slot->private = eeepc; | ||||
| 	eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug; | ||||
| 	eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops; | ||||
| 	eeepc_get_adapter_status(eeepc->hotplug_slot, | ||||
| 				 &eeepc->hotplug_slot->info->adapter_status); | ||||
|  | @ -837,8 +830,11 @@ static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc) | |||
| 		eeepc->wlan_rfkill = NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	if (eeepc->hotplug_slot) | ||||
| 	if (eeepc->hotplug_slot) { | ||||
| 		pci_hp_deregister(eeepc->hotplug_slot); | ||||
| 		kfree(eeepc->hotplug_slot->info); | ||||
| 		kfree(eeepc->hotplug_slot); | ||||
| 	} | ||||
| 
 | ||||
| 	if (eeepc->bluetooth_rfkill) { | ||||
| 		rfkill_unregister(eeepc->bluetooth_rfkill); | ||||
|  |  | |||
|  | @ -388,6 +388,7 @@ struct pci_dev { | |||
| 	unsigned int	is_virtfn:1; | ||||
| 	unsigned int	reset_fn:1; | ||||
| 	unsigned int	is_hotplug_bridge:1; | ||||
| 	unsigned int	shpc_managed:1;		/* SHPC owned by shpchp */ | ||||
| 	unsigned int	is_thunderbolt:1;	/* Thunderbolt controller */ | ||||
| 	unsigned int	__aer_firmware_first_valid:1; | ||||
| 	unsigned int	__aer_firmware_first:1; | ||||
|  |  | |||
|  | @ -80,15 +80,12 @@ struct hotplug_slot_info { | |||
|  * @ops: pointer to the &struct hotplug_slot_ops to be used for this slot | ||||
|  * @info: pointer to the &struct hotplug_slot_info for the initial values for | ||||
|  * this slot. | ||||
|  * @release: called during pci_hp_deregister to free memory allocated in a | ||||
|  * hotplug_slot structure. | ||||
|  * @private: used by the hotplug pci controller driver to store whatever it | ||||
|  * needs. | ||||
|  */ | ||||
| struct hotplug_slot { | ||||
| 	struct hotplug_slot_ops		*ops; | ||||
| 	struct hotplug_slot_info	*info; | ||||
| 	void (*release) (struct hotplug_slot *slot); | ||||
| 	void				*private; | ||||
| 
 | ||||
| 	/* Variables below this are for use only by the hotplug pci core. */ | ||||
|  | @ -104,13 +101,23 @@ static inline const char *hotplug_slot_name(const struct hotplug_slot *slot) | |||
| int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *pbus, int nr, | ||||
| 		      const char *name, struct module *owner, | ||||
| 		      const char *mod_name); | ||||
| int pci_hp_deregister(struct hotplug_slot *slot); | ||||
| int __pci_hp_initialize(struct hotplug_slot *slot, struct pci_bus *bus, int nr, | ||||
| 			const char *name, struct module *owner, | ||||
| 			const char *mod_name); | ||||
| int pci_hp_add(struct hotplug_slot *slot); | ||||
| 
 | ||||
| void pci_hp_del(struct hotplug_slot *slot); | ||||
| void pci_hp_destroy(struct hotplug_slot *slot); | ||||
| void pci_hp_deregister(struct hotplug_slot *slot); | ||||
| 
 | ||||
| int __must_check pci_hp_change_slot_info(struct hotplug_slot *slot, | ||||
| 					 struct hotplug_slot_info *info); | ||||
| 
 | ||||
| /* use a define to avoid include chaining to get THIS_MODULE & friends */ | ||||
| #define pci_hp_register(slot, pbus, devnr, name) \ | ||||
| 	__pci_hp_register(slot, pbus, devnr, name, THIS_MODULE, KBUILD_MODNAME) | ||||
| #define pci_hp_initialize(slot, bus, nr, name) \ | ||||
| 	__pci_hp_initialize(slot, bus, nr, name, THIS_MODULE, KBUILD_MODNAME) | ||||
| 
 | ||||
| /* PCI Setting Record (Type 0) */ | ||||
| struct hpp_type0 { | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Bjorn Helgaas
						Bjorn Helgaas