$line: ";
@@ -143,6 +153,7 @@ function usb_physical_port($usbbusdev) {
// By default lspci does not output the when the only domain in the system is 0000. Add it back.
$pciaddress = "0000:".$pciaddress;
}
+ if ( in_array($pciaddress,$sriovvfs)) continue;
echo ($append) ? "" : "";
exec("lspci -v -s $pciaddress", $outputvfio);
if (preg_grep("/vfio-pci/i", $outputvfio)) {
@@ -179,6 +190,112 @@ function usb_physical_port($usbbusdev) {
echo " ";
}
}
+
+
+ if (array_key_exists($pciaddress,$sriov) && in_array(substr($sriov[$pciaddress]['class_id'],0,4),$allowedPCIClass)) {
+ echo "";
+ echo _("SRIOV Available VFs").":{$sriov[$pciaddress]['total_vfs']}";
+ $num_vfs= $sriov[$pciaddress]['num_vfs'];
+
+ if (isset($sriov_devices[$pciaddress]))
+ $file_numvfs = $sriov_devices[$pciaddress]['vf_count'];
+ else $file_numvfs = 0;
+
+ echo ' '._('Select number of VFs').': ';
+ echo '';
+
+ // First option: None
+ $selected = ($file_numvfs == 0) ? ' selected' : '';
+ echo "None ";
+
+ // Generate numeric options
+ for ($i = 1; $i <= $sriov[$pciaddress]['total_vfs']; $i++) {
+ $selected = ($file_numvfs == $i) ? ' selected' : '';
+ echo "$i ";
+ }
+
+ echo ' ';
+ echo " "._("Current").":".$num_vfs;
+ echo ' ';
+ echo ' ';
+
+ if ($file_numvfs != $num_vfs) echo " ".sprintf(_("Pending action or reboot"))." ";
+
+ echo " ";
+ foreach($sriov[$pciaddress]['vfs'] as $vrf) {
+ $pciaddress = $vrf['pci'];
+ if ($removed) $line=preg_replace('/R/', '', $line, 1);
+ if (preg_match($BDF_REGEX, $pciaddress)) {
+ // By default lspci does not output the when the only domain in the system is 0000. Add it back.
+ $pciaddress = "0000:".$pciaddress;
+ }
+ echo "";
+ $outputvfio = $vrfline =[];
+ exec('lspci -ns "'.$pciaddress.'"|awk \'BEGIN{ORS=" "}{print "["$3"]"}\';lspci -s "'.$pciaddress.'"',$vrfline);
+
+ $vd = trim(explode(" ", $vrfline[0])[0], "[]");
+ exec("lspci -v -s $pciaddress", $outputvfio);
+ if (preg_grep("/vfio-pci/i", $outputvfio)) {
+ echo " ";
+ $isbound = "true";
+ }
+ echo " ";
+ if ((strpos($line, 'Host bridge') === false) && (strpos($line, 'PCI bridge') === false)) {
+ if (file_exists('/sys/bus/pci/devices/'.$pciaddress.'/reset')) echo " ";
+ echo " ";
+ if (!$removed) {
+ echo ' | or just
+ echo (array_key_exists($pciaddress,$sriov_devices_settings) && $sriov_devices_settings[$pciaddress]['vfio'] == 1) ? " checked>" : ">";
+ }
+ } else { echo " "; }
+ echo ' IOMMU Group '.$vrf['iommu_group'].": ",$vrfline[0],' ';
+ if (array_key_exists($pciaddress,$pci_device_diffs)) {
+ echo "";
+ echo " ";
+ echo _("PCI Device change");
+ echo " "._("Action").":".ucfirst(_($pci_device_diffs[$pciaddress]['status']))." ";
+ $ackparm .= $pciaddress.",".$pci_device_diffs[$pciaddress]['status'].";";
+ if ($pci_device_diffs[$pciaddress]['status']!="removed") echo $pci_device_diffs[$pciaddress]['device']['description'];
+ echo " ";
+ if ($pci_device_diffs[$pciaddress]['status']=="changed") {
+ echo "";
+ echo _("Differences");
+ foreach($pci_device_diffs[$pciaddress]['differences'] as $key => $changes){
+ echo " $key "._("before").":{$changes['old']} "._("after").":{$changes['new']} ";
+ }
+ echo " ";
+ }
+ }
+ if (isset($sriov_devices_settings[$pciaddress])) {
+ $mac = $sriov_devices_settings[$pciaddress]['mac'];
+ } else {
+ $mac = null;
+ }
+ $placeholder = empty($mac) ? _('Dynamic allocation') : '';
+ $saved_mac = empty($mac) ? '' : htmlspecialchars($mac, ENT_QUOTES);
+ $current_mac = empty($vrf['mac']) ? '' : htmlspecialchars(strtoupper($vrf['mac']), ENT_QUOTES);
+ echo "";
+ echo ''._("MAC Address").': ';
+ echo " ";
+ echo ' ';
+ echo ' ';
+ if (isset($sriov_devices_settings[$pciaddress])) {
+ $filevfio = $sriov_devices_settings[$pciaddress]['vfio'] == 1 ? true : false;
+ } else $filevfio = false;
+ $vfiocheck = $vrf['driver'] == "vfio-pci" ? true:false;
+ echo ' ';
+ if ($vrf['driver'] != "vfio-pci") echo _("Current").": ";
+ echo $vrf['driver'] == "vfio-pci" ? _("Bound to VFIO") : strtoupper($vrf['mac']);
+ $vfstatus ="";
+ if ($filevfio != $vfiocheck || (strtoupper($vrf['mac']) != $mac && $mac != null && $vrf['mac']!=null )) $vfstatus = " ".sprintf(_("Pending action or reboot"));
+ echo " $vfstatus ";
+ echo " ";
+ }
+ }
+
unset($outputvfio);
switch (true) {
case (strpos($line, 'USB controller') !== false):
diff --git a/emhttp/plugins/dynamix/include/apply.sriov-cfg.php b/emhttp/plugins/dynamix/include/apply.sriov-cfg.php
new file mode 100644
index 0000000000..47f6e39774
--- /dev/null
+++ b/emhttp/plugins/dynamix/include/apply.sriov-cfg.php
@@ -0,0 +1,284 @@
+
+
+
+#VFSETTINGS=0000:04:11.5|8086:1520|0|62:00:04:11:05:01 0000:04:10.5|8086:1520|1|62:00:04:10:05:01
+#VFS=0000:04:00.1|8086:1521|3 0000:04:00.0|8086:1521|2
+
+$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
+require_once "$docroot/webGui/include/Helpers.php";
+require_once "$docroot/webGui/include/Wrappers.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
+require_once "$docroot/webGui/include/SriovHelpers.php";
+require_once "$docroot/webGui/include/Translations.php";
+
+function acknowledge_PCI($pciaddr)
+{
+ $savedfile = "/boot/config/savedpcidata.json";
+ $saved = loadSavedData($savedfile);
+ if (!$saved) {echo "ERROR"; return;};
+ $current = loadCurrentPCIData();
+ $saved[$pciaddr] = $current[$pciaddr];
+ file_put_contents($savedfile,json_encode($saved,JSON_PRETTY_PRINT));
+}
+
+function json_response($success, $error = null, $details = [])
+{
+ header('Content-Type: application/json');
+
+ # Normalize details
+ if (is_array($details)) {
+ if (count($details) === 1) {
+ # Collapse single-item arrays to a single value
+ $details = reset($details);
+ } elseif (empty($details)) {
+ $details = null;
+ }
+ }
+
+ $response = [
+ 'success' => (bool)$success,
+ 'error' => $error,
+ 'details' => $details
+ ];
+
+ $json = json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+ echo $json;
+ exit;
+}
+
+function safe_file_put_contents($path, $data)
+{
+ # If the file exists but is not writable
+ if (file_exists($path) && !is_writable($path)) {
+ throw new RuntimeException("File not writable: $path", 1001);
+ }
+
+ # If file does not exist → make sure directory is writable
+ $dir = dirname($path);
+ if (!file_exists($path) && !is_writable($dir)) {
+ throw new RuntimeException("Directory not writable for file creation: $dir", 1002);
+ }
+
+ # Attempt the write
+ $result = @file_put_contents($path, $data);
+
+ if ($result === false) {
+ # PHP error (rare for sysfs)
+ $e = error_get_last();
+ if (!empty($e['message'])) {
+ throw new RuntimeException(
+ "Failed writing to $path: " . $e['message'],
+ 1003
+ );
+ }
+
+ # Sysfs-specific: read back and check
+ $after = @file_get_contents($path);
+ if ((string)$after !== (string)$data) {
+ throw new RuntimeException(
+ "Kernel rejected write to sysfs file ($path)",
+ 1004
+ );
+ }
+
+ throw new RuntimeException("Unknown write failure: $path", 1005);
+ }
+
+ return $result;
+}
+
+
+function action_settings($pciid)
+{
+ $sriov = json_decode(getSriovInfoJson(), true);
+ $sriov_devices_settings = parseVFSettings();
+ $vfs = $sriov[$pciid]['vfs'] ?? [];
+ $pci_device_diffs = comparePCIData();
+
+
+ $results = [];
+
+ foreach ($vfs as $vf) {
+ $vfpci = $vf['pci'];
+ if (array_key_exists($vfpci,$pci_device_diffs)) {
+ #Acknowledge PCI addition
+ if ($pci_device_diffs[$vfpci]['status'] == "added") acknowledge_PCI($vfpci);
+ }
+ if (!isset($sriov_devices_settings[$vfpci])) continue;
+
+ $vfio = $sriov_devices_settings[$vfpci]['vfio'];
+ $mac = $sriov_devices_settings[$vfpci]['mac'];
+
+ # Skip if no action needed
+ if ($vfio == 0 && $mac == "") continue;
+
+ if ($mac == "") $mac="00:00:00:00:00:00";
+
+ $cmd = "/usr/local/sbin/sriov-vfsettings.sh " .
+ escapeshellarg($vfpci) . " " .
+ escapeshellarg($vf['vd']) . " " .
+ escapeshellarg($vfio) . " " .
+ escapeshellarg($mac) . " 2>&1"; # capture stderr too
+
+ $output = [];
+ $ret = 0;
+ exec($cmd, $output, $ret);
+
+ # Clean output: remove blank lines and trim whitespace
+ $output = array_filter(array_map('trim', $output));
+
+ if ($ret !== 0) {
+ # Only include relevant lines for error reporting
+ $results[$vfpci] = [
+ 'success' => false,
+ 'error' => implode("\n", $output) ?: sprintf(_("Unknown error (exit code %s)"),$ret)
+ ];
+ } else {
+ # Success: include minimal details or last few lines
+ $results[$vfpci] = [
+ 'success' => true,
+ 'details' => _('Applied VF settings')
+ ];
+ }
+ }
+ return $results;
+}
+
+
+$type = _var($_POST, 'type');
+$pciid = _var($_POST, 'pciid');
+$vd = _var($_POST, 'vd');
+
+if (!isset($pciid) || !isset($vd)) {
+ json_response(false, _("Missing PCI ID or virtual device"));
+ exit;
+}
+
+
+
+switch ($type) {
+ # --------------------------------------------------------
+ # SR-IOV enable/disable & VF count changes
+ # --------------------------------------------------------
+ case "sriov":
+ $numvfs = _var($_POST, 'numvfs');
+ $currentvfs = _var($_POST, 'currentvfs');
+ $filepath = "/sys/bus/pci/devices/$pciid/sriov_numvfs";
+
+ if (!is_writable($filepath)) {
+ json_response(false, _("Cannot modify VF configuration file") . ": $filepath");
+ }
+
+ try {
+ # Disable all VFs
+ if ($numvfs == 0) {
+ safe_file_put_contents($filepath, 0);
+ json_response(true, null, [_("Disabled all VFs")]);
+ }
+
+ # — VF count changed
+ if ($numvfs != $currentvfs) {
+
+ # Reset VFs and apply new count
+ safe_file_put_contents($filepath, 0);
+ safe_file_put_contents($filepath, $numvfs);
+
+ # Now apply fresh VF settings
+ $results = action_settings($pciid);
+
+ $all_success = array_reduce($results, fn($ok, $r) =>
+ $ok && ($r['success'] ?? false), true
+ );
+
+ json_response(true, null, [
+ sprintf(_("Changed VF count to %d"), $numvfs),
+ $results
+ ]);
+ }
+
+ # No changes
+ json_response(true, null, [_("No changes needed")]);
+
+ } catch (Throwable $e) {
+ json_response(false, _("Failed to change VF configuration") . ": " . $e->getMessage());
+ }
+
+
+ # --------------------------------------------------------
+ # VF driver binding, MAC changes
+ # --------------------------------------------------------
+ case "sriovsettings":
+
+ $mac = _var($_POST, 'mac');
+ $vfio = _var($_POST, 'vfio');
+ $currentvfio = _var($_POST, 'currentvfio');
+ $currentmac = _var($_POST, 'currentmac');
+
+ # Normalize booleans
+ $vfio = filter_var($vfio, FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
+ $currentvfio = filter_var($currentvfio, FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
+
+ # Normalize empty MAC to safe zero-MAC
+ if ($mac === "") $mac = "00:00:00:00:00:00";
+
+ $sriov = json_decode(getSriovInfoJson(), true);
+
+ try {
+ # MAC changed AND currently bound to VFIO
+ if ($currentmac !== $mac) {
+ #Check if driver is required to change before actioning the MAC change.
+ $driver = ($vfio == 1) ? "vfio-pci" : "original";
+ $rtn = setVfMacAddress($pciid, $sriov, $mac, $driver);
+ json_response(
+ $rtn['success'] ?? false,
+ $rtn['error'] ?? null,
+ $rtn['details'] ?? _("MAC address updated under VFIO")
+ );
+ }
+
+ # VFIO binding changed but MAC unchanged
+ if ($currentvfio !== $vfio && $currentmac === $mac) {
+ $driver = ($vfio == 1) ? "vfio-pci" : "original";
+ $rtn = rebindVfDriver($pciid, $sriov, $driver);
+ json_response(
+ $rtn['success'] ?? false,
+ $rtn['error'] ?? null,
+ $rtn['details'] ?? _("VFIO binding updated")
+ );
+ }
+
+ # No changes
+ json_response(true, null, [_("No changes detected")]);
+
+ } catch (Throwable $e) {
+ json_response(false, _("Error applying VF settings") . ": " . $e->getMessage());
+ }
+
+ break;
+
+
+ # --------------------------------------------------------
+ # Check PCI device in use
+ # --------------------------------------------------------
+ case "inuse":
+ $pcitype = _var($_POST, 'pcitype');
+ is_pci_inuse($pciid, $pcitype);
+ // Note: is_pci_inuse() sends JSON response and exits
+ break;
+
+ default:
+ json_response(false, _("Unknown request type").": $type");
+ break;
+}
+
diff --git a/emhttp/plugins/dynamix/include/update.sriov-cfg.php b/emhttp/plugins/dynamix/include/update.sriov-cfg.php
new file mode 100644
index 0000000000..54cbed70f9
--- /dev/null
+++ b/emhttp/plugins/dynamix/include/update.sriov-cfg.php
@@ -0,0 +1,90 @@
+
+
+
+#VFSETTINGS=0000:04:11.5|8086:1520|0|62:00:04:11:05:01 0000:04:10.5|8086:1520|1|62:00:04:10:05:01
+#VFS=0000:04:00.1|8086:1521|3 0000:04:00.0|8086:1521|2
+
+$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
+require_once "$docroot/webGui/include/Secure.php";
+require_once "$docroot/webGui/include/Wrappers.php";
+
+$sriov = '/boot/config/sriov.cfg';
+$sriovvfs = '/boot/config/sriovvfs.cfg';
+
+$type = _var($_POST,'type');
+$pciid = _var($_POST,'pciid');
+$vd = _var($_POST,'vd');
+
+if (isset($pciid) && isset($vd)) {
+ $newelement_check = $pciid.'|'.$vd.'|';
+
+ switch($type) {
+ case "sriov":
+ $old = is_file($sriov) ? rtrim(file_get_contents($sriov)) : '';
+ $newexplode = preg_split('/\s+/', str_replace("VFS=","",$old), -1, PREG_SPLIT_NO_EMPTY);
+ $new = $old;
+ $numvfs= _var($_POST,'numvfs');
+ $found = false;
+ foreach($newexplode as $key => $newelement) {
+ if (strpos($newelement,$newelement_check) !== false) {
+ $found = true;
+ if($numvfs == "0") {
+ unset($newexplode[$key]) ;
+ break;
+ } else {
+ $newexplode[$key] = $newelement_check.$numvfs;
+ break;
+ }
+ }
+ }
+ if (!$found && $numvfs != "0") $newexplode[] = $newelement_check.$numvfs;
+ $new = "VFS=".implode(" ",$newexplode);
+ $file = $sriov;
+ break;
+ case "sriovsettings":
+ $old = is_file($sriovvfs) ? rtrim(file_get_contents($sriovvfs)) : '';
+ $newexplode = preg_split('/\s+/', str_replace("VFSETTINGS=","",$old), -1, PREG_SPLIT_NO_EMPTY);
+ $mac= _var($_POST,'mac');
+ if ($mac == "") $mac = "00:00:00:00:00:00";
+ $vfio= _var($_POST,'vfio');
+ if ($vfio == "true") $vfio = 1; else $vfio = 0;
+ $found = false;
+ foreach($newexplode as $key => $newelement) {
+ if (strpos($newelement,$newelement_check) !== false) {
+ $found = true;
+ if($mac == "00:00:00:00:00:00" && $vfio == 0) {
+ unset($newexplode[$key]) ;
+ break;
+ } else {
+ $newexplode[$key] = $newelement_check.$vfio."|".$mac;
+ break;
+ }
+ }
+ }
+ if (!$found && ($vfio != 0 || $mac != "00:00:00:00:00:00")) $newexplode[] = $newelement_check.$vfio."|".$mac;
+ if ($newexplode) $new = "VFSETTINGS=".implode(" ",$newexplode);else $new = null;
+ $file = $sriovvfs;
+ break;
+ }
+}
+
+$reply = 0;
+if ($new != $old) {
+ if ($old) copy($file,"$file.bak");
+ if ($new) file_put_contents($file,$new); else @unlink($file);
+ $reply = 1;
+}
+
+echo $reply;
+?>
diff --git a/emhttp/plugins/dynamix/include/update.vfio-pci-cfg.php b/emhttp/plugins/dynamix/include/update.vfio-pci-cfg.php
index 99e7e74a0f..3fb1505293 100644
--- a/emhttp/plugins/dynamix/include/update.vfio-pci-cfg.php
+++ b/emhttp/plugins/dynamix/include/update.vfio-pci-cfg.php
@@ -15,7 +15,67 @@
require_once "$docroot/webGui/include/Secure.php";
require_once "$docroot/webGui/include/Wrappers.php";
+function parseVF($str)
+{
+ $blocks = preg_split('/\s+/', trim($str));
+ $result = [];
+ foreach ($blocks as $block) {
+ if ($block === '') continue;
+ $parts = explode('|', $block);
+ for ($i = 0; $i < 4; $i++) if (!isset($parts[$i])) $parts[$i] = '';
+ $key = $parts[0] . '|' . $parts[1];
+ $result[$key] = [$parts[2], $parts[3]];
+ }
+ return $result;
+}
+
+function isValidVF($fields)
+{
+ list($fn, $mac) = $fields;
+ $mac = strtolower(trim($mac));
+ $isZeroMac = ($mac === '00:00:00:00:00:00');
+ $hasMac = ($mac !== '' && !$isZeroMac);
+ if ($fn === 1) return true;
+ if ($fn > 1) return true;
+ if ($fn === 0) return $hasMac;
+ return $hasMac;
+}
+
+function updateVFSettings($input, $saved)
+{
+ $inputParsed = parseVF($input);
+ $savedParsed = parseVF($saved);
+ $updated = [];
+ foreach ($savedParsed as $key => $_) {
+ if (isset($inputParsed[$key]) && isValidVF($inputParsed[$key])) $updated[$key] = $inputParsed[$key];
+ }
+ foreach ($inputParsed as $key => $fields) {
+ if (!isset($savedParsed[$key]) && isValidVF($fields)) $updated[$key] = $fields;
+ }
+ $result = [];
+ foreach ($savedParsed as $key => $_) {
+ if (!isset($updated[$key])) continue;
+ list($pci,$vd) = explode('|',$key);
+ list($fn,$mac) = $updated[$key];
+ if ($fn === '1' && ($mac === '' || $mac === null)) $mac = '00:00:00:00:00:00';
+ $result[] = "$pci|$vd|$fn|$mac";
+ }
+ foreach ($inputParsed as $key => $_) {
+ if (isset($savedParsed[$key])) continue;
+ if (!isset($updated[$key])) continue;
+ list($pci,$vd) = explode('|',$key);
+ list($fn,$mac) = $updated[$key];
+ if ($fn === '1' && ($mac === '' || $mac === null)) $mac = '00:00:00:00:00:00';
+ $result[] = "$pci|$vd|$fn|$mac";
+ }
+ return implode(' ', $result);
+}
+
+
$vfio = '/boot/config/vfio-pci.cfg';
+$sriovvfs = '/boot/config/sriovvfs.cfg';
+
+#Save Normal VFIOs
$old = is_file($vfio) ? rtrim(file_get_contents($vfio)) : '';
$new = _var($_POST,'cfg');
@@ -23,7 +83,21 @@
if ($new != $old) {
if ($old) copy($vfio,"$vfio.bak");
if ($new) file_put_contents($vfio,$new); else @unlink($vfio);
- $reply = 1;
+ $reply |= 1;
+}
+
+#Save SRIOV VFS
+$oldvfcfg = is_file($sriovvfs) ? rtrim(file_get_contents($sriovvfs)) : '';
+$newvfcfg = _var($_POST,'vfcfg');
+$oldvfcfg_updated = updateVFSettings($newvfcfg,$oldvfcfg);
+if (strpos($oldvfcfg_updated,"VFSETTINGS=") !== 0 && $oldvfcfg_updated != "") $oldvfcfg_updated = "VFSETTINGS=".$oldvfcfg_updated;
+
+#file_put_contents("/tmp/updatevfs",[json_encode($oldvfcfg_updated),json_encode($oldvfcfg)]);
+if ($oldvfcfg_updated != $oldvfcfg) {
+ if ($oldvfcfg) copy($sriovvfs,"$sriovvfs.bak");
+ if ($oldvfcfg_updated) file_put_contents($sriovvfs,$oldvfcfg_updated); else @unlink($sriovvfs);
+ $reply |= 2;
}
+
echo $reply;
?>
diff --git a/emhttp/plugins/dynamix/scripts/diagnostics b/emhttp/plugins/dynamix/scripts/diagnostics
index 97563e220f..e6d3be1f83 100755
--- a/emhttp/plugins/dynamix/scripts/diagnostics
+++ b/emhttp/plugins/dynamix/scripts/diagnostics
@@ -776,6 +776,20 @@ if (file_exists($graphql)) {
anonymize_email($graphql);
}
+// copy sriov log
+$sriov = "/var/log/sriov";
+if (file_exists($sriov)) {
+ $log = "/$diag/logs/sriov.txt";
+ run("todos <$sriov >".escapeshellarg($log));
+}
+
+// copy sriov log
+$srioverrors = "/var/log/sriov-errors";
+if (file_exists($srioverrors)) {
+ $log = "/$diag/logs/sriov-errors.txt";
+ run("todos <$srioverrors >".escapeshellarg($log));
+}
+
// copy vfio-pci log
$vfiopci = "/var/log/vfio-pci";
if (file_exists($vfiopci)) {
diff --git a/emhttp/plugins/dynamix/sheets/SysDevs.css b/emhttp/plugins/dynamix/sheets/SysDevs.css
index 42597cef92..3da284b96b 100644
--- a/emhttp/plugins/dynamix/sheets/SysDevs.css
+++ b/emhttp/plugins/dynamix/sheets/SysDevs.css
@@ -13,3 +13,12 @@ table tr td.thin {
line-height: 8px;
height: 8px;
}
+.sweet-alert.swal-hostid-input input {
+ width: 60px !important;
+ margin: 12px auto 0 auto !important;
+ text-align: center !important;
+ display: block !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ box-sizing: border-box !important;
+}
\ No newline at end of file
diff --git a/etc/rc.d/rc.local b/etc/rc.d/rc.local
index 292bf1fdeb..d59ea8fca3 100755
--- a/etc/rc.d/rc.local
+++ b/etc/rc.d/rc.local
@@ -10,10 +10,14 @@
#
# LimeTech - modified for Unraid OS
# Bergware - modified for Unraid OS, October 2023
+# Simon Fairweather - modified for Unraid OS, November 2025(SRIOV)
# run & log functions
. /etc/rc.d/rc.runlog
+# LimeTech/SF - setup selected devices for sriov
+/usr/local/sbin/sriov 1>/var/log/sriov 2>/var/log/sriov-errors
+
# import CA proxy
UNPROXY=/boot/config/plugins/dynamix/outgoingproxy.cfg
CAPROXY=/boot/config/plugins/community.applications/proxy.cfg
diff --git a/sbin/sriov b/sbin/sriov
new file mode 100755
index 0000000000..385f4cd6c7
--- /dev/null
+++ b/sbin/sriov
@@ -0,0 +1,75 @@
+#!/bin/bash
+# limetech - wrapper for SRIOV processing
+#
+# Invoked early in startup before any devices are bound.
+#
+# Order does not matter. If both are provided, must be separated by "|".
+# Multiple entries must be separated by space.
+#
+
+# Invoke script for each device referenced via /boot/config/sriov.cfg & sriovvfs.cfg
+# Accept string enclosed in quotes or not
+# accepts space-separated list of or followed by an optional "|" and |
+# example: VFS=0000:04:00.1|8086:1521|3 0000:04:00.0|8086:1521|2
+# accepts space-separated list of or followed by an optional "|" and |
+# example:VFSETTINGS=0000:04:11.5|8086:1520|62:00:04:11:05:01
+
+SRIOV_DISABLED_FILE=/boot/config/sriov_disabled
+if [[ -f $SRIOV_DISABLED_FILE ]] ; then
+ echo 'SRIOV Processing disabled.'
+ exit
+fi
+
+CFG=/boot/config/sriov.cfg
+
+[[ ! -f "$CFG" ]] && exit
+grep -q "^VFS=" "$CFG" || exit
+echo -e "Loading VFs config from $CFG\n"
+cat $CFG
+echo "---"
+
+if [[ ! "$(ls -A /sys/kernel/iommu_groups/)" ]]; then
+ echo "Error: IOMMU not available"
+ exit 1
+fi
+
+# Read the line properly (don’t let bash squash the spaces)
+VFS_LINE=$(grep "^VFS=" "$CFG" | cut -d= -f2- | tr -d '"')
+[[ -z "$VFS_LINE" ]] && exit
+
+for PARAM in $VFS_LINE; do
+ IFS='|' read -r arg1 arg2 arg3 <<< "$PARAM"
+ echo "Processing $arg1 $arg2 set VFs to $arg3"
+ /usr/local/sbin/sriov-setvfs.sh "$arg1" "$arg2" "$arg3"
+ echo "---"
+done
+
+echo 'Devices VFs defined:'
+ls -l /sys/bus/pci/devices/*/virtfn*| egrep [[:xdigit:]]{4}:
+
+printf "\n"
+
+CFG_VFS=/boot/config/sriovvfs.cfg
+
+[[ ! -f "$CFG_VFS" ]] && exit
+grep -q "VFSETTINGS=" "$CFG_VFS" || exit
+echo -e "Loading settings config from $CFG_VFS/n"
+cat "$CFG_VFS"
+echo "---"
+
+if [[ ! "$(ls -A /sys/kernel/iommu_groups/ 2>/dev/null)" ]]; then
+ echo "Error: IOMMU not available"
+ exit 1
+fi
+
+VFSETTINGS_LINE=$(grep "^VFSETTINGS=" "$CFG_VFS" | cut -d= -f2- | tr -d '"')
+
+for PARAM_VFS in $VFSETTINGS_LINE; do
+ IFS='|' read -r arg1 arg2 arg3 arg4 <<< "$PARAM_VFS"
+ echo "Processing $arg1 $arg2 set Mac to $arg4"
+ /usr/local/sbin/sriov-vfsettings.sh "$arg1" "$arg2" "$arg3" "$arg4"
+ echo "---"
+done
+
+
+echo "SRIOV processing complete"
diff --git a/sbin/sriov-setvfs.sh b/sbin/sriov-setvfs.sh
new file mode 100755
index 0000000000..13470b13a0
--- /dev/null
+++ b/sbin/sriov-setvfs.sh
@@ -0,0 +1,142 @@
+#!/usr/bin/env bash
+# -*- coding: utf-8 -*-
+#
+# =============================================================================
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2025- Limetech
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# =============================================================================
+#
+# Author(s):
+# Simon Fairweather based on code from: Andre Richter,
+#
+# =============================================================================
+#
+# This script takes three parameters:
+# i.e. dddd:bb:dd.f
+# i.e. vvvv:dddd
+#
+# and then:
+#
+# (1) If both and were provided,
+# validate that the requested exists at
+#
+# (2) Set numvfs to value supplied as 3rd parameter:
+#
+
+BDF_REGEX="^[[:xdigit:]]{2}:[[:xdigit:]]{2}.[[:xdigit:]]$"
+DBDF_REGEX="^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}.[[:xdigit:]]$"
+VD_REGEX="^[[:xdigit:]]{4}:[[:xdigit:]]{4}$"
+
+if [[ $EUID -ne 0 ]]; then
+ echo "Error: This script must be run as root" 1>&2
+ exit 1
+fi
+
+if [[ $# -eq 0 ]]; then
+ echo "Error: Please provide Domain:Bus:Device.Function (dddd:bb:dd.f) and/or Vendor:Device (vvvv:dddd)" 1>&2
+ exit 1
+fi
+
+# Check that 3 parameters are supplied
+if [[ $# -ne 3 ]]; then
+ echo "Error: Expected 3 parameters, but got $#." 1>&2
+ echo "Usage: $0 " 1>&2
+ echo "Example: $0 0000:01:00.0 10de:1fb8 numvfs" 1>&2
+ exit 1
+fi
+
+unset VD BDF NUMVFS
+for arg in "$@"
+do
+ if [[ $arg =~ $VD_REGEX ]]; then
+ VD=$arg
+ elif [[ $arg =~ $DBDF_REGEX ]]; then
+ BDF=$arg
+ elif [[ $arg =~ $BDF_REGEX ]]; then
+ BDF="0000:${arg}"
+ echo "Warning: You did not supply a PCI domain, assuming ${BDF}" 1>&2
+ else
+ # Treat as 3rd parameter (not a PCI ID)
+ if [[ -z $NUMVFS ]]; then
+ NUMVFS=$arg
+ else
+ echo "Error: Unrecognized argument '$arg'" 1>&2
+ exit 1
+ fi
+ fi
+done
+
+if [[ -z "$BDF" ]]; then
+ echo "Error: No valid Bus:Device.Function provided" 1>&2
+ exit 1
+fi
+
+TARGET_DEV_SYSFS_PATH="/sys/bus/pci/devices/$BDF"
+
+if [[ ! -d $TARGET_DEV_SYSFS_PATH ]]; then
+ echo "Error: Device ${BDF} does not exist, unable to action VFs setting" 1>&2
+ exit 1
+fi
+
+if [[ ! -d "$TARGET_DEV_SYSFS_PATH/iommu/" ]]; then
+ echo "Error: No signs of an IOMMU. Check your hardware and/or linux cmdline parameters. Use intel_iommu=on or iommu=pt iommu=1" 1>&2
+ exit 1
+fi
+
+# validate that the correct Vendor:Device was found for this BDF
+if [[ ! -z $VD ]]; then
+ if [[ $(lspci -n -s ${BDF} -d ${VD} 2>/dev/null | wc -l) -eq 0 ]]; then
+ echo "Error: Vendor:Device ${VD} not found at ${BDF}, unable to action VFs setting" 1>&2
+ exit 1
+ else
+ echo "Vendor:Device ${VD} found at ${BDF}"
+ fi
+else
+ echo "Warning: You did not specify a Vendor:Device (vvvv:dddd), unable to validate ${BDF}" 1>&2
+fi
+
+if [[ -z "$NUMVFS" ]]; then
+ echo "Error: No VF count provided" 1>&2
+ exit 1
+fi
+
+if ! [[ "$NUMVFS" =~ ^[0-9]+$ ]]; then
+ echo "Error: VF count must be a non-negative integer" 1>&2
+ exit 1
+fi
+
+printf "\nSetting...\n"
+
+# Capture stderr output from echo into a variable
+error_msg=$( (echo "$NUMVFS" > "$TARGET_DEV_SYSFS_PATH/sriov_numvfs") 2>&1 )
+
+if [[ $? -ne 0 ]]; then
+ echo "Error: Failed to set sriov_numvfs at $TARGET_DEV_SYSFS_PATH" >&2
+ clean_msg=$(echo "$error_msg" | sed -n 's/.*error: \(.*\)/\1/p')
+ echo "System message: $clean_msg" >&2
+ exit 1
+fi
+
+printf "\n"
+echo "Device ${VD} at ${BDF} set numvfs to ${NUMVFS}"
diff --git a/sbin/sriov-vfsettings.sh b/sbin/sriov-vfsettings.sh
new file mode 100755
index 0000000000..cd15e53287
--- /dev/null
+++ b/sbin/sriov-vfsettings.sh
@@ -0,0 +1,209 @@
+#!/usr/bin/env bash
+# -*- coding: utf-8 -*-
+#
+# =============================================================================
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2025- Limetech
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# =============================================================================
+#
+# Author(s):
+# Simon Fairweather based on code from: Andre Richter,
+#
+# =============================================================================
+#
+# This script takes three parameters:
+# i.e. dddd:bb:dd.f
+# i.e. vvvv:dddd
+#
+# and then:
+#
+# (1) If both and were provided,
+# validate that the requested exists at
+#
+# (2) Set MAC to value supplied as 3rd parameter:
+#
+# (3) Unbind and rebind network driver for change to take effect.
+#
+
+BDF_REGEX="^[[:xdigit:]]{2}:[[:xdigit:]]{2}.[[:xdigit:]]$"
+DBDF_REGEX="^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}.[[:xdigit:]]$"
+VD_REGEX="^[[:xdigit:]]{4}:[[:xdigit:]]{4}$"
+
+if [[ $EUID -ne 0 ]]; then
+ echo "Error: This script must be run as root" 1>&2
+ exit 1
+fi
+
+if [[ $# -eq 0 ]]; then
+ echo "Error: Please provide Domain:Bus:Device.Function (dddd:bb:dd.f) and/or Vendor:Device (vvvv:dddd)" 1>&2
+ exit 1
+fi
+
+# Check that 4 parameters are supplied
+if [[ $# -ne 4 ]]; then
+ echo "Error: Expected 4 parameters, but got $#." 1>&2
+ echo "Usage: $0 " 1>&2
+ echo "Example: $0 0000:01:00.0 10de:1fb8 1 62:00:01:00:00:99" 1>&2
+ echo "parm3 is binding to VFIO, parm 4 is mac address."
+ exit 1
+fi
+
+unset VD BDF VFIO MAC
+for arg in "$@"; do
+ if [[ $arg =~ $VD_REGEX ]]; then
+ VD=$arg
+ elif [[ $arg =~ $DBDF_REGEX ]]; then
+ BDF=$arg
+ elif [[ $arg =~ $BDF_REGEX ]]; then
+ BDF="0000:${arg}"
+ echo "Warning: You did not supply a PCI domain, assuming ${BDF}" 1>&2
+ elif [[ $arg =~ ^[01]$ ]]; then
+ # 3rd argument: VFIO flag (0 or 1)
+ VFIO=$arg
+ elif [[ $arg =~ ^([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}$ ]]; then
+ # 4th argument: MAC address
+ MAC=$arg
+ else
+ echo "Error: Unrecognized argument '$arg'" 1>&2
+ exit 1
+ fi
+done
+
+if [[ -z "$BDF" ]]; then
+ echo "Error: No valid Bus:Device.Function provided" 1>&2
+ exit 1
+fi
+
+TARGET_DEV_SYSFS_PATH="/sys/bus/pci/devices/$BDF"
+
+if [[ ! -d $TARGET_DEV_SYSFS_PATH ]]; then
+ echo "Error: Device ${BDF} does not exist, unable to action VFs setting" 1>&2
+ exit 1
+fi
+
+if [[ ! -d "$TARGET_DEV_SYSFS_PATH/iommu/" ]]; then
+ echo "Error: No signs of an IOMMU. Check your hardware and/or linux cmdline parameters. Use intel_iommu=on or iommu=pt iommu=1" 1>&2
+ exit 1
+fi
+
+# validate that the correct Vendor:Device was found for this BDF
+if [[ ! -z $VD ]]; then
+ if [[ $(lspci -n -s ${BDF} -d ${VD} 2>/dev/null | wc -l) -eq 0 ]]; then
+ echo "Error: Vendor:Device ${VD} not found at ${BDF}, unable to action VFs setting" 1>&2
+ exit 1
+ else
+ echo "Vendor:Device ${VD} found at ${BDF}"
+ fi
+else
+ echo "Warning: You did not specify a Vendor:Device (vvvv:dddd), unable to validate ${BDF}" 1>&2
+fi
+
+printf "\nSetting...\n"
+
+# Locate PF device
+
+VF_PCI=$BDF
+VF_PATH="/sys/bus/pci/devices/$VF_PCI"
+PF_PATH=$(readlink -f "$VF_PATH/physfn" 2>/dev/null)
+
+if [ ! -d "$PF_PATH" ]; then
+ echo "Error: No PF found for VF $VF_PCI"
+ exit 1
+fi
+
+# Determine PF interface name
+PF_IFACE=$(basename "$(readlink -f "$PF_PATH/net"/* 2>/dev/null)")
+if [ -z "$PF_IFACE" ]; then
+ PF_IFACE=$(basename "$(ls -d /sys/class/net/*/device 2>/dev/null | grep "$PF_PATH" | head -n1 | cut -d/ -f5)")
+fi
+
+if [ -z "$PF_IFACE" ]; then
+ echo "Error: Could not determine PF interface for $VF_PCI"
+ exit 1
+fi
+
+
+# Determine VF index
+VF_INDEX=""
+for vfdir in /sys/class/net/$PF_IFACE/device/virtfn*; do
+ [ -e "$vfdir" ] || continue
+ vf_pci=$(basename "$(readlink -f "$vfdir")")
+ if [ "$vf_pci" = "$VF_PCI" ]; then
+ VF_INDEX=${vfdir##*/virtfn}
+ break
+ fi
+done
+
+if [ -z "$VF_INDEX" ]; then
+ echo "Error: VF index not found for $VF_PCI under PF $BDF"
+ exit 1
+fi
+
+if [[ -z "$MAC" ]]; then
+ echo "Error: No MAC address provided" 1>&2
+ exit 1
+fi
+
+if ! [[ "$MAC" =~ ^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$ ]]; then
+ echo "Error: Invalid MAC address format. Expected XX:XX:XX:XX:XX:XX" 1>&2
+ exit 1
+fi
+
+echo "Setting MAC for VF:"
+echo " PF: $PF_IFACE"
+echo " VF Index: $VF_INDEX"
+echo " PCI: $VF_PCI"
+echo " MAC: $MAC"
+
+# Determine current driver for this VF
+VF_DRIVER=$(basename "$(readlink -f "$VF_PATH/driver" 2>/dev/null)")
+
+# Unbind VF from driver if loaded
+if [ -n "$VF_DRIVER" ]; then
+ echo "Unbinding VF from driver $VF_DRIVER..."
+ echo "$VF_PCI" > "/sys/bus/pci/drivers/$VF_DRIVER/unbind"
+fi
+
+# Set MAC
+if ! ip link set "$PF_IFACE" vf "$VF_INDEX" mac "$MAC"; then
+ echo "Error: Failed to set MAC address $MAC on VF $VF_INDEX" >&2
+ exit 1
+fi
+
+# Rebind VF to driver if it was bound before
+if [ -n "$VF_DRIVER" ]; then
+ echo "Rebinding VF to driver $VF_DRIVER..."
+ echo "$VF_PCI" > "/sys/bus/pci/drivers/$VF_DRIVER/bind"
+fi
+
+echo "MAC Address set"
+
+if [[ "$VFIO" == "1" ]]; then
+ echo "Binding VF to vfio"
+ /usr/local/sbin/vfio-pci-bind.sh "$BDF" "$VD" \
+ 1>>/var/log/vfio-pci \
+ 2>>/var/log/vfio-pci-errors
+fi
+
+