Add bind to vfio when selections inuse

This commit is contained in:
SimonFair
2025-12-14 16:19:28 +00:00
parent 2c7369a3f3
commit 2a6446fff6
4 changed files with 152 additions and 101 deletions
+8 -5
View File
@@ -151,14 +151,17 @@ function applyCfg() {
var string2 = "VFSETTINGS=";
var elements = document.getElementById("vfiopci").elements;
for (var i = 0, element; element = elements[i++];) {
if (element.type === "checkbox" && element.checked === true && element.className.substring(0, 5) === "iommu")
string = string + element.value + " ";
if (element.type === "checkbox" && element.className.substring(0, 5) === "iommu") {
var elvfio = element.checked;
if (elvfio == true) var vfio = "1"; else vfio = "0";
string = string + element.value + "|" + vfio + " ";
}
if (element.type === "checkbox" && element.className.substring(0, 7) === "vfiommu") {
var pciId = element.id.replace(/^vfvfio/, "");
var mac = document.getElementById("vfmac" + pciId).value;
var elvfio = document.getElementById("vfvfio" + pciId).checked;
if (elvfio == true) var vfio = "1"; else vfio = "0";
string2 = string2 + element.value + "|" + vfio + "|" + mac + " ";
var elvfvfio = document.getElementById("vfvfio" + pciId).checked;
if (elvfvfio == true) var vfvfio = "1"; else vfvfio = "0";
string2 = string2 + element.value + "|" + vfvfio + "|" + mac + " ";
}
}
+31 -10
View File
@@ -39,6 +39,16 @@ function usb_physical_port($usbbusdev) {
return($physical_busid);
}
function createIOMMUCheckBox($pciaddress,$vd,$iommu,$line) {
global $iommuinuse,$vfio_cfg_devices;
if ((strpos($line, 'Host bridge') === false) && (strpos($line, 'PCI bridge') === false)) {
echo in_array($iommu, $iommuinuse) ? '<tr hidden><td><input type="checkbox" value="" title="'._('In use by Unraid').'" disabled ' : '<tr hidden><td><input type="checkbox" hidden class="iommu'.$iommu.'" value="'.$pciaddress."|".$vd.'" ';
// check config file for two formats: <Domain:Bus:Device.Function>|<Vendor:Device> or just <Domain:Bus:Device.Function>
echo (in_array($pciaddress."|".$vd, $vfio_cfg_devices) || in_array($pciaddress, $vfio_cfg_devices)) ? " checked>" : ">";
echo "</td></tr>";
}
}
switch ($_POST['table']) {
case 't1':
$sriov = json_decode(getSriovInfoJson(true),true);
@@ -202,23 +212,39 @@ case 't1':
$pciidcheck = array_key_first($group);
if (in_array($pciidcheck,$sriovvfs)) continue;
# Filter devices.
if ($showsriov && !array_key_exists($pciidcheck,$sriov)) continue;
if ($showsriov && !in_array(substr($sriov[$pciidcheck]['class_id'],0,4),$allowedPCIClass)) continue;
$iommu = $key;
$iommushow = true;
foreach ($group as $pciaddress => $line) {
if (!$line) continue;
[$class, $class_id, $name] = getPciClassNameAndId($pciaddress);
$class_prefix = substr(strtolower((string)$class_id), 0, 4);
if (in_array($class_prefix, $filter, true)) continue;
# Do filter checks, but if filtered still create IOMMU check box.
$line = preg_replace("/^\t/","",$line);
$vd = trim(explode(" ", $line)[0], "[]");
if ($showsriov && !array_key_exists($pciidcheck,$sriov)) {
createIOMMUCheckBox($pciaddress, $vd, $iommu, $line);
break;
}
if ($showsriov && !in_array(substr($sriov[$pciidcheck]['class_id'],0,4),$allowedPCIClass)) {
createIOMMUCheckBox($pciaddress, $vd, $iommu, $line);
break;
}
if (in_array($class_prefix, $filter, true)) {
createIOMMUCheckBox($pciaddress, $vd, $iommu, $line);
continue;
}
if ($iommushow) {
if (isset($spacer)) echo "<tr><td colspan='2' class='thin'></td>"; else $spacer = true;
echo "</tr><tr><td>IOMMU group $key:</td><td>";
$iommushow = false;
$append = true;
}
$line = preg_replace("/^\t/","",$line);
$vd = trim(explode(" ", $line)[0], "[]");
$removed = $line[0]=='R' ? true : false;
if ($removed) $line=preg_replace('/R/', '', $line, 1);
if (preg_match($BDF_REGEX, $pciaddress)) {
@@ -432,7 +458,6 @@ case 't1':
echo '<input id="viewlog" type="button" value="'._('View VFIO-PCI Log').'" onclick="openTerminal(\'log\',\'vfio-pci\',\'vfio-pci\')">';
}
if ($ackparm == "") $ackdisable =" disabled "; else $ackdisable = "";
if ($showall == "yes") $applyhidden = ""; else $applyhidden = " hidden ";
echo '<input id="applycfg" type="submit"'.$applyhidden.' disabled value="'._('Bind selected to VFIO at Boot').'" onclick="applyCfg();" '.(isset($noiommu) ? "style=\"display:none\"" : "").'>';
echo '<span id="warning"></span>';
echo '<input id="applypci" type="submit"'.$ackdisable.' value="'._('Acknowledge all PCI changes').'" onclick="ackPCI(\''.htmlentities($ackparm).'\',\'all\')" >';
@@ -440,10 +465,6 @@ case 't1':
echo <<<EOT
<script>
$("#t1 input[type='checkbox']").change(function() {
// Only allow mass-changing when showall is ON
if (!$(".showall").is(":checked")) {
return; // showall = filtered → do nothing
}
var matches = document.querySelectorAll("." + this.className);
for (var i=0, len=matches.length|0; i<len; i=i+1|0) {
matches[i].checked = this.checked ? true : false;
@@ -1,6 +1,6 @@
<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, Bergware International.
/* Copyright 2005-2025, Lime Technology
* Copyright 2012-2025, Bergware International.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2,
@@ -15,126 +15,152 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Secure.php";
require_once "$docroot/webGui/include/Wrappers.php";
function stripVFPrefix($str)
{
return preg_replace('/^VFSETTINGS=/','', trim($str));
/* ============================================================
* VFIO CONFIG
* Format: BIND=pci|vendor pci|vendor ...
* Input : BIND=pci|vendor|1 pci|vendor|0 ...
* ============================================================ */
function parseVFIO($str) {
if (!preg_match('/^BIND=(.*)$/', trim($str), $m)) return [];
$out = [];
foreach (preg_split('/\s+/', trim($m[1])) as $e) {
if ($e === '') continue;
[$pci, $vd] = array_pad(explode('|', $e, 2), 2, '');
if ($pci && $vd) $out[$pci] = $vd;
}
return $out;
}
function parseVF($str)
{
$str = stripVFPrefix($str);
if ($str === '') return [];
function parseVFIOInput($str) {
if (!preg_match('/^BIND=(.*)$/', trim($str), $m)) return [];
$out = [];
foreach (preg_split('/\s+/', trim($m[1])) as $e) {
if ($e === '') continue;
[$pci, $vd, $req] = array_pad(explode('|', $e, 3), 3, '1');
if ($pci && $vd && ($req === '0' || $req === '1')) {
$out[$pci] = [$vd, $req];
}
}
return $out;
}
$blocks = preg_split('/\s+/', trim($str));
$result = [];
function updateVFIO($input, $saved) {
$existing = parseVFIO($saved);
$requested = parseVFIOInput($input);
foreach ($blocks as $block) {
if ($block === '') continue;
$parts = explode('|', $block);
// Normalize fields: pci|vd|fn|mac
$pci = $parts[0] ?? '';
$vd = $parts[1] ?? '';
$fn = $parts[2] ?? '';
$mac = $parts[3] ?? '';
// Unique key: pci + vendor:device
$key = $pci . '|' . $vd;
$result[$key] = [$fn, $mac];
foreach ($requested as $pci => [$vd, $req]) {
if ($req === '1') {
$existing[$pci] = $vd;
} else {
unset($existing[$pci]);
}
}
return $result;
if (empty($existing)) return '';
ksort($existing, SORT_NATURAL);
$out = [];
foreach ($existing as $pci => $vd) $out[] = "$pci|$vd";
return 'BIND=' . implode(' ', $out);
}
function isValidVF($fields)
{
list($fn, $mac) = $fields;
/* ============================================================
* SR-IOV CONFIG
* Format: VFSETTINGS=pci|vd|fn|mac ...
* ============================================================ */
function stripVFPrefix($str) {
return preg_replace('/^VFSETTINGS=/', '', trim($str));
}
function parseVF($str) {
$str = stripVFPrefix($str);
if ($str === '') return [];
$out = [];
foreach (preg_split('/\s+/', $str) as $b) {
if ($b === '') continue;
[$pci, $vd, $fn, $mac] = array_pad(explode('|', $b), 4, '');
if ($pci && $vd) $out["$pci|$vd"] = [$fn, $mac];
}
return $out;
}
function isValidVF($fields) {
[$fn, $mac] = $fields;
$mac = strtolower(trim($mac));
// Common empty MAC
$isZeroMac = ($mac === '' || $mac === '00:00:00:00:00:00');
// Rule fixes:
// - fn=0 is allowed even if empty MAC
// - fn=1 must always be accepted
// - fn>1 always accepted
if ($fn === '0') return true;
if ($fn === '1') return true;
if ($fn === '0' || $fn === '1') return true;
if (intval($fn) > 1) return true;
// fallback: require some MAC
return !$isZeroMac;
return ($mac !== '' && $mac !== '00:00:00:00:00:00');
}
function updateVFSettings($input, $saved)
{
function updateVFSettings($input, $saved) {
$inputParsed = parseVF($input);
$savedParsed = parseVF($saved);
$updated = [];
// Update existing entries
foreach ($savedParsed as $key => $oldFields) {
if (isset($inputParsed[$key]) && isValidVF($inputParsed[$key])) {
$updated[$key] = $inputParsed[$key];
}
}
// Add new entries not in saved
foreach ($inputParsed as $key => $fields) {
if (!isset($savedParsed[$key]) && isValidVF($fields)) {
$updated[$key] = $fields;
if (isValidVF($fields)) {
$savedParsed[$key] = $fields;
}
}
// Reassemble output
$result = [];
if (empty($savedParsed)) return '';
foreach ($updated as $key => $fields) {
list($pci, $vd) = explode('|', $key);
list($fn, $mac) = $fields;
// Normalize MAC for fn=1 if empty
ksort($savedParsed, SORT_NATURAL);
$out = [];
foreach ($savedParsed as $key => [$fn, $mac]) {
[$pci, $vd] = explode('|', $key);
if ($fn === '1' && trim($mac) === '') {
$mac = '00:00:00:00:00:00';
}
$result[] = "$pci|$vd|$fn|$mac";
$out[] = "$pci|$vd|$fn|$mac";
}
if (empty($result)) {
return "";
}
return "VFSETTINGS=" . implode(' ', $result);
return 'VFSETTINGS=' . implode(' ', $out);
}
/* ============================================================
* FILE PATHS
* ============================================================ */
$vfio = '/boot/config/vfio-pci.cfg';
$vfio = '/boot/config/vfio-pci.cfg';
$sriovvfs = '/boot/config/sriovvfs.cfg';
$reply = 0;
#Save Normal VFIOs
$old = is_file($vfio) ? rtrim(file_get_contents($vfio)) : '';
$new = _var($_POST,'cfg');
/* ================= VFIO ================= */
$reply = 0;
if ($new != $old) {
if ($old) copy($vfio,"$vfio.bak");
if ($new) file_put_contents($vfio,$new); else @unlink($vfio);
$reply |= 1;
$old_vfio = is_file($vfio) ? rtrim(file_get_contents($vfio)) : '';
$new_vfio = _var($_POST, 'cfg');
$merged_vfio = updateVFIO($new_vfio, $old_vfio);
if ($merged_vfio !== $old_vfio) {
if ($old_vfio) copy($vfio, "$vfio.bak");
if ($merged_vfio) file_put_contents($vfio, $merged_vfio);
else @unlink($vfio);
}
#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;
/* Reply bit 1: differs from boot */
$boot_vfio = is_file("$vfio.boot") ? rtrim(file_get_contents("$vfio.boot")) : '';
if ($merged_vfio !== $boot_vfio) {
$reply |= 1;
}
if ($oldvfcfg_updated != $oldvfcfg) {
if ($oldvfcfg) copy($sriovvfs,"$sriovvfs.bak");
if ($oldvfcfg_updated) file_put_contents($sriovvfs,$oldvfcfg_updated); else @unlink($sriovvfs);
$reply |= 2;
/* ================= SR-IOV ================= */
$old_vfcfg = is_file($sriovvfs) ? rtrim(file_get_contents($sriovvfs)) : '';
$new_vfcfg = _var($_POST, 'vfcfg');
$merged_vfcfg = updateVFSettings($new_vfcfg, $old_vfcfg);
if ($merged_vfcfg !== $old_vfcfg) {
if ($old_vfcfg) copy($sriovvfs, "$sriovvfs.bak");
if ($merged_vfcfg) file_put_contents($sriovvfs, $merged_vfcfg);
else @unlink($sriovvfs);
/* Reply bit 2: changed from previous version */
$reply |= 2;
}
echo $reply;
+1
View File
@@ -22,6 +22,7 @@ fi
# LimeTech - bind selected devices to vfio-pci
/usr/local/sbin/vfio-pci 1>/var/log/vfio-pci 2>/var/log/vfio-pci-errors
cp /boot/config/vfio-pci.cfg /boot/config/vfio-pci.boot 2>/dev/null
# Run the kernel module script. This updates the module dependencies and
# also supports manually loading kernel modules through rc.modules.local.