]>
Commit | Line | Data |
---|---|---|
11fa9568 DC |
1 | package PVE::Mapping::PCI; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
59b33631 TL |
6 | use PVE::Cluster qw( |
7 | cfs_lock_file | |
8 | cfs_read_file | |
9 | cfs_register_file | |
10 | cfs_write_file | |
11 | ); | |
12 | use PVE::INotify (); | |
11fa9568 | 13 | use PVE::JSONSchema qw(get_standard_option parse_property_string); |
59b33631 | 14 | use PVE::SysFSTools (); |
11fa9568 DC |
15 | |
16 | use base qw(PVE::SectionConfig); | |
17 | ||
18 | my $FILENAME = 'mapping/pci.cfg'; | |
19 | ||
20 | cfs_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 | |
26 | sub 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 | ||
40 | sub format_section_header { | |
41 | my ($class, $type, $sectionId, $scfg, $done_hash) = @_; | |
42 | ||
43 | return "$sectionId\n"; | |
44 | } | |
45 | ||
46 | sub type { | |
47 | return 'pci'; | |
48 | } | |
49 | ||
50 | my $PCI_RE = "[a-f0-9]{4,}:[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?"; | |
51 | ||
52 | my $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 | ||
89 | my $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 | ||
118 | sub private { | |
119 | return $defaultData; | |
120 | } | |
121 | ||
122 | sub 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 | |
131 | sub 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 | ||
181 | sub config { | |
182 | return cfs_read_file($FILENAME); | |
183 | } | |
184 | ||
185 | sub 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 | ||
194 | sub write_pci_config { | |
195 | my ($cfg) = @_; | |
196 | ||
197 | cfs_write_file($FILENAME, $cfg); | |
198 | } | |
199 | ||
200 | sub 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 | ||
210 | sub 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 | ||
227 | PVE::Mapping::PCI->register(); | |
228 | PVE::Mapping::PCI->init(); | |
229 | ||
230 | 1; |