aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2009-01-23 18:23:22 -0500
committerJesse Barnes <jbarnes@hobbes.lan>2009-03-19 22:29:25 -0400
commitb43d451385ef833e0696032aac2629da04d46c59 (patch)
tree8bc4e838fdef893934d2cee19ae8f2629549bd4f
parenta52e2e3513d4beafe8fe8699f1519b021c2d05ba (diff)
PCI/PCIe portdrv: Fix allocation of interrupts
If MSI-X interrupt mode is used by the PCI Express port driver, too many vectors are allocated and it is not ensured that the right vectors will be used for the right services. Namely, the PCI Express specification states that both PCI Express native PME and PCI Express hotplug will always use the same MSI or MSI-X message for signalling interrupts, which implies that the same vector will be used by both of them. Also, the VC service does not use interrupts at all. Moreover, is not clear which of the vectors allocated by pci_enable_msix() in the current code will be used for PME and hotplug and which of them will be used for AER if all of these services are configured. For these reasons, rework the allocation of interrupts for PCI Express ports so that if MSI-X are enabled, the right vectors will be used for the right purposes. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Reviewed-by: Hidetoshi Seto <seto.hidetoshi@jp.fujitsu.com> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
-rw-r--r--drivers/pci/pcie/portdrv.h6
-rw-r--r--drivers/pci/pcie/portdrv_core.c206
-rw-r--r--include/linux/pcieport_if.h12
3 files changed, 181 insertions, 43 deletions
diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h
index ad4d082a0344..5b818bd835ef 100644
--- a/drivers/pci/pcie/portdrv.h
+++ b/drivers/pci/pcie/portdrv.h
@@ -25,6 +25,12 @@
25#define PCIE_CAPABILITIES_REG 0x2 25#define PCIE_CAPABILITIES_REG 0x2
26#define PCIE_SLOT_CAPABILITIES_REG 0x14 26#define PCIE_SLOT_CAPABILITIES_REG 0x14
27#define PCIE_PORT_DEVICE_MAXSERVICES 4 27#define PCIE_PORT_DEVICE_MAXSERVICES 4
28#define PCIE_PORT_MSI_VECTOR_MASK 0x1f
29/*
30 * According to the PCI Express Base Specification 2.0, the indices of the MSI-X
31 * table entires used by port services must not exceed 31
32 */
33#define PCIE_PORT_MAX_MSIX_ENTRIES 32
28 34
29#define get_descriptor_id(type, service) (((type - 4) << 4) | service) 35#define get_descriptor_id(type, service) (((type - 4) << 4) | service)
30 36
diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c
index 843d9e30dd3b..3aea92a92928 100644
--- a/drivers/pci/pcie/portdrv_core.c
+++ b/drivers/pci/pcie/portdrv_core.c
@@ -31,6 +31,152 @@ static void release_pcie_device(struct device *dev)
31} 31}
32 32
33/** 33/**
34 * pcie_port_msix_add_entry - add entry to given array of MSI-X entries
35 * @entries: Array of MSI-X entries
36 * @new_entry: Index of the entry to add to the array
37 * @nr_entries: Number of entries aleady in the array
38 *
39 * Return value: Position of the added entry in the array
40 */
41static int pcie_port_msix_add_entry(
42 struct msix_entry *entries, int new_entry, int nr_entries)
43{
44 int j;
45
46 for (j = 0; j < nr_entries; j++)
47 if (entries[j].entry == new_entry)
48 return j;
49
50 entries[j].entry = new_entry;
51 return j;
52}
53
54/**
55 * pcie_port_enable_msix - try to set up MSI-X as interrupt mode for given port
56 * @dev: PCI Express port to handle
57 * @vectors: Array of interrupt vectors to populate
58 * @mask: Bitmask of port capabilities returned by get_port_device_capability()
59 *
60 * Return value: 0 on success, error code on failure
61 */
62static int pcie_port_enable_msix(struct pci_dev *dev, int *vectors, int mask)
63{
64 struct msix_entry *msix_entries;
65 int idx[PCIE_PORT_DEVICE_MAXSERVICES];
66 int nr_entries, status, pos, i, nvec;
67 u16 reg16;
68 u32 reg32;
69
70 nr_entries = pci_msix_table_size(dev);
71 if (!nr_entries)
72 return -EINVAL;
73 if (nr_entries > PCIE_PORT_MAX_MSIX_ENTRIES)
74 nr_entries = PCIE_PORT_MAX_MSIX_ENTRIES;
75
76 msix_entries = kzalloc(sizeof(*msix_entries) * nr_entries, GFP_KERNEL);
77 if (!msix_entries)
78 return -ENOMEM;
79
80 /*
81 * Allocate as many entries as the port wants, so that we can check
82 * which of them will be useful. Moreover, if nr_entries is correctly
83 * equal to the number of entries this port actually uses, we'll happily
84 * go through without any tricks.
85 */
86 for (i = 0; i < nr_entries; i++)
87 msix_entries[i].entry = i;
88
89 status = pci_enable_msix(dev, msix_entries, nr_entries);
90 if (status)
91 goto Exit;
92
93 for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
94 idx[i] = -1;
95 status = -EIO;
96 nvec = 0;
97
98 if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP)) {
99 int entry;
100
101 /*
102 * The code below follows the PCI Express Base Specification 2.0
103 * stating in Section 6.1.6 that "PME and Hot-Plug Event
104 * interrupts (when both are implemented) always share the same
105 * MSI or MSI-X vector, as indicated by the Interrupt Message
106 * Number field in the PCI Express Capabilities register", where
107 * according to Section 7.8.2 of the specification "For MSI-X,
108 * the value in this field indicates which MSI-X Table entry is
109 * used to generate the interrupt message."
110 */
111 pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
112 pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, &reg16);
113 entry = (reg16 >> 9) & PCIE_PORT_MSI_VECTOR_MASK;
114 if (entry >= nr_entries)
115 goto Error;
116
117 i = pcie_port_msix_add_entry(msix_entries, entry, nvec);
118 if (i == nvec)
119 nvec++;
120
121 idx[PCIE_PORT_SERVICE_PME_SHIFT] = i;
122 idx[PCIE_PORT_SERVICE_HP_SHIFT] = i;
123 }
124
125 if (mask & PCIE_PORT_SERVICE_AER) {
126 int entry;
127
128 /*
129 * The code below follows Section 7.10.10 of the PCI Express
130 * Base Specification 2.0 stating that bits 31-27 of the Root
131 * Error Status Register contain a value indicating which of the
132 * MSI/MSI-X vectors assigned to the port is going to be used
133 * for AER, where "For MSI-X, the value in this register
134 * indicates which MSI-X Table entry is used to generate the
135 * interrupt message."
136 */
137 pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR);
138 pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &reg32);
139 entry = reg32 >> 27;
140 if (entry >= nr_entries)
141 goto Error;
142
143 i = pcie_port_msix_add_entry(msix_entries, entry, nvec);
144 if (i == nvec)
145 nvec++;
146
147 idx[PCIE_PORT_SERVICE_AER_SHIFT] = i;
148 }
149
150 /*
151 * If nvec is equal to the allocated number of entries, we can just use
152 * what we have. Otherwise, the port has some extra entries not for the
153 * services we know and we need to work around that.
154 */
155 if (nvec == nr_entries) {
156 status = 0;
157 } else {
158 /* Drop the temporary MSI-X setup */
159 pci_disable_msix(dev);
160
161 /* Now allocate the MSI-X vectors for real */
162 status = pci_enable_msix(dev, msix_entries, nvec);
163 if (status)
164 goto Exit;
165 }
166
167 for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
168 vectors[i] = idx[i] >= 0 ? msix_entries[idx[i]].vector : -1;
169
170 Exit:
171 kfree(msix_entries);
172 return status;
173
174 Error:
175 pci_disable_msix(dev);
176 goto Exit;
177}
178
179/**
34 * assign_interrupt_mode - choose interrupt mode for PCI Express port services 180 * assign_interrupt_mode - choose interrupt mode for PCI Express port services
35 * (INTx, MSI-X, MSI) and set up vectors 181 * (INTx, MSI-X, MSI) and set up vectors
36 * @dev: PCI Express port to handle 182 * @dev: PCI Express port to handle
@@ -42,49 +188,31 @@ static void release_pcie_device(struct device *dev)
42static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) 188static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask)
43{ 189{
44 struct pcie_port_data *port_data = pci_get_drvdata(dev); 190 struct pcie_port_data *port_data = pci_get_drvdata(dev);
45 int i, pos, nvec, status = -EINVAL; 191 int irq, interrupt_mode = PCIE_PORT_NO_IRQ;
46 int interrupt_mode = PCIE_PORT_NO_IRQ; 192 int i;
47
48 /* Set INTx as default */
49 for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
50 if (mask & (1 << i))
51 nvec++;
52 vectors[i] = dev->irq;
53 }
54 if (dev->pin)
55 interrupt_mode = PCIE_PORT_INTx_MODE;
56 193
57 /* Check MSI quirk */ 194 /* Check MSI quirk */
58 if (port_data->port_type == PCIE_RC_PORT && pcie_mch_quirk) 195 if (port_data->port_type == PCIE_RC_PORT && pcie_mch_quirk)
59 return interrupt_mode; 196 goto Fallback;
197
198 /* Try to use MSI-X if supported */
199 if (!pcie_port_enable_msix(dev, vectors, mask))
200 return PCIE_PORT_MSIX_MODE;
201
202 /* We're not going to use MSI-X, so try MSI and fall back to INTx */
203 if (!pci_enable_msi(dev))
204 interrupt_mode = PCIE_PORT_MSI_MODE;
205
206 Fallback:
207 if (interrupt_mode == PCIE_PORT_NO_IRQ && dev->pin)
208 interrupt_mode = PCIE_PORT_INTx_MODE;
209
210 irq = interrupt_mode != PCIE_PORT_NO_IRQ ? dev->irq : -1;
211 for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
212 vectors[i] = irq;
213
214 vectors[PCIE_PORT_SERVICE_VC_SHIFT] = -1;
60 215
61 /* Select MSI-X over MSI if supported */
62 pos = pci_find_capability(dev, PCI_CAP_ID_MSIX);
63 if (pos) {
64 struct msix_entry msix_entries[PCIE_PORT_DEVICE_MAXSERVICES] =
65 {{0, 0}, {0, 1}, {0, 2}, {0, 3}};
66 status = pci_enable_msix(dev, msix_entries, nvec);
67 if (!status) {
68 int j = 0;
69
70 interrupt_mode = PCIE_PORT_MSIX_MODE;
71 for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
72 if (mask & (1 << i))
73 vectors[i] = msix_entries[j++].vector;
74 }
75 }
76 }
77 if (status) {
78 pos = pci_find_capability(dev, PCI_CAP_ID_MSI);
79 if (pos) {
80 status = pci_enable_msi(dev);
81 if (!status) {
82 interrupt_mode = PCIE_PORT_MSI_MODE;
83 for (i = 0;i < PCIE_PORT_DEVICE_MAXSERVICES;i++)
84 vectors[i] = dev->irq;
85 }
86 }
87 }
88 return interrupt_mode; 216 return interrupt_mode;
89} 217}
90 218
diff --git a/include/linux/pcieport_if.h b/include/linux/pcieport_if.h
index a3832079508e..5d2afcfa6bc1 100644
--- a/include/linux/pcieport_if.h
+++ b/include/linux/pcieport_if.h
@@ -16,10 +16,14 @@
16#define PCIE_ANY_PORT 7 16#define PCIE_ANY_PORT 7
17 17
18/* Service Type */ 18/* Service Type */
19#define PCIE_PORT_SERVICE_PME 1 /* Power Management Event */ 19#define PCIE_PORT_SERVICE_PME_SHIFT 0 /* Power Management Event */
20#define PCIE_PORT_SERVICE_AER 2 /* Advanced Error Reporting */ 20#define PCIE_PORT_SERVICE_PME (1 << PCIE_PORT_SERVICE_PME_SHIFT)
21#define PCIE_PORT_SERVICE_HP 4 /* Native Hotplug */ 21#define PCIE_PORT_SERVICE_AER_SHIFT 1 /* Advanced Error Reporting */
22#define PCIE_PORT_SERVICE_VC 8 /* Virtual Channel */ 22#define PCIE_PORT_SERVICE_AER (1 << PCIE_PORT_SERVICE_AER_SHIFT)
23#define PCIE_PORT_SERVICE_HP_SHIFT 2 /* Native Hotplug */
24#define PCIE_PORT_SERVICE_HP (1 << PCIE_PORT_SERVICE_HP_SHIFT)
25#define PCIE_PORT_SERVICE_VC_SHIFT 3 /* Virtual Channel */
26#define PCIE_PORT_SERVICE_VC (1 << PCIE_PORT_SERVICE_VC_SHIFT)
23 27
24/* Root/Upstream/Downstream Port's Interrupt Mode */ 28/* Root/Upstream/Downstream Port's Interrupt Mode */
25#define PCIE_PORT_NO_IRQ (-1) 29#define PCIE_PORT_NO_IRQ (-1)