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