aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pci
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2010-02-17 17:39:08 -0500
committerJesse Barnes <jbarnes@virtuousgeek.org>2010-02-22 19:20:31 -0500
commitc7f486567c1d0acd2e4166c47069835b9f75e77b (patch)
tree5552890ac80fc53f61dd9c53a6211610375efa1f /drivers/pci
parent58ff463396ad00828e922d50998787e97fd32512 (diff)
PCI PM: PCIe PME root port service driver
PCIe native PME detection mechanism is based on interrupts generated by root ports or event collectors every time a PCIe device sends a PME message upstream. Once a PME message has been sent by an endpoint device and received by its root port (or event collector in the case of root complex integrated endpoints), the Requester ID from the message header is registered in the root port's Root Status register. At the same time, the PME Status bit of the Root Status register is set to indicate that there's a PME to handle. If PCIe PME interrupt is enabled for the root port, it generates an interrupt once the PME Status has been set. After receiving the interrupt, the kernel can identify the PCIe device that generated the PME using the Requester ID from the root port's Root Status register. [For details, see PCI Express Base Specification, Rev. 2.0.] Implement a driver for the PCIe PME root port service working in accordance with the above description. Based on a patch from Shaohua Li <shaohua.li@intel.com>. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Diffstat (limited to 'drivers/pci')
-rw-r--r--drivers/pci/pcie/Kconfig4
-rw-r--r--drivers/pci/pcie/Makefile2
-rw-r--r--drivers/pci/pcie/pme/Makefile8
-rw-r--r--drivers/pci/pcie/pme/pcie_pme.c493
-rw-r--r--drivers/pci/pcie/pme/pcie_pme.h28
-rw-r--r--drivers/pci/pcie/pme/pcie_pme_acpi.c54
6 files changed, 589 insertions, 0 deletions
diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig
index 5a0c6ad53f8e..b8b494b3e0d0 100644
--- a/drivers/pci/pcie/Kconfig
+++ b/drivers/pci/pcie/Kconfig
@@ -46,3 +46,7 @@ config PCIEASPM_DEBUG
46 help 46 help
47 This enables PCI Express ASPM debug support. It will add per-device 47 This enables PCI Express ASPM debug support. It will add per-device
48 interface to control ASPM. 48 interface to control ASPM.
49
50config PCIE_PME
51 def_bool y
52 depends on PCIEPORTBUS && PM_RUNTIME && EXPERIMENTAL && ACPI
diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile
index 11f6bb1eae24..ea654545e7c4 100644
--- a/drivers/pci/pcie/Makefile
+++ b/drivers/pci/pcie/Makefile
@@ -11,3 +11,5 @@ obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o
11 11
12# Build PCI Express AER if needed 12# Build PCI Express AER if needed
13obj-$(CONFIG_PCIEAER) += aer/ 13obj-$(CONFIG_PCIEAER) += aer/
14
15obj-$(CONFIG_PCIE_PME) += pme/
diff --git a/drivers/pci/pcie/pme/Makefile b/drivers/pci/pcie/pme/Makefile
new file mode 100644
index 000000000000..8b9238053080
--- /dev/null
+++ b/drivers/pci/pcie/pme/Makefile
@@ -0,0 +1,8 @@
1#
2# Makefile for PCI-Express Root Port PME signaling driver
3#
4
5obj-$(CONFIG_PCIE_PME) += pmedriver.o
6
7pmedriver-objs := pcie_pme.o
8pmedriver-$(CONFIG_ACPI) += pcie_pme_acpi.o
diff --git a/drivers/pci/pcie/pme/pcie_pme.c b/drivers/pci/pcie/pme/pcie_pme.c
new file mode 100644
index 000000000000..b5f96fb3cd83
--- /dev/null
+++ b/drivers/pci/pcie/pme/pcie_pme.c
@@ -0,0 +1,493 @@
1/*
2 * PCIe Native PME support
3 *
4 * Copyright (C) 2007 - 2009 Intel Corp
5 * Copyright (C) 2007 - 2009 Shaohua Li <shaohua.li@intel.com>
6 * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
7 *
8 * This file is subject to the terms and conditions of the GNU General Public
9 * License V2. See the file "COPYING" in the main directory of this archive
10 * for more details.
11 */
12
13#include <linux/module.h>
14#include <linux/pci.h>
15#include <linux/kernel.h>
16#include <linux/errno.h>
17#include <linux/init.h>
18#include <linux/interrupt.h>
19#include <linux/device.h>
20#include <linux/pcieport_if.h>
21#include <linux/acpi.h>
22#include <linux/pci-acpi.h>
23#include <linux/pm_runtime.h>
24
25#include "../../pci.h"
26#include "pcie_pme.h"
27
28#define PCI_EXP_RTSTA_PME 0x10000 /* PME status */
29#define PCI_EXP_RTSTA_PENDING 0x20000 /* PME pending */
30
31/*
32 * If set, this switch will prevent the PCIe root port PME service driver from
33 * being registered. Consequently, the interrupt-based PCIe PME signaling will
34 * not be used by any PCIe root ports in that case.
35 */
36static bool pcie_pme_disabled;
37
38/*
39 * The PCI Express Base Specification 2.0, Section 6.1.8, states the following:
40 * "In order to maintain compatibility with non-PCI Express-aware system
41 * software, system power management logic must be configured by firmware to use
42 * the legacy mechanism of signaling PME by default. PCI Express-aware system
43 * software must notify the firmware prior to enabling native, interrupt-based
44 * PME signaling." However, if the platform doesn't provide us with a suitable
45 * notification mechanism or the notification fails, it is not clear whether or
46 * not we are supposed to use the interrupt-based PCIe PME signaling. The
47 * switch below can be used to indicate the desired behaviour. When set, it
48 * will make the kernel use the interrupt-based PCIe PME signaling regardless of
49 * the platform notification status, although the kernel will attempt to notify
50 * the platform anyway. When unset, it will prevent the kernel from using the
51 * the interrupt-based PCIe PME signaling if the platform notification fails,
52 * which is the default.
53 */
54static bool pcie_pme_force_enable;
55
56static int __init pcie_pme_setup(char *str)
57{
58 if (!strcmp(str, "off"))
59 pcie_pme_disabled = true;
60 else if (!strcmp(str, "force"))
61 pcie_pme_force_enable = true;
62 return 1;
63}
64__setup("pcie_pme=", pcie_pme_setup);
65
66/**
67 * pcie_pme_platform_setup - Ensure that the kernel controls the PCIe PME.
68 * @srv: PCIe PME root port service to use for carrying out the check.
69 *
70 * Notify the platform that the native PCIe PME is going to be used and return
71 * 'true' if the control of the PCIe PME registers has been acquired from the
72 * platform.
73 */
74static bool pcie_pme_platform_setup(struct pcie_device *srv)
75{
76 return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable;
77}
78
79struct pcie_pme_service_data {
80 spinlock_t lock;
81 struct pcie_device *srv;
82 struct work_struct work;
83 bool noirq; /* Don't enable the PME interrupt used by this service. */
84};
85
86/**
87 * pcie_pme_interrupt_enable - Enable/disable PCIe PME interrupt generation.
88 * @dev: PCIe root port or event collector.
89 * @enable: Enable or disable the interrupt.
90 */
91static void pcie_pme_interrupt_enable(struct pci_dev *dev, bool enable)
92{
93 int rtctl_pos;
94 u16 rtctl;
95
96 rtctl_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTCTL;
97
98 pci_read_config_word(dev, rtctl_pos, &rtctl);
99 if (enable)
100 rtctl |= PCI_EXP_RTCTL_PMEIE;
101 else
102 rtctl &= ~PCI_EXP_RTCTL_PMEIE;
103 pci_write_config_word(dev, rtctl_pos, rtctl);
104}
105
106/**
107 * pcie_pme_clear_status - Clear root port PME interrupt status.
108 * @dev: PCIe root port or event collector.
109 */
110static void pcie_pme_clear_status(struct pci_dev *dev)
111{
112 int rtsta_pos;
113 u32 rtsta;
114
115 rtsta_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
116
117 pci_read_config_dword(dev, rtsta_pos, &rtsta);
118 rtsta |= PCI_EXP_RTSTA_PME;
119 pci_write_config_dword(dev, rtsta_pos, rtsta);
120}
121
122/**
123 * pcie_pme_walk_bus - Scan a PCI bus for devices asserting PME#.
124 * @bus: PCI bus to scan.
125 *
126 * Scan given PCI bus and all buses under it for devices asserting PME#.
127 */
128static bool pcie_pme_walk_bus(struct pci_bus *bus)
129{
130 struct pci_dev *dev;
131 bool ret = false;
132
133 list_for_each_entry(dev, &bus->devices, bus_list) {
134 /* Skip PCIe devices in case we started from a root port. */
135 if (!dev->is_pcie && pci_check_pme_status(dev)) {
136 pm_request_resume(&dev->dev);
137 ret = true;
138 }
139
140 if (dev->subordinate && pcie_pme_walk_bus(dev->subordinate))
141 ret = true;
142 }
143
144 return ret;
145}
146
147/**
148 * pcie_pme_from_pci_bridge - Check if PCIe-PCI bridge generated a PME.
149 * @bus: Secondary bus of the bridge.
150 * @devfn: Device/function number to check.
151 *
152 * PME from PCI devices under a PCIe-PCI bridge may be converted to an in-band
153 * PCIe PME message. In such that case the bridge should use the Requester ID
154 * of device/function number 0 on its secondary bus.
155 */
156static bool pcie_pme_from_pci_bridge(struct pci_bus *bus, u8 devfn)
157{
158 struct pci_dev *dev;
159 bool found = false;
160
161 if (devfn)
162 return false;
163
164 dev = pci_dev_get(bus->self);
165 if (!dev)
166 return false;
167
168 if (dev->is_pcie && dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) {
169 down_read(&pci_bus_sem);
170 if (pcie_pme_walk_bus(bus))
171 found = true;
172 up_read(&pci_bus_sem);
173 }
174
175 pci_dev_put(dev);
176 return found;
177}
178
179/**
180 * pcie_pme_handle_request - Find device that generated PME and handle it.
181 * @port: Root port or event collector that generated the PME interrupt.
182 * @req_id: PCIe Requester ID of the device that generated the PME.
183 */
184static void pcie_pme_handle_request(struct pci_dev *port, u16 req_id)
185{
186 u8 busnr = req_id >> 8, devfn = req_id & 0xff;
187 struct pci_bus *bus;
188 struct pci_dev *dev;
189 bool found = false;
190
191 /* First, check if the PME is from the root port itself. */
192 if (port->devfn == devfn && port->bus->number == busnr) {
193 if (pci_check_pme_status(port)) {
194 pm_request_resume(&port->dev);
195 found = true;
196 } else {
197 /*
198 * Apparently, the root port generated the PME on behalf
199 * of a non-PCIe device downstream. If this is done by
200 * a root port, the Requester ID field in its status
201 * register may contain either the root port's, or the
202 * source device's information (PCI Express Base
203 * Specification, Rev. 2.0, Section 6.1.9).
204 */
205 down_read(&pci_bus_sem);
206 found = pcie_pme_walk_bus(port->subordinate);
207 up_read(&pci_bus_sem);
208 }
209 goto out;
210 }
211
212 /* Second, find the bus the source device is on. */
213 bus = pci_find_bus(pci_domain_nr(port->bus), busnr);
214 if (!bus)
215 goto out;
216
217 /* Next, check if the PME is from a PCIe-PCI bridge. */
218 found = pcie_pme_from_pci_bridge(bus, devfn);
219 if (found)
220 goto out;
221
222 /* Finally, try to find the PME source on the bus. */
223 down_read(&pci_bus_sem);
224 list_for_each_entry(dev, &bus->devices, bus_list) {
225 pci_dev_get(dev);
226 if (dev->devfn == devfn) {
227 found = true;
228 break;
229 }
230 pci_dev_put(dev);
231 }
232 up_read(&pci_bus_sem);
233
234 if (found) {
235 /* The device is there, but we have to check its PME status. */
236 found = pci_check_pme_status(dev);
237 if (found)
238 pm_request_resume(&dev->dev);
239 pci_dev_put(dev);
240 } else if (devfn) {
241 /*
242 * The device is not there, but we can still try to recover by
243 * assuming that the PME was reported by a PCIe-PCI bridge that
244 * used devfn different from zero.
245 */
246 dev_dbg(&port->dev, "PME interrupt generated for "
247 "non-existent device %02x:%02x.%d\n",
248 busnr, PCI_SLOT(devfn), PCI_FUNC(devfn));
249 found = pcie_pme_from_pci_bridge(bus, 0);
250 }
251
252 out:
253 if (!found)
254 dev_dbg(&port->dev, "Spurious native PME interrupt!\n");
255}
256
257/**
258 * pcie_pme_work_fn - Work handler for PCIe PME interrupt.
259 * @work: Work structure giving access to service data.
260 */
261static void pcie_pme_work_fn(struct work_struct *work)
262{
263 struct pcie_pme_service_data *data =
264 container_of(work, struct pcie_pme_service_data, work);
265 struct pci_dev *port = data->srv->port;
266 int rtsta_pos;
267 u32 rtsta;
268
269 rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
270
271 spin_lock_irq(&data->lock);
272
273 for (;;) {
274 if (data->noirq)
275 break;
276
277 pci_read_config_dword(port, rtsta_pos, &rtsta);
278 if (rtsta & PCI_EXP_RTSTA_PME) {
279 /*
280 * Clear PME status of the port. If there are other
281 * pending PMEs, the status will be set again.
282 */
283 pcie_pme_clear_status(port);
284
285 spin_unlock_irq(&data->lock);
286 pcie_pme_handle_request(port, rtsta & 0xffff);
287 spin_lock_irq(&data->lock);
288
289 continue;
290 }
291
292 /* No need to loop if there are no more PMEs pending. */
293 if (!(rtsta & PCI_EXP_RTSTA_PENDING))
294 break;
295
296 spin_unlock_irq(&data->lock);
297 cpu_relax();
298 spin_lock_irq(&data->lock);
299 }
300
301 if (!data->noirq)
302 pcie_pme_interrupt_enable(port, true);
303
304 spin_unlock_irq(&data->lock);
305}
306
307/**
308 * pcie_pme_irq - Interrupt handler for PCIe root port PME interrupt.
309 * @irq: Interrupt vector.
310 * @context: Interrupt context pointer.
311 */
312static irqreturn_t pcie_pme_irq(int irq, void *context)
313{
314 struct pci_dev *port;
315 struct pcie_pme_service_data *data;
316 int rtsta_pos;
317 u32 rtsta;
318 unsigned long flags;
319
320 port = ((struct pcie_device *)context)->port;
321 data = get_service_data((struct pcie_device *)context);
322
323 rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
324
325 spin_lock_irqsave(&data->lock, flags);
326 pci_read_config_dword(port, rtsta_pos, &rtsta);
327
328 if (!(rtsta & PCI_EXP_RTSTA_PME)) {
329 spin_unlock_irqrestore(&data->lock, flags);
330 return IRQ_NONE;
331 }
332
333 pcie_pme_interrupt_enable(port, false);
334 spin_unlock_irqrestore(&data->lock, flags);
335
336 /* We don't use pm_wq, because it's freezable. */
337 schedule_work(&data->work);
338
339 return IRQ_HANDLED;
340}
341
342/**
343 * pcie_pme_set_native - Set the PME interrupt flag for given device.
344 * @dev: PCI device to handle.
345 * @ign: Ignored.
346 */
347static int pcie_pme_set_native(struct pci_dev *dev, void *ign)
348{
349 dev_info(&dev->dev, "Signaling PME through PCIe PME interrupt\n");
350
351 device_set_run_wake(&dev->dev, true);
352 dev->pme_interrupt = true;
353 return 0;
354}
355
356/**
357 * pcie_pme_mark_devices - Set the PME interrupt flag for devices below a port.
358 * @port: PCIe root port or event collector to handle.
359 *
360 * For each device below given root port, including the port itself (or for each
361 * root complex integrated endpoint if @port is a root complex event collector)
362 * set the flag indicating that it can signal run-time wake-up events via PCIe
363 * PME interrupts.
364 */
365static void pcie_pme_mark_devices(struct pci_dev *port)
366{
367 pcie_pme_set_native(port, NULL);
368 if (port->subordinate) {
369 pci_walk_bus(port->subordinate, pcie_pme_set_native, NULL);
370 } else {
371 struct pci_bus *bus = port->bus;
372 struct pci_dev *dev;
373
374 /* Check if this is a root port event collector. */
375 if (port->pcie_type != PCI_EXP_TYPE_RC_EC || !bus)
376 return;
377
378 down_read(&pci_bus_sem);
379 list_for_each_entry(dev, &bus->devices, bus_list)
380 if (dev->is_pcie
381 && dev->pcie_type == PCI_EXP_TYPE_RC_END)
382 pcie_pme_set_native(dev, NULL);
383 up_read(&pci_bus_sem);
384 }
385}
386
387/**
388 * pcie_pme_probe - Initialize PCIe PME service for given root port.
389 * @srv: PCIe service to initialize.
390 */
391static int pcie_pme_probe(struct pcie_device *srv)
392{
393 struct pci_dev *port;
394 struct pcie_pme_service_data *data;
395 int ret;
396
397 if (!pcie_pme_platform_setup(srv))
398 return -EACCES;
399
400 data = kzalloc(sizeof(*data), GFP_KERNEL);
401 if (!data)
402 return -ENOMEM;
403
404 spin_lock_init(&data->lock);
405 INIT_WORK(&data->work, pcie_pme_work_fn);
406 data->srv = srv;
407 set_service_data(srv, data);
408
409 port = srv->port;
410 pcie_pme_interrupt_enable(port, false);
411 pcie_pme_clear_status(port);
412
413 ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED, "PCIe PME", srv);
414 if (ret) {
415 kfree(data);
416 } else {
417 pcie_pme_mark_devices(port);
418 pcie_pme_interrupt_enable(port, true);
419 }
420
421 return ret;
422}
423
424/**
425 * pcie_pme_suspend - Suspend PCIe PME service device.
426 * @srv: PCIe service device to suspend.
427 */
428static int pcie_pme_suspend(struct pcie_device *srv)
429{
430 struct pcie_pme_service_data *data = get_service_data(srv);
431 struct pci_dev *port = srv->port;
432
433 spin_lock_irq(&data->lock);
434 pcie_pme_interrupt_enable(port, false);
435 pcie_pme_clear_status(port);
436 data->noirq = true;
437 spin_unlock_irq(&data->lock);
438
439 synchronize_irq(srv->irq);
440
441 return 0;
442}
443
444/**
445 * pcie_pme_resume - Resume PCIe PME service device.
446 * @srv - PCIe service device to resume.
447 */
448static int pcie_pme_resume(struct pcie_device *srv)
449{
450 struct pcie_pme_service_data *data = get_service_data(srv);
451 struct pci_dev *port = srv->port;
452
453 spin_lock_irq(&data->lock);
454 data->noirq = false;
455 pcie_pme_clear_status(port);
456 pcie_pme_interrupt_enable(port, true);
457 spin_unlock_irq(&data->lock);
458
459 return 0;
460}
461
462/**
463 * pcie_pme_remove - Prepare PCIe PME service device for removal.
464 * @srv - PCIe service device to resume.
465 */
466static void pcie_pme_remove(struct pcie_device *srv)
467{
468 pcie_pme_suspend(srv);
469 free_irq(srv->irq, srv);
470 kfree(get_service_data(srv));
471}
472
473static struct pcie_port_service_driver pcie_pme_driver = {
474 .name = "pcie_pme",
475 .port_type = PCI_EXP_TYPE_ROOT_PORT,
476 .service = PCIE_PORT_SERVICE_PME,
477
478 .probe = pcie_pme_probe,
479 .suspend = pcie_pme_suspend,
480 .resume = pcie_pme_resume,
481 .remove = pcie_pme_remove,
482};
483
484/**
485 * pcie_pme_service_init - Register the PCIe PME service driver.
486 */
487static int __init pcie_pme_service_init(void)
488{
489 return pcie_pme_disabled ?
490 -ENODEV : pcie_port_service_register(&pcie_pme_driver);
491}
492
493module_init(pcie_pme_service_init);
diff --git a/drivers/pci/pcie/pme/pcie_pme.h b/drivers/pci/pcie/pme/pcie_pme.h
new file mode 100644
index 000000000000..b30d2b7c9775
--- /dev/null
+++ b/drivers/pci/pcie/pme/pcie_pme.h
@@ -0,0 +1,28 @@
1/*
2 * drivers/pci/pcie/pme/pcie_pme.h
3 *
4 * PCI Express Root Port PME signaling support
5 *
6 * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
7 */
8
9#ifndef _PCIE_PME_H_
10#define _PCIE_PME_H_
11
12struct pcie_device;
13
14#ifdef CONFIG_ACPI
15extern int pcie_pme_acpi_setup(struct pcie_device *srv);
16
17static inline int pcie_pme_platform_notify(struct pcie_device *srv)
18{
19 return pcie_pme_acpi_setup(srv);
20}
21#else /* !CONFIG_ACPI */
22static inline int pcie_pme_platform_notify(struct pcie_device *srv)
23{
24 return 0;
25}
26#endif /* !CONFIG_ACPI */
27
28#endif
diff --git a/drivers/pci/pcie/pme/pcie_pme_acpi.c b/drivers/pci/pcie/pme/pcie_pme_acpi.c
new file mode 100644
index 000000000000..83ab2287ae3f
--- /dev/null
+++ b/drivers/pci/pcie/pme/pcie_pme_acpi.c
@@ -0,0 +1,54 @@
1/*
2 * PCIe Native PME support, ACPI-related part
3 *
4 * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License V2. See the file "COPYING" in the main directory of this archive
8 * for more details.
9 */
10
11#include <linux/pci.h>
12#include <linux/kernel.h>
13#include <linux/errno.h>
14#include <linux/acpi.h>
15#include <linux/pci-acpi.h>
16#include <linux/pcieport_if.h>
17
18/**
19 * pcie_pme_acpi_setup - Request the ACPI BIOS to release control over PCIe PME.
20 * @srv - PCIe PME service for a root port or event collector.
21 *
22 * Invoked when the PCIe bus type loads PCIe PME service driver. To avoid
23 * conflict with the BIOS PCIe support requires the BIOS to yield PCIe PME
24 * control to the kernel.
25 */
26int pcie_pme_acpi_setup(struct pcie_device *srv)
27{
28 acpi_status status = AE_NOT_FOUND;
29 struct pci_dev *port = srv->port;
30 acpi_handle handle;
31 int error = 0;
32
33 if (acpi_pci_disabled)
34 return -ENOSYS;
35
36 dev_info(&port->dev, "Requesting control of PCIe PME from ACPI BIOS\n");
37
38 handle = acpi_find_root_bridge_handle(port);
39 if (!handle)
40 return -EINVAL;
41
42 status = acpi_pci_osc_control_set(handle,
43 OSC_PCI_EXPRESS_PME_CONTROL |
44 OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL);
45 if (ACPI_FAILURE(status)) {
46 dev_info(&port->dev,
47 "Failed to receive control of PCIe PME service: %s\n",
48 (status == AE_SUPPORT || status == AE_NOT_FOUND) ?
49 "no _OSC support" : "ACPI _OSC failed");
50 error = -ENODEV;
51 }
52
53 return error;
54}