diff options
Diffstat (limited to 'arch/x86/kernel/efi.c')
-rw-r--r-- | arch/x86/kernel/efi.c | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/arch/x86/kernel/efi.c b/arch/x86/kernel/efi.c new file mode 100644 index 000000000000..1411324a625c --- /dev/null +++ b/arch/x86/kernel/efi.c | |||
@@ -0,0 +1,512 @@ | |||
1 | /* | ||
2 | * Common EFI (Extensible Firmware Interface) support functions | ||
3 | * Based on Extensible Firmware Interface Specification version 1.0 | ||
4 | * | ||
5 | * Copyright (C) 1999 VA Linux Systems | ||
6 | * Copyright (C) 1999 Walt Drummond <drummond@valinux.com> | ||
7 | * Copyright (C) 1999-2002 Hewlett-Packard Co. | ||
8 | * David Mosberger-Tang <davidm@hpl.hp.com> | ||
9 | * Stephane Eranian <eranian@hpl.hp.com> | ||
10 | * Copyright (C) 2005-2008 Intel Co. | ||
11 | * Fenghua Yu <fenghua.yu@intel.com> | ||
12 | * Bibo Mao <bibo.mao@intel.com> | ||
13 | * Chandramouli Narayanan <mouli@linux.intel.com> | ||
14 | * Huang Ying <ying.huang@intel.com> | ||
15 | * | ||
16 | * Copied from efi_32.c to eliminate the duplicated code between EFI | ||
17 | * 32/64 support code. --ying 2007-10-26 | ||
18 | * | ||
19 | * All EFI Runtime Services are not implemented yet as EFI only | ||
20 | * supports physical mode addressing on SoftSDV. This is to be fixed | ||
21 | * in a future version. --drummond 1999-07-20 | ||
22 | * | ||
23 | * Implemented EFI runtime services and virtual mode calls. --davidm | ||
24 | * | ||
25 | * Goutham Rao: <goutham.rao@intel.com> | ||
26 | * Skip non-WB memory and ignore empty memory ranges. | ||
27 | */ | ||
28 | |||
29 | #include <linux/kernel.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/efi.h> | ||
32 | #include <linux/bootmem.h> | ||
33 | #include <linux/spinlock.h> | ||
34 | #include <linux/uaccess.h> | ||
35 | #include <linux/time.h> | ||
36 | #include <linux/io.h> | ||
37 | #include <linux/reboot.h> | ||
38 | #include <linux/bcd.h> | ||
39 | |||
40 | #include <asm/setup.h> | ||
41 | #include <asm/efi.h> | ||
42 | #include <asm/time.h> | ||
43 | #include <asm/cacheflush.h> | ||
44 | #include <asm/tlbflush.h> | ||
45 | |||
46 | #define EFI_DEBUG 1 | ||
47 | #define PFX "EFI: " | ||
48 | |||
49 | int efi_enabled; | ||
50 | EXPORT_SYMBOL(efi_enabled); | ||
51 | |||
52 | struct efi efi; | ||
53 | EXPORT_SYMBOL(efi); | ||
54 | |||
55 | struct efi_memory_map memmap; | ||
56 | |||
57 | struct efi efi_phys __initdata; | ||
58 | static efi_system_table_t efi_systab __initdata; | ||
59 | |||
60 | static int __init setup_noefi(char *arg) | ||
61 | { | ||
62 | efi_enabled = 0; | ||
63 | return 0; | ||
64 | } | ||
65 | early_param("noefi", setup_noefi); | ||
66 | |||
67 | static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc) | ||
68 | { | ||
69 | return efi_call_virt2(get_time, tm, tc); | ||
70 | } | ||
71 | |||
72 | static efi_status_t virt_efi_set_time(efi_time_t *tm) | ||
73 | { | ||
74 | return efi_call_virt1(set_time, tm); | ||
75 | } | ||
76 | |||
77 | static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled, | ||
78 | efi_bool_t *pending, | ||
79 | efi_time_t *tm) | ||
80 | { | ||
81 | return efi_call_virt3(get_wakeup_time, | ||
82 | enabled, pending, tm); | ||
83 | } | ||
84 | |||
85 | static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm) | ||
86 | { | ||
87 | return efi_call_virt2(set_wakeup_time, | ||
88 | enabled, tm); | ||
89 | } | ||
90 | |||
91 | static efi_status_t virt_efi_get_variable(efi_char16_t *name, | ||
92 | efi_guid_t *vendor, | ||
93 | u32 *attr, | ||
94 | unsigned long *data_size, | ||
95 | void *data) | ||
96 | { | ||
97 | return efi_call_virt5(get_variable, | ||
98 | name, vendor, attr, | ||
99 | data_size, data); | ||
100 | } | ||
101 | |||
102 | static efi_status_t virt_efi_get_next_variable(unsigned long *name_size, | ||
103 | efi_char16_t *name, | ||
104 | efi_guid_t *vendor) | ||
105 | { | ||
106 | return efi_call_virt3(get_next_variable, | ||
107 | name_size, name, vendor); | ||
108 | } | ||
109 | |||
110 | static efi_status_t virt_efi_set_variable(efi_char16_t *name, | ||
111 | efi_guid_t *vendor, | ||
112 | unsigned long attr, | ||
113 | unsigned long data_size, | ||
114 | void *data) | ||
115 | { | ||
116 | return efi_call_virt5(set_variable, | ||
117 | name, vendor, attr, | ||
118 | data_size, data); | ||
119 | } | ||
120 | |||
121 | static efi_status_t virt_efi_get_next_high_mono_count(u32 *count) | ||
122 | { | ||
123 | return efi_call_virt1(get_next_high_mono_count, count); | ||
124 | } | ||
125 | |||
126 | static void virt_efi_reset_system(int reset_type, | ||
127 | efi_status_t status, | ||
128 | unsigned long data_size, | ||
129 | efi_char16_t *data) | ||
130 | { | ||
131 | efi_call_virt4(reset_system, reset_type, status, | ||
132 | data_size, data); | ||
133 | } | ||
134 | |||
135 | static efi_status_t virt_efi_set_virtual_address_map( | ||
136 | unsigned long memory_map_size, | ||
137 | unsigned long descriptor_size, | ||
138 | u32 descriptor_version, | ||
139 | efi_memory_desc_t *virtual_map) | ||
140 | { | ||
141 | return efi_call_virt4(set_virtual_address_map, | ||
142 | memory_map_size, descriptor_size, | ||
143 | descriptor_version, virtual_map); | ||
144 | } | ||
145 | |||
146 | static efi_status_t __init phys_efi_set_virtual_address_map( | ||
147 | unsigned long memory_map_size, | ||
148 | unsigned long descriptor_size, | ||
149 | u32 descriptor_version, | ||
150 | efi_memory_desc_t *virtual_map) | ||
151 | { | ||
152 | efi_status_t status; | ||
153 | |||
154 | efi_call_phys_prelog(); | ||
155 | status = efi_call_phys4(efi_phys.set_virtual_address_map, | ||
156 | memory_map_size, descriptor_size, | ||
157 | descriptor_version, virtual_map); | ||
158 | efi_call_phys_epilog(); | ||
159 | return status; | ||
160 | } | ||
161 | |||
162 | static efi_status_t __init phys_efi_get_time(efi_time_t *tm, | ||
163 | efi_time_cap_t *tc) | ||
164 | { | ||
165 | efi_status_t status; | ||
166 | |||
167 | efi_call_phys_prelog(); | ||
168 | status = efi_call_phys2(efi_phys.get_time, tm, tc); | ||
169 | efi_call_phys_epilog(); | ||
170 | return status; | ||
171 | } | ||
172 | |||
173 | int efi_set_rtc_mmss(unsigned long nowtime) | ||
174 | { | ||
175 | int real_seconds, real_minutes; | ||
176 | efi_status_t status; | ||
177 | efi_time_t eft; | ||
178 | efi_time_cap_t cap; | ||
179 | |||
180 | status = efi.get_time(&eft, &cap); | ||
181 | if (status != EFI_SUCCESS) { | ||
182 | printk(KERN_ERR "Oops: efitime: can't read time!\n"); | ||
183 | return -1; | ||
184 | } | ||
185 | |||
186 | real_seconds = nowtime % 60; | ||
187 | real_minutes = nowtime / 60; | ||
188 | if (((abs(real_minutes - eft.minute) + 15)/30) & 1) | ||
189 | real_minutes += 30; | ||
190 | real_minutes %= 60; | ||
191 | eft.minute = real_minutes; | ||
192 | eft.second = real_seconds; | ||
193 | |||
194 | status = efi.set_time(&eft); | ||
195 | if (status != EFI_SUCCESS) { | ||
196 | printk(KERN_ERR "Oops: efitime: can't write time!\n"); | ||
197 | return -1; | ||
198 | } | ||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | unsigned long efi_get_time(void) | ||
203 | { | ||
204 | efi_status_t status; | ||
205 | efi_time_t eft; | ||
206 | efi_time_cap_t cap; | ||
207 | |||
208 | status = efi.get_time(&eft, &cap); | ||
209 | if (status != EFI_SUCCESS) | ||
210 | printk(KERN_ERR "Oops: efitime: can't read time!\n"); | ||
211 | |||
212 | return mktime(eft.year, eft.month, eft.day, eft.hour, | ||
213 | eft.minute, eft.second); | ||
214 | } | ||
215 | |||
216 | #if EFI_DEBUG | ||
217 | static void __init print_efi_memmap(void) | ||
218 | { | ||
219 | efi_memory_desc_t *md; | ||
220 | void *p; | ||
221 | int i; | ||
222 | |||
223 | for (p = memmap.map, i = 0; | ||
224 | p < memmap.map_end; | ||
225 | p += memmap.desc_size, i++) { | ||
226 | md = p; | ||
227 | printk(KERN_INFO PFX "mem%02u: type=%u, attr=0x%llx, " | ||
228 | "range=[0x%016llx-0x%016llx) (%lluMB)\n", | ||
229 | i, md->type, md->attribute, md->phys_addr, | ||
230 | md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT), | ||
231 | (md->num_pages >> (20 - EFI_PAGE_SHIFT))); | ||
232 | } | ||
233 | } | ||
234 | #endif /* EFI_DEBUG */ | ||
235 | |||
236 | void __init efi_init(void) | ||
237 | { | ||
238 | efi_config_table_t *config_tables; | ||
239 | efi_runtime_services_t *runtime; | ||
240 | efi_char16_t *c16; | ||
241 | char vendor[100] = "unknown"; | ||
242 | int i = 0; | ||
243 | void *tmp; | ||
244 | |||
245 | #ifdef CONFIG_X86_32 | ||
246 | efi_phys.systab = (efi_system_table_t *)boot_params.efi_info.efi_systab; | ||
247 | memmap.phys_map = (void *)boot_params.efi_info.efi_memmap; | ||
248 | #else | ||
249 | efi_phys.systab = (efi_system_table_t *) | ||
250 | (boot_params.efi_info.efi_systab | | ||
251 | ((__u64)boot_params.efi_info.efi_systab_hi<<32)); | ||
252 | memmap.phys_map = (void *) | ||
253 | (boot_params.efi_info.efi_memmap | | ||
254 | ((__u64)boot_params.efi_info.efi_memmap_hi<<32)); | ||
255 | #endif | ||
256 | memmap.nr_map = boot_params.efi_info.efi_memmap_size / | ||
257 | boot_params.efi_info.efi_memdesc_size; | ||
258 | memmap.desc_version = boot_params.efi_info.efi_memdesc_version; | ||
259 | memmap.desc_size = boot_params.efi_info.efi_memdesc_size; | ||
260 | |||
261 | efi.systab = early_ioremap((unsigned long)efi_phys.systab, | ||
262 | sizeof(efi_system_table_t)); | ||
263 | if (efi.systab == NULL) | ||
264 | printk(KERN_ERR "Couldn't map the EFI system table!\n"); | ||
265 | memcpy(&efi_systab, efi.systab, sizeof(efi_system_table_t)); | ||
266 | early_iounmap(efi.systab, sizeof(efi_system_table_t)); | ||
267 | efi.systab = &efi_systab; | ||
268 | |||
269 | /* | ||
270 | * Verify the EFI Table | ||
271 | */ | ||
272 | if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) | ||
273 | printk(KERN_ERR "EFI system table signature incorrect!\n"); | ||
274 | if ((efi.systab->hdr.revision >> 16) == 0) | ||
275 | printk(KERN_ERR "Warning: EFI system table version " | ||
276 | "%d.%02d, expected 1.00 or greater!\n", | ||
277 | efi.systab->hdr.revision >> 16, | ||
278 | efi.systab->hdr.revision & 0xffff); | ||
279 | |||
280 | /* | ||
281 | * Show what we know for posterity | ||
282 | */ | ||
283 | c16 = tmp = early_ioremap(efi.systab->fw_vendor, 2); | ||
284 | if (c16) { | ||
285 | for (i = 0; i < sizeof(vendor) && *c16; ++i) | ||
286 | vendor[i] = *c16++; | ||
287 | vendor[i] = '\0'; | ||
288 | } else | ||
289 | printk(KERN_ERR PFX "Could not map the firmware vendor!\n"); | ||
290 | early_iounmap(tmp, 2); | ||
291 | |||
292 | printk(KERN_INFO "EFI v%u.%.02u by %s \n", | ||
293 | efi.systab->hdr.revision >> 16, | ||
294 | efi.systab->hdr.revision & 0xffff, vendor); | ||
295 | |||
296 | /* | ||
297 | * Let's see what config tables the firmware passed to us. | ||
298 | */ | ||
299 | config_tables = early_ioremap( | ||
300 | efi.systab->tables, | ||
301 | efi.systab->nr_tables * sizeof(efi_config_table_t)); | ||
302 | if (config_tables == NULL) | ||
303 | printk(KERN_ERR "Could not map EFI Configuration Table!\n"); | ||
304 | |||
305 | printk(KERN_INFO); | ||
306 | for (i = 0; i < efi.systab->nr_tables; i++) { | ||
307 | if (!efi_guidcmp(config_tables[i].guid, MPS_TABLE_GUID)) { | ||
308 | efi.mps = config_tables[i].table; | ||
309 | printk(" MPS=0x%lx ", config_tables[i].table); | ||
310 | } else if (!efi_guidcmp(config_tables[i].guid, | ||
311 | ACPI_20_TABLE_GUID)) { | ||
312 | efi.acpi20 = config_tables[i].table; | ||
313 | printk(" ACPI 2.0=0x%lx ", config_tables[i].table); | ||
314 | } else if (!efi_guidcmp(config_tables[i].guid, | ||
315 | ACPI_TABLE_GUID)) { | ||
316 | efi.acpi = config_tables[i].table; | ||
317 | printk(" ACPI=0x%lx ", config_tables[i].table); | ||
318 | } else if (!efi_guidcmp(config_tables[i].guid, | ||
319 | SMBIOS_TABLE_GUID)) { | ||
320 | efi.smbios = config_tables[i].table; | ||
321 | printk(" SMBIOS=0x%lx ", config_tables[i].table); | ||
322 | } else if (!efi_guidcmp(config_tables[i].guid, | ||
323 | HCDP_TABLE_GUID)) { | ||
324 | efi.hcdp = config_tables[i].table; | ||
325 | printk(" HCDP=0x%lx ", config_tables[i].table); | ||
326 | } else if (!efi_guidcmp(config_tables[i].guid, | ||
327 | UGA_IO_PROTOCOL_GUID)) { | ||
328 | efi.uga = config_tables[i].table; | ||
329 | printk(" UGA=0x%lx ", config_tables[i].table); | ||
330 | } | ||
331 | } | ||
332 | printk("\n"); | ||
333 | early_iounmap(config_tables, | ||
334 | efi.systab->nr_tables * sizeof(efi_config_table_t)); | ||
335 | |||
336 | /* | ||
337 | * Check out the runtime services table. We need to map | ||
338 | * the runtime services table so that we can grab the physical | ||
339 | * address of several of the EFI runtime functions, needed to | ||
340 | * set the firmware into virtual mode. | ||
341 | */ | ||
342 | runtime = early_ioremap((unsigned long)efi.systab->runtime, | ||
343 | sizeof(efi_runtime_services_t)); | ||
344 | if (runtime != NULL) { | ||
345 | /* | ||
346 | * We will only need *early* access to the following | ||
347 | * two EFI runtime services before set_virtual_address_map | ||
348 | * is invoked. | ||
349 | */ | ||
350 | efi_phys.get_time = (efi_get_time_t *)runtime->get_time; | ||
351 | efi_phys.set_virtual_address_map = | ||
352 | (efi_set_virtual_address_map_t *) | ||
353 | runtime->set_virtual_address_map; | ||
354 | /* | ||
355 | * Make efi_get_time can be called before entering | ||
356 | * virtual mode. | ||
357 | */ | ||
358 | efi.get_time = phys_efi_get_time; | ||
359 | } else | ||
360 | printk(KERN_ERR "Could not map the EFI runtime service " | ||
361 | "table!\n"); | ||
362 | early_iounmap(runtime, sizeof(efi_runtime_services_t)); | ||
363 | |||
364 | /* Map the EFI memory map */ | ||
365 | memmap.map = early_ioremap((unsigned long)memmap.phys_map, | ||
366 | memmap.nr_map * memmap.desc_size); | ||
367 | if (memmap.map == NULL) | ||
368 | printk(KERN_ERR "Could not map the EFI memory map!\n"); | ||
369 | memmap.map_end = memmap.map + (memmap.nr_map * memmap.desc_size); | ||
370 | if (memmap.desc_size != sizeof(efi_memory_desc_t)) | ||
371 | printk(KERN_WARNING "Kernel-defined memdesc" | ||
372 | "doesn't match the one from EFI!\n"); | ||
373 | |||
374 | /* Setup for EFI runtime service */ | ||
375 | reboot_type = BOOT_EFI; | ||
376 | |||
377 | #if EFI_DEBUG | ||
378 | print_efi_memmap(); | ||
379 | #endif | ||
380 | } | ||
381 | |||
382 | #if defined(CONFIG_X86_64) || defined(CONFIG_X86_PAE) | ||
383 | static void __init runtime_code_page_mkexec(void) | ||
384 | { | ||
385 | efi_memory_desc_t *md; | ||
386 | unsigned long end; | ||
387 | void *p; | ||
388 | |||
389 | if (!(__supported_pte_mask & _PAGE_NX)) | ||
390 | return; | ||
391 | |||
392 | /* Make EFI runtime service code area executable */ | ||
393 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | ||
394 | md = p; | ||
395 | end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT); | ||
396 | if (md->type == EFI_RUNTIME_SERVICES_CODE && | ||
397 | (end >> PAGE_SHIFT) <= max_pfn_mapped) { | ||
398 | set_memory_x(md->virt_addr, md->num_pages); | ||
399 | set_memory_uc(md->virt_addr, md->num_pages); | ||
400 | } | ||
401 | } | ||
402 | __flush_tlb_all(); | ||
403 | } | ||
404 | #else | ||
405 | static inline void __init runtime_code_page_mkexec(void) { } | ||
406 | #endif | ||
407 | |||
408 | /* | ||
409 | * This function will switch the EFI runtime services to virtual mode. | ||
410 | * Essentially, look through the EFI memmap and map every region that | ||
411 | * has the runtime attribute bit set in its memory descriptor and update | ||
412 | * that memory descriptor with the virtual address obtained from ioremap(). | ||
413 | * This enables the runtime services to be called without having to | ||
414 | * thunk back into physical mode for every invocation. | ||
415 | */ | ||
416 | void __init efi_enter_virtual_mode(void) | ||
417 | { | ||
418 | efi_memory_desc_t *md; | ||
419 | efi_status_t status; | ||
420 | unsigned long end; | ||
421 | void *p; | ||
422 | |||
423 | efi.systab = NULL; | ||
424 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | ||
425 | md = p; | ||
426 | if (!(md->attribute & EFI_MEMORY_RUNTIME)) | ||
427 | continue; | ||
428 | end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT); | ||
429 | if ((md->attribute & EFI_MEMORY_WB) && | ||
430 | ((end >> PAGE_SHIFT) <= max_pfn_mapped)) | ||
431 | md->virt_addr = (unsigned long)__va(md->phys_addr); | ||
432 | else | ||
433 | md->virt_addr = (unsigned long) | ||
434 | efi_ioremap(md->phys_addr, | ||
435 | md->num_pages << EFI_PAGE_SHIFT); | ||
436 | if (!md->virt_addr) | ||
437 | printk(KERN_ERR PFX "ioremap of 0x%llX failed!\n", | ||
438 | (unsigned long long)md->phys_addr); | ||
439 | if ((md->phys_addr <= (unsigned long)efi_phys.systab) && | ||
440 | ((unsigned long)efi_phys.systab < end)) | ||
441 | efi.systab = (efi_system_table_t *)(unsigned long) | ||
442 | (md->virt_addr - md->phys_addr + | ||
443 | (unsigned long)efi_phys.systab); | ||
444 | } | ||
445 | |||
446 | BUG_ON(!efi.systab); | ||
447 | |||
448 | status = phys_efi_set_virtual_address_map( | ||
449 | memmap.desc_size * memmap.nr_map, | ||
450 | memmap.desc_size, | ||
451 | memmap.desc_version, | ||
452 | memmap.phys_map); | ||
453 | |||
454 | if (status != EFI_SUCCESS) { | ||
455 | printk(KERN_ALERT "Unable to switch EFI into virtual mode " | ||
456 | "(status=%lx)!\n", status); | ||
457 | panic("EFI call to SetVirtualAddressMap() failed!"); | ||
458 | } | ||
459 | |||
460 | /* | ||
461 | * Now that EFI is in virtual mode, update the function | ||
462 | * pointers in the runtime service table to the new virtual addresses. | ||
463 | * | ||
464 | * Call EFI services through wrapper functions. | ||
465 | */ | ||
466 | efi.get_time = virt_efi_get_time; | ||
467 | efi.set_time = virt_efi_set_time; | ||
468 | efi.get_wakeup_time = virt_efi_get_wakeup_time; | ||
469 | efi.set_wakeup_time = virt_efi_set_wakeup_time; | ||
470 | efi.get_variable = virt_efi_get_variable; | ||
471 | efi.get_next_variable = virt_efi_get_next_variable; | ||
472 | efi.set_variable = virt_efi_set_variable; | ||
473 | efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count; | ||
474 | efi.reset_system = virt_efi_reset_system; | ||
475 | efi.set_virtual_address_map = virt_efi_set_virtual_address_map; | ||
476 | runtime_code_page_mkexec(); | ||
477 | early_iounmap(memmap.map, memmap.nr_map * memmap.desc_size); | ||
478 | memmap.map = NULL; | ||
479 | } | ||
480 | |||
481 | /* | ||
482 | * Convenience functions to obtain memory types and attributes | ||
483 | */ | ||
484 | u32 efi_mem_type(unsigned long phys_addr) | ||
485 | { | ||
486 | efi_memory_desc_t *md; | ||
487 | void *p; | ||
488 | |||
489 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | ||
490 | md = p; | ||
491 | if ((md->phys_addr <= phys_addr) && | ||
492 | (phys_addr < (md->phys_addr + | ||
493 | (md->num_pages << EFI_PAGE_SHIFT)))) | ||
494 | return md->type; | ||
495 | } | ||
496 | return 0; | ||
497 | } | ||
498 | |||
499 | u64 efi_mem_attributes(unsigned long phys_addr) | ||
500 | { | ||
501 | efi_memory_desc_t *md; | ||
502 | void *p; | ||
503 | |||
504 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | ||
505 | md = p; | ||
506 | if ((md->phys_addr <= phys_addr) && | ||
507 | (phys_addr < (md->phys_addr + | ||
508 | (md->num_pages << EFI_PAGE_SHIFT)))) | ||
509 | return md->attribute; | ||
510 | } | ||
511 | return 0; | ||
512 | } | ||