From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <d.csapak@proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by lists.proxmox.com (Postfix) with ESMTPS id 9C0CA74521
 for <pve-devel@lists.proxmox.com>; Mon, 21 Jun 2021 15:56:37 +0200 (CEST)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
 by firstgate.proxmox.com (Proxmox) with ESMTP id 07BF81C489
 for <pve-devel@lists.proxmox.com>; Mon, 21 Jun 2021 15:55:46 +0200 (CEST)
Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com
 [94.136.29.106])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by firstgate.proxmox.com (Proxmox) with ESMTPS id 9445E1C307
 for <pve-devel@lists.proxmox.com>; Mon, 21 Jun 2021 15:55:36 +0200 (CEST)
Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1])
 by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 6ECEA43074
 for <pve-devel@lists.proxmox.com>; Mon, 21 Jun 2021 15:55:36 +0200 (CEST)
From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Date: Mon, 21 Jun 2021 15:55:17 +0200
Message-Id: <20210621135534.14807-5-d.csapak@proxmox.com>
X-Mailer: git-send-email 2.20.1
In-Reply-To: <20210621135534.14807-1-d.csapak@proxmox.com>
References: <20210621135534.14807-1-d.csapak@proxmox.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-SPAM-LEVEL: Spam detection results:  0
 AWL 0.801 Adjusted score from AWL reputation of From: address
 BAYES_00                 -1.9 Bayes spam probability is 0 to 1%
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
Subject: [pve-devel] [PATCH common 3/3] add PVE/HardwareMap and Plugins
X-BeenThere: pve-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox VE development discussion <pve-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/>
List-Post: <mailto:pve-devel@lists.proxmox.com>
List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe>
X-List-Received-Date: Mon, 21 Jun 2021 13:56:37 -0000

adds the Top level package PVE::HardwareMap that
registers the Plugins for the config, as well
as provides some convenience methods
(find_device_on_current_node, lock/write/get config)

The Plugins themselves are usual SectionConfigs plugins
with (for now) two types: usb, pci

each type gets an 'assert_device_valid' method that
checks the local node for validity of the device

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/Makefile                     |  4 ++
 src/PVE/HardwareMap.pm           | 54 ++++++++++++++++++++
 src/PVE/HardwareMap/PCIPlugin.pm | 87 ++++++++++++++++++++++++++++++++
 src/PVE/HardwareMap/Plugin.pm    | 82 ++++++++++++++++++++++++++++++
 src/PVE/HardwareMap/USBPlugin.pm | 69 +++++++++++++++++++++++++
 5 files changed, 296 insertions(+)
 create mode 100644 src/PVE/HardwareMap.pm
 create mode 100644 src/PVE/HardwareMap/PCIPlugin.pm
 create mode 100644 src/PVE/HardwareMap/Plugin.pm
 create mode 100644 src/PVE/HardwareMap/USBPlugin.pm

diff --git a/src/Makefile b/src/Makefile
index 13de6c6..7cd20d5 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -17,6 +17,10 @@ LIB_SOURCES = \
 	Daemon.pm \
 	Exception.pm \
 	Format.pm \
+	HardwareMap.pm \
+	HardwareMap/Plugin.pm \
+	HardwareMap/PCIPlugin.pm \
+	HardwareMap/USBPlugin.pm \
 	INotify.pm \
 	JSONSchema.pm \
 	LDAP.pm \
diff --git a/src/PVE/HardwareMap.pm b/src/PVE/HardwareMap.pm
new file mode 100644
index 0000000..a0f4a9f
--- /dev/null
+++ b/src/PVE/HardwareMap.pm
@@ -0,0 +1,54 @@
+package PVE::HardwareMap;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::HardwareMap::Plugin;
+use PVE::HardwareMap::PCIPlugin;
+use PVE::HardwareMap::USBPlugin;
+use PVE::INotify;
+
+use base qw(Exporter);
+
+our @EXPORT_OK = qw(find_device_on_current_node);
+
+PVE::HardwareMap::PCIPlugin->register();
+PVE::HardwareMap::USBPlugin->register();
+
+PVE::HardwareMap::Plugin->init();
+
+sub find_device_on_current_node {
+    my ($type, $id) = @_;
+
+    my $data = cfs_read_file($PVE::HardwareMap::Plugin::FILENAME);
+
+    my $res = {};
+
+    my $node = PVE::INotify::nodename();
+    my $sectionid = "$node:$id";
+
+    return $data->{ids}->{$sectionid};
+}
+
+sub config {
+    return cfs_read_file($PVE::HardwareMap::Plugin::FILENAME);
+}
+
+sub lock_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file($PVE::HardwareMap::Plugin::FILENAME, undef, $code);
+    if (my $err = $@) {
+	$errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+sub write_config {
+    my ($cfg) = @_;
+
+    cfs_write_file($PVE::HardwareMap::Plugin::FILENAME, $cfg);
+}
+
+
+1;
diff --git a/src/PVE/HardwareMap/PCIPlugin.pm b/src/PVE/HardwareMap/PCIPlugin.pm
new file mode 100644
index 0000000..eb6d090
--- /dev/null
+++ b/src/PVE/HardwareMap/PCIPlugin.pm
@@ -0,0 +1,87 @@
+package PVE::HardwareMap::PCIPlugin;
+
+use strict;
+use warnings;
+
+use PVE::HardwareMap::Plugin;
+use PVE::SysFSTools;
+
+use base qw(PVE::HardwareMap::Plugin);
+
+sub type {
+    return 'pci';
+}
+
+sub properties {
+    return {
+	pcipath => {
+	    description => "The path to the device. If the function is omitted, the whole device is mapped. In that case use the attrubes of the first device.",
+	    type => 'string',
+	    pattern => qr/^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}(:?.[0-9A-Fa-f])?$/,
+	},
+	mdev => {
+	    description => "The Device supports mediated devices.",
+	    type => 'boolean',
+	},
+	iommugroup => {
+	    type => 'integer',
+	    description => "The IOMMU group in which the device is in.",
+	}
+    };
+}
+
+sub options {
+    return {
+	node => { fixed => 1 },
+	name => { fixed => 1 },
+	pcipath => { },
+	vendor => { },
+	device => { },
+	iommugroup => { },
+	subsystem_vendor => { optional => 1 },
+	subsystem_device => { optional => 1 },
+	mdev => { optional => 1 },
+    };
+}
+
+sub assert_device_valid {
+    my ($class, $cfg) = @_;
+
+    my $path = $cfg->{pcipath};
+
+    if ($path !~ m/\.[a-f0-9]/i) {
+	# whole device, add .0 (must exist)
+	$path = "$path.0";
+    }
+
+    my $info = PVE::SysFSTools::pci_device_info($path, 1);
+    die "pci device '$path' not found\n" if !defined($info);
+
+    my $props = {
+	vendor => $cfg->{vendor},
+	device => $cfg->{device},
+	subsystem_vendor => $cfg->{subsystem_vendor},
+	subsystem_device => $cfg->{subsystem_device},
+	iommugroup => $cfg->{iommugroup},
+	mdev => $cfg->{mdev},
+    };
+
+    for my $prop (keys %$props) {
+	next if !defined($info->{$prop});
+	die "no '$prop' for device '$path'\n"
+	    if defined($info->{$prop}) && !$props->{$prop};
+
+
+	my $correct_prop = $info->{$prop};
+	$correct_prop =~ s/^0x//;
+	my $configured_prop = $props->{$prop};
+	$configured_prop =~ s/^0x//;
+
+	die "'$prop' does not match for '$cfg->{name}' ($correct_prop != $configured_prop)\n"
+	    if $correct_prop ne $configured_prop;
+    }
+
+    return 1;
+}
+
+1;
diff --git a/src/PVE/HardwareMap/Plugin.pm b/src/PVE/HardwareMap/Plugin.pm
new file mode 100644
index 0000000..2d33785
--- /dev/null
+++ b/src/PVE/HardwareMap/Plugin.pm
@@ -0,0 +1,82 @@
+package PVE::HardwareMap::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_register_file cfs_read_file);
+use PVE::JSONSchema qw(get_standard_option);
+
+use base qw(PVE::SectionConfig);
+
+our $FILENAME = "nodes/hardware-map.conf";
+cfs_register_file($FILENAME,
+		  sub { PVE::HardwareMap::Plugin->parse_config(@_); },
+		  sub { PVE::HardwareMap::Plugin->write_config(@_); });
+
+my $defaultData = {
+    propertyList => {
+	type => { description => "Hardware Type", },
+	name => {
+	    description => "The custom name for the device",
+	    type => 'string',
+	    format => 'pve-configid',
+	},
+	node => get_standard_option('pve-node'),
+	vendor => {
+	    description => "The vendor ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	},
+	device => {
+	    description => "The device ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	},
+	subsystem_vendor => {
+	    description => "The subsystem vendor ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	    optional => 1,
+	},
+	subsystem_device => {
+	    description => "The subsystem device ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	    optional => 1,
+	},
+    },
+};
+
+sub private {
+    return $defaultData;
+}
+
+sub parse_section_header {
+    my ($class, $line) = @_;
+
+    if ($line =~ m/^(\S+):\s*(\S+):(\S+)\s*$/) {
+	my ($type, $node, $name) = ($1, $2, $3);
+	# TODO verify properties
+	my $errmsg = undef;
+	my $config = {
+	    node => $node,
+	    name => $name,
+	};
+	return ($type, "$node:$name", $errmsg, $config);
+    }
+    return undef;
+}
+
+sub format_section_header {
+    my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
+    $done_hash->{name} = 1;
+    $done_hash->{node} = 1;
+    return $class->SUPER::format_section_header($type, $sectionId, $scfg, $done_hash);
+}
+
+sub assert_device_valid {
+    my ($cfg) = @_;
+    die "implement me";
+}
+
+1;
diff --git a/src/PVE/HardwareMap/USBPlugin.pm b/src/PVE/HardwareMap/USBPlugin.pm
new file mode 100644
index 0000000..a1fff77
--- /dev/null
+++ b/src/PVE/HardwareMap/USBPlugin.pm
@@ -0,0 +1,69 @@
+package PVE::HardwareMap::USBPlugin;
+
+use strict;
+use warnings;
+
+use PVE::HardwareMap::Plugin;
+
+use base qw(PVE::HardwareMap::Plugin);
+
+sub type {
+    return 'usb';
+}
+
+sub properties {
+    return {
+	usbpath => {
+	    description => "The path to the usb device.",
+	    type => 'string',
+	    pattern => qr/^(\d+)\-(\d+(\.\d+)*)$/,
+	},
+    };
+}
+
+sub options {
+    return {
+	node => { fixed => 1 },
+	name => { fixed => 1 },
+	vendor => { },
+	device => { },
+	usbpath => { optional => 1 },
+    };
+}
+
+sub assert_device_valid {
+    my ($class, $cfg) = @_;
+
+    my $name = $cfg->{name};
+    my $vendor = $cfg->{vendor};
+    my $device = $cfg->{device};
+
+    my $usb_list = PVE::SysFSTools::scan_usb();
+
+    my $info;
+    if (my $path = $cfg->{usbpath}) {
+	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);
+
+	die "'vendor' does not match for '$name'\n"
+	    if $info->{vendid} ne $cfg->{vendor};
+	die "'device' does not match for '$name'\n"
+	    if $info->{prodid} ne $cfg->{device};
+    } else {
+	for my $dev (@$usb_list) {
+	    next if $dev->{vendid} ne $vendor;
+	    next if $dev->{prodid} ne $device;
+	    $info = $dev;
+	}
+	die "usb device '$vendor:$device' not found\n" if !defined($info);
+    }
+
+    return 1;
+}
+
+1;
-- 
2.20.1