Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * File: portdrv_pci.c | |
3 | * Purpose: PCI Express Port Bus Driver | |
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/init.h> | |
4e57b681 | 15 | #include <linux/slab.h> |
1da177e4 | 16 | #include <linux/pcieport_if.h> |
4bf3392e | 17 | #include <linux/aer.h> |
1da177e4 LT |
18 | |
19 | #include "portdrv.h" | |
4bf3392e | 20 | #include "aer/aerdrv.h" |
1da177e4 LT |
21 | |
22 | /* | |
23 | * Version Information | |
24 | */ | |
25 | #define DRIVER_VERSION "v1.0" | |
26 | #define DRIVER_AUTHOR "tom.l.nguyen@intel.com" | |
7e8af37a | 27 | #define DRIVER_DESC "PCIe Port Bus Driver" |
1da177e4 LT |
28 | MODULE_AUTHOR(DRIVER_AUTHOR); |
29 | MODULE_DESCRIPTION(DRIVER_DESC); | |
30 | MODULE_LICENSE("GPL"); | |
31 | ||
32 | /* global data */ | |
1da177e4 | 33 | |
4bf3392e ZY |
34 | static int pcie_portdrv_restore_config(struct pci_dev *dev) |
35 | { | |
36 | int retval; | |
37 | ||
4bf3392e ZY |
38 | retval = pci_enable_device(dev); |
39 | if (retval) | |
40 | return retval; | |
41 | pci_set_master(dev); | |
42 | return 0; | |
43 | } | |
44 | ||
0bed208e | 45 | #ifdef CONFIG_PM |
47145210 | 46 | static const struct dev_pm_ops pcie_portdrv_pm_ops = { |
3a3c244c RW |
47 | .suspend = pcie_port_device_suspend, |
48 | .resume = pcie_port_device_resume, | |
49 | .freeze = pcie_port_device_suspend, | |
50 | .thaw = pcie_port_device_resume, | |
51 | .poweroff = pcie_port_device_suspend, | |
52 | .restore = pcie_port_device_resume, | |
53 | }; | |
4bf3392e | 54 | |
3a3c244c | 55 | #define PCIE_PORTDRV_PM_OPS (&pcie_portdrv_pm_ops) |
a79d682f | 56 | |
3a3c244c RW |
57 | #else /* !PM */ |
58 | ||
59 | #define PCIE_PORTDRV_PM_OPS NULL | |
60 | #endif /* !PM */ | |
4bf3392e | 61 | |
1da177e4 LT |
62 | /* |
63 | * pcie_portdrv_probe - Probe PCI-Express port devices | |
64 | * @dev: PCI-Express port device being probed | |
65 | * | |
40da4186 | 66 | * If detected invokes the pcie_port_device_register() method for |
1da177e4 LT |
67 | * this port device. |
68 | * | |
69 | */ | |
898294c9 KK |
70 | static int __devinit pcie_portdrv_probe(struct pci_dev *dev, |
71 | const struct pci_device_id *id) | |
1da177e4 | 72 | { |
898294c9 | 73 | int status; |
1da177e4 | 74 | |
898294c9 KK |
75 | if (!pci_is_pcie(dev) || |
76 | ((dev->pcie_type != PCI_EXP_TYPE_ROOT_PORT) && | |
77 | (dev->pcie_type != PCI_EXP_TYPE_UPSTREAM) && | |
78 | (dev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM))) | |
79 | return -ENODEV; | |
1da177e4 | 80 | |
40da4186 | 81 | if (!dev->irq && dev->pin) { |
34a2e15e | 82 | dev_warn(&dev->dev, "device [%04x:%04x] has invalid IRQ; " |
34438ba6 | 83 | "check vendor BIOS\n", dev->vendor, dev->device); |
1da177e4 | 84 | } |
f118c0c3 RW |
85 | status = pcie_port_device_register(dev); |
86 | if (status) | |
87 | return status; | |
1da177e4 | 88 | |
87d2e2ec | 89 | pci_save_state(dev); |
4bf3392e | 90 | |
1da177e4 LT |
91 | return 0; |
92 | } | |
93 | ||
40da4186 | 94 | static void pcie_portdrv_remove(struct pci_dev *dev) |
1da177e4 LT |
95 | { |
96 | pcie_port_device_remove(dev); | |
d8998719 | 97 | pci_disable_device(dev); |
1da177e4 LT |
98 | } |
99 | ||
4bf3392e | 100 | static int error_detected_iter(struct device *device, void *data) |
60854838 | 101 | { |
4bf3392e ZY |
102 | struct pcie_device *pcie_device; |
103 | struct pcie_port_service_driver *driver; | |
104 | struct aer_broadcast_data *result_data; | |
105 | pci_ers_result_t status; | |
106 | ||
107 | result_data = (struct aer_broadcast_data *) data; | |
108 | ||
109 | if (device->bus == &pcie_port_bus_type && device->driver) { | |
110 | driver = to_service_driver(device->driver); | |
111 | if (!driver || | |
112 | !driver->err_handler || | |
113 | !driver->err_handler->error_detected) | |
114 | return 0; | |
115 | ||
116 | pcie_device = to_pcie_device(device); | |
117 | ||
118 | /* Forward error detected message to service drivers */ | |
119 | status = driver->err_handler->error_detected( | |
120 | pcie_device->port, | |
121 | result_data->state); | |
122 | result_data->result = | |
123 | merge_result(result_data->result, status); | |
124 | } | |
125 | ||
126 | return 0; | |
60854838 HK |
127 | } |
128 | ||
4bf3392e ZY |
129 | static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev, |
130 | enum pci_channel_state error) | |
60854838 | 131 | { |
40da4186 HS |
132 | struct aer_broadcast_data data = {error, PCI_ERS_RESULT_CAN_RECOVER}; |
133 | int ret; | |
4bf3392e | 134 | |
b19441af | 135 | /* can not fail */ |
40da4186 | 136 | ret = device_for_each_child(&dev->dev, &data, error_detected_iter); |
4bf3392e | 137 | |
40da4186 | 138 | return data.result; |
4bf3392e ZY |
139 | } |
140 | ||
141 | static int mmio_enabled_iter(struct device *device, void *data) | |
142 | { | |
143 | struct pcie_device *pcie_device; | |
144 | struct pcie_port_service_driver *driver; | |
145 | pci_ers_result_t status, *result; | |
146 | ||
147 | result = (pci_ers_result_t *) data; | |
148 | ||
149 | if (device->bus == &pcie_port_bus_type && device->driver) { | |
150 | driver = to_service_driver(device->driver); | |
151 | if (driver && | |
152 | driver->err_handler && | |
153 | driver->err_handler->mmio_enabled) { | |
154 | pcie_device = to_pcie_device(device); | |
155 | ||
156 | /* Forward error message to service drivers */ | |
157 | status = driver->err_handler->mmio_enabled( | |
158 | pcie_device->port); | |
159 | *result = merge_result(*result, status); | |
160 | } | |
161 | } | |
60854838 | 162 | |
60854838 HK |
163 | return 0; |
164 | } | |
165 | ||
4bf3392e | 166 | static pci_ers_result_t pcie_portdrv_mmio_enabled(struct pci_dev *dev) |
1da177e4 | 167 | { |
4bf3392e | 168 | pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED; |
b19441af | 169 | int retval; |
5823d100 | 170 | |
b19441af GKH |
171 | /* get true return value from &status */ |
172 | retval = device_for_each_child(&dev->dev, &status, mmio_enabled_iter); | |
4bf3392e | 173 | return status; |
1da177e4 LT |
174 | } |
175 | ||
4bf3392e | 176 | static int slot_reset_iter(struct device *device, void *data) |
1da177e4 | 177 | { |
4bf3392e ZY |
178 | struct pcie_device *pcie_device; |
179 | struct pcie_port_service_driver *driver; | |
180 | pci_ers_result_t status, *result; | |
181 | ||
182 | result = (pci_ers_result_t *) data; | |
183 | ||
184 | if (device->bus == &pcie_port_bus_type && device->driver) { | |
185 | driver = to_service_driver(device->driver); | |
186 | if (driver && | |
187 | driver->err_handler && | |
188 | driver->err_handler->slot_reset) { | |
189 | pcie_device = to_pcie_device(device); | |
190 | ||
191 | /* Forward error message to service drivers */ | |
192 | status = driver->err_handler->slot_reset( | |
193 | pcie_device->port); | |
194 | *result = merge_result(*result, status); | |
195 | } | |
196 | } | |
197 | ||
198 | return 0; | |
199 | } | |
200 | ||
201 | static pci_ers_result_t pcie_portdrv_slot_reset(struct pci_dev *dev) | |
202 | { | |
029091df | 203 | pci_ers_result_t status = PCI_ERS_RESULT_RECOVERED; |
b19441af | 204 | int retval; |
4bf3392e ZY |
205 | |
206 | /* If fatal, restore cfg space for possible link reset at upstream */ | |
207 | if (dev->error_state == pci_channel_io_frozen) { | |
e9d82888 | 208 | dev->state_saved = true; |
a79d682f | 209 | pci_restore_state(dev); |
4bf3392e ZY |
210 | pcie_portdrv_restore_config(dev); |
211 | pci_enable_pcie_error_reporting(dev); | |
212 | } | |
213 | ||
b19441af GKH |
214 | /* get true return value from &status */ |
215 | retval = device_for_each_child(&dev->dev, &status, slot_reset_iter); | |
4bf3392e ZY |
216 | |
217 | return status; | |
218 | } | |
219 | ||
220 | static int resume_iter(struct device *device, void *data) | |
221 | { | |
222 | struct pcie_device *pcie_device; | |
223 | struct pcie_port_service_driver *driver; | |
224 | ||
225 | if (device->bus == &pcie_port_bus_type && device->driver) { | |
226 | driver = to_service_driver(device->driver); | |
227 | if (driver && | |
228 | driver->err_handler && | |
229 | driver->err_handler->resume) { | |
230 | pcie_device = to_pcie_device(device); | |
231 | ||
232 | /* Forward error message to service drivers */ | |
233 | driver->err_handler->resume(pcie_device->port); | |
234 | } | |
235 | } | |
236 | ||
237 | return 0; | |
238 | } | |
239 | ||
240 | static void pcie_portdrv_err_resume(struct pci_dev *dev) | |
241 | { | |
b19441af GKH |
242 | int retval; |
243 | /* nothing to do with error value, if it ever happens */ | |
244 | retval = device_for_each_child(&dev->dev, NULL, resume_iter); | |
1da177e4 | 245 | } |
1da177e4 LT |
246 | |
247 | /* | |
248 | * LINUX Device Driver Model | |
249 | */ | |
250 | static const struct pci_device_id port_pci_ids[] = { { | |
251 | /* handle any PCI-Express port */ | |
252 | PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0), | |
253 | }, { /* end: all zeroes */ } | |
254 | }; | |
255 | MODULE_DEVICE_TABLE(pci, port_pci_ids); | |
256 | ||
4bf3392e ZY |
257 | static struct pci_error_handlers pcie_portdrv_err_handler = { |
258 | .error_detected = pcie_portdrv_error_detected, | |
259 | .mmio_enabled = pcie_portdrv_mmio_enabled, | |
260 | .slot_reset = pcie_portdrv_slot_reset, | |
261 | .resume = pcie_portdrv_err_resume, | |
262 | }; | |
263 | ||
3603a6a3 | 264 | static struct pci_driver pcie_portdriver = { |
e3fb20f9 | 265 | .name = "pcieport", |
1da177e4 LT |
266 | .id_table = &port_pci_ids[0], |
267 | ||
268 | .probe = pcie_portdrv_probe, | |
269 | .remove = pcie_portdrv_remove, | |
270 | ||
4bf3392e | 271 | .err_handler = &pcie_portdrv_err_handler, |
3a3c244c RW |
272 | |
273 | .driver.pm = PCIE_PORTDRV_PM_OPS, | |
1da177e4 LT |
274 | }; |
275 | ||
276 | static int __init pcie_portdrv_init(void) | |
277 | { | |
20d51660 | 278 | int retval; |
1da177e4 | 279 | |
20d51660 RD |
280 | retval = pcie_port_bus_register(); |
281 | if (retval) { | |
282 | printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval); | |
283 | goto out; | |
284 | } | |
3603a6a3 | 285 | retval = pci_register_driver(&pcie_portdriver); |
1da177e4 LT |
286 | if (retval) |
287 | pcie_port_bus_unregister(); | |
20d51660 | 288 | out: |
1da177e4 LT |
289 | return retval; |
290 | } | |
291 | ||
40da4186 | 292 | static void __exit pcie_portdrv_exit(void) |
1da177e4 | 293 | { |
3603a6a3 | 294 | pci_unregister_driver(&pcie_portdriver); |
1da177e4 LT |
295 | pcie_port_bus_unregister(); |
296 | } | |
297 | ||
298 | module_init(pcie_portdrv_init); | |
299 | module_exit(pcie_portdrv_exit); |