aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci
diff options
context:
space:
mode:
authorInaky Perez-Gonzalez <inaky@linux.intel.com>2006-11-22 15:40:31 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2006-12-01 17:36:59 -0500
commitbae94d02371c402408a4edfb95e71e88dbd3e973 (patch)
tree8886acf5950d8f95d5d4d5a9737c462035709914 /drivers/pci
parent039d09a845209122c5193e650ab2d8b3c849ca7c (diff)
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>
Diffstat (limited to 'drivers/pci')
-rw-r--r--drivers/pci/pci-driver.c4
-rw-r--r--drivers/pci/pci-sysfs.c33
-rw-r--r--drivers/pci/pci.c40
-rw-r--r--drivers/pci/pci.h1
4 files changed, 57 insertions, 21 deletions
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index 84ec9c8f6703..e5ae3a0c13bb 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -329,8 +329,8 @@ static int pci_default_resume(struct pci_dev *pci_dev)
329 /* restore the PCI config space */ 329 /* restore the PCI config space */
330 pci_restore_state(pci_dev); 330 pci_restore_state(pci_dev);
331 /* if the device was enabled before suspend, reenable */ 331 /* if the device was enabled before suspend, reenable */
332 if (pci_dev->is_enabled) 332 if (atomic_read(&pci_dev->enable_cnt))
333 retval = pci_enable_device(pci_dev); 333 retval = __pci_enable_device(pci_dev);
334 /* if the device was busmaster before the suspend, make it busmaster again */ 334 /* if the device was busmaster before the suspend, make it busmaster again */
335 if (pci_dev->is_busmaster) 335 if (pci_dev->is_busmaster)
336 pci_set_master(pci_dev); 336 pci_set_master(pci_dev);
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index f952bfea48a6..7a94076752d0 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -42,7 +42,6 @@ pci_config_attr(subsystem_vendor, "0x%04x\n");
42pci_config_attr(subsystem_device, "0x%04x\n"); 42pci_config_attr(subsystem_device, "0x%04x\n");
43pci_config_attr(class, "0x%06x\n"); 43pci_config_attr(class, "0x%06x\n");
44pci_config_attr(irq, "%u\n"); 44pci_config_attr(irq, "%u\n");
45pci_config_attr(is_enabled, "%u\n");
46 45
47static ssize_t broken_parity_status_show(struct device *dev, 46static ssize_t broken_parity_status_show(struct device *dev,
48 struct device_attribute *attr, 47 struct device_attribute *attr,
@@ -112,26 +111,36 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
112 (u8)(pci_dev->class >> 16), (u8)(pci_dev->class >> 8), 111 (u8)(pci_dev->class >> 16), (u8)(pci_dev->class >> 8),
113 (u8)(pci_dev->class)); 112 (u8)(pci_dev->class));
114} 113}
115static ssize_t 114
116is_enabled_store(struct device *dev, struct device_attribute *attr, 115static ssize_t is_enabled_store(struct device *dev,
117 const char *buf, size_t count) 116 struct device_attribute *attr, const char *buf,
117 size_t count)
118{ 118{
119 ssize_t result = -EINVAL;
119 struct pci_dev *pdev = to_pci_dev(dev); 120 struct pci_dev *pdev = to_pci_dev(dev);
120 int retval = 0;
121 121
122 /* this can crash the machine when done on the "wrong" device */ 122 /* this can crash the machine when done on the "wrong" device */
123 if (!capable(CAP_SYS_ADMIN)) 123 if (!capable(CAP_SYS_ADMIN))
124 return count; 124 return count;
125 125
126 if (*buf == '0') 126 if (*buf == '0') {
127 pci_disable_device(pdev); 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);
133
134 return result < 0 ? result : count;
135}
128 136
129 if (*buf == '1') 137static ssize_t is_enabled_show(struct device *dev,
130 retval = pci_enable_device(pdev); 138 struct device_attribute *attr, char *buf)
139{
140 struct pci_dev *pdev;
131 141
132 if (retval) 142 pdev = to_pci_dev (dev);
133 return retval; 143 return sprintf (buf, "%u\n", atomic_read(&pdev->enable_cnt));
134 return count;
135} 144}
136 145
137static ssize_t 146static ssize_t
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 427991741cf3..5a14b73cf3a1 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -612,30 +612,51 @@ pci_enable_device_bars(struct pci_dev *dev, int bars)
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 * @dev: PCI device to be initialized 616 * @dev: PCI device to be initialized
617 * 617 *
618 * Initialize device before it's used by a driver. Ask low-level code 618 * Initialize device before it's used by a driver. Ask low-level code
619 * to enable I/O and memory. Wake up the device if it was suspended. 619 * to enable I/O and memory. Wake up the device if it was suspended.
620 * Beware, this function can fail. 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 */
622int 625int
623pci_enable_device(struct pci_dev *dev) 626__pci_enable_device(struct pci_dev *dev)
624{ 627{
625 int err; 628 int err;
626 629
627 if (dev->is_enabled)
628 return 0;
629
630 err = pci_enable_device_bars(dev, (1 << PCI_NUM_RESOURCES) - 1); 630 err = pci_enable_device_bars(dev, (1 << PCI_NUM_RESOURCES) - 1);
631 if (err) 631 if (err)
632 return err; 632 return err;
633 pci_fixup_device(pci_fixup_enable, dev); 633 pci_fixup_device(pci_fixup_enable, dev);
634 dev->is_enabled = 1;
635 return 0; 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 */
648int 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 * pcibios_disable_device - disable arch specific PCI resources for device dev 660 * pcibios_disable_device - disable arch specific PCI resources for device dev
640 * @dev: the PCI device to disable 661 * @dev: the PCI device to disable
641 * 662 *
@@ -651,12 +672,18 @@ void __attribute__ ((weak)) pcibios_disable_device (struct pci_dev *dev) {}
651 * 672 *
652 * Signal to the system that the PCI device is not in use by the system 673 * Signal to the system that the PCI device is not in use by the system
653 * anymore. This only involves disabling PCI bus-mastering, if active. 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 */
655void 679void
656pci_disable_device(struct pci_dev *dev) 680pci_disable_device(struct pci_dev *dev)
657{ 681{
658 u16 pci_command; 682 u16 pci_command;
659 683
684 if (atomic_sub_return(1, &dev->enable_cnt) != 0)
685 return;
686
660 if (dev->msi_enabled) 687 if (dev->msi_enabled)
661 disable_msi_mode(dev, pci_find_capability(dev, PCI_CAP_ID_MSI), 688 disable_msi_mode(dev, pci_find_capability(dev, PCI_CAP_ID_MSI),
662 PCI_CAP_ID_MSI); 689 PCI_CAP_ID_MSI);
@@ -672,7 +699,6 @@ pci_disable_device(struct pci_dev *dev)
672 dev->is_busmaster = 0; 699 dev->is_busmaster = 0;
673 700
674 pcibios_disable_device(dev); 701 pcibios_disable_device(dev);
675 dev->is_enabled = 0;
676} 702}
677 703
678/** 704/**
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 6bf327db5c5e..398852f526a6 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1,5 +1,6 @@
1/* Functions internal to the PCI core code */ 1/* Functions internal to the PCI core code */
2 2
3extern int __must_check __pci_enable_device(struct pci_dev *);
3extern int pci_uevent(struct device *dev, char **envp, int num_envp, 4extern int pci_uevent(struct device *dev, char **envp, int num_envp,
4 char *buffer, int buffer_size); 5 char *buffer, int buffer_size);
5extern int pci_create_sysfs_dev_files(struct pci_dev *pdev); 6extern int pci_create_sysfs_dev_files(struct pci_dev *pdev);