diff options
-rw-r--r-- | drivers/pci/pcie/portdrv.h | 6 | ||||
-rw-r--r-- | drivers/pci/pcie/portdrv_core.c | 206 | ||||
-rw-r--r-- | include/linux/pcieport_if.h | 12 |
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 | */ | ||
41 | static 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 | */ | ||
62 | static 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, ®16); | ||
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, ®32); | ||
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) | |||
42 | static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) | 188 | static 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) |