License cleanup: add SPDX GPL-2.0 license identifier to files with no license
Many source files in the tree are missing licensing information, which
makes it harder for compliance tools to determine the correct license.
By default all files without license information are under the default
license of the kernel, which is GPL version 2.
Update the files which contain no license information with the 'GPL-2.0'
SPDX license identifier. The SPDX identifier is a legally binding
shorthand, which can be used instead of the full boiler plate text.
This patch is based on work done by Thomas Gleixner and Kate Stewart and
Philippe Ombredanne.
How this work was done:
Patches were generated and checked against linux-4.14-rc6 for a subset of
the use cases:
- file had no licensing information it it.
- file was a */uapi/* one with no licensing information in it,
- file was a */uapi/* one with existing licensing information,
Further patches will be generated in subsequent months to fix up cases
where non-standard license headers were used, and references to license
had to be inferred by heuristics based on keywords.
The analysis to determine which SPDX License Identifier to be applied to
a file was done in a spreadsheet of side by side results from of the
output of two independent scanners (ScanCode & Windriver) producing SPDX
tag:value files created by Philippe Ombredanne. Philippe prepared the
base worksheet, and did an initial spot review of a few 1000 files.
The 4.13 kernel was the starting point of the analysis with 60,537 files
assessed. Kate Stewart did a file by file comparison of the scanner
results in the spreadsheet to determine which SPDX license identifier(s)
to be applied to the file. She confirmed any determination that was not
immediately clear with lawyers working with the Linux Foundation.
Criteria used to select files for SPDX license identifier tagging was:
- Files considered eligible had to be source code files.
- Make and config files were included as candidates if they contained >5
lines of source
- File already had some variant of a license header in it (even if <5
lines).
All documentation files were explicitly excluded.
The following heuristics were used to determine which SPDX license
identifiers to apply.
- when both scanners couldn't find any license traces, file was
considered to have no license information in it, and the top level
COPYING file license applied.
For non */uapi/* files that summary was:
SPDX license identifier # files
---------------------------------------------------|-------
GPL-2.0 11139
and resulted in the first patch in this series.
If that file was a */uapi/* path one, it was "GPL-2.0 WITH
Linux-syscall-note" otherwise it was "GPL-2.0". Results of that was:
SPDX license identifier # files
---------------------------------------------------|-------
GPL-2.0 WITH Linux-syscall-note 930
and resulted in the second patch in this series.
- if a file had some form of licensing information in it, and was one
of the */uapi/* ones, it was denoted with the Linux-syscall-note if
any GPL family license was found in the file or had no licensing in
it (per prior point). Results summary:
SPDX license identifier # files
---------------------------------------------------|------
GPL-2.0 WITH Linux-syscall-note 270
GPL-2.0+ WITH Linux-syscall-note 169
((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) 21
((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) 17
LGPL-2.1+ WITH Linux-syscall-note 15
GPL-1.0+ WITH Linux-syscall-note 14
((GPL-2.0+ WITH Linux-syscall-note) OR BSD-3-Clause) 5
LGPL-2.0+ WITH Linux-syscall-note 4
LGPL-2.1 WITH Linux-syscall-note 3
((GPL-2.0 WITH Linux-syscall-note) OR MIT) 3
((GPL-2.0 WITH Linux-syscall-note) AND MIT) 1
and that resulted in the third patch in this series.
- when the two scanners agreed on the detected license(s), that became
the concluded license(s).
- when there was disagreement between the two scanners (one detected a
license but the other didn't, or they both detected different
licenses) a manual inspection of the file occurred.
- In most cases a manual inspection of the information in the file
resulted in a clear resolution of the license that should apply (and
which scanner probably needed to revisit its heuristics).
- When it was not immediately clear, the license identifier was
confirmed with lawyers working with the Linux Foundation.
- If there was any question as to the appropriate license identifier,
the file was flagged for further research and to be revisited later
in time.
In total, over 70 hours of logged manual review was done on the
spreadsheet to determine the SPDX license identifiers to apply to the
source files by Kate, Philippe, Thomas and, in some cases, confirmation
by lawyers working with the Linux Foundation.
Kate also obtained a third independent scan of the 4.13 code base from
FOSSology, and compared selected files where the other two scanners
disagreed against that SPDX file, to see if there was new insights. The
Windriver scanner is based on an older version of FOSSology in part, so
they are related.
Thomas did random spot checks in about 500 files from the spreadsheets
for the uapi headers and agreed with SPDX license identifier in the
files he inspected. For the non-uapi files Thomas did random spot checks
in about 15000 files.
In initial set of patches against 4.14-rc6, 3 files were found to have
copy/paste license identifier errors, and have been fixed to reflect the
correct identifier.
Additionally Philippe spent 10 hours this week doing a detailed manual
inspection and review of the 12,461 patched files from the initial patch
version early this week with:
- a full scancode scan run, collecting the matched texts, detected
license ids and scores
- reviewing anything where there was a license detected (about 500+
files) to ensure that the applied SPDX license was correct
- reviewing anything where there was no detection but the patch license
was not GPL-2.0 WITH Linux-syscall-note to ensure that the applied
SPDX license was correct
This produced a worksheet with 20 files needing minor correction. This
worksheet was then exported into 3 different .csv files for the
different types of files to be modified.
These .csv files were then reviewed by Greg. Thomas wrote a script to
parse the csv files and add the proper SPDX tag to the file, in the
format that the file expected. This script was further refined by Greg
based on the output to detect more types of files automatically and to
distinguish between header and source .c files (which need different
comment types.) Finally Greg ran the script using the .csv files to
generate the patches.
Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org>
Reviewed-by: Philippe Ombredanne <pombredanne@nexb.com>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-11-01 22:07:57 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2007-02-07 04:29:00 +08:00
|
|
|
/*
|
|
|
|
* machine_kexec.c - handle transition of Linux booting another kernel
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/kexec.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/reboot.h>
|
2008-09-06 19:10:45 +08:00
|
|
|
#include <linux/io.h>
|
2012-02-03 21:48:01 +08:00
|
|
|
#include <linux/irq.h>
|
2012-09-22 01:56:11 +08:00
|
|
|
#include <linux/memblock.h>
|
2012-09-22 01:56:02 +08:00
|
|
|
#include <linux/of_fdt.h>
|
2007-02-07 04:29:00 +08:00
|
|
|
#include <asm/mmu_context.h>
|
|
|
|
#include <asm/cacheflush.h>
|
2013-11-25 21:54:47 +08:00
|
|
|
#include <asm/fncpy.h>
|
2007-02-07 04:29:00 +08:00
|
|
|
#include <asm/mach-types.h>
|
2013-08-03 03:52:49 +08:00
|
|
|
#include <asm/smp_plat.h>
|
2012-03-29 01:30:01 +08:00
|
|
|
#include <asm/system_misc.h>
|
2017-05-09 06:58:02 +08:00
|
|
|
#include <asm/set_memory.h>
|
2007-02-07 04:29:00 +08:00
|
|
|
|
2013-11-25 21:54:47 +08:00
|
|
|
extern void relocate_new_kernel(void);
|
2009-02-10 04:35:39 +08:00
|
|
|
extern const unsigned int relocate_new_kernel_size;
|
2007-02-07 04:29:00 +08:00
|
|
|
|
|
|
|
extern unsigned long kexec_start_address;
|
|
|
|
extern unsigned long kexec_indirection_page;
|
|
|
|
extern unsigned long kexec_mach_type;
|
2008-01-02 07:56:46 +08:00
|
|
|
extern unsigned long kexec_boot_atags;
|
2007-02-07 04:29:00 +08:00
|
|
|
|
2010-12-03 17:53:38 +08:00
|
|
|
static atomic_t waiting_for_crash_ipi;
|
|
|
|
|
2007-02-07 04:29:00 +08:00
|
|
|
/*
|
|
|
|
* Provide a dummy crash_notes definition while crash dump arrives to arm.
|
|
|
|
* This prevents breakage of crash_notes attribute in kernel/ksysfs.c.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int machine_kexec_prepare(struct kimage *image)
|
|
|
|
{
|
2012-09-22 01:56:02 +08:00
|
|
|
struct kexec_segment *current_segment;
|
|
|
|
__be32 header;
|
|
|
|
int i, err;
|
|
|
|
|
2017-07-20 06:09:58 +08:00
|
|
|
image->arch.kernel_r2 = image->start - KEXEC_ARM_ZIMAGE_OFFSET
|
|
|
|
+ KEXEC_ARM_ATAGS_OFFSET;
|
|
|
|
|
2013-08-03 03:52:49 +08:00
|
|
|
/*
|
|
|
|
* Validate that if the current HW supports SMP, then the SW supports
|
|
|
|
* and implements CPU hotplug for the current HW. If not, we won't be
|
|
|
|
* able to kexec reliably, so fail the prepare operation.
|
|
|
|
*/
|
2015-04-01 20:36:57 +08:00
|
|
|
if (num_possible_cpus() > 1 && platform_can_secondary_boot() &&
|
|
|
|
!platform_can_cpu_hotplug())
|
2013-08-03 03:52:49 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2012-09-22 01:56:02 +08:00
|
|
|
/*
|
|
|
|
* No segment at default ATAGs address. try to locate
|
|
|
|
* a dtb using magic.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < image->nr_segments; i++) {
|
|
|
|
current_segment = &image->segment[i];
|
|
|
|
|
2016-08-03 05:06:10 +08:00
|
|
|
if (!memblock_is_region_memory(idmap_to_phys(current_segment->mem),
|
2012-10-17 02:35:14 +08:00
|
|
|
current_segment->memsz))
|
|
|
|
return -EINVAL;
|
2012-09-22 01:56:11 +08:00
|
|
|
|
2012-09-22 01:56:02 +08:00
|
|
|
err = get_user(header, (__be32*)current_segment->buf);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2017-07-20 06:09:58 +08:00
|
|
|
if (header == cpu_to_be32(OF_DT_HEADER))
|
|
|
|
image->arch.kernel_r2 = current_segment->mem;
|
2012-09-22 01:56:02 +08:00
|
|
|
}
|
2007-02-07 04:29:00 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void machine_kexec_cleanup(struct kimage *image)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2010-12-03 17:53:38 +08:00
|
|
|
void machine_crash_nonpanic_core(void *unused)
|
|
|
|
{
|
|
|
|
struct pt_regs regs;
|
|
|
|
|
2018-04-12 02:35:19 +08:00
|
|
|
crash_setup_regs(®s, get_irq_regs());
|
2010-12-03 17:53:38 +08:00
|
|
|
printk(KERN_DEBUG "CPU %u will stop doing anything useful since another CPU has crashed\n",
|
|
|
|
smp_processor_id());
|
|
|
|
crash_save_cpu(®s, smp_processor_id());
|
|
|
|
flush_cache_all();
|
|
|
|
|
2013-08-14 20:28:28 +08:00
|
|
|
set_cpu_online(smp_processor_id(), false);
|
2010-12-03 17:53:38 +08:00
|
|
|
atomic_dec(&waiting_for_crash_ipi);
|
ARM: avoid Cortex-A9 livelock on tight dmb loops
machine_crash_nonpanic_core() does this:
while (1)
cpu_relax();
because the kernel has crashed, and we have no known safe way to deal
with the CPU. So, we place the CPU into an infinite loop which we
expect it to never exit - at least not until the system as a whole is
reset by some method.
In the absence of erratum 754327, this code assembles to:
b .
In other words, an infinite loop. When erratum 754327 is enabled,
this becomes:
1: dmb
b 1b
It has been observed that on some systems (eg, OMAP4) where, if a
crash is triggered, the system tries to kexec into the panic kernel,
but fails after taking the secondary CPU down - placing it into one
of these loops. This causes the system to livelock, and the most
noticable effect is the system stops after issuing:
Loading crashdump kernel...
to the system console.
The tested as working solution I came up with was to add wfe() to
these infinite loops thusly:
while (1) {
cpu_relax();
wfe();
}
which, without 754327 builds to:
1: wfe
b 1b
or with 754327 is enabled:
1: dmb
wfe
b 1b
Adding "wfe" does two things depending on the environment we're running
under:
- where we're running on bare metal, and the processor implements
"wfe", it stops us spinning endlessly in a loop where we're never
going to do any useful work.
- if we're running in a VM, it allows the CPU to be given back to the
hypervisor and rescheduled for other purposes (maybe a different VM)
rather than wasting CPU cycles inside a crashed VM.
However, in light of erratum 794072, Will Deacon wanted to see 10 nops
as well - which is reasonable to cover the case where we have erratum
754327 enabled _and_ we have a processor that doesn't implement the
wfe hint.
So, we now end up with:
1: wfe
b 1b
when erratum 754327 is disabled, or:
1: dmb
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
wfe
b 1b
when erratum 754327 is enabled. We also get the dmb + 10 nop
sequence elsewhere in the kernel, in terminating loops.
This is reasonable - it means we get the workaround for erratum
794072 when erratum 754327 is enabled, but still relinquish the dead
processor - either by placing it in a lower power mode when wfe is
implemented as such or by returning it to the hypervisior, or in the
case where wfe is a no-op, we use the workaround specified in erratum
794072 to avoid the problem.
These as two entirely orthogonal problems - the 10 nops addresses
erratum 794072, and the wfe is an optimisation that makes the system
more efficient when crashed either in terms of power consumption or
by allowing the host/other VMs to make use of the CPU.
I don't see any reason not to use kexec() inside a VM - it has the
potential to provide automated recovery from a failure of the VMs
kernel with the opportunity for saving a crashdump of the failure.
A panic() with a reboot timeout won't do that, and reading the
libvirt documentation, setting on_reboot to "preserve" won't either
(the documentation states "The preserve action for an on_reboot event
is treated as a destroy".) Surely it has to be a good thing to
avoiding having CPUs spinning inside a VM that is doing no useful
work.
Acked-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
2018-04-10 18:35:36 +08:00
|
|
|
|
|
|
|
while (1) {
|
2010-12-03 17:53:38 +08:00
|
|
|
cpu_relax();
|
ARM: avoid Cortex-A9 livelock on tight dmb loops
machine_crash_nonpanic_core() does this:
while (1)
cpu_relax();
because the kernel has crashed, and we have no known safe way to deal
with the CPU. So, we place the CPU into an infinite loop which we
expect it to never exit - at least not until the system as a whole is
reset by some method.
In the absence of erratum 754327, this code assembles to:
b .
In other words, an infinite loop. When erratum 754327 is enabled,
this becomes:
1: dmb
b 1b
It has been observed that on some systems (eg, OMAP4) where, if a
crash is triggered, the system tries to kexec into the panic kernel,
but fails after taking the secondary CPU down - placing it into one
of these loops. This causes the system to livelock, and the most
noticable effect is the system stops after issuing:
Loading crashdump kernel...
to the system console.
The tested as working solution I came up with was to add wfe() to
these infinite loops thusly:
while (1) {
cpu_relax();
wfe();
}
which, without 754327 builds to:
1: wfe
b 1b
or with 754327 is enabled:
1: dmb
wfe
b 1b
Adding "wfe" does two things depending on the environment we're running
under:
- where we're running on bare metal, and the processor implements
"wfe", it stops us spinning endlessly in a loop where we're never
going to do any useful work.
- if we're running in a VM, it allows the CPU to be given back to the
hypervisor and rescheduled for other purposes (maybe a different VM)
rather than wasting CPU cycles inside a crashed VM.
However, in light of erratum 794072, Will Deacon wanted to see 10 nops
as well - which is reasonable to cover the case where we have erratum
754327 enabled _and_ we have a processor that doesn't implement the
wfe hint.
So, we now end up with:
1: wfe
b 1b
when erratum 754327 is disabled, or:
1: dmb
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
wfe
b 1b
when erratum 754327 is enabled. We also get the dmb + 10 nop
sequence elsewhere in the kernel, in terminating loops.
This is reasonable - it means we get the workaround for erratum
794072 when erratum 754327 is enabled, but still relinquish the dead
processor - either by placing it in a lower power mode when wfe is
implemented as such or by returning it to the hypervisior, or in the
case where wfe is a no-op, we use the workaround specified in erratum
794072 to avoid the problem.
These as two entirely orthogonal problems - the 10 nops addresses
erratum 794072, and the wfe is an optimisation that makes the system
more efficient when crashed either in terms of power consumption or
by allowing the host/other VMs to make use of the CPU.
I don't see any reason not to use kexec() inside a VM - it has the
potential to provide automated recovery from a failure of the VMs
kernel with the opportunity for saving a crashdump of the failure.
A panic() with a reboot timeout won't do that, and reading the
libvirt documentation, setting on_reboot to "preserve" won't either
(the documentation states "The preserve action for an on_reboot event
is treated as a destroy".) Surely it has to be a good thing to
avoiding having CPUs spinning inside a VM that is doing no useful
work.
Acked-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
2018-04-10 18:35:36 +08:00
|
|
|
wfe();
|
|
|
|
}
|
2010-12-03 17:53:38 +08:00
|
|
|
}
|
|
|
|
|
2018-04-12 01:24:01 +08:00
|
|
|
void crash_smp_send_stop(void)
|
|
|
|
{
|
|
|
|
static int cpus_stopped;
|
|
|
|
unsigned long msecs;
|
|
|
|
|
|
|
|
if (cpus_stopped)
|
|
|
|
return;
|
|
|
|
|
|
|
|
atomic_set(&waiting_for_crash_ipi, num_online_cpus() - 1);
|
|
|
|
smp_call_function(machine_crash_nonpanic_core, NULL, false);
|
|
|
|
msecs = 1000; /* Wait at most a second for the other cpus to stop */
|
|
|
|
while ((atomic_read(&waiting_for_crash_ipi) > 0) && msecs) {
|
|
|
|
mdelay(1);
|
|
|
|
msecs--;
|
|
|
|
}
|
|
|
|
if (atomic_read(&waiting_for_crash_ipi) > 0)
|
|
|
|
pr_warn("Non-crashing CPUs did not react to IPI\n");
|
|
|
|
|
|
|
|
cpus_stopped = 1;
|
|
|
|
}
|
|
|
|
|
2012-02-03 21:48:01 +08:00
|
|
|
static void machine_kexec_mask_interrupts(void)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
struct irq_desc *desc;
|
|
|
|
|
|
|
|
for_each_irq_desc(i, desc) {
|
|
|
|
struct irq_chip *chip;
|
|
|
|
|
|
|
|
chip = irq_desc_get_chip(desc);
|
|
|
|
if (!chip)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (chip->irq_eoi && irqd_irq_inprogress(&desc->irq_data))
|
|
|
|
chip->irq_eoi(&desc->irq_data);
|
|
|
|
|
|
|
|
if (chip->irq_mask)
|
|
|
|
chip->irq_mask(&desc->irq_data);
|
|
|
|
|
|
|
|
if (chip->irq_disable && !irqd_irq_disabled(&desc->irq_data))
|
|
|
|
chip->irq_disable(&desc->irq_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-02-07 04:29:00 +08:00
|
|
|
void machine_crash_shutdown(struct pt_regs *regs)
|
|
|
|
{
|
2010-05-10 16:23:37 +08:00
|
|
|
local_irq_disable();
|
2018-04-12 01:24:01 +08:00
|
|
|
crash_smp_send_stop();
|
2010-12-03 17:53:38 +08:00
|
|
|
|
2010-05-10 16:23:37 +08:00
|
|
|
crash_save_cpu(regs, smp_processor_id());
|
2012-02-03 21:48:01 +08:00
|
|
|
machine_kexec_mask_interrupts();
|
2010-05-10 16:23:37 +08:00
|
|
|
|
2014-10-28 19:26:42 +08:00
|
|
|
pr_info("Loading crashdump kernel...\n");
|
2007-02-07 04:29:00 +08:00
|
|
|
}
|
|
|
|
|
2011-02-03 06:16:09 +08:00
|
|
|
/*
|
|
|
|
* Function pointer to optional machine-specific reinitialization
|
|
|
|
*/
|
|
|
|
void (*kexec_reinit)(void);
|
|
|
|
|
2007-02-07 04:29:00 +08:00
|
|
|
void machine_kexec(struct kimage *image)
|
|
|
|
{
|
2016-01-12 01:03:54 +08:00
|
|
|
unsigned long page_list, reboot_entry_phys;
|
|
|
|
void (*reboot_entry)(void);
|
2007-02-07 04:29:00 +08:00
|
|
|
void *reboot_code_buffer;
|
|
|
|
|
2013-08-03 03:52:49 +08:00
|
|
|
/*
|
|
|
|
* This can only happen if machine_shutdown() failed to disable some
|
|
|
|
* CPU, and that can only happen if the checks in
|
|
|
|
* machine_kexec_prepare() were not correct. If this fails, we can't
|
|
|
|
* reliably kexec anyway, so BUG_ON is appropriate.
|
|
|
|
*/
|
|
|
|
BUG_ON(num_online_cpus() > 1);
|
2011-11-08 03:04:36 +08:00
|
|
|
|
|
|
|
page_list = image->head & PAGE_MASK;
|
|
|
|
|
2007-02-07 04:29:00 +08:00
|
|
|
reboot_code_buffer = page_address(image->control_code_page);
|
|
|
|
|
2011-11-08 03:04:36 +08:00
|
|
|
/* Prepare parameters for reboot_code_buffer*/
|
2014-04-04 04:29:50 +08:00
|
|
|
set_kernel_text_rw();
|
2011-11-08 03:04:36 +08:00
|
|
|
kexec_start_address = image->start;
|
|
|
|
kexec_indirection_page = page_list;
|
|
|
|
kexec_mach_type = machine_arch_type;
|
2017-07-20 06:09:58 +08:00
|
|
|
kexec_boot_atags = image->arch.kernel_r2;
|
2011-11-08 03:04:36 +08:00
|
|
|
|
|
|
|
/* copy our kernel relocation code to the control code page */
|
2013-11-25 21:54:47 +08:00
|
|
|
reboot_entry = fncpy(reboot_code_buffer,
|
2016-01-12 01:03:54 +08:00
|
|
|
&relocate_new_kernel,
|
2013-11-25 21:54:47 +08:00
|
|
|
relocate_new_kernel_size);
|
2016-01-12 01:03:54 +08:00
|
|
|
|
|
|
|
/* get the identity mapping physical address for the reboot code */
|
|
|
|
reboot_entry_phys = virt_to_idmap(reboot_entry);
|
2011-11-08 03:04:36 +08:00
|
|
|
|
2014-10-28 19:26:42 +08:00
|
|
|
pr_info("Bye!\n");
|
2007-02-07 04:29:00 +08:00
|
|
|
|
2011-02-03 06:16:09 +08:00
|
|
|
if (kexec_reinit)
|
|
|
|
kexec_reinit();
|
2011-06-06 19:35:46 +08:00
|
|
|
|
2013-11-25 21:54:47 +08:00
|
|
|
soft_restart(reboot_entry_phys);
|
2007-02-07 04:29:00 +08:00
|
|
|
}
|
2014-04-18 14:45:36 +08:00
|
|
|
|
|
|
|
void arch_crash_save_vmcoreinfo(void)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_ARM_LPAE
|
|
|
|
VMCOREINFO_CONFIG(ARM_LPAE);
|
|
|
|
#endif
|
|
|
|
}
|