]> git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/CPUConfig.pm
cpu config: map depreacated IceLake-Client CPU type to IceLake-Server
[qemu-server.git] / PVE / QemuServer / CPUConfig.pm
1 package PVE::QemuServer::CPUConfig;
2
3 use strict;
4 use warnings;
5
6 use PVE::JSONSchema;
7 use PVE::Cluster qw(cfs_register_file cfs_read_file);
8 use PVE::QemuServer::Helpers qw(min_version);
9
10 use base qw(PVE::SectionConfig Exporter);
11
12 our @EXPORT_OK = qw(
13 print_cpu_device
14 get_cpu_options
15 );
16
17 # under certain race-conditions, this module might be loaded before pve-cluster
18 # has started completely, so ensure we don't prevent the FUSE mount with our dir
19 if (PVE::Cluster::check_cfs_is_mounted(1)) {
20 mkdir "/etc/pve/virtual-guest";
21 }
22
23 my $default_filename = "virtual-guest/cpu-models.conf";
24 cfs_register_file($default_filename,
25 sub { PVE::QemuServer::CPUConfig->parse_config(@_); },
26 sub { PVE::QemuServer::CPUConfig->write_config(@_); });
27
28 sub load_custom_model_conf {
29 return cfs_read_file($default_filename);
30 }
31
32 my $depreacated_cpu_map = {
33 # there never was such a client CPU, so map it to the server one for backward compat
34 'Icelake-Client' => 'Icelake-Server',
35 'Icelake-Client-noTSX' => 'Icelake-Server-noTSX',
36 };
37
38 my $cpu_vendor_list = {
39 # Intel CPUs
40 486 => 'GenuineIntel',
41 pentium => 'GenuineIntel',
42 pentium2 => 'GenuineIntel',
43 pentium3 => 'GenuineIntel',
44 coreduo => 'GenuineIntel',
45 core2duo => 'GenuineIntel',
46 Conroe => 'GenuineIntel',
47 Penryn => 'GenuineIntel',
48 Nehalem => 'GenuineIntel',
49 'Nehalem-IBRS' => 'GenuineIntel',
50 Westmere => 'GenuineIntel',
51 'Westmere-IBRS' => 'GenuineIntel',
52 SandyBridge => 'GenuineIntel',
53 'SandyBridge-IBRS' => 'GenuineIntel',
54 IvyBridge => 'GenuineIntel',
55 'IvyBridge-IBRS' => 'GenuineIntel',
56 Haswell => 'GenuineIntel',
57 'Haswell-IBRS' => 'GenuineIntel',
58 'Haswell-noTSX' => 'GenuineIntel',
59 'Haswell-noTSX-IBRS' => 'GenuineIntel',
60 Broadwell => 'GenuineIntel',
61 'Broadwell-IBRS' => 'GenuineIntel',
62 'Broadwell-noTSX' => 'GenuineIntel',
63 'Broadwell-noTSX-IBRS' => 'GenuineIntel',
64 'Skylake-Client' => 'GenuineIntel',
65 'Skylake-Client-IBRS' => 'GenuineIntel',
66 'Skylake-Client-noTSX-IBRS' => 'GenuineIntel',
67 'Skylake-Server' => 'GenuineIntel',
68 'Skylake-Server-IBRS' => 'GenuineIntel',
69 'Skylake-Server-noTSX-IBRS' => 'GenuineIntel',
70 'Cascadelake-Server' => 'GenuineIntel',
71 'Cascadelake-Server-noTSX' => 'GenuineIntel',
72 KnightsMill => 'GenuineIntel',
73 'Icelake-Client' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
74 'Icelake-Client-noTSX' => 'GenuineIntel', # depreacated, removed with QEMU 7.1
75 'Icelake-Server' => 'GenuineIntel',
76 'Icelake-Server-noTSX' => 'GenuineIntel',
77
78 # AMD CPUs
79 athlon => 'AuthenticAMD',
80 phenom => 'AuthenticAMD',
81 Opteron_G1 => 'AuthenticAMD',
82 Opteron_G2 => 'AuthenticAMD',
83 Opteron_G3 => 'AuthenticAMD',
84 Opteron_G4 => 'AuthenticAMD',
85 Opteron_G5 => 'AuthenticAMD',
86 EPYC => 'AuthenticAMD',
87 'EPYC-IBPB' => 'AuthenticAMD',
88 'EPYC-Rome' => 'AuthenticAMD',
89 'EPYC-Milan' => 'AuthenticAMD',
90
91 # generic types, use vendor from host node
92 host => 'default',
93 kvm32 => 'default',
94 kvm64 => 'default',
95 qemu32 => 'default',
96 qemu64 => 'default',
97 max => 'default',
98 };
99
100 my @supported_cpu_flags = (
101 'pcid',
102 'spec-ctrl',
103 'ibpb',
104 'ssbd',
105 'virt-ssbd',
106 'amd-ssbd',
107 'amd-no-ssb',
108 'pdpe1gb',
109 'md-clear',
110 'hv-tlbflush',
111 'hv-evmcs',
112 'aes'
113 );
114 my $cpu_flag_supported_re = qr/([+-])(@{[join('|', @supported_cpu_flags)]})/;
115 my $cpu_flag_any_re = qr/([+-])([a-zA-Z0-9\-_\.]+)/;
116
117 our $qemu_cmdline_cpu_re = qr/^((?>[+-]?[\w\-\._=]+,?)+)$/;
118
119 my $cpu_fmt = {
120 cputype => {
121 description => "Emulated CPU type. Can be default or custom name (custom model names must be prefixed with 'custom-').",
122 type => 'string',
123 format_description => 'string',
124 default => 'kvm64',
125 default_key => 1,
126 optional => 1,
127 },
128 'reported-model' => {
129 description => "CPU model and vendor to report to the guest. Must be a QEMU/KVM supported model."
130 ." Only valid for custom CPU model definitions, default models will always report themselves to the guest OS.",
131 type => 'string',
132 enum => [ sort { lc("$a") cmp lc("$b") } keys %$cpu_vendor_list ],
133 default => 'kvm64',
134 optional => 1,
135 },
136 hidden => {
137 description => "Do not identify as a KVM virtual machine.",
138 type => 'boolean',
139 optional => 1,
140 default => 0
141 },
142 'hv-vendor-id' => {
143 type => 'string',
144 pattern => qr/[a-zA-Z0-9]{1,12}/,
145 format_description => 'vendor-id',
146 description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
147 optional => 1,
148 },
149 flags => {
150 description => "List of additional CPU flags separated by ';'. Use '+FLAG' to enable,"
151 ." '-FLAG' to disable a flag. Custom CPU models can specify any flag supported by"
152 ." QEMU/KVM, VM-specific flags must be from the following set for security reasons: "
153 . join(', ', @supported_cpu_flags),
154 format_description => '+FLAG[;-FLAG...]',
155 type => 'string',
156 pattern => qr/$cpu_flag_any_re(;$cpu_flag_any_re)*/,
157 optional => 1,
158 },
159 'phys-bits' => {
160 type => 'string',
161 format => 'pve-phys-bits',
162 format_description => '8-64|host',
163 description => "The physical memory address bits that are reported to the guest OS. Should"
164 ." be smaller or equal to the host's. Set to 'host' to use value from host CPU, but"
165 ." note that doing so will break live migration to CPUs with other values.",
166 optional => 1,
167 },
168 };
169
170 PVE::JSONSchema::register_format('pve-phys-bits', \&parse_phys_bits);
171 sub parse_phys_bits {
172 my ($str, $noerr) = @_;
173
174 my $err_msg = "value must be an integer between 8 and 64 or 'host'\n";
175
176 if ($str !~ m/^(host|\d{1,2})$/) {
177 die $err_msg if !$noerr;
178 return;
179 }
180
181 if ($str =~ m/^\d+$/ && (int($str) < 8 || int($str) > 64)) {
182 die $err_msg if !$noerr;
183 return;
184 }
185
186 return $str;
187 }
188
189 # $cpu_fmt describes both the CPU config passed as part of a VM config, as well
190 # as the definition of a custom CPU model. There are some slight differences
191 # though, which we catch in the custom validation functions below.
192 PVE::JSONSchema::register_format('pve-cpu-conf', $cpu_fmt, \&validate_cpu_conf);
193 sub validate_cpu_conf {
194 my ($cpu) = @_;
195
196 # required, but can't be forced in schema since it's encoded in section
197 # header for custom models
198 die "CPU is missing cputype\n" if !$cpu->{cputype};
199
200 return $cpu;
201 }
202 PVE::JSONSchema::register_format('pve-vm-cpu-conf', $cpu_fmt, \&validate_vm_cpu_conf);
203 sub validate_vm_cpu_conf {
204 my ($cpu) = @_;
205
206 validate_cpu_conf($cpu);
207
208 my $cputype = $cpu->{cputype};
209
210 # a VM-specific config is only valid if the cputype exists
211 if (is_custom_model($cputype)) {
212 # dies on unknown model
213 get_custom_model($cputype);
214 } else {
215 die "Built-in cputype '$cputype' is not defined (missing 'custom-' prefix?)\n"
216 if !defined($cpu_vendor_list->{$cputype});
217 }
218
219 # in a VM-specific config, certain properties are limited/forbidden
220
221 die "VM-specific CPU flags must be a subset of: @{[join(', ', @supported_cpu_flags)]}\n"
222 if ($cpu->{flags} && $cpu->{flags} !~ m/^$cpu_flag_supported_re(;$cpu_flag_supported_re)*$/);
223
224 die "Property 'reported-model' not allowed in VM-specific CPU config.\n"
225 if defined($cpu->{'reported-model'});
226
227 return $cpu;
228 }
229
230 # Section config settings
231 my $defaultData = {
232 # shallow copy, since SectionConfig modifies propertyList internally
233 propertyList => { %$cpu_fmt },
234 };
235
236 sub private {
237 return $defaultData;
238 }
239
240 sub options {
241 return { %$cpu_fmt };
242 }
243
244 sub type {
245 return 'cpu-model';
246 }
247
248 sub parse_section_header {
249 my ($class, $line) = @_;
250
251 my ($type, $sectionId, $errmsg, $config) =
252 $class->SUPER::parse_section_header($line);
253
254 return if !$type;
255 return ($type, $sectionId, $errmsg, {
256 # name is given by section header, and we can always prepend 'custom-'
257 # since we're reading the custom CPU file
258 cputype => "custom-$sectionId",
259 });
260 }
261
262 sub write_config {
263 my ($class, $filename, $cfg) = @_;
264
265 mkdir "/etc/pve/virtual-guest";
266
267 for my $model (keys %{$cfg->{ids}}) {
268 my $model_conf = $cfg->{ids}->{$model};
269
270 die "internal error: tried saving built-in CPU model (or missing prefix): $model_conf->{cputype}\n"
271 if !is_custom_model($model_conf->{cputype});
272
273 die "internal error: tried saving custom cpumodel with cputype (ignoring prefix: $model_conf->{cputype}) not equal to \$cfg->ids entry ($model)\n"
274 if "custom-$model" ne $model_conf->{cputype};
275
276 # saved in section header
277 delete $model_conf->{cputype};
278 }
279
280 $class->SUPER::write_config($filename, $cfg);
281 }
282
283 sub add_cpu_json_properties {
284 my ($prop) = @_;
285
286 foreach my $opt (keys %$cpu_fmt) {
287 $prop->{$opt} = $cpu_fmt->{$opt};
288 }
289
290 return $prop;
291 }
292
293 sub get_cpu_models {
294 my ($include_custom) = @_;
295
296 my $models = [];
297
298 for my $default_model (keys %{$cpu_vendor_list}) {
299 push @$models, {
300 name => $default_model,
301 custom => 0,
302 vendor => $cpu_vendor_list->{$default_model},
303 };
304 }
305
306 return $models if !$include_custom;
307
308 my $conf = load_custom_model_conf();
309 for my $custom_model (keys %{$conf->{ids}}) {
310 my $reported_model = $conf->{ids}->{$custom_model}->{'reported-model'};
311 $reported_model //= $cpu_fmt->{'reported-model'}->{default};
312 my $vendor = $cpu_vendor_list->{$reported_model};
313 push @$models, {
314 name => "custom-$custom_model",
315 custom => 1,
316 vendor => $vendor,
317 };
318 }
319
320 return $models;
321 }
322
323 sub is_custom_model {
324 my ($cputype) = @_;
325 return $cputype =~ m/^custom-/;
326 }
327
328 # Use this to get a single model in the format described by $cpu_fmt.
329 # Allows names with and without custom- prefix.
330 sub get_custom_model {
331 my ($name, $noerr) = @_;
332
333 $name =~ s/^custom-//;
334 my $conf = load_custom_model_conf();
335
336 my $entry = $conf->{ids}->{$name};
337 if (!defined($entry)) {
338 die "Custom cputype '$name' not found\n" if !$noerr;
339 return;
340 }
341
342 my $model = {};
343 for my $property (keys %$cpu_fmt) {
344 if (my $value = $entry->{$property}) {
345 $model->{$property} = $value;
346 }
347 }
348
349 return $model;
350 }
351
352 # Print a QEMU device node for a given VM configuration for hotplugging CPUs
353 sub print_cpu_device {
354 my ($conf, $id) = @_;
355
356 my $kvm = $conf->{kvm} // 1;
357 my $cpu = $kvm ? "kvm64" : "qemu64";
358 if (my $cputype = $conf->{cpu}) {
359 my $cpuconf = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cputype)
360 or die "Cannot parse cpu description: $cputype\n";
361 $cpu = $cpuconf->{cputype};
362
363 if (is_custom_model($cpu)) {
364 my $custom_cpu = get_custom_model($cpu);
365
366 $cpu = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
367 } elsif (my $replacement_type = $depreacated_cpu_map->{$cpu}) {
368 $cpu = $replacement_type;
369 }
370 }
371
372 my $cores = $conf->{cores} || 1;
373
374 my $current_core = ($id - 1) % $cores;
375 my $current_socket = int(($id - 1 - $current_core)/$cores);
376
377 # FIXME: hot plugging other architectures like our unofficial arch64 support?
378 return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
379 }
380
381 # Resolves multiple arrays of hashes representing CPU flags with metadata to a
382 # single string in QEMU "-cpu" compatible format. Later arrays have higher
383 # priority.
384 #
385 # Hashes take the following format:
386 # {
387 # aes => {
388 # op => "+", # defaults to "" if undefined
389 # reason => "to support AES acceleration", # for override warnings
390 # value => "" # needed for kvm=off (value: off) etc...
391 # },
392 # ...
393 # }
394 sub resolve_cpu_flags {
395 my $flags = {};
396
397 for my $hash (@_) {
398 for my $flag_name (keys %$hash) {
399 my $flag = $hash->{$flag_name};
400 my $old_flag = $flags->{$flag_name};
401
402 $flag->{op} //= "";
403 $flag->{reason} //= "unknown origin";
404
405 if ($old_flag) {
406 my $value_changed = (defined($flag->{value}) != defined($old_flag->{value})) ||
407 (defined($flag->{value}) && $flag->{value} ne $old_flag->{value});
408
409 if ($old_flag->{op} eq $flag->{op} && !$value_changed) {
410 $flags->{$flag_name}->{reason} .= " & $flag->{reason}";
411 next;
412 }
413
414 my $old = print_cpuflag_hash($flag_name, $flags->{$flag_name});
415 my $new = print_cpuflag_hash($flag_name, $flag);
416 warn "warning: CPU flag/setting $new overwrites $old\n";
417 }
418
419 $flags->{$flag_name} = $flag;
420 }
421 }
422
423 my $flag_str = '';
424 # sort for command line stability
425 for my $flag_name (sort keys %$flags) {
426 $flag_str .= ',';
427 $flag_str .= $flags->{$flag_name}->{op};
428 $flag_str .= $flag_name;
429 $flag_str .= "=$flags->{$flag_name}->{value}"
430 if $flags->{$flag_name}->{value};
431 }
432
433 return $flag_str;
434 }
435
436 sub print_cpuflag_hash {
437 my ($flag_name, $flag) = @_;
438 my $formatted = "'$flag->{op}$flag_name";
439 $formatted .= "=$flag->{value}" if defined($flag->{value});
440 $formatted .= "'";
441 $formatted .= " ($flag->{reason})" if defined($flag->{reason});
442 return $formatted;
443 }
444
445 sub parse_cpuflag_list {
446 my ($re, $reason, $flaglist) = @_;
447
448 my $res = {};
449 return $res if !$flaglist;
450
451 foreach my $flag (split(";", $flaglist)) {
452 if ($flag =~ m/^$re$/) {
453 $res->{$2} = { op => $1, reason => $reason };
454 }
455 }
456
457 return $res;
458 }
459
460 # Calculate QEMU's '-cpu' argument from a given VM configuration
461 sub get_cpu_options {
462 my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
463
464 my $cputype = $kvm ? "kvm64" : "qemu64";
465 if ($arch eq 'aarch64') {
466 $cputype = 'cortex-a57';
467 }
468
469 my $cpu = {};
470 my $custom_cpu;
471 my $hv_vendor_id;
472 if (my $cpu_prop_str = $conf->{cpu}) {
473 $cpu = PVE::JSONSchema::parse_property_string('pve-vm-cpu-conf', $cpu_prop_str)
474 or die "Cannot parse cpu description: $cpu_prop_str\n";
475
476 $cputype = $cpu->{cputype};
477
478 if (is_custom_model($cputype)) {
479 $custom_cpu = get_custom_model($cputype);
480
481 $cputype = $custom_cpu->{'reported-model'} // $cpu_fmt->{'reported-model'}->{default};
482 $kvm_off = $custom_cpu->{hidden} if defined($custom_cpu->{hidden});
483 $hv_vendor_id = $custom_cpu->{'hv-vendor-id'};
484 }
485
486 if (my $replacement_type = $depreacated_cpu_map->{$cputype}) {
487 $cputype = $replacement_type;
488 }
489
490 # VM-specific settings override custom CPU config
491 $kvm_off = $cpu->{hidden} if defined($cpu->{hidden});
492 $hv_vendor_id = $cpu->{'hv-vendor-id'} if defined($cpu->{'hv-vendor-id'});
493 }
494
495 my $pve_flags = get_pve_cpu_flags($conf, $kvm, $cputype, $arch, $machine_version);
496
497 my $hv_flags = $kvm
498 ? get_hyperv_enlightenments(
499 $winversion,
500 $machine_version,
501 $conf->{bios},
502 $gpu_passthrough,
503 $hv_vendor_id,
504 )
505 : undef;
506
507 my $custom_cputype_flags = parse_cpuflag_list(
508 $cpu_flag_any_re, "set by custom CPU model", $custom_cpu->{flags});
509
510 my $vm_flags = parse_cpuflag_list(
511 $cpu_flag_supported_re, "manually set for VM", $cpu->{flags});
512
513 my $pve_forced_flags = {};
514 $pve_forced_flags->{'enforce'} = {
515 reason => "error if requested CPU settings not available",
516 } if $cputype ne 'host' && $kvm && $arch eq 'x86_64';
517 $pve_forced_flags->{'kvm'} = {
518 value => "off",
519 reason => "hide KVM virtualization from guest",
520 } if $kvm_off;
521
522 # $cputype is the "reported-model" for custom types, so we can just look up
523 # the vendor in the default list
524 my $cpu_vendor = $cpu_vendor_list->{$cputype};
525 if ($cpu_vendor) {
526 $pve_forced_flags->{'vendor'} = {
527 value => $cpu_vendor,
528 } if $cpu_vendor ne 'default';
529 } elsif ($arch ne 'aarch64') {
530 die "internal error"; # should not happen
531 }
532
533 my $cpu_str = $cputype;
534
535 # will be resolved in parameter order
536 $cpu_str .= resolve_cpu_flags(
537 $pve_flags, $hv_flags, $custom_cputype_flags, $vm_flags, $pve_forced_flags);
538
539 my $phys_bits = '';
540 foreach my $conf ($custom_cpu, $cpu) {
541 next if !defined($conf);
542 my $conf_val = $conf->{'phys-bits'};
543 next if !$conf_val;
544 if ($conf_val eq 'host') {
545 $phys_bits = ",host-phys-bits=true";
546 } else {
547 $phys_bits = ",phys-bits=$conf_val";
548 }
549 }
550 $cpu_str .= $phys_bits;
551
552 return ('-cpu', $cpu_str);
553 }
554
555 # Some hardcoded flags required by certain configurations
556 sub get_pve_cpu_flags {
557 my ($conf, $kvm, $cputype, $arch, $machine_version) = @_;
558
559 my $pve_flags = {};
560 my $pve_msg = "set by PVE;";
561
562 $pve_flags->{'lahf_lm'} = {
563 op => '+',
564 reason => "$pve_msg to support Windows 8.1+",
565 } if $cputype eq 'kvm64' && $arch eq 'x86_64';
566
567 $pve_flags->{'x2apic'} = {
568 op => '-',
569 reason => "$pve_msg incompatible with Solaris",
570 } if $conf->{ostype} && $conf->{ostype} eq 'solaris';
571
572 $pve_flags->{'sep'} = {
573 op => '+',
574 reason => "$pve_msg to support Windows 8+ and improve Windows XP+",
575 } if $cputype eq 'kvm64' || $cputype eq 'kvm32';
576
577 $pve_flags->{'rdtscp'} = {
578 op => '-',
579 reason => "$pve_msg broken on AMD Opteron",
580 } if $cputype =~ m/^Opteron/;
581
582 if (min_version($machine_version, 2, 3) && $kvm && $arch eq 'x86_64') {
583 $pve_flags->{'kvm_pv_unhalt'} = {
584 op => '+',
585 reason => "$pve_msg to improve Linux guest spinlock performance",
586 };
587 $pve_flags->{'kvm_pv_eoi'} = {
588 op => '+',
589 reason => "$pve_msg to improve Linux guest interrupt performance",
590 };
591 }
592
593 return $pve_flags;
594 }
595
596 sub get_hyperv_enlightenments {
597 my ($winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
598
599 return if $winversion < 6;
600 return if $bios && $bios eq 'ovmf' && $winversion < 8;
601
602 my $flags = {};
603 my $default_reason = "automatic Hyper-V enlightenment for Windows";
604 my $flagfn = sub {
605 my ($flag, $value, $reason) = @_;
606 $flags->{$flag} = {
607 reason => $reason // $default_reason,
608 value => $value,
609 }
610 };
611
612 my $hv_vendor_set = defined($hv_vendor_id);
613 if ($gpu_passthrough || $hv_vendor_set) {
614 $hv_vendor_id //= 'proxmox';
615 $flagfn->('hv_vendor_id', $hv_vendor_id, $hv_vendor_set ?
616 "custom hv_vendor_id set" : "NVIDIA workaround for GPU passthrough");
617 }
618
619 if (min_version($machine_version, 2, 3)) {
620 $flagfn->('hv_spinlocks', '0x1fff');
621 $flagfn->('hv_vapic');
622 $flagfn->('hv_time');
623 } else {
624 $flagfn->('hv_spinlocks', '0xffff');
625 }
626
627 if (min_version($machine_version, 2, 6)) {
628 $flagfn->('hv_reset');
629 $flagfn->('hv_vpindex');
630 $flagfn->('hv_runtime');
631 }
632
633 if ($winversion >= 7) {
634 my $win7_reason = $default_reason . " 7 and higher";
635 $flagfn->('hv_relaxed', undef, $win7_reason);
636
637 if (min_version($machine_version, 2, 12)) {
638 $flagfn->('hv_synic', undef, $win7_reason);
639 $flagfn->('hv_stimer', undef, $win7_reason);
640 }
641
642 if (min_version($machine_version, 3, 1)) {
643 $flagfn->('hv_ipi', undef, $win7_reason);
644 }
645 }
646
647 return $flags;
648 }
649
650 sub get_cpu_from_running_vm {
651 my ($pid) = @_;
652
653 my $cmdline = PVE::QemuServer::Helpers::parse_cmdline($pid);
654 die "could not read commandline of running machine\n"
655 if !$cmdline->{cpu}->{value};
656
657 # sanitize and untaint value
658 $cmdline->{cpu}->{value} =~ $qemu_cmdline_cpu_re;
659 return $1;
660 }
661
662 __PACKAGE__->register();
663 __PACKAGE__->init();
664
665 1;