1 package PVE
::QemuServer
::CPUConfig
;
7 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
8 use PVE
::QemuServer
::Helpers
qw(min_version);
10 use base
qw(PVE::SectionConfig Exporter);
18 # under certain race-conditions, this module might be loaded before pve-cluster
19 # has started completely, so ensure we don't prevent the FUSE mount with our dir
20 if (PVE
::Cluster
::check_cfs_is_mounted
(1)) {
21 mkdir "/etc/pve/virtual-guest";
24 my $default_filename = "virtual-guest/cpu-models.conf";
27 sub { PVE
::QemuServer
::CPUConfig-
>parse_config(@_); },
28 sub { PVE
::QemuServer
::CPUConfig-
>write_config(@_); },
31 sub load_custom_model_conf
{
32 return cfs_read_file
($default_filename);
35 #builtin models : reported-model is mandatory
36 my $builtin_models = {
38 'reported-model' => 'qemu64',
39 flags
=> "+popcnt;+pni;+sse4.1;+sse4.2;+ssse3",
42 'reported-model' => 'qemu64',
43 flags
=> "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3",
46 'reported-model' => 'qemu64',
47 flags
=> "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave",
50 'reported-model' => 'qemu64',
51 flags
=> "+aes;+popcnt;+pni;+sse4.1;+sse4.2;+ssse3;+avx;+avx2;+bmi1;+bmi2;+f16c;+fma;+abm;+movbe;+xsave;+avx512f;+avx512bw;+avx512cd;+avx512dq;+avx512vl",
55 my $depreacated_cpu_map = {
56 # there never was such a client CPU, so map it to the server one for backward compat
57 'Icelake-Client' => 'Icelake-Server',
58 'Icelake-Client-noTSX' => 'Icelake-Server-noTSX',
61 my $cputypes_32bit = {
72 my $cpu_vendor_list = {
74 486 => 'GenuineIntel',
75 pentium
=> 'GenuineIntel',
76 pentium2
=> 'GenuineIntel',
77 pentium3
=> 'GenuineIntel',
78 coreduo
=> 'GenuineIntel',
79 core2duo
=> 'GenuineIntel',
80 Conroe
=> 'GenuineIntel',
81 Penryn
=> 'GenuineIntel',
82 Nehalem
=> 'GenuineIntel',
83 'Nehalem-IBRS' => 'GenuineIntel',
84 Westmere
=> 'GenuineIntel',
85 'Westmere-IBRS' => 'GenuineIntel',
86 SandyBridge
=> 'GenuineIntel',
87 'SandyBridge-IBRS' => 'GenuineIntel',
88 IvyBridge
=> 'GenuineIntel',
89 'IvyBridge-IBRS' => 'GenuineIntel',
90 Haswell
=> 'GenuineIntel',
91 'Haswell-IBRS' => 'GenuineIntel',
92 'Haswell-noTSX' => 'GenuineIntel',
93 'Haswell-noTSX-IBRS' => 'GenuineIntel',
94 Broadwell
=> 'GenuineIntel',
95 'Broadwell-IBRS' => 'GenuineIntel',
96 'Broadwell-noTSX' => 'GenuineIntel',
97 'Broadwell-noTSX-IBRS' => 'GenuineIntel',
98 'Skylake-Client' => 'GenuineIntel',
99 'Skylake-Client-IBRS' => 'GenuineIntel',
100 'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',
101 'Skylake-Client-v4' => 'GenuineIntel',
102 'Skylake-Server' => 'GenuineIntel',
103 'Skylake-Server-IBRS' => 'GenuineIntel',
104 'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',
105 'Skylake-Server-v4' => 'GenuineIntel',
106 'Skylake-Server-v5' => 'GenuineIntel',
107 'Cascadelake-Server' => 'GenuineIntel',
108 'Cascadelake-Server-v2' => 'GenuineIntel',
109 'Cascadelake-Server-noTSX' => 'GenuineIntel',
110 'Cascadelake-Server-v4' => 'GenuineIntel',
111 'Cascadelake-Server-v5' => 'GenuineIntel',
112 'Cooperlake' => 'GenuineIntel',
113 'Cooperlake-v2' => 'GenuineIntel',
114 KnightsMill
=> 'GenuineIntel',
115 'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
116 'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
117 'Icelake-Server' => 'GenuineIntel',
118 'Icelake-Server-noTSX' => 'GenuineIntel',
119 'Icelake-Server-v3' => 'GenuineIntel',
120 'Icelake-Server-v4' => 'GenuineIntel',
121 'Icelake-Server-v5' => 'GenuineIntel',
122 'Icelake-Server-v6' => 'GenuineIntel',
123 'SapphireRapids' => 'GenuineIntel',
124 'SapphireRapids-v2' => 'GenuineIntel',
125 'GraniteRapids' => 'GenuineIntel',
128 athlon
=> 'AuthenticAMD',
129 phenom
=> 'AuthenticAMD',
130 Opteron_G1
=> 'AuthenticAMD',
131 Opteron_G2
=> 'AuthenticAMD',
132 Opteron_G3
=> 'AuthenticAMD',
133 Opteron_G4
=> 'AuthenticAMD',
134 Opteron_G5
=> 'AuthenticAMD',
135 EPYC
=> 'AuthenticAMD',
136 'EPYC-IBPB' => 'AuthenticAMD',
137 'EPYC-v3' => 'AuthenticAMD',
138 'EPYC-v4' => 'AuthenticAMD',
139 'EPYC-Rome' => 'AuthenticAMD',
140 'EPYC-Rome-v2' => 'AuthenticAMD',
141 'EPYC-Rome-v3' => 'AuthenticAMD',
142 'EPYC-Rome-v4' => 'AuthenticAMD',
143 'EPYC-Milan' => 'AuthenticAMD',
144 'EPYC-Milan-v2' => 'AuthenticAMD',
145 'EPYC-Genoa' => 'AuthenticAMD',
147 # generic types, use vendor from host node
156 my @supported_cpu_flags = (
170 my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/;
171 my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/;
173 our $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\w\-\._=]+,?)+)$/;
177 description
=> "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').",
179 format_description
=> 'string',
184 'reported-model' => {
185 description
=> "CPU model and vendor to report to the guest. Must be a QEMU/KVM supported model."
186 ." Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.",
188 enum
=> [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ],
193 description
=> "Do not identify as a KVM virtual machine.",
200 pattern
=> qr/[a-zA-Z0-9]{1,12}/,
201 format_description
=> 'vendor-id',
202 description
=> 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
206 description
=> "List of additional CPU flags separated by ';'. Use '+FLAG' to enable,"
207 ." '-FLAG' to disable a flag. Custom CPU models can specify any flag supported by"
208 ." QEMU/KVM, VM-specific flags must be from the following set for security reasons: "
209 . join(', ', @supported_cpu_flags),
210 format_description
=> '+FLAG[;-FLAG...]',
212 pattern
=> qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,
217 format
=> 'pve-phys-bits',
218 format_description
=> '8-64|host',
219 description
=> "The physical memory address bits that are reported to the guest OS. Should"
220 ." be smaller or equal to the host's. Set to 'host' to use value from host CPU, but"
221 ." note that doing so will break live migration to CPUs with other values.",
226 PVE
::JSONSchema
::register_format
('pve-phys-bits', \
&parse_phys_bits
);
227 sub parse_phys_bits
{
228 my ($str, $noerr) = @_;
230 my $err_msg = "value must be an integer between 8 and 64 or 'host'\n";
232 if ($str !~ m/^(host|\d{1,2})$/) {
233 die $err_msg if !$noerr;
237 if ($str =~ m/^\d+$/ && (int($str) < 8 || int($str) > 64)) {
238 die $err_msg if !$noerr;
245 # $cpu_fmt describes both the CPU config passed as part of a VM config, as well
246 # as the definition of a custom CPU model. There are some slight differences
247 # though, which we catch in the custom validation functions below.
248 PVE
::JSONSchema
::register_format
('pve-cpu-conf', $cpu_fmt, \
&validate_cpu_conf
);
249 sub validate_cpu_conf
{
251 # required, but can't be forced in schema since it's encoded in section header for custom models
252 die "CPU is missing cputype\n" if !$cpu->{cputype
};
255 PVE
::JSONSchema
::register_format
('pve-vm-cpu-conf', $cpu_fmt, \
&validate_vm_cpu_conf
);
256 sub validate_vm_cpu_conf
{
259 validate_cpu_conf
($cpu);
261 my $cputype = $cpu->{cputype
};
263 # a VM-specific config is only valid if the cputype exists
264 if (is_custom_model
($cputype)) {
265 # dies on unknown model
266 get_custom_model
($cputype);
268 die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n"
269 if !defined($cpu_vendor_list->{$cputype}) && !defined($builtin_models->{$cputype});
272 # in a VM-specific config, certain properties are limited/forbidden
274 die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n"
275 if ($cpu->{flags
} && $cpu->{flags
} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/);
277 die "Property 'reported-model' not allowed in VM-specific CPU config.\n"
278 if defined($cpu->{'reported-model'});
283 # Section config settings
285 # shallow copy, since SectionConfig modifies propertyList internally
286 propertyList
=> { %$cpu_fmt },
294 return { %$cpu_fmt };
301 sub parse_section_header
{
302 my ($class, $line) = @_;
304 my ($type, $sectionId, $errmsg, $config) =
305 $class->SUPER::parse_section_header
($line);
308 return ($type, $sectionId, $errmsg, {
309 # name is given by section header, and we can always prepend 'custom-'
310 # since we're reading the custom CPU file
311 cputype
=> "custom-$sectionId",
316 my ($class, $filename, $cfg) = @_;
318 mkdir "/etc/pve/virtual-guest";
320 for my $model (keys %{$cfg->{ids
}}) {
321 my $model_conf = $cfg->{ids
}->{$model};
323 die "internal error: tried saving built-in CPU model (or missing prefix): $model_conf->{cputype}\n"
324 if !is_custom_model
($model_conf->{cputype
});
326 die "internal error: tried saving custom cpumodel with cputype (ignoring prefix: $model_conf->{cputype}) not equal to \$cfg->ids entry ($model)\n"
327 if "custom-$model" ne $model_conf->{cputype
};
329 # saved in section header
330 delete $model_conf->{cputype
};
333 $class->SUPER::write_config
($filename, $cfg);
336 sub add_cpu_json_properties
{
339 foreach my $opt (keys %$cpu_fmt) {
340 $prop->{$opt} = $cpu_fmt->{$opt};
347 my ($include_custom) = @_;
351 for my $default_model (keys %{$cpu_vendor_list}) {
353 name
=> $default_model,
355 vendor
=> $cpu_vendor_list->{$default_model},
359 for my $model (keys %{$builtin_models}) {
360 my $reported_model = $builtin_models->{$model}->{'reported-model'};
361 my $vendor = $cpu_vendor_list->{$reported_model};
369 return $models if !$include_custom;
371 my $conf = load_custom_model_conf
();
372 for my $custom_model (keys %{$conf->{ids
}}) {
373 my $reported_model = $conf->{ids
}->{$custom_model}->{'reported-model'};
374 $reported_model //= $cpu_fmt->{'reported-model'}->{default};
375 my $vendor = $cpu_vendor_list->{$reported_model};
377 name
=> "custom-$custom_model",
386 sub is_custom_model
{
388 return $cputype =~ m/^custom-/;
391 # Use this to get a single model in the format described by $cpu_fmt.
392 # Allows names with and without custom- prefix.
393 sub get_custom_model
{
394 my ($name, $noerr) = @_;
396 $name =~ s/^custom-//;
397 my $conf = load_custom_model_conf
();
399 my $entry = $conf->{ids
}->{$name};
400 if (!defined($entry)) {
401 die "Custom cputype '$name' not found\n" if !$noerr;
406 for my $property (keys %$cpu_fmt) {
407 if (my $value = $entry->{$property}) {
408 $model->{$property} = $value;
415 # Print a QEMU device node for a given VM configuration for hotplugging CPUs
416 sub print_cpu_device
{
417 my ($conf, $id) = @_;
419 my $kvm = $conf->{kvm
} // 1;
420 my $cpu = get_default_cpu_type
('x86_64', $kvm);
421 if (my $cputype = $conf->{cpu
}) {
422 my $cpuconf = PVE
::JSONSchema
::parse_property_string
('pve-vm-cpu-conf', $cputype)
423 or die "Cannot parse cpu description: $cputype\n";
424 $cpu = $cpuconf->{cputype
};
426 if (my $model = $builtin_models->{$cpu}) {
427 $cpu = $model->{'reported-model'};
428 } elsif (is_custom_model
($cputype)) {
429 my $custom_cpu = get_custom_model
($cpu);
431 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
433 if (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
434 $cpu = $replacement_type;
438 my $cores = $conf->{cores
} || 1;
440 my $current_core = ($id - 1) % $cores;
441 my $current_socket = int(($id - 1 - $current_core)/$cores);
443 # FIXME: hot plugging other architectures like our unofficial arch64 support?
444 return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
447 # Resolves multiple arrays of hashes representing CPU flags with metadata to a
448 # single string in QEMU "-cpu" compatible format. Later arrays have higher
451 # Hashes take the following format:
454 # op => "+", # defaults to "" if undefined
455 # reason => "to support AES acceleration", # for override warnings
456 # value => "" # needed for kvm=off (value: off) etc...
460 sub resolve_cpu_flags
{
464 for my $flag_name (keys %$hash) {
465 my $flag = $hash->{$flag_name};
466 my $old_flag = $flags->{$flag_name};
469 $flag->{reason
} //= "unknown origin";
472 my $value_changed = (defined($flag->{value
}) != defined($old_flag->{value
})) ||
473 (defined($flag->{value
}) && $flag->{value
} ne $old_flag->{value
});
475 if ($old_flag->{op
} eq $flag->{op
} && !$value_changed) {
476 $flags->{$flag_name}->{reason
} .= " & $flag->{reason}";
480 my $old = print_cpuflag_hash
($flag_name, $flags->{$flag_name});
481 my $new = print_cpuflag_hash
($flag_name, $flag);
482 warn "warning: CPU flag/setting $new overwrites $old\n";
485 $flags->{$flag_name} = $flag;
490 # sort for command line stability
491 for my $flag_name (sort keys %$flags) {
493 $flag_str .= $flags->{$flag_name}->{op
};
494 $flag_str .= $flag_name;
495 $flag_str .= "=$flags->{$flag_name}->{value}"
496 if $flags->{$flag_name}->{value
};
502 sub print_cpuflag_hash
{
503 my ($flag_name, $flag) = @_;
504 my $formatted = "'$flag->{op}$flag_name";
505 $formatted .= "=$flag->{value}" if defined($flag->{value
});
507 $formatted .= " ($flag->{reason})" if defined($flag->{reason
});
511 sub parse_cpuflag_list
{
512 my ($re, $reason, $flaglist) = @_;
515 return $res if !$flaglist;
517 foreach my $flag (split(";", $flaglist)) {
518 if ($flag =~ m/^$re$/) {
519 $res->{$2} = { op
=> $1, reason
=> $reason };
526 # Calculate QEMU's '-cpu' argument from a given VM configuration
527 sub get_cpu_options
{
528 my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
530 my $cputype = get_default_cpu_type
($arch, $kvm);
536 if (my $cpu_prop_str = $conf->{cpu
}) {
537 $cpu = PVE
::JSONSchema
::parse_property_string
('pve-vm-cpu-conf', $cpu_prop_str)
538 or die "Cannot parse cpu description: $cpu_prop_str\n";
540 $cputype = $cpu->{cputype
};
541 if (my $model = $builtin_models->{$cputype}) {
542 $cputype = $model->{'reported-model'};
543 $builtin_cpu->{flags
} = $model->{'flags'};
544 } elsif (is_custom_model
($cputype)) {
545 $custom_cpu = get_custom_model
($cputype);
547 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
548 $kvm_off = $custom_cpu->{hidden
} if defined($custom_cpu->{hidden
});
549 $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};
552 if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {
553 $cputype = $replacement_type;
556 # VM-specific settings override custom CPU config
557 $kvm_off = $cpu->{hidden
} if defined($cpu->{hidden
});
558 $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});
561 my $pve_flags = get_pve_cpu_flags
($conf, $kvm, $cputype, $arch, $machine_version);
564 ? get_hyperv_enlightenments
(
573 my $builtin_cputype_flags = parse_cpuflag_list
(
574 $cpu_flag_any_re, "set by builtin CPU model", $builtin_cpu->{flags
});
576 my $custom_cputype_flags = parse_cpuflag_list
(
577 $cpu_flag_any_re, "set by custom CPU model", $custom_cpu->{flags
});
579 my $vm_flags = parse_cpuflag_list
(
580 $cpu_flag_supported_re, "manually set for VM", $cpu->{flags
});
582 my $pve_forced_flags = {};
583 $pve_forced_flags->{'enforce'} = {
584 reason
=> "error if requested CPU settings not available",
585 } if $cputype ne 'host' && $kvm && $arch eq 'x86_64';
586 $pve_forced_flags->{'kvm'} = {
588 reason
=> "hide KVM virtualization from guest",
591 # $cputype is the "reported-model" for custom types, so we can just look up
592 # the vendor in the default list
593 my $cpu_vendor = $cpu_vendor_list->{$cputype};
595 $pve_forced_flags->{'vendor'} = {
596 value
=> $cpu_vendor,
597 } if $cpu_vendor ne 'default';
598 } elsif ($arch ne 'aarch64') {
599 die "internal error"; # should not happen
602 my $cpu_str = $cputype;
604 # will be resolved in parameter order
605 $cpu_str .= resolve_cpu_flags
(
606 $pve_flags, $hv_flags, $builtin_cputype_flags, $custom_cputype_flags, $vm_flags, $pve_forced_flags);
609 foreach my $conf ($custom_cpu, $cpu) {
610 next if !defined($conf);
611 my $conf_val = $conf->{'phys-bits'};
613 if ($conf_val eq 'host') {
614 $phys_bits = ",host-phys-bits=true";
616 $phys_bits = ",phys-bits=$conf_val";
619 $cpu_str .= $phys_bits;
621 return ('-cpu', $cpu_str);
624 # Some hardcoded flags required by certain configurations
625 sub get_pve_cpu_flags
{
626 my ($conf, $kvm, $cputype, $arch, $machine_version) = @_;
629 my $pve_msg = "set by PVE;";
631 $pve_flags->{'lahf_lm'} = {
633 reason
=> "$pve_msg to support Windows 8.1+",
634 } if $cputype eq 'kvm64' && $arch eq 'x86_64';
636 $pve_flags->{'x2apic'} = {
638 reason
=> "$pve_msg incompatible with Solaris",
639 } if $conf->{ostype
} && $conf->{ostype
} eq 'solaris';
641 $pve_flags->{'sep'} = {
643 reason
=> "$pve_msg to support Windows 8+ and improve Windows XP+",
644 } if $cputype eq 'kvm64' || $cputype eq 'kvm32';
646 $pve_flags->{'rdtscp'} = {
648 reason
=> "$pve_msg broken on AMD Opteron",
649 } if $cputype =~ m/^Opteron/;
651 if (min_version
($machine_version, 2, 3) && $kvm && $arch eq 'x86_64') {
652 $pve_flags->{'kvm_pv_unhalt'} = {
654 reason
=> "$pve_msg to improve Linux guest spinlock performance",
656 $pve_flags->{'kvm_pv_eoi'} = {
658 reason
=> "$pve_msg to improve Linux guest interrupt performance",
665 sub get_hyperv_enlightenments
{
666 my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
668 return if $winversion < 6;
669 return if $bios && $bios eq 'ovmf' && $winversion < 8;
672 my $default_reason = "automatic Hyper-V enlightenment for Windows";
674 my ($flag, $value, $reason) = @_;
676 reason
=> $reason // $default_reason,
681 my $hv_vendor_set = defined($hv_vendor_id);
682 if ($gpu_passthrough || $hv_vendor_set) {
683 $hv_vendor_id //= 'proxmox';
684 $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ?
685 "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough");
688 if (min_version
($machine_version, 2, 3)) {
689 $flagfn->('hv_spinlocks', '0x1fff');
690 $flagfn->('hv_vapic');
691 $flagfn->('hv_time');
693 $flagfn->('hv_spinlocks', '0xffff');
696 if (min_version
($machine_version, 2, 6)) {
697 $flagfn->('hv_reset');
698 $flagfn->('hv_vpindex');
699 $flagfn->('hv_runtime');
702 if ($winversion >= 7) {
703 my $win7_reason = $default_reason . " 7 and higher";
704 $flagfn->('hv_relaxed', undef, $win7_reason);
706 if (min_version
($machine_version, 2, 12)) {
707 $flagfn->('hv_synic', undef, $win7_reason);
708 $flagfn->('hv_stimer', undef, $win7_reason);
711 if (min_version
($machine_version, 3, 1)) {
712 $flagfn->('hv_ipi', undef, $win7_reason);
719 sub get_cpu_from_running_vm
{
722 my $cmdline = PVE
::QemuServer
::Helpers
::parse_cmdline
($pid);
723 die "could not read commandline of running machine\n"
724 if !$cmdline->{cpu
}->{value
};
726 # sanitize and untaint value
727 $cmdline->{cpu
}->{value
} =~ $qemu_cmdline_cpu_re;
731 sub get_default_cpu_type
{
732 my ($arch, $kvm) = @_;
734 my $cputype = $kvm ?
'kvm64' : 'qemu64';
735 $cputype = 'cortex-a57' if $arch eq 'aarch64';
740 sub get_cpu_bitness
{
741 my ($cpu_prop_str, $arch) = @_;
743 $arch //= get_host_arch
();
745 my $cputype = get_default_cpu_type
($arch, 0);
748 my $cpu = PVE
::JSONSchema
::parse_property_string
('pve-vm-cpu-conf', $cpu_prop_str)
749 or die "Cannot parse cpu description: $cpu_prop_str\n";
751 my $cputype = $cpu->{cputype
};
753 if (my $model = $builtin_models->{$cputype}) {
754 $cputype = $model->{'reported-model'};
755 } elsif (is_custom_model
($cputype)) {
756 my $custom_cpu = get_custom_model
($cputype);
757 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
761 return $cputypes_32bit->{$cputype} ?
32 : 64 if $arch eq 'x86_64';
762 return 64 if $arch eq 'aarch64';
764 die "unsupported architecture '$arch'\n";
767 __PACKAGE__-
>register();