Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * File: portdrv_core.c | |
3 | * Purpose: PCI Express Port Bus Driver's Core Functions | |
4 | * | |
5 | * Copyright (C) 2004 Intel | |
6 | * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) | |
7 | */ | |
8 | ||
9 | #include <linux/module.h> | |
10 | #include <linux/pci.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/errno.h> | |
13 | #include <linux/pm.h> | |
4e57b681 TS |
14 | #include <linux/string.h> |
15 | #include <linux/slab.h> | |
1da177e4 LT |
16 | #include <linux/pcieport_if.h> |
17 | ||
18 | #include "portdrv.h" | |
19 | ||
20 | extern int pcie_mch_quirk; /* MSI-quirk Indicator */ | |
21 | ||
22 | static int pcie_port_probe_service(struct device *dev) | |
23 | { | |
24 | struct pcie_device *pciedev; | |
25 | struct pcie_port_service_driver *driver; | |
34438ba6 | 26 | int status; |
1da177e4 LT |
27 | |
28 | if (!dev || !dev->driver) | |
34438ba6 | 29 | return -ENODEV; |
1da177e4 LT |
30 | |
31 | driver = to_service_driver(dev->driver); | |
32 | if (!driver || !driver->probe) | |
34438ba6 | 33 | return -ENODEV; |
1da177e4 LT |
34 | |
35 | pciedev = to_pcie_device(dev); | |
36 | status = driver->probe(pciedev, driver->id_table); | |
37 | if (!status) { | |
34438ba6 BH |
38 | dev_printk(KERN_DEBUG, dev, "service driver %s loaded\n", |
39 | driver->name); | |
1da177e4 LT |
40 | get_device(dev); |
41 | } | |
42 | return status; | |
43 | } | |
44 | ||
45 | static int pcie_port_remove_service(struct device *dev) | |
46 | { | |
47 | struct pcie_device *pciedev; | |
48 | struct pcie_port_service_driver *driver; | |
49 | ||
50 | if (!dev || !dev->driver) | |
51 | return 0; | |
52 | ||
53 | pciedev = to_pcie_device(dev); | |
54 | driver = to_service_driver(dev->driver); | |
55 | if (driver && driver->remove) { | |
34438ba6 BH |
56 | dev_printk(KERN_DEBUG, dev, "unloading service driver %s\n", |
57 | driver->name); | |
1da177e4 LT |
58 | driver->remove(pciedev); |
59 | put_device(dev); | |
60 | } | |
61 | return 0; | |
62 | } | |
63 | ||
64 | static void pcie_port_shutdown_service(struct device *dev) {} | |
65 | ||
facf6d16 RW |
66 | /** |
67 | * release_pcie_device - free PCI Express port service device structure | |
68 | * @dev: Port service device to release | |
69 | * | |
70 | * Invoked automatically when device is being removed in response to | |
71 | * device_unregister(dev). Release all resources being claimed. | |
1da177e4 LT |
72 | */ |
73 | static void release_pcie_device(struct device *dev) | |
74 | { | |
1da177e4 LT |
75 | kfree(to_pcie_device(dev)); |
76 | } | |
77 | ||
78 | static int is_msi_quirked(struct pci_dev *dev) | |
79 | { | |
80 | int port_type, quirk = 0; | |
81 | u16 reg16; | |
82 | ||
83 | pci_read_config_word(dev, | |
84 | pci_find_capability(dev, PCI_CAP_ID_EXP) + | |
85 | PCIE_CAPABILITIES_REG, ®16); | |
86 | port_type = (reg16 >> 4) & PORT_TYPE_MASK; | |
87 | switch(port_type) { | |
88 | case PCIE_RC_PORT: | |
89 | if (pcie_mch_quirk == 1) | |
90 | quirk = 1; | |
91 | break; | |
92 | case PCIE_SW_UPSTREAM_PORT: | |
93 | case PCIE_SW_DOWNSTREAM_PORT: | |
94 | default: | |
95 | break; | |
96 | } | |
97 | return quirk; | |
98 | } | |
facf6d16 RW |
99 | |
100 | /** | |
101 | * assign_interrupt_mode - choose interrupt mode for PCI Express port services | |
102 | * (INTx, MSI-X, MSI) and set up vectors | |
103 | * @dev: PCI Express port to handle | |
104 | * @vectors: Array of interrupt vectors to populate | |
105 | * @mask: Bitmask of port capabilities returned by get_port_device_capability() | |
106 | * | |
107 | * Return value: Interrupt mode associated with the port | |
108 | */ | |
1da177e4 LT |
109 | static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask) |
110 | { | |
111 | int i, pos, nvec, status = -EINVAL; | |
112 | int interrupt_mode = PCIE_PORT_INTx_MODE; | |
113 | ||
114 | /* Set INTx as default */ | |
115 | for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { | |
116 | if (mask & (1 << i)) | |
117 | nvec++; | |
118 | vectors[i] = dev->irq; | |
119 | } | |
120 | ||
121 | /* Check MSI quirk */ | |
122 | if (is_msi_quirked(dev)) | |
123 | return interrupt_mode; | |
124 | ||
125 | /* Select MSI-X over MSI if supported */ | |
126 | pos = pci_find_capability(dev, PCI_CAP_ID_MSIX); | |
127 | if (pos) { | |
128 | struct msix_entry msix_entries[PCIE_PORT_DEVICE_MAXSERVICES] = | |
129 | {{0, 0}, {0, 1}, {0, 2}, {0, 3}}; | |
1da177e4 LT |
130 | status = pci_enable_msix(dev, msix_entries, nvec); |
131 | if (!status) { | |
132 | int j = 0; | |
133 | ||
134 | interrupt_mode = PCIE_PORT_MSIX_MODE; | |
135 | for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { | |
136 | if (mask & (1 << i)) | |
137 | vectors[i] = msix_entries[j++].vector; | |
138 | } | |
139 | } | |
140 | } | |
141 | if (status) { | |
142 | pos = pci_find_capability(dev, PCI_CAP_ID_MSI); | |
143 | if (pos) { | |
1da177e4 LT |
144 | status = pci_enable_msi(dev); |
145 | if (!status) { | |
146 | interrupt_mode = PCIE_PORT_MSI_MODE; | |
147 | for (i = 0;i < PCIE_PORT_DEVICE_MAXSERVICES;i++) | |
148 | vectors[i] = dev->irq; | |
149 | } | |
150 | } | |
151 | } | |
152 | return interrupt_mode; | |
153 | } | |
154 | ||
facf6d16 RW |
155 | /** |
156 | * get_port_device_capability - discover capabilities of a PCI Express port | |
157 | * @dev: PCI Express port to examine | |
158 | * | |
159 | * The capabilities are read from the port's PCI Express configuration registers | |
160 | * as described in PCI Express Base Specification 1.0a sections 7.8.2, 7.8.9 and | |
161 | * 7.9 - 7.11. | |
162 | * | |
163 | * Return value: Bitmask of discovered port capabilities | |
164 | */ | |
1da177e4 LT |
165 | static int get_port_device_capability(struct pci_dev *dev) |
166 | { | |
167 | int services = 0, pos; | |
168 | u16 reg16; | |
169 | u32 reg32; | |
170 | ||
171 | pos = pci_find_capability(dev, PCI_CAP_ID_EXP); | |
172 | pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®16); | |
173 | /* Hot-Plug Capable */ | |
174 | if (reg16 & PORT_TO_SLOT_MASK) { | |
175 | pci_read_config_dword(dev, | |
176 | pos + PCIE_SLOT_CAPABILITIES_REG, ®32); | |
177 | if (reg32 & SLOT_HP_CAPABLE_MASK) | |
178 | services |= PCIE_PORT_SERVICE_HP; | |
179 | } | |
39ec4561 SL |
180 | /* PME Capable - root port capability */ |
181 | if (((reg16 >> 4) & PORT_TYPE_MASK) == PCIE_RC_PORT) | |
1da177e4 | 182 | services |= PCIE_PORT_SERVICE_PME; |
0927678f JB |
183 | |
184 | if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR)) | |
185 | services |= PCIE_PORT_SERVICE_AER; | |
186 | if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_VC)) | |
187 | services |= PCIE_PORT_SERVICE_VC; | |
1da177e4 LT |
188 | |
189 | return services; | |
190 | } | |
191 | ||
facf6d16 RW |
192 | /** |
193 | * pcie_device_init - initialize PCI Express port service device | |
194 | * @dev: Port service device to initialize | |
195 | * @parent: PCI Express port to associate the service device with | |
196 | * @port_type: Type of the port | |
197 | * @service_type: Type of service to associate with the service device | |
198 | * @irq: Interrupt vector to associate with the service device | |
199 | * @irq_mode: Interrupt mode of the service (INTx, MSI-X, MSI) | |
200 | */ | |
1da177e4 LT |
201 | static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev, |
202 | int port_type, int service_type, int irq, int irq_mode) | |
203 | { | |
204 | struct device *device; | |
205 | ||
206 | dev->port = parent; | |
207 | dev->interrupt_mode = irq_mode; | |
208 | dev->irq = irq; | |
209 | dev->id.vendor = parent->vendor; | |
210 | dev->id.device = parent->device; | |
211 | dev->id.port_type = port_type; | |
212 | dev->id.service_type = (1 << service_type); | |
213 | ||
214 | /* Initialize generic device interface */ | |
215 | device = &dev->device; | |
216 | memset(device, 0, sizeof(struct device)); | |
1da177e4 LT |
217 | device->bus = &pcie_port_bus_type; |
218 | device->driver = NULL; | |
d0e2b4a0 | 219 | device->driver_data = NULL; |
1da177e4 | 220 | device->release = release_pcie_device; /* callback to free pcie dev */ |
1a927133 | 221 | dev_set_name(device, "%s:pcie%02x", |
8c9ad508 | 222 | pci_name(parent), get_descriptor_id(port_type, service_type)); |
1da177e4 LT |
223 | device->parent = &parent->dev; |
224 | } | |
225 | ||
facf6d16 RW |
226 | /** |
227 | * alloc_pcie_device - allocate PCI Express port service device structure | |
228 | * @parent: PCI Express port to associate the service device with | |
229 | * @port_type: Type of the port | |
230 | * @service_type: Type of service to associate with the service device | |
231 | * @irq: Interrupt vector to associate with the service device | |
232 | * @irq_mode: Interrupt mode of the service (INTx, MSI-X, MSI) | |
233 | */ | |
d0e2b4a0 | 234 | static struct pcie_device* alloc_pcie_device(struct pci_dev *parent, |
1da177e4 LT |
235 | int port_type, int service_type, int irq, int irq_mode) |
236 | { | |
237 | struct pcie_device *device; | |
238 | ||
f5afe806 | 239 | device = kzalloc(sizeof(struct pcie_device), GFP_KERNEL); |
1da177e4 LT |
240 | if (!device) |
241 | return NULL; | |
242 | ||
1da177e4 | 243 | pcie_device_init(parent, device, port_type, service_type, irq,irq_mode); |
1da177e4 LT |
244 | return device; |
245 | } | |
246 | ||
facf6d16 RW |
247 | /** |
248 | * pcie_port_device_probe - check if device is a PCI Express port | |
249 | * @dev: Device to check | |
250 | */ | |
1da177e4 LT |
251 | int pcie_port_device_probe(struct pci_dev *dev) |
252 | { | |
253 | int pos, type; | |
254 | u16 reg; | |
255 | ||
256 | if (!(pos = pci_find_capability(dev, PCI_CAP_ID_EXP))) | |
257 | return -ENODEV; | |
258 | ||
259 | pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, ®); | |
260 | type = (reg >> 4) & PORT_TYPE_MASK; | |
261 | if ( type == PCIE_RC_PORT || type == PCIE_SW_UPSTREAM_PORT || | |
d0e2b4a0 | 262 | type == PCIE_SW_DOWNSTREAM_PORT ) |
1da177e4 | 263 | return 0; |
d0e2b4a0 | 264 | |
1da177e4 LT |
265 | return -ENODEV; |
266 | } | |
267 | ||
facf6d16 RW |
268 | /** |
269 | * pcie_port_device_register - register PCI Express port | |
270 | * @dev: PCI Express port to register | |
271 | * | |
272 | * Allocate the port extension structure and register services associated with | |
273 | * the port. | |
274 | */ | |
1da177e4 LT |
275 | int pcie_port_device_register(struct pci_dev *dev) |
276 | { | |
5823d100 | 277 | struct pcie_port_device_ext *p_ext; |
1da177e4 LT |
278 | int status, type, capabilities, irq_mode, i; |
279 | int vectors[PCIE_PORT_DEVICE_MAXSERVICES]; | |
280 | u16 reg16; | |
281 | ||
5823d100 | 282 | /* Allocate port device extension */ |
283 | if (!(p_ext = kmalloc(sizeof(struct pcie_port_device_ext), GFP_KERNEL))) | |
284 | return -ENOMEM; | |
285 | ||
286 | pci_set_drvdata(dev, p_ext); | |
287 | ||
1da177e4 | 288 | /* Get port type */ |
d0e2b4a0 | 289 | pci_read_config_word(dev, |
290 | pci_find_capability(dev, PCI_CAP_ID_EXP) + | |
1da177e4 LT |
291 | PCIE_CAPABILITIES_REG, ®16); |
292 | type = (reg16 >> 4) & PORT_TYPE_MASK; | |
293 | ||
294 | /* Now get port services */ | |
295 | capabilities = get_port_device_capability(dev); | |
296 | irq_mode = assign_interrupt_mode(dev, vectors, capabilities); | |
5823d100 | 297 | p_ext->interrupt_mode = irq_mode; |
1da177e4 LT |
298 | |
299 | /* Allocate child services if any */ | |
300 | for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) { | |
301 | struct pcie_device *child; | |
302 | ||
303 | if (capabilities & (1 << i)) { | |
304 | child = alloc_pcie_device( | |
305 | dev, /* parent */ | |
d0e2b4a0 | 306 | type, /* port type */ |
1da177e4 LT |
307 | i, /* service type */ |
308 | vectors[i], /* irq */ | |
309 | irq_mode /* interrupt mode */); | |
d0e2b4a0 | 310 | if (child) { |
1da177e4 LT |
311 | status = device_register(&child->device); |
312 | if (status) { | |
313 | kfree(child); | |
314 | continue; | |
315 | } | |
316 | get_device(&child->device); | |
317 | } | |
318 | } | |
319 | } | |
320 | return 0; | |
321 | } | |
322 | ||
323 | #ifdef CONFIG_PM | |
d0e2b4a0 | 324 | static int suspend_iter(struct device *dev, void *data) |
1da177e4 | 325 | { |
1da177e4 | 326 | struct pcie_port_service_driver *service_driver; |
2a569579 | 327 | pm_message_t state = * (pm_message_t *) data; |
d0e2b4a0 | 328 | |
329 | if ((dev->bus == &pcie_port_bus_type) && | |
330 | (dev->driver)) { | |
331 | service_driver = to_service_driver(dev->driver); | |
332 | if (service_driver->suspend) | |
333 | service_driver->suspend(to_pcie_device(dev), state); | |
334 | } | |
335 | return 0; | |
336 | } | |
1da177e4 | 337 | |
facf6d16 RW |
338 | /** |
339 | * pcie_port_device_suspend - suspend port services associated with a PCIe port | |
340 | * @dev: PCI Express port to handle | |
341 | * @state: Representation of system power management transition in progress | |
342 | */ | |
2a569579 | 343 | int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state) |
d0e2b4a0 | 344 | { |
b19441af | 345 | return device_for_each_child(&dev->dev, &state, suspend_iter); |
1da177e4 LT |
346 | } |
347 | ||
d0e2b4a0 | 348 | static int resume_iter(struct device *dev, void *data) |
349 | { | |
1da177e4 LT |
350 | struct pcie_port_service_driver *service_driver; |
351 | ||
d0e2b4a0 | 352 | if ((dev->bus == &pcie_port_bus_type) && |
353 | (dev->driver)) { | |
354 | service_driver = to_service_driver(dev->driver); | |
355 | if (service_driver->resume) | |
356 | service_driver->resume(to_pcie_device(dev)); | |
1da177e4 | 357 | } |
d0e2b4a0 | 358 | return 0; |
359 | } | |
1da177e4 | 360 | |
facf6d16 RW |
361 | /** |
362 | * pcie_port_device_suspend - resume port services associated with a PCIe port | |
363 | * @dev: PCI Express port to handle | |
364 | */ | |
d0e2b4a0 | 365 | int pcie_port_device_resume(struct pci_dev *dev) |
366 | { | |
b19441af | 367 | return device_for_each_child(&dev->dev, NULL, resume_iter); |
1da177e4 LT |
368 | } |
369 | #endif | |
370 | ||
d0e2b4a0 | 371 | static int remove_iter(struct device *dev, void *data) |
1da177e4 | 372 | { |
1da177e4 | 373 | struct pcie_port_service_driver *service_driver; |
1da177e4 | 374 | |
d0e2b4a0 | 375 | if (dev->bus == &pcie_port_bus_type) { |
376 | if (dev->driver) { | |
377 | service_driver = to_service_driver(dev->driver); | |
378 | if (service_driver->remove) | |
379 | service_driver->remove(to_pcie_device(dev)); | |
1da177e4 | 380 | } |
d0e2b4a0 | 381 | *(unsigned long*)data = (unsigned long)dev; |
382 | return 1; | |
1da177e4 | 383 | } |
d0e2b4a0 | 384 | return 0; |
385 | } | |
386 | ||
facf6d16 RW |
387 | /** |
388 | * pcie_port_device_remove - unregister PCI Express port service devices | |
389 | * @dev: PCI Express port the service devices to unregister are associated with | |
390 | * | |
391 | * Remove PCI Express port service devices associated with given port and | |
392 | * disable MSI-X or MSI for the port. | |
393 | */ | |
d0e2b4a0 | 394 | void pcie_port_device_remove(struct pci_dev *dev) |
395 | { | |
396 | struct device *device; | |
397 | unsigned long device_addr; | |
398 | int interrupt_mode = PCIE_PORT_INTx_MODE; | |
399 | int status; | |
400 | ||
401 | do { | |
402 | status = device_for_each_child(&dev->dev, &device_addr, remove_iter); | |
403 | if (status) { | |
404 | device = (struct device*)device_addr; | |
405 | interrupt_mode = (to_pcie_device(device))->interrupt_mode; | |
406 | put_device(device); | |
407 | device_unregister(device); | |
408 | } | |
409 | } while (status); | |
1da177e4 LT |
410 | /* Switch to INTx by default if MSI enabled */ |
411 | if (interrupt_mode == PCIE_PORT_MSIX_MODE) | |
412 | pci_disable_msix(dev); | |
413 | else if (interrupt_mode == PCIE_PORT_MSI_MODE) | |
414 | pci_disable_msi(dev); | |
415 | } | |
416 | ||
3ec6a8d0 | 417 | int pcie_port_bus_register(void) |
1da177e4 | 418 | { |
20d51660 | 419 | return bus_register(&pcie_port_bus_type); |
1da177e4 LT |
420 | } |
421 | ||
422 | void pcie_port_bus_unregister(void) | |
423 | { | |
424 | bus_unregister(&pcie_port_bus_type); | |
425 | } | |
426 | ||
427 | int pcie_port_service_register(struct pcie_port_service_driver *new) | |
428 | { | |
429 | new->driver.name = (char *)new->name; | |
430 | new->driver.bus = &pcie_port_bus_type; | |
431 | new->driver.probe = pcie_port_probe_service; | |
432 | new->driver.remove = pcie_port_remove_service; | |
433 | new->driver.shutdown = pcie_port_shutdown_service; | |
1da177e4 LT |
434 | |
435 | return driver_register(&new->driver); | |
d0e2b4a0 | 436 | } |
1da177e4 LT |
437 | |
438 | void pcie_port_service_unregister(struct pcie_port_service_driver *new) | |
439 | { | |
440 | driver_unregister(&new->driver); | |
441 | } | |
442 | ||
443 | EXPORT_SYMBOL(pcie_port_service_register); | |
444 | EXPORT_SYMBOL(pcie_port_service_unregister); |