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