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