Commit bae94d02371c402408a4edfb95e71e88dbd3e973

Authored by Inaky Perez-Gonzalez
Committed by Greg Kroah-Hartman
1 parent 039d09a845

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
... ... @@ -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 /**
1 1 /* Functions internal to the PCI core code */
2 2  
  3 +extern int __must_check __pci_enable_device(struct pci_dev *);
3 4 extern int pci_uevent(struct device *dev, char **envp, int num_envp,
4 5 char *buffer, int buffer_size);
5 6 extern int pci_create_sysfs_dev_files(struct pci_dev *pdev);
... ... @@ -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;