Commit bae94d02371c402408a4edfb95e71e88dbd3e973
Committed by
Greg Kroah-Hartman
1 parent
039d09a845
Exists in
master
and in
7 other branches
PCI: switch pci_{enable,disable}_device() to be nestable
Changes the pci_{enable,disable}_device() functions to work in a nested basis, so that eg, three calls to enable_device() require three calls to disable_device(). The reason for this is to simplify PCI drivers for multi-interface/capability devices. These are devices that cram more than one interface in a single function. A relevant example of that is the Wireless [USB] Host Controller Interface (similar to EHCI) [see http://www.intel.com/technology/comms/wusb/whci.htm]. In these kind of devices, multiple interfaces are accessed through a single bar and IRQ line. For that, the drivers map only the smallest area of the bar to access their register banks and use shared IRQ handlers. However, because the order at which those drivers load cannot be known ahead of time, the sequence in which the calls to pci_enable_device() and pci_disable_device() cannot be predicted. Thus: 1. driverA starts pci_enable_device() 2. driverB starts pci_enable_device() 3. driverA shutdown pci_disable_device() 4. driverB shutdown pci_disable_device() between steps 3 and 4, driver B would loose access to it's device, even if it didn't intend to. By using this modification, the device won't be disabled until all the callers to enable() have called disable(). This is implemented by replacing 'struct pci_dev->is_enabled' from a bitfield to an atomic use count. Each caller to enable increments it, each caller to disable decrements it. When the count increments from 0 to 1, __pci_enable_device() is called to actually enable the device. When it drops to zero, pci_disable_device() actually does the disabling. We keep the backend __pci_enable_device() for pci_default_resume() to use and also change the sysfs method implementation, so that userspace enabling/disabling the device doesn't disable it one time too much. Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Showing 5 changed files with 59 additions and 22 deletions Side-by-side Diff
drivers/pci/pci-driver.c
... | ... | @@ -329,8 +329,8 @@ |
329 | 329 | /* restore the PCI config space */ |
330 | 330 | pci_restore_state(pci_dev); |
331 | 331 | /* if the device was enabled before suspend, reenable */ |
332 | - if (pci_dev->is_enabled) | |
333 | - retval = pci_enable_device(pci_dev); | |
332 | + if (atomic_read(&pci_dev->enable_cnt)) | |
333 | + retval = __pci_enable_device(pci_dev); | |
334 | 334 | /* if the device was busmaster before the suspend, make it busmaster again */ |
335 | 335 | if (pci_dev->is_busmaster) |
336 | 336 | pci_set_master(pci_dev); |
drivers/pci/pci-sysfs.c
... | ... | @@ -42,7 +42,6 @@ |
42 | 42 | pci_config_attr(subsystem_device, "0x%04x\n"); |
43 | 43 | pci_config_attr(class, "0x%06x\n"); |
44 | 44 | pci_config_attr(irq, "%u\n"); |
45 | -pci_config_attr(is_enabled, "%u\n"); | |
46 | 45 | |
47 | 46 | static ssize_t broken_parity_status_show(struct device *dev, |
48 | 47 | struct device_attribute *attr, |
49 | 48 | |
50 | 49 | |
51 | 50 | |
52 | 51 | |
53 | 52 | |
... | ... | @@ -112,26 +111,36 @@ |
112 | 111 | (u8)(pci_dev->class >> 16), (u8)(pci_dev->class >> 8), |
113 | 112 | (u8)(pci_dev->class)); |
114 | 113 | } |
115 | -static ssize_t | |
116 | -is_enabled_store(struct device *dev, struct device_attribute *attr, | |
117 | - const char *buf, size_t count) | |
114 | + | |
115 | +static ssize_t is_enabled_store(struct device *dev, | |
116 | + struct device_attribute *attr, const char *buf, | |
117 | + size_t count) | |
118 | 118 | { |
119 | + ssize_t result = -EINVAL; | |
119 | 120 | struct pci_dev *pdev = to_pci_dev(dev); |
120 | - int retval = 0; | |
121 | 121 | |
122 | 122 | /* this can crash the machine when done on the "wrong" device */ |
123 | 123 | if (!capable(CAP_SYS_ADMIN)) |
124 | 124 | return count; |
125 | 125 | |
126 | - if (*buf == '0') | |
127 | - pci_disable_device(pdev); | |
126 | + if (*buf == '0') { | |
127 | + if (atomic_read(&pdev->enable_cnt) != 0) | |
128 | + pci_disable_device(pdev); | |
129 | + else | |
130 | + result = -EIO; | |
131 | + } else if (*buf == '1') | |
132 | + result = pci_enable_device(pdev); | |
128 | 133 | |
129 | - if (*buf == '1') | |
130 | - retval = pci_enable_device(pdev); | |
134 | + return result < 0 ? result : count; | |
135 | +} | |
131 | 136 | |
132 | - if (retval) | |
133 | - return retval; | |
134 | - return count; | |
137 | +static ssize_t is_enabled_show(struct device *dev, | |
138 | + struct device_attribute *attr, char *buf) | |
139 | +{ | |
140 | + struct pci_dev *pdev; | |
141 | + | |
142 | + pdev = to_pci_dev (dev); | |
143 | + return sprintf (buf, "%u\n", atomic_read(&pdev->enable_cnt)); | |
135 | 144 | } |
136 | 145 | |
137 | 146 | static ssize_t |
drivers/pci/pci.c
... | ... | @@ -612,30 +612,51 @@ |
612 | 612 | } |
613 | 613 | |
614 | 614 | /** |
615 | - * pci_enable_device - Initialize device before it's used by a driver. | |
615 | + * __pci_enable_device - Initialize device before it's used by a driver. | |
616 | 616 | * @dev: PCI device to be initialized |
617 | 617 | * |
618 | 618 | * Initialize device before it's used by a driver. Ask low-level code |
619 | 619 | * to enable I/O and memory. Wake up the device if it was suspended. |
620 | 620 | * Beware, this function can fail. |
621 | + * | |
622 | + * Note this function is a backend and is not supposed to be called by | |
623 | + * normal code, use pci_enable_device() instead. | |
621 | 624 | */ |
622 | 625 | int |
623 | -pci_enable_device(struct pci_dev *dev) | |
626 | +__pci_enable_device(struct pci_dev *dev) | |
624 | 627 | { |
625 | 628 | int err; |
626 | 629 | |
627 | - if (dev->is_enabled) | |
628 | - return 0; | |
629 | - | |
630 | 630 | err = pci_enable_device_bars(dev, (1 << PCI_NUM_RESOURCES) - 1); |
631 | 631 | if (err) |
632 | 632 | return err; |
633 | 633 | pci_fixup_device(pci_fixup_enable, dev); |
634 | - dev->is_enabled = 1; | |
635 | 634 | return 0; |
636 | 635 | } |
637 | 636 | |
638 | 637 | /** |
638 | + * pci_enable_device - Initialize device before it's used by a driver. | |
639 | + * @dev: PCI device to be initialized | |
640 | + * | |
641 | + * Initialize device before it's used by a driver. Ask low-level code | |
642 | + * to enable I/O and memory. Wake up the device if it was suspended. | |
643 | + * Beware, this function can fail. | |
644 | + * | |
645 | + * Note we don't actually enable the device many times if we call | |
646 | + * this function repeatedly (we just increment the count). | |
647 | + */ | |
648 | +int pci_enable_device(struct pci_dev *dev) | |
649 | +{ | |
650 | + int result; | |
651 | + if (atomic_add_return(1, &dev->enable_cnt) > 1) | |
652 | + return 0; /* already enabled */ | |
653 | + result = __pci_enable_device(dev); | |
654 | + if (result < 0) | |
655 | + atomic_dec(&dev->enable_cnt); | |
656 | + return result; | |
657 | +} | |
658 | + | |
659 | +/** | |
639 | 660 | * pcibios_disable_device - disable arch specific PCI resources for device dev |
640 | 661 | * @dev: the PCI device to disable |
641 | 662 | * |
642 | 663 | |
... | ... | @@ -651,12 +672,18 @@ |
651 | 672 | * |
652 | 673 | * Signal to the system that the PCI device is not in use by the system |
653 | 674 | * anymore. This only involves disabling PCI bus-mastering, if active. |
675 | + * | |
676 | + * Note we don't actually disable the device until all callers of | |
677 | + * pci_device_enable() have called pci_device_disable(). | |
654 | 678 | */ |
655 | 679 | void |
656 | 680 | pci_disable_device(struct pci_dev *dev) |
657 | 681 | { |
658 | 682 | u16 pci_command; |
659 | 683 | |
684 | + if (atomic_sub_return(1, &dev->enable_cnt) != 0) | |
685 | + return; | |
686 | + | |
660 | 687 | if (dev->msi_enabled) |
661 | 688 | disable_msi_mode(dev, pci_find_capability(dev, PCI_CAP_ID_MSI), |
662 | 689 | PCI_CAP_ID_MSI); |
... | ... | @@ -672,7 +699,6 @@ |
672 | 699 | dev->is_busmaster = 0; |
673 | 700 | |
674 | 701 | pcibios_disable_device(dev); |
675 | - dev->is_enabled = 0; | |
676 | 702 | } |
677 | 703 | |
678 | 704 | /** |
drivers/pci/pci.h
include/linux/pci.h
... | ... | @@ -51,6 +51,7 @@ |
51 | 51 | #include <linux/list.h> |
52 | 52 | #include <linux/compiler.h> |
53 | 53 | #include <linux/errno.h> |
54 | +#include <asm/atomic.h> | |
54 | 55 | #include <linux/device.h> |
55 | 56 | |
56 | 57 | /* File state for mmap()s on /proc/bus/pci/X/Y */ |
... | ... | @@ -159,7 +160,6 @@ |
159 | 160 | unsigned int transparent:1; /* Transparent PCI bridge */ |
160 | 161 | unsigned int multifunction:1;/* Part of multi-function device */ |
161 | 162 | /* keep track of device state */ |
162 | - unsigned int is_enabled:1; /* pci_enable_device has been called */ | |
163 | 163 | unsigned int is_busmaster:1; /* device is busmaster */ |
164 | 164 | unsigned int no_msi:1; /* device may not use msi */ |
165 | 165 | unsigned int no_d1d2:1; /* only allow d0 or d3 */ |
... | ... | @@ -167,6 +167,7 @@ |
167 | 167 | unsigned int broken_parity_status:1; /* Device generates false positive parity */ |
168 | 168 | unsigned int msi_enabled:1; |
169 | 169 | unsigned int msix_enabled:1; |
170 | + atomic_t enable_cnt; /* pci_enable_device has been called */ | |
170 | 171 | |
171 | 172 | u32 saved_config_space[16]; /* config space saved at suspend time */ |
172 | 173 | struct hlist_head saved_cap_space; |