Files
webgui/emhttp/plugins/dynamix/include/SysDevs.php
2025-11-03 21:12:03 +00:00

609 lines
28 KiB
PHP

<?PHP
/* Copyright 2005-2023, Lime Technology
* Copyright 2012-2023, 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,
* as published by the Free Software Foundation.
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
?>
<?
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
// add translations
$_SERVER['REQUEST_URI'] = 'tools';
require_once "$docroot/webGui/include/Translations.php";
$pci_device_diffs = comparePCIData();
$allowedPCIClass = ['0x02','0x03'];
function usb_physical_port($usbbusdev) {
if (preg_match('/^Bus (?P<bus>\S+) Device (?P<dev>\S+): ID (?P<id>\S+)(?P<name>.*)$/', $usbbusdev, $usbMatch)) {
//udevadm info -a --name=/dev/bus/usb/003/002 | grep KERNEL==
$udevcmd = "udevadm info -a --name=/dev/bus/usb/".$usbMatch['bus']."/".$usbMatch['dev']." | grep KERNEL==";
$physical_busid = _("None");
exec($udevcmd , $udev);
if (isset($udev)) {
$physical_busid = trim(substr($udev[0], 13) , '"');
if (substr($physical_busid,0,3) =='usb') {
$physical_busid = substr($physical_busid,3).'-0';
}
}
}
return($physical_busid);
}
/**
* Enumerate SR-IOV capable PCI devices (keyed by PCI address).
*
* Example JSON:
* {
* "0000:03:00.0": {
* "class": "Ethernet controller",
* "class_id": "0x0200",
* "name": "Intel Corporation X710 for 10GbE SFP+",
* "driver": "i40e",
* "module": "i40e",
* "vf_param": "max_vfs",
* "total_vfs": 64,
* "num_vfs": 8,
* "vfs": [
* {"pci": "0000:03:10.0", "iface": "enp3s0f0v0", "mac": "52:54:00:aa:00:01"}
* ]
* }
* }
*/
function getSriovInfoJson(bool $includeVfDetails = true): string {
$results = [];
$paths = glob('/sys/bus/pci/devices/*/sriov_totalvfs') ?: [];
foreach ($paths as $totalvfFile) {
$devdir = dirname($totalvfFile);
$pci = basename($devdir);
$total_vfs = (int) @file_get_contents($totalvfFile);
$num_vfs = (int) @file_get_contents("$devdir/sriov_numvfs");
// Driver/module detection
$driver = $module = $vf_param = null;
$driver_link = "$devdir/driver";
if (is_link($driver_link)) {
$driver = basename(readlink($driver_link));
$module_link = "$driver_link/module";
$module = is_link($module_link) ? basename(readlink($module_link)) : $driver;
$vf_param = detectVfParam($driver);
}
// Device class + numeric class + name
[$class, $class_id, $name] = getPciClassNameAndId($pci);
// Virtual functions
$vfs = [];
foreach (glob("$devdir/virtfn*") as $vf) {
if (!is_link($vf)) continue;
$vf_pci = basename(readlink($vf));
$vf_entry = ['pci' => $vf_pci];
if ($includeVfDetails) {
// Network interface info
$net = glob("/sys/bus/pci/devices/{$vf_pci}/net/*");
if ($net && isset($net[0])) {
$iface = basename($net[0]);
$vf_entry['iface'] = $iface;
$macFile = "/sys/class/net/{$iface}/address";
if (is_readable($macFile)) {
$vf_entry['mac'] = trim(file_get_contents($macFile));
}
}
// IOMMU group
$iommu_link = "/sys/bus/pci/devices/{$vf_pci}/iommu_group";
if (is_link($iommu_link)) {
$vf_entry['iommu_group'] = basename(readlink($iommu_link));
} else {
$vf_entry['iommu_group'] = null;
}
}
$vfs[] = $vf_entry;
}
$results[$pci] = [
'class' => $class,
'class_id' => $class_id,
'name' => $name,
'driver' => $driver,
'module' => $module,
'vf_param' => $vf_param,
'total_vfs' => $total_vfs,
'num_vfs' => $num_vfs,
'vfs' => $vfs
];
}
ksort($results, SORT_NATURAL);
return json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
function detectVfParam(string $driver): ?string {
if (!function_exists('shell_exec')) return null;
$out = @shell_exec('modinfo ' . escapeshellarg($driver) . ' 2>/dev/null');
if (!$out) return null;
$lines = explode("\n", strtolower($out));
$params = [];
foreach ($lines as $line) {
if (preg_match('/^parm:\s+(\S+)/', $line, $m)) $params[] = $m[1];
}
foreach (['max_vfs', 'num_vfs', 'sriov_numvfs', 'sriov_vfs'] as $key)
if (in_array($key, $params, true)) return $key;
foreach ($params as $p)
if (preg_match('/vf/', $p)) return $p;
return null;
}
/**
* Robustly get PCI class (text + numeric ID) and device name from lspci/sysfs.
*/
function getPciClassNameAndId(string $pci): array {
$class = 'Unknown';
$class_id = null;
$name = 'Unknown';
// Numeric class code from sysfs
$classFile = "/sys/bus/pci/devices/{$pci}/class";
if (is_readable($classFile)) {
$raw = trim(file_get_contents($classFile));
$class_id = sprintf("0x%04x", (hexdec($raw) >> 8) & 0xFFFF);
}
// Try lspci -mm for machine-readable info
$out = trim(@shell_exec('lspci -mm -s ' . escapeshellarg($pci) . ' 2>/dev/null'));
if ($out && preg_match('/"([^"]+)"\s+"([^"]+)"\s+"([^"]+)"/', $out, $m)) {
$class = $m[1];
$name = trim($m[3]);
return [$class, $class_id, $name];
}
// Fallback to regular lspci output
$alt = trim(@shell_exec('lspci -s ' . escapeshellarg($pci) . ' 2>/dev/null'));
if ($alt && preg_match('/^[\da-fA-F:.]+\s+([^:]+):\s+(.+)/', $alt, $m)) {
$class = trim($m[1]);
$name = trim($m[2]);
}
return [$class, $class_id, $name];
}
/* --- CLI Entry --- */
$sriov = json_decode(getSriovInfoJson(true),true);
/**
* Enumerate all VFs and group them by IOMMU group
* Output: associative array or JSON with keys like "IOMMU group 29"
*/
function getVfListByIommuGroup(): array {
$groups = [];
foreach (glob('/sys/bus/pci/devices/*/physfn') as $vf_physfn) {
$vf_dir = dirname($vf_physfn);
$vf_pci = basename($vf_dir);
$iommu_link = "$vf_dir/iommu_group";
if (is_link($iommu_link)) {
$iommu_group = basename(readlink($iommu_link));
} else {
$iommu_group = "unknown";
}
$groups[] = "IOMMU group " . $iommu_group;
$groups[] = $vf_pci;
}
ksort($groups, SORT_NATURAL);
return $groups;
}
$sriovvfs = getVfListByIommuGroup();
switch ($_POST['table']) {
case 't1':
exec('for group in $(ls /sys/kernel/iommu_groups/ -1|sort -n);do echo "IOMMU group $group";for device in $(ls -1 "/sys/kernel/iommu_groups/$group"/devices/);do echo -n $\'\t\';lspci -ns "$device"|awk \'BEGIN{ORS=" "}{print "["$3"]"}\';lspci -s "$device";done;done',$groups);
if (empty($groups)) {
exec('lspci -n|awk \'{print "["$3"]"}\'',$iommu);
exec('lspci',$lspci);
$i = 0;
foreach ($lspci as $line) echo "<tr><td>",$iommu[$i++],"</td><td>$line</td></tr>";
$noiommu = true;
} else {
$BDF_VD_REGEX = '/^[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]](\|[[:xdigit:]]{4}:[[:xdigit:]]{4})?$/';
$DBDF_VD_REGEX = '/^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]](\|[[:xdigit:]]{4}:[[:xdigit:]]{4})?$/';
$BDF_REGEX = '/^[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]$/';
$DBDF_PARTIAL_REGEX = '/[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]/';
$vfio_cfg_devices = array ();
if (is_file("/boot/config/vfio-pci.cfg")) {
// accepts space-separated list of <Bus:Device.Function> or <Domain:Bus:Device.Function> followed by an optional "|" and <Vendor:Device>
// example: BIND=03:00.0 0000:03:00.0 03:00.0|8086:1533 0000:03:00.0|8086:1533
// this front-end does not accept <Vendor:Device> by itself, altough the underlying vfio-pci script does
$file = file_get_contents("/boot/config/vfio-pci.cfg");
$file = trim(str_replace("BIND=", "", $file));
$file_contents = explode(" ", $file);
foreach ($file_contents as $vfio_cfg_device) {
if (preg_match($BDF_VD_REGEX, $vfio_cfg_device)) {
// only <Bus:Device.Function> was provided, assume Domain is 0000 (may be followed by optional <Vendor:Device> too)
$vfio_cfg_devices[] = "0000:".$vfio_cfg_device;
} else if (preg_match($DBDF_VD_REGEX, $vfio_cfg_device)) {
// full <Domain:Bus:Device.Function> was provided (may be followed by optional <Vendor:Device> too)
$vfio_cfg_devices[] = $vfio_cfg_device;
} else {
// entry in wrong format, discard
}
}
$vfio_cfg_devices = array_values(array_unique($vfio_cfg_devices, SORT_STRING));
}
$DBDF_SRIOV_REGEX = '/^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]\|[[:xdigit:]]{4}:[[:xdigit:]]{4}\|[[:xdigit:]]+$/';
$DBDF_SRIOV_MAC_REGEX = '/^[[:xdigit:]]{4}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\.[[:xdigit:]]\|[[:xdigit:]]{4}:[[:xdigit:]]{4}\|([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}$/';
$sriov_devices = array ();
if (is_file("/boot/config/sriov.cfg")) {
// accepts space-separated list of <Bus:Device.Function> or <Domain:Bus:Device.Function> followed by an optional "|" and <Vendor:Device>
// example: VFS=0000:04:00.1|8086:1521|3 0000:04:00.0|8086:1521|2
// this front-end does not accept <Vendor:Device> by itself, altough the underlying vfio-pci script does
$file = file_get_contents("/boot/config/sriov.cfg");
$file = trim(str_replace("VFS=", "", $file));
$file_contents = explode(" ", $file);
foreach ($file_contents as $sriov_device) {
if (preg_match($DBDF_SRIOV_REGEX, $sriov_device)) {
// full <Domain:Bus:Device.Function> was provided (may be followed by optional <Vendor:Device> too)
$sriov_devices[] = $sriov_device;
} else {
// entry in wrong format, discard
}
}
$sriov_devices = array_values(array_unique($sriov_devices, SORT_STRING));
}
$sriov_devices_mac = array ();
if (is_file("/boot/config/sriovvfs.cfg")) {
// accepts space-separated list of <Bus:Device.Function> or <Domain:Bus:Device.Function> followed by an optional "|" and <Vendor:Device>
// example:VFMAC=0000:04:11.5|8086:1520|62:00:04:11:05:01
// this front-end does not accept <Vendor:Device> by itself, altough the underlying vfio-pci script does
$file = file_get_contents("/boot/config/sriovvfs.cfg");
$file = trim(str_replace("VFMAC=", "", $file));
$file_contents = explode(" ", $file);
foreach ($file_contents as $sriov_device_mac) {
if (preg_match($DBDF_SRIOV_MAC_REGEX, $sriov_device_mac)) {
// full <Domain:Bus:Device.Function> was provided (may be followed by optional <Vendor:Device> too)
$sriov_devices_mac[] = $sriov_device_mac;
} else {
// entry in wrong format, discard
}
}
$sriov_devices_mac = array_values(array_unique($sriov_devices_mac, SORT_STRING));
}
$disks = (array)parse_ini_file('state/disks.ini',true);
$devicelist = array_column($disks, 'device');
$lines = array ();
foreach ($devicelist as $line) {
if (!empty($line)) {
exec('udevadm info --path=$(udevadm info -q path /dev/'.$line.' | cut -d / -f 1-7) --query=path',$linereturn);
if(isset($linereturn[0])) {
preg_match_all($DBDF_PARTIAL_REGEX, $linereturn[0], $inuse);
foreach ($inuse[0] as $line) {
$lines[] = $line;
}
}
unset($inuse);
unset($linereturn);
}
}
$networks = (array)parse_ini_file('state/network.ini',true);
$networklist = array_merge(array_column($networks, 'BRNICS'), array_column($networks, 'BONDNICS'));
foreach ($networklist as $niclist) {
if (!empty($niclist)) {
$nics = explode(",", $niclist);
if (!empty($nics)) {
foreach ($nics as $line) {
if (!empty($line)) {
exec('readlink /sys/class/net/'.$line,$linereturn);
if(isset($linereturn[0])) {
preg_match_all($DBDF_PARTIAL_REGEX, $linereturn[0], $inuse);
foreach ($inuse[0] as $line) {
$lines[] = $line;
}
}
unset($inuse);
unset($linereturn);
}
}
}
}
}
$lines = array_values(array_unique($lines, SORT_STRING));
$iommuinuse = array ();
foreach ($lines as $pciinuse){
$string = exec("ls /sys/kernel/iommu_groups/*/devices/$pciinuse -1 -d");
$string = substr($string,25,2);
$iommuinuse[] = (strpos($string,'/')) ? strstr($string, '/', true) : $string;
}
exec('lsscsi -s',$lsscsi);
// Filter for 'removed' devices
$removedArr = array_filter($pci_device_diffs, function($entry) {
return isset($entry['status']) && $entry['status'] === 'removed';
});
foreach ($removedArr as $removedpci => $removeddata) {
$groups[] = "IOMMU "._("Removed");
$groups[] = "\tR[{$removeddata['device']['vendor_id']}:{$removeddata['device']['device_id']}] ".str_replace("0000:","",$removedpci)." ".trim($removeddata['device']['description'],"\n");
}
$ackparm = "";
#echo "<tr><td>";var_dump($sriov, $sriovvfs);echo"</td></tr>";
foreach ($groups as $line) {
if (!$line) continue;
if (in_array($line,$sriovvfs)) continue;
if ($line[0]=='I') {
if (isset($spacer)) echo "<tr><td colspan='2' class='thin'></td>"; else $spacer = true;
echo "</tr><tr><td>$line:</td><td>";
$iommu = substr($line, 12);
$append = true;
} else {
$line = preg_replace("/^\t/","",$line);
$vd = trim(explode(" ", $line)[0], "[]");
$pciaddress = explode(" ", $line)[1];
$removed = $line[0]=='R' ? true : false;
if ($removed) $line=preg_replace('/R/', '', $line, 1);
if (preg_match($BDF_REGEX, $pciaddress)) {
// By default lspci does not output the <Domain> when the only domain in the system is 0000. Add it back.
$pciaddress = "0000:".$pciaddress;
}
if ( in_array($pciaddress,$sriovvfs)) continue;
echo ($append) ? "" : "<tr><td></td><td>";
exec("lspci -v -s $pciaddress", $outputvfio);
if (preg_grep("/vfio-pci/i", $outputvfio)) {
echo "<i class=\"fa fa-circle orb green-orb middle\" title=\"",_('Kernel driver in use: vfio-pci'),"\"></i>";
$isbound = "true";
}
echo "</td><td>";
if ((strpos($line, 'Host bridge') === false) && (strpos($line, 'PCI bridge') === false)) {
if (file_exists('/sys/kernel/iommu_groups/'.$iommu.'/devices/'.$pciaddress.'/reset')) echo "<i class=\"fa fa-retweet grey-orb middle\" title=\"",_('Function Level Reset (FLR) supported'),".\"></i>";
echo "</td><td>";
if (!$removed) {
echo in_array($iommu, $iommuinuse) ? '<input type="checkbox" value="" title="'._('In use by Unraid').'" disabled ' : '<input type="checkbox" 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>" : ">";
}
} else { echo "</td><td>"; }
echo '</td><td title="';
foreach ($outputvfio as $line2) echo htmlentities($line2,ENT_QUOTES)."&#10;";
echo '">',$line,'</td></tr>';
if (array_key_exists($pciaddress,$pci_device_diffs)) {
echo "<tr><td></td><td><td></td><td></td><td>";
echo "<i class=\"fa fa-warning fa-fw orange-text\" title=\""._('PCI Change')."\n"._('Click to acknowledge').".\" onclick=\"ackPCI('".htmlentities($pciaddress)."','".htmlentities($pci_device_diffs[$pciaddress]['status'])."')\"></i>";
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 "</td></tr>";
if ($pci_device_diffs[$pciaddress]['status']=="changed") {
echo "<tr><td></td><td><td></td><td></td><td>";
echo _("Differences");
foreach($pci_device_diffs[$pciaddress]['differences'] as $key => $changes){
echo " $key "._("before").":{$changes['old']} "._("after").":{$changes['new']} ";
}
echo "</td></tr>";
}
}
if (array_key_exists($pciaddress,$sriov) && in_array(substr($sriov[$pciaddress]['class_id'],0,4),$allowedPCIClass)) {
echo "<tr><td></td><td></td><td></td><td></td><td>";
echo "SRIOV Available VFs:{$sriov[$pciaddress]['total_vfs']}";
$num_vfs= $sriov[$pciaddress]['num_vfs'];
$matches = array_filter($sriov_devices, function($entry) use ($pciaddress) {
return explode('|', $entry)[0] === $pciaddress;
});
if ($matches) {
$match = reset($matches); // first matching entry
$file_numvfs = explode('|', $match)[2];
} else $file_numvfs = 0;
echo '<label for="vf_select"> Select number of VFs: </label>';
echo '<select class="narrow" name="vf'.$pciaddress.'" id="vf'.$pciaddress.'">';
// First option: None
$selected = ($file_numvfs == 0) ? ' selected' : '';
echo "<option value=\"0\"$selected>None</option>";
// Generate numeric options
for ($i = 1; $i <= $sriov[$pciaddress]['total_vfs']; $i++) {
$selected = ($file_numvfs == $i) ? ' selected' : '';
echo "<option value=\"$i\"$selected>$i</option>";
}
echo '</select>';
echo " -> ".$num_vfs;
echo ' <a class="info" href="#" title="'._("Save VFs config").'" onclick="saveVFsConfig(\''.htmlentities($pciaddress).'\',\''.htmlentities($vd).'\'); return false;"><i class="fa fa-save"> </i></a>';
echo ' <a class="info" href="#" title="'._("Action VFs update").'" onclick="applyVFsConfig(\''.htmlentities($pciaddress).'\',\''.htmlentities($vd).'\'); return false;"><i title="Apply now" class="fa fa-play"></i></a>';
if ($file_numvfs != $num_vfs) echo " <span id='vfnotice".$pciaddress."'><i class=\"fa fa-warning fa-fw orange-text\"></i> ".sprintf(_("Pending action or reboot"));
echo "</td></tr>";
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 <Domain> when the only domain in the system is 0000. Add it back.
$pciaddress = "0000:".$pciaddress;
}
echo "<tr><td></td><td>";
$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 "<i class=\"fa fa-circle orb green-orb middle\" title=\"",_('Kernel driver in use: vfio-pci'),"\"></i>";
$isbound = "true";
}
echo "</td><td>";
if ((strpos($line, 'Host bridge') === false) && (strpos($line, 'PCI bridge') === false)) {
if (file_exists('/sys/bus/pci/devices/'.$pciaddress.'/reset')) echo "<i class=\"fa fa-retweet grey-orb middle\" title=\"",_('Function Level Reset (FLR) supported'),".\"></i>";
echo "</td><td>";
if (!$removed) {
echo '<input type="checkbox" class="iommu'.$vrf['iommu_group'].'" 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>" : ">";
}
} else { echo "</td><td>"; }
echo '</td><td title="';
foreach ($outputvfio as $line2) echo htmlentities($line2,ENT_QUOTES)."&#10;";
echo '">IOMMU Group '.$vrf['iommu_group'].": ",$vrfline[0],'</td></tr>';
if (array_key_exists($pciaddress,$pci_device_diffs)) {
echo "<tr><td></td><td><td></td><td></td><td>";
echo "<i class=\"fa fa-warning fa-fw orange-text\" title=\""._('PCI Change')."\n"._('Click to acknowledge').".\" onclick=\"ackPCI('".htmlentities($pciaddress)."','".htmlentities($pci_device_diffs[$pciaddress]['status'])."')\"></i>";
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 "</td></tr>";
if ($pci_device_diffs[$pciaddress]['status']=="changed") {
echo "<tr><td></td><td><td></td><td></td><td>";
echo _("Differences");
foreach($pci_device_diffs[$pciaddress]['differences'] as $key => $changes){
echo " $key "._("before").":{$changes['old']} "._("after").":{$changes['new']} ";
}
echo "</td></tr>";
}
}
$matches = array_filter($sriov_devices_mac, function($entry) use ($pciaddress) {
return explode('|', $entry)[0] === $pciaddress;
});
if ($matches) {
$match = reset($matches); // first matching entry
$mac = explode('|', $match)[2];
} else {
$mac = null;
}
$placeholder = empty($mac) ? 'Undefined dynamic allocation' : '';
$value_attr = empty($mac) ? '' : htmlspecialchars($mac, ENT_QUOTES);
echo "<tr><td></td><td></td><td></td><td></td><td>";
echo '<label for="mac_address">MAC Address:</label>';
echo "<input class='narrow' type=\"text\" name=\"vfmac$pciaddress\" id=\"vfmac$pciaddress\" value=\"$value_attr\" placeholder=\"$placeholder\">";
echo '<a class="info" href="#" title="'._("Generate MAC").'" onclick="generateMAC(\''.htmlentities($pciaddress).'\'); return false;"><i class="fa fa-refresh mac_generate"> </i></a>';
echo '<a class="info" href="#" title="'._("Save MAC config").'" onclick="saveMACConfig(\''.htmlentities($pciaddress).'\',\''.htmlentities($vd).'\'); return false;"><i class="fa fa-save"> </i></a>';
echo "-> {$vrf['mac']}";
echo "</td></tr>";
}
}
unset($outputvfio);
switch (true) {
case (strpos($line, 'USB controller') !== false):
if (isset($isbound)) {
echo '<tr><td></td><td></td><td></td><td></td><td>',_('This controller is bound to vfio, connected USB devices are not visible'),'.</td></tr>';
} else {
exec('for usb_ctrl in $(find /sys/bus/usb/devices/usb* -maxdepth 0 -type l);do path="$(realpath "${usb_ctrl}")";if [[ $path == *'.$pciaddress.'* ]];then bus="$(cat "${usb_ctrl}/busnum")";lsusb -s $bus:|sort;fi;done',$getusb);
foreach($getusb as $usbdevice) {
[$bus,$id] = my_explode(':',$usbdevice);
$usbport = usb_physical_port($usbdevice);
if (strlen($usbport) > 7 ) {$usbport .= "\t"; } else { $usbport .= "\t\t"; }
echo "<tr><td></td><td></td><td></td><td></td><td>$bus Port $usbport",trim($id),"</td></tr>";
}
unset($getusb);
}
break;
case (strpos($line, 'SATA controller') !== false):
case (strpos($line, 'Serial Attached SCSI controller') !== false):
case (strpos($line, 'RAID bus controller') !== false):
case (strpos($line, 'SCSI storage controller') !== false):
case (strpos($line, 'IDE interface') !== false):
case (strpos($line, 'Mass storage controller') !== false):
case (strpos($line, 'Non-Volatile memory controller') !== false):
if (isset($isbound)) {
echo '<tr><td></td><td></td><td></td><td></td><td>',_('This controller is bound to vfio, connected drives are not visible'),'.</td></tr>';
} else {
exec('ls -al /sys/block/sd* /sys/block/hd* /sys/block/sr* /sys/block/nvme* 2>/dev/null | grep -i "'.$pciaddress.'"',$getsata);
foreach($getsata as $satadevice) {
$satadevice = substr($satadevice, strrpos($satadevice, '/', -1)+1);
$search = preg_grep('/'.$satadevice.'.*/', $lsscsi);
foreach ($search as $deviceline) {
echo '<tr><td></td><td></td><td></td><td></td><td>',$deviceline,'</td></tr>';
}
}
unset($search);
unset($getsata);
}
break;
}
unset($isbound);
$append = false;
}
}
echo '<tr><td></td><td></td><td></td><td></td><td><br>';
if (file_exists("/var/log/vfio-pci") && filesize("/var/log/vfio-pci")) {
echo '<input id="viewlog" type="button" value="'._('View VFIO-PCI Log').'" onclick="openTerminal(\'log\',\'vfio-pci\',\'vfio-pci\')">';
}
if ($ackparm == "") $ackdisable =" disabled "; else $ackdisable = "";
echo '<input id="applycfg" type="submit" 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\')" >';
echo '</td></tr>';
echo <<<EOT
<script>
$("#t1 input[type='checkbox']").change(function() {
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;
}
$("#applycfg").attr("disabled", false);
});
</script>
EOT;
}
break;
case 't2':
$is_intel_cpu = is_intel_cpu();
$core_types = $is_intel_cpu ? get_intel_core_types() : [];
exec('cat /sys/devices/system/cpu/*/topology/thread_siblings_list|sort -nu',$pairs);
$i = 1;
foreach ($pairs as $line) {
$line2 = $line;
$line = preg_replace(['/(\d+)[-,](\d+)/','/(\d+)\b/'],['$1 / $2','cpu $1'],$line);
if ($is_intel_cpu && count($core_types) > 0) {
[$cpu1, $cpu2] = my_preg_split('/[,-]/',$line2);
$core = $cpu1;
$core_type = "({$core_types[$core]})";
} else $core_type = "";
echo "<tr><td>".(strpos($line,'/')===false?"Single":"Pair ".$i++).":</td><td>$line $core_type</td></tr>";
}
break;
case 't3':
exec('lsusb|sort',$lsusb);
foreach ($lsusb as $line) {
[$bus,$id] = my_explode(':',$line);
$usbport = usb_physical_port($line);
echo "<tr><td>$bus Port $usbport</td><td>".trim($id)."</td></tr>";
}
break;
case 't4':
exec('lsscsi -s',$lsscsi);
foreach ($lsscsi as $line) {
if (strpos($line,'/dev/')===false) continue;
echo "<tr><td>",preg_replace('/\] +/',']</td><td>',$line),"</td></tr>";
}
break;
}
?>