diff options
author | Inaky Perez-Gonzalez <inaky@linux.intel.com> | 2006-11-22 15:40:31 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2006-12-01 17:36:59 -0500 |
commit | bae94d02371c402408a4edfb95e71e88dbd3e973 (patch) | |
tree | 8886acf5950d8f95d5d4d5a9737c462035709914 /drivers/pci/pci-sysfs.c | |
parent | 039d09a845209122c5193e650ab2d8b3c849ca7c (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/pci-sysfs.c')
-rw-r--r-- | drivers/pci/pci-sysfs.c | 33 |
1 files changed, 21 insertions, 12 deletions
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"); | |||
42 | pci_config_attr(subsystem_device, "0x%04x\n"); | 42 | pci_config_attr(subsystem_device, "0x%04x\n"); |
43 | pci_config_attr(class, "0x%06x\n"); | 43 | pci_config_attr(class, "0x%06x\n"); |
44 | pci_config_attr(irq, "%u\n"); | 44 | pci_config_attr(irq, "%u\n"); |
45 | pci_config_attr(is_enabled, "%u\n"); | ||
46 | 45 | ||
47 | static ssize_t broken_parity_status_show(struct device *dev, | 46 | static 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 | } |
115 | static ssize_t | 114 | |
116 | is_enabled_store(struct device *dev, struct device_attribute *attr, | 115 | static 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') | 137 | static 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 | ||
137 | static ssize_t | 146 | static ssize_t |