]> git.proxmox.com Git - pve-guest-common.git/blame - src/PVE/Mapping/PCI.pm
mappings: cleanup imports
[pve-guest-common.git] / src / PVE / Mapping / PCI.pm
CommitLineData
11fa9568
DC
1package PVE::Mapping::PCI;
2
3use strict;
4use warnings;
5
59b33631
TL
6use PVE::Cluster qw(
7 cfs_lock_file
8 cfs_read_file
9 cfs_register_file
10 cfs_write_file
11);
12use PVE::INotify ();
11fa9568 13use PVE::JSONSchema qw(get_standard_option parse_property_string);
59b33631 14use PVE::SysFSTools ();
11fa9568
DC
15
16use base qw(PVE::SectionConfig);
17
18my $FILENAME = 'mapping/pci.cfg';
19
20cfs_register_file($FILENAME,
21 sub { __PACKAGE__->parse_config(@_); },
22 sub { __PACKAGE__->write_config(@_); });
23
24
25# so we don't have to repeat the type every time
26sub parse_section_header {
27 my ($class, $line) = @_;
28
29 if ($line =~ m/^(\S+)\s*$/) {
30 my $id = $1;
31 my $errmsg = undef; # set if you want to skip whole section
32 eval { PVE::JSONSchema::pve_verify_configid($id) };
33 $errmsg = $@ if $@;
34 my $config = {}; # to return additional attributes
35 return ('pci', $id, $errmsg, $config);
36 }
37 return undef;
38}
39
40sub format_section_header {
41 my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
42
43 return "$sectionId\n";
44}
45
46sub type {
47 return 'pci';
48}
49
50my $PCI_RE = "[a-f0-9]{4,}:[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?";
51
52my $map_fmt = {
53 node => get_standard_option('pve-node'),
54 id =>{
55 description => "The vendor and device ID that is expected. Used for".
56 " detecting hardware changes",
57 type => 'string',
58 pattern => qr/^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{4}$/,
59 },
60 'subsystem-id' => {
61 description => "The subsystem vendor and device ID that is expected. Used".
62 " for detecting hardware changes.",
63 type => 'string',
64 pattern => qr/^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{4}$/,
65 optional => 1,
66 },
67 path => {
68 description => "The path to the device. If the function is omitted, the whole device is"
69 ." mapped. In that case use the attributes of the first device. You can give"
70 ." multiple paths as a semicolon seperated list, the first available will then"
71 ." be chosen on guest start.",
72 type => 'string',
73 pattern => "(?:${PCI_RE};)*${PCI_RE}",
74 },
75 iommugroup => {
76 type => 'integer',
77 description => "The IOMMU group in which the device is to be expected in.".
78 "Used for detecting hardware changes.",
79 optional => 1,
80 },
81 description => {
82 description => "Description of the node specific device.",
83 type => 'string',
84 optional => 1,
85 maxLength => 4096,
86 },
87};
88
89my $defaultData = {
90 propertyList => {
91 id => {
92 type => 'string',
93 description => "The ID of the logical PCI mapping.",
94 format => 'pve-configid',
95 },
96 description => {
97 description => "Description of the logical PCI device.",
98 type => 'string',
99 optional => 1,
100 maxLength => 4096,
101 },
102 mdev => {
103 type => 'boolean',
104 optional => 1,
105 },
106 map => {
107 type => 'array',
108 description => 'A list of maps for the cluster nodes.',
109 optional => 1,
110 items => {
111 type => 'string',
112 format => $map_fmt,
113 },
114 },
115 },
116};
117
118sub private {
119 return $defaultData;
120}
121
122sub options {
123 return {
124 description => { optional => 1 },
125 mdev => { optional => 1 },
126 map => {},
127 };
128}
129
130# checks if the given config is valid for the current node
131sub assert_valid {
132 my ($name, $cfg) = @_;
133
134 my @paths = split(';', $cfg->{path} // '');
135
136 my $idx = 0;
137 for my $path (@paths) {
138
139 my $multifunction = 0;
140 if ($path !~ m/\.[a-f0-9]/i) {
141 # whole device, add .0 (must exist)
142 $path = "$path.0";
143 $multifunction = 1;
144 }
145
146 my $info = PVE::SysFSTools::pci_device_info($path, 1);
147 die "pci device '$path' not found\n" if !defined($info);
148
149 my $correct_props = {
150 id => "$info->{vendor}:$info->{device}",
151 iommugroup => $info->{iommugroup},
152 };
153
154 if (defined($info->{'subsystem_vendor'}) && defined($info->{'subsystem_device'})) {
155 $correct_props->{'subsystem-id'} = "$info->{'subsystem_vendor'}:$info->{'subsystem_device'}";
156 }
157
158 for my $prop (sort keys %$correct_props) {
159 next if $prop eq 'iommugroup' && $idx > 0; # check iommu only on the first device
160
161 next if !defined($correct_props->{$prop}) && !defined($cfg->{$prop});
162 die "no '$prop' for device '$path'\n"
163 if defined($correct_props->{$prop}) && !defined($cfg->{$prop});
164 die "'$prop' configured but should not be\n"
165 if !defined($correct_props->{$prop}) && defined($cfg->{$prop});
166
167 my $correct_prop = $correct_props->{$prop};
168 $correct_prop =~ s/0x//g;
169 my $configured_prop = $cfg->{$prop};
170 $configured_prop =~ s/0x//g;
171
172 die "'$prop' does not match for '$name' ($correct_prop != $configured_prop)\n"
173 if $correct_prop ne $configured_prop;
174 }
175 $idx++;
176 }
177
178 return 1;
179};
180
181sub config {
182 return cfs_read_file($FILENAME);
183}
184
185sub lock_pci_config {
186 my ($code, $errmsg) = @_;
187
188 cfs_lock_file($FILENAME, undef, $code);
189 if (my $err = $@) {
190 $errmsg ? die "$errmsg: $err" : die $err;
191 }
192}
193
194sub write_pci_config {
195 my ($cfg) = @_;
196
197 cfs_write_file($FILENAME, $cfg);
198}
199
200sub find_on_current_node {
201 my ($id) = @_;
202
203 my $cfg = PVE::Mapping::PCI::config();
204 my $node = PVE::INotify::nodename();
205
206 # ignore errors
207 return get_node_mapping($cfg, $id, $node);
208}
209
210sub get_node_mapping {
211 my ($cfg, $id, $nodename) = @_;
212
213 return undef if !defined($cfg->{ids}->{$id});
214
215 my $res = [];
216 for my $map ($cfg->{ids}->{$id}->{map}->@*) {
217 my $entry = eval { parse_property_string($map_fmt, $map) };
218 warn $@ if $@;
219 if ($entry && $entry->{node} eq $nodename) {
220 push $res->@*, $entry;
221 }
222 }
223
224 return $res;
225}
226
227PVE::Mapping::PCI->register();
228PVE::Mapping::PCI->init();
229
2301;