all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH guest-common v5 1/1] add PCI/USB Mapping configs
Date: Tue,  6 Jun 2023 15:52:01 +0200	[thread overview]
Message-ID: <20230606135222.984747-3-d.csapak@proxmox.com> (raw)
In-Reply-To: <20230606135222.984747-1-d.csapak@proxmox.com>

adds a config file for each type of resource (usb/pci) by using a 'map'
array propertystring for each node mapping

in each mapping we save the path(s) and some other information to detect
hardware changes (if possible) like the vendor/device id

both configs have custom header parser/formatter to omit the type (since
we only want one type per config here)

also each config has some helpers like find_on_current_node

the resulting config (e.g. for pci) would look like this:

---
some-pci-device
    description some device
    map node=node1,path=0000:00:01.0,id=1234:1234,iommugroup=4
    map node=node2,path=0000:01:01.0,id=1234:1234,iommugroup=5
    map node=node3,path=0000:02:01.0,id=1234:1234,iommugroup=6
---

some special notes:
* mdev is a per config entry, since it does not make sense to mix mdev
  and non-mdev devices
* to have multiple mappings for a node (for choosing a single one during
  vm startup), the config has a 'map' line for each such mapping

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
changes from v4:
* rename s/resource/mapping/i

 src/Makefile           |   3 +
 src/PVE/Mapping/PCI.pm | 226 +++++++++++++++++++++++++++++++++++++++++
 src/PVE/Mapping/USB.pm | 183 +++++++++++++++++++++++++++++++++
 3 files changed, 412 insertions(+)
 create mode 100644 src/PVE/Mapping/PCI.pm
 create mode 100644 src/PVE/Mapping/USB.pm

diff --git a/src/Makefile b/src/Makefile
index 57a360b..cbc40c1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -14,6 +14,9 @@ install: PVE
 	install -m 0644 PVE/Replication.pm ${PERL5DIR}/PVE/
 	install -m 0644 PVE/StorageTunnel.pm ${PERL5DIR}/PVE/
 	install -m 0644 PVE/Tunnel.pm ${PERL5DIR}/PVE/
+	install -d ${PERL5DIR}/PVE/Mapping
+	install -m 0644 PVE/Mapping/PCI.pm ${PERL5DIR}/PVE/Mapping/
+	install -m 0644 PVE/Mapping/USB.pm ${PERL5DIR}/PVE/Mapping/
 	install -d ${PERL5DIR}/PVE/VZDump
 	install -m 0644 PVE/VZDump/Plugin.pm ${PERL5DIR}/PVE/VZDump/
 	install -m 0644 PVE/VZDump/Common.pm ${PERL5DIR}/PVE/VZDump/
diff --git a/src/PVE/Mapping/PCI.pm b/src/PVE/Mapping/PCI.pm
new file mode 100644
index 0000000..5b9d6b3
--- /dev/null
+++ b/src/PVE/Mapping/PCI.pm
@@ -0,0 +1,226 @@
+package PVE::Mapping::PCI;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_lock_file cfs_write_file);
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option parse_property_string);
+use PVE::SectionConfig;
+use PVE::SysFSTools;
+
+use base qw(PVE::SectionConfig);
+
+my $FILENAME = 'mapping/pci.cfg';
+
+cfs_register_file($FILENAME,
+		  sub { __PACKAGE__->parse_config(@_); },
+		  sub { __PACKAGE__->write_config(@_); });
+
+
+# so we don't have to repeat the type every time
+sub parse_section_header {
+    my ($class, $line) = @_;
+
+    if ($line =~ m/^(\S+)\s*$/) {
+	my $id = $1;
+	my $errmsg = undef; # set if you want to skip whole section
+	eval { PVE::JSONSchema::pve_verify_configid($id) };
+	$errmsg = $@ if $@;
+	my $config = {}; # to return additional attributes
+	return ('pci', $id, $errmsg, $config);
+    }
+    return undef;
+}
+
+sub format_section_header {
+    my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
+
+    return "$sectionId\n";
+}
+
+sub type {
+    return 'pci';
+}
+
+my $PCI_RE = "[a-f0-9]{4,}:[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?";
+
+my $map_fmt = {
+    node => get_standard_option('pve-node'),
+    id =>{
+	description => "The vendor and device ID that is expected. Used for".
+	" detecting hardware changes",
+	type => 'string',
+	pattern => qr/^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{4}$/,
+    },
+    'subsystem-id' => {
+	description => "The subsystem vendor and device ID that is expected. Used".
+	" for detecting hardware changes.",
+	type => 'string',
+	pattern => qr/^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{4}$/,
+	optional => 1,
+    },
+    path => {
+	description => "The path to the device. If the function is omitted, the whole device is"
+	." mapped. In that case use the attributes of the first device. You can give"
+	." multiple paths as a semicolon seperated list, the first available will then"
+	." be chosen on guest start.",
+	type => 'string',
+	pattern => "(?:${PCI_RE};)*${PCI_RE}",
+    },
+    iommugroup => {
+	type => 'integer',
+	description => "The IOMMU group in which the device is to be expected in.".
+	"Used for detecting hardware changes.",
+	optional => 1,
+    },
+    description => {
+	description => "Description of the node specific device.",
+	type => 'string',
+	optional => 1,
+	maxLength => 4096,
+    },
+};
+
+my $defaultData = {
+    propertyList => {
+	id => {
+	    type => 'string',
+	    description => "The ID of the logical PCI mapping.",
+	    format => 'pve-configid',
+	},
+	description => {
+	    description => "Description of the logical PCI device.",
+	    type => 'string',
+	    optional => 1,
+	    maxLength => 4096,
+	},
+	mdev => {
+	    type => 'boolean',
+	    optional => 1,
+	},
+	map => {
+	    type => 'array',
+	    description => 'A list of maps for the cluster nodes.',
+	    optional => 1,
+	    items => {
+		type => 'string',
+		format => $map_fmt,
+	    },
+	},
+    },
+};
+
+sub private {
+    return $defaultData;
+}
+
+sub options {
+    return {
+	description => { optional => 1 },
+	mdev => { optional => 1 },
+	map => {},
+    };
+}
+
+# checks if the given config is valid for the current node
+sub assert_valid {
+    my ($name, $cfg) = @_;
+
+    my @paths = split(';', $cfg->{path} // '');
+
+    my $idx = 0;
+    for my $path (@paths) {
+
+	my $multifunction = 0;
+	if ($path !~ m/\.[a-f0-9]/i) {
+	    # whole device, add .0 (must exist)
+	    $path = "$path.0";
+	    $multifunction = 1;
+	}
+
+	my $info = PVE::SysFSTools::pci_device_info($path, 1);
+	die "pci device '$path' not found\n" if !defined($info);
+
+	my $correct_props = {
+	    id => "$info->{vendor}:$info->{device}",
+	    iommugroup => $info->{iommugroup},
+	};
+
+	if (defined($info->{'subsystem_vendor'}) && defined($info->{'subsystem_device'})) {
+	    $correct_props->{'subsystem-id'} = "$info->{'subsystem_vendor'}:$info->{'subsystem_device'}";
+	}
+
+	for my $prop (sort keys %$correct_props) {
+	    next if $prop eq 'iommugroup' && $idx > 0; # check iommu only on the first device
+
+	    next if !defined($correct_props->{$prop}) && !defined($cfg->{$prop});
+	    die "no '$prop' for device '$path'\n"
+		if defined($correct_props->{$prop}) && !defined($cfg->{$prop});
+	    die "'$prop' configured but should not be\n"
+		if !defined($correct_props->{$prop}) && defined($cfg->{$prop});
+
+	    my $correct_prop = $correct_props->{$prop};
+	    $correct_prop =~ s/0x//g;
+	    my $configured_prop = $cfg->{$prop};
+	    $configured_prop =~ s/0x//g;
+
+	    die "'$prop' does not match for '$name' ($correct_prop != $configured_prop)\n"
+		if $correct_prop ne $configured_prop;
+	}
+	$idx++;
+    }
+
+    return 1;
+};
+
+sub config {
+    return cfs_read_file($FILENAME);
+}
+
+sub lock_pci_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file($FILENAME, undef, $code);
+    if (my $err = $@) {
+	$errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+sub write_pci_config {
+    my ($cfg) = @_;
+
+    cfs_write_file($FILENAME, $cfg);
+}
+
+sub find_on_current_node {
+    my ($id) = @_;
+
+    my $cfg = PVE::Mapping::PCI::config();
+    my $node = PVE::INotify::nodename();
+
+    # ignore errors
+    return get_node_mapping($cfg, $id, $node);
+}
+
+sub get_node_mapping {
+    my ($cfg, $id, $nodename) = @_;
+
+    return undef if !defined($cfg->{ids}->{$id});
+
+    my $res = [];
+    for my $map ($cfg->{ids}->{$id}->{map}->@*) {
+	my $entry = eval { parse_property_string($map_fmt, $map) };
+	warn $@ if $@;
+	if ($entry && $entry->{node} eq $nodename) {
+	    push $res->@*, $entry;
+	}
+    }
+
+    return $res;
+}
+
+PVE::Mapping::PCI->register();
+PVE::Mapping::PCI->init();
+
+1;
diff --git a/src/PVE/Mapping/USB.pm b/src/PVE/Mapping/USB.pm
new file mode 100644
index 0000000..483e92b
--- /dev/null
+++ b/src/PVE/Mapping/USB.pm
@@ -0,0 +1,183 @@
+package PVE::Mapping::USB;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_lock_file cfs_write_file);
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option parse_property_string);
+use PVE::SectionConfig;
+use PVE::SysFSTools;
+
+use base qw(PVE::SectionConfig);
+
+my $FILENAME = 'mapping/usb.cfg';
+
+cfs_register_file($FILENAME,
+		  sub { __PACKAGE__->parse_config(@_); },
+		  sub { __PACKAGE__->write_config(@_); });
+
+
+# so we don't have to repeat the type every time
+sub parse_section_header {
+    my ($class, $line) = @_;
+
+    if ($line =~ m/^(\S+)\s*$/) {
+	my $id = $1;
+	my $errmsg = undef; # set if you want to skip whole section
+	eval { PVE::JSONSchema::pve_verify_configid($id) };
+	$errmsg = $@ if $@;
+	my $config = {}; # to return additional attributes
+	return ('usb', $id, $errmsg, $config);
+    }
+    return undef;
+}
+
+sub format_section_header {
+    my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
+
+    return "$sectionId\n";
+}
+
+sub type {
+    return 'usb';
+}
+
+my $map_fmt = {
+    node => get_standard_option('pve-node'),
+    'id' => {
+	description => "The vendor and device ID that is expected. If a USB path".
+	" is given, it is only used for detecting hardware changes",
+	type => 'string',
+	pattern => qr/^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{4}$/,
+    },
+    path => {
+	description => "The path to the usb device.",
+	type => 'string',
+	optional => 1,
+	pattern => qr/^(\d+)\-(\d+(\.\d+)*)$/,
+    },
+    description => {
+	description => "Description of the node specific device.",
+	type => 'string',
+	optional => 1,
+	maxLength => 4096,
+    },
+};
+
+my $defaultData = {
+    propertyList => {
+	id => {
+	    type => 'string',
+	    description => "The ID of the logical PCI mapping.",
+	    format => 'pve-configid',
+	},
+	description => {
+	    description => "Description of the logical PCI device.",
+	    type => 'string',
+	    optional => 1,
+	    maxLength => 4096,
+	},
+	map => {
+	    type => 'array',
+	    description => 'A list of maps for the cluster nodes.',
+	    items => {
+		type => 'string',
+		format => $map_fmt,
+	    },
+	},
+    },
+};
+sub private {
+    return $defaultData;
+}
+
+sub options {
+    return {
+	description => { optional => 1 },
+	map => {},
+    };
+}
+
+# checks if the given device is valid for the current node
+sub assert_valid {
+    my ($name, $cfg) = @_;
+
+    my $id = $cfg->{id};
+
+    my $usb_list = PVE::SysFSTools::scan_usb();
+
+    my $info;
+    if (my $path = $cfg->{path}) {
+	for my $dev (@$usb_list) {
+	    next if !$dev->{usbpath} || !$dev->{busnum};
+	    my $usbpath = "$dev->{busnum}-$dev->{usbpath}";
+	    next if $usbpath ne $path;
+	    $info = $dev;
+	}
+	die "usb device '$path' not found\n" if !defined($info);
+
+	my $realId = "$info->{vendid}:$info->{prodid}";
+	die "'id' does not match for '$name' ($realId != $id)\n"
+	    if $realId ne $id;
+    } else {
+	for my $dev (@$usb_list) {
+	    my $realId = "$dev->{vendid}:$dev->{prodid}";
+	    next if $realId ne $id;
+	    $info = $dev;
+	}
+	die "usb device '$id' not found\n" if !defined($info);
+    }
+
+    return 1;
+};
+
+sub config {
+    return cfs_read_file($FILENAME);
+}
+
+sub lock_usb_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file($FILENAME, undef, $code);
+    if (my $err = $@) {
+	$errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+sub write_usb_config {
+    my ($cfg) = @_;
+
+    cfs_write_file($FILENAME, $cfg);
+}
+
+sub find_on_current_node {
+    my ($id) = @_;
+
+    my $cfg = config();
+    my $node = PVE::INotify::nodename();
+
+    return get_node_mapping($cfg, $id, $node);
+}
+
+sub get_node_mapping {
+    my ($cfg, $id, $nodename) = @_;
+
+    return undef if !defined($cfg->{ids}->{$id});
+
+    my $res = [];
+    for my $map ($cfg->{ids}->{$id}->{map}->@*) {
+	my $entry = eval { parse_property_string($map_fmt, $map) };
+	warn $@ if $@;
+	if ($entry && $entry->{node} eq $nodename) {
+	    push $res->@*, $entry;
+	}
+    }
+
+    return $res;
+}
+
+PVE::Mapping::USB->register();
+PVE::Mapping::USB->init();
+
+1;
-- 
2.30.2





  parent reply	other threads:[~2023-06-06 13:52 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-06-06 13:51 [pve-devel] [PATCH access-control/guest-common/qemu-server/manager v5] cluster mapping Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH access-control v5 1/1] add privileges and paths for cluster resource mapping Dominik Csapak
2023-06-07 17:03   ` [pve-devel] applied: " Thomas Lamprecht
2023-06-06 13:52 ` Dominik Csapak [this message]
2023-06-07 17:17   ` [pve-devel] applied: [PATCH guest-common v5 1/1] add PCI/USB Mapping configs Thomas Lamprecht
2023-06-06 13:52 ` [pve-devel] [PATCH qemu-server v5 1/6] enable cluster mapped USB devices for guests Dominik Csapak
2023-06-13 12:23   ` Wolfgang Bumiller
2023-06-06 13:52 ` [pve-devel] [PATCH qemu-server v5 2/6] enable cluster mapped PCI " Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH qemu-server v5 3/6] check_local_resources: extend for mapped resources Dominik Csapak
2023-06-13 12:43   ` Wolfgang Bumiller
2023-06-06 13:52 ` [pve-devel] [PATCH qemu-server v5 4/6] api: migrate preconditions: use new check_local_resources info Dominik Csapak
2023-06-13 12:46   ` Wolfgang Bumiller
2023-06-06 13:52 ` [pve-devel] [PATCH qemu-server v5 5/6] migration: check for mapped resources Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH qemu-server v5 6/6] add test for mapped pci devices Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 01/15] pvesh: fix parameters for proxyto_callback Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 02/15] api: add resource map api endpoints for PCI and USB Dominik Csapak
2023-06-13 11:26   ` Wolfgang Bumiller
2023-06-06 13:52 ` [pve-devel] [PATCH v5 03/15] ui: parser: add helpers for lists of property strings Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 04/15] ui: form/USBSelector: make it more flexible with nodename Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 05/15] ui: form: add PCIMapSelector Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 06/15] ui: form: add USBMapSelector Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 07/15] ui: qemu/PCIEdit: rework panel to add a mapped configuration Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 08/15] ui: qemu/USBEdit: add 'mapped' device case Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 09/15] ui: form: add MultiPCISelector Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 10/15] ui: add edit window for pci mappings Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 11/15] ui: add edit window for usb mappings Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 12/15] ui: add ResourceMapTree Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 13/15] ui: allow configuring pci and usb mapping Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 14/15] ui: window/Migrate: allow mapped devices Dominik Csapak
2023-06-06 13:52 ` [pve-devel] [PATCH v5 15/15] ui: improve permission handling for hardware Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230606135222.984747-3-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal