mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
## Description We are attempting to hookup maitred to the API Maitred duties will be: - [ ] Hookup to the API - [ ] Wait for signal (from the API) to start Steam - [ ] Stop signal to stop the gaming session, clean up Steam... and maybe do the backup ## Summary by CodeRabbit - **New Features** - Introduced Docker-based deployment configurations for both the main and relay applications. - Added new API endpoints enabling real-time machine messaging and enhanced IoT operations. - Expanded database schema and actor types to support improved machine tracking. - **Improvements** - Enhanced real-time communication and relay management with streamlined room handling. - Upgraded dependencies, logging, and error handling for greater stability and performance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> Co-authored-by: Kristian Ollikainen <14197772+DatCaptainHorse@users.noreply.github.com>
185 lines
4.3 KiB
Go
185 lines
4.3 KiB
Go
package system
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
pciClassVGA = 0x0300 // VGA compatible controller
|
|
pciClass3D = 0x0302 // 3D controller
|
|
pciClassDisplay = 0x0380 // Display controller
|
|
pciClassCoProcessor = 0x0b40 // Co-processor (e.g., NVIDIA Tesla)
|
|
)
|
|
|
|
type infoPair struct {
|
|
Name string
|
|
ID int
|
|
}
|
|
|
|
type PCIInfo struct {
|
|
Slot string
|
|
Class infoPair
|
|
Vendor infoPair
|
|
Device infoPair
|
|
SVendor infoPair
|
|
SDevice infoPair
|
|
Rev string
|
|
ProgIf string
|
|
Driver string
|
|
Modules []string
|
|
IOMMUGroup string
|
|
}
|
|
|
|
const (
|
|
VendorIntel = 0x8086
|
|
VendorNVIDIA = 0x10de
|
|
VendorAMD = 0x1002
|
|
)
|
|
|
|
func GetAllGPUInfo() ([]PCIInfo, error) {
|
|
var gpus []PCIInfo
|
|
|
|
cmd := exec.Command("lspci", "-mmvvvnnkD")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sections := bytes.Split(output, []byte("\n\n"))
|
|
for _, section := range sections {
|
|
var info PCIInfo
|
|
|
|
lines := bytes.Split(section, []byte("\n"))
|
|
for _, line := range lines {
|
|
parts := bytes.SplitN(line, []byte(":"), 2)
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
|
|
key := strings.TrimSpace(string(parts[0]))
|
|
value := strings.TrimSpace(string(parts[1]))
|
|
|
|
switch key {
|
|
case "Slot":
|
|
info.Slot = value
|
|
case "Class":
|
|
info.Class, err = parseInfoPair(value)
|
|
case "Vendor":
|
|
info.Vendor, err = parseInfoPair(value)
|
|
case "Device":
|
|
info.Device, err = parseInfoPair(value)
|
|
case "SVendor":
|
|
info.SVendor, err = parseInfoPair(value)
|
|
case "SDevice":
|
|
info.SDevice, err = parseInfoPair(value)
|
|
case "Rev":
|
|
info.Rev = value
|
|
case "ProgIf":
|
|
info.ProgIf = value
|
|
case "Driver":
|
|
info.Driver = value
|
|
case "Module":
|
|
info.Modules = append(info.Modules, value)
|
|
case "IOMMUGroup":
|
|
info.IOMMUGroup = value
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Check if this is a GPU device
|
|
if isGPUClass(info.Class.ID) {
|
|
gpus = append(gpus, info)
|
|
}
|
|
}
|
|
|
|
return gpus, nil
|
|
}
|
|
|
|
// gets infoPair from "SomeName [SomeID]"
|
|
// example: "DG2 [Arc A770] [56a0]" -> Name: "DG2 [Arc A770]", ID: "56a0"
|
|
func parseInfoPair(pair string) (infoPair, error) {
|
|
parts := strings.Split(pair, "[")
|
|
if len(parts) < 2 {
|
|
return infoPair{}, errors.New("invalid info pair")
|
|
}
|
|
|
|
id := strings.TrimSuffix(parts[len(parts)-1], "]")
|
|
name := strings.TrimSuffix(pair, "["+id)
|
|
name = strings.TrimSpace(name)
|
|
id = strings.TrimSpace(id)
|
|
|
|
// Remove ID including square brackets from name
|
|
name = strings.ReplaceAll(name, "["+id+"]", "")
|
|
name = strings.TrimSpace(name)
|
|
|
|
idHex, err := parseHexID(id)
|
|
if err != nil {
|
|
return infoPair{}, err
|
|
}
|
|
|
|
return infoPair{
|
|
Name: name,
|
|
ID: idHex,
|
|
}, nil
|
|
}
|
|
|
|
func parseHexID(id string) (int, error) {
|
|
if strings.HasPrefix(id, "0x") {
|
|
id = id[2:]
|
|
}
|
|
parsed, err := strconv.ParseInt(id, 16, 32)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int(parsed), nil
|
|
}
|
|
|
|
func isGPUClass(class int) bool {
|
|
return class == pciClassVGA || class == pciClass3D || class == pciClassDisplay || class == pciClassCoProcessor
|
|
}
|
|
|
|
// GetCardDevices returns the /dev/dri/cardX and /dev/dri/renderDXXX device
|
|
func (info PCIInfo) GetCardDevices() (cardPath, renderPath string, err error) {
|
|
busID := strings.ToLower(info.Slot)
|
|
if !strings.HasPrefix(busID, "0000:") || len(busID) != 12 || busID[4] != ':' || busID[7] != ':' || busID[10] != '.' {
|
|
return "", "", fmt.Errorf("invalid PCI Bus ID format: %s (expected 0000:XX:YY.Z)", busID)
|
|
}
|
|
|
|
byPathDir := "/dev/dri/by-path/"
|
|
entries, err := os.ReadDir(byPathDir)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("failed to read %s: %v", byPathDir, err)
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
name := entry.Name()
|
|
if strings.HasPrefix(name, "pci-"+busID+"-card") {
|
|
cardPath, err = filepath.EvalSymlinks(filepath.Join(byPathDir, name))
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("failed to resolve card symlink %s: %v", name, err)
|
|
}
|
|
}
|
|
if strings.HasPrefix(name, "pci-"+busID+"-render") {
|
|
renderPath, err = filepath.EvalSymlinks(filepath.Join(byPathDir, name))
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("failed to resolve render symlink %s: %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if cardPath == "" && renderPath == "" {
|
|
return "", "", fmt.Errorf("no DRM devices found for PCI Bus ID: %s", busID)
|
|
}
|
|
return cardPath, renderPath, nil
|
|
}
|