Skip to content
Draft
31 changes: 31 additions & 0 deletions pkg/engines/busses.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ func (device Device) validateBus(extraFields []string) error {
return device.validatePci(extraFields)
case "usb":
return device.validateUsb(extraFields)
case "fastrpc":
return device.validateFastRpc(extraFields)
case "": // default to pci bus
return device.validatePci(extraFields)
default:
Expand Down Expand Up @@ -49,3 +51,32 @@ func (device Device) validatePci(extraFields []string) error {

return nil
}

func (device Device) validateFastRpc(extraFields []string) error {
if device.Type != "" && device.Type != "npu" {
return fmt.Errorf("fastrpc bus only supports npu devices")
}

validFields := []string{
"Type",
"Bus",
"NodeGlob",
"SnapConnections",
}
validFields = append(validFields, extraFields...)

t := reflect.TypeOf(device)
v := reflect.ValueOf(device)

for i := 0; i < t.NumField(); i++ {
fieldName := t.Field(i).Name
fieldValue := v.FieldByName(fieldName)
if fieldValue.IsValid() && !fieldValue.IsZero() {
if !slices.Contains(validFields, fieldName) {
return fmt.Errorf("fastrpc: invalid field: %s", fieldName)
}
}
}

return nil
}
9 changes: 9 additions & 0 deletions pkg/engines/busses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,13 @@ func TestDeviceBus(t *testing.T) {
}
t.Log(err)
})

t.Run("FastRPC bus with non-NPU type", func(t *testing.T) {
device.Bus = "fastrpc"
err := device.validate()
if err == nil {
t.Fatal("FastRPC bus should be invalid for GPU type")
}
t.Log(err)
})
}
23 changes: 23 additions & 0 deletions pkg/engines/devices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,29 @@ func TestDeviceNpu(t *testing.T) {
}
t.Log(err)
})

t.Run("NPU fastrpc valid fields", func(t *testing.T) {
device = Device{Type: "npu", Bus: "fastrpc"}
nodeGlob := "/dev/fastrpc-cdsp*"
device.NodeGlob = &nodeGlob

err := device.validate()
if err != nil {
t.Fatalf("NPU fastrpc fields should be valid: %v", err)
}
})

t.Run("NPU fastrpc invalid fields", func(t *testing.T) {
device = Device{Type: "npu", Bus: "fastrpc"}
hexValue := types.HexInt(0xAA)
device.VendorId = &hexValue

err := device.validate()
if err == nil {
t.Fatal("NPU fastrpc fields should be invalid")
}
t.Log(err)
})
}

func TestDeviceTypeless(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/engines/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type Devices struct {
type Device struct {
// General
Type string `yaml:"type,omitempty" json:"type,omitempty"` // cpu, gpu, npu or nil
Bus string `yaml:"bus,omitempty" json:"bus,omitempty"` // pci, usb or nil
Bus string `yaml:"bus,omitempty" json:"bus,omitempty"` // pci, usb, fastrpc or nil

// CPUs
Architecture *string `yaml:"architecture,omitempty" json:"architecture,omitempty"`
Expand All @@ -69,7 +69,7 @@ type Device struct {
ComputeCapability *string `yaml:"compute-capability,omitempty" json:"compute-capability,omitempty"`

// NPU
// no additional properties for now
NodeGlob *string `yaml:"node-glob,omitempty" json:"node-glob,omitempty"`

// Drivers
SnapConnections []string `yaml:"snap-connections,omitempty" json:"snap-connections,omitempty"`
Expand Down
9 changes: 9 additions & 0 deletions pkg/hardware_info/disk/disk_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package disk

import (
"fmt"
"os"

"github.com/canonical/inference-snaps-cli/pkg/constants"
"github.com/canonical/inference-snaps-cli/pkg/types"
Expand All @@ -16,6 +17,14 @@ func Info() (map[string]types.DirStats, error) {
var info = make(map[string]types.DirStats)

for _, dir := range directories {
// Skip directories that don't exist
if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
continue
}
return nil, fmt.Errorf("checking directory %s: %v", dir, err)
}

dirInfo, err := statFs(dir)
if err != nil {
return nil, fmt.Errorf("getting directory info for %s: %v", dir, err)
Expand Down
30 changes: 30 additions & 0 deletions pkg/hardware_info/hardware-info.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"testing"

"github.com/canonical/inference-snaps-cli/pkg/hardware_info/cpu"
"github.com/canonical/inference-snaps-cli/pkg/hardware_info/disk"
"github.com/canonical/inference-snaps-cli/pkg/hardware_info/mediatek"
"github.com/canonical/inference-snaps-cli/pkg/hardware_info/memory"
"github.com/canonical/inference-snaps-cli/pkg/hardware_info/pci"
"github.com/canonical/inference-snaps-cli/pkg/hardware_info/qualcomm"
"github.com/canonical/inference-snaps-cli/pkg/types"
)

Expand Down Expand Up @@ -100,6 +103,33 @@ func GetFromRawData(t *testing.T, device string, friendlyNames bool, testDir str
}
hwInfo.PciDevices = pciDevices

fastrpcNodesFile := devicePath + "fastrpc-nodes.txt"
if _, err := os.Stat(fastrpcNodesFile); err == nil {
nodesData, err := os.ReadFile(fastrpcNodesFile)
if err != nil {
t.Fatal(err)
}

nodes := strings.Fields(string(nodesData))
devices := qualcomm.DetectFromNodes(nodes)
hwInfo.Devices = devices
} else if !os.IsNotExist(err) {
t.Fatalf("error checking file '%s': %v\n", fastrpcNodesFile, err)
}

mediatekCompatiblesFile := devicePath + "mediatek-compatible.txt"
if _, err := os.Stat(mediatekCompatiblesFile); err == nil {
compatiblesData, err := os.ReadFile(mediatekCompatiblesFile)
if err != nil {
t.Fatal(err)
}

devices := mediatek.DetectFromCompatibles(strings.Fields(string(compatiblesData)))
hwInfo.Devices = append(hwInfo.Devices, devices...)
} else if !os.IsNotExist(err) {
t.Fatalf("error checking file '%s': %v\n", mediatekCompatiblesFile, err)
}

// Additional properties - we append these directly from a file, as we can not run the vendor specific tools on the machine
addPropsFile := devicePath + "additional-properties.json"
_, err = os.Stat(addPropsFile)
Expand Down
131 changes: 131 additions & 0 deletions pkg/hardware_info/mediatek/mediatek.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package mediatek

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"

"github.com/canonical/inference-snaps-cli/pkg/types"
)

var deviceTreeRoot = "/sys/firmware/devicetree/base"

var socToPlatform = map[string]string{
"mt8195": "genio-1200",
"mt8188": "genio-510-700",
"mt8189": "genio-520-720",
}

func Info() ([]types.DetectedDevice, error) {
compatibleValues, err := readCompatibleValues(deviceTreeRoot)
if err != nil {
return nil, fmt.Errorf("detecting mediatek apusys: %v", err)
}

devices := DetectFromCompatibles(compatibleValues)
return devices, nil
}

func DetectFromCompatibles(compatibleValues []string) []types.DetectedDevice {
platSet := make(map[string]struct{})

for _, value := range compatibleValues {
soc, ok := extractAPUSoC(value)
if !ok {
continue
}

platformName, found := socToPlatform[soc]
if !found {
platformName = "mediatek-" + soc
}

platSet[platformName] = struct{}{}
}

if len(platSet) == 0 {
return nil
}

platformNames := make([]string, 0, len(platSet))
for name := range platSet {
platformNames = append(platformNames, name)
}
sort.Strings(platformNames)

devices := make([]types.DetectedDevice, 0, len(platformNames))
for _, name := range platformNames {
devices = append(devices, types.DetectedDevice{
Type: "npu",
Bus: "mdla",
Metadata: &types.DeviceMetadata{
VendorName: "mediatek",
ProductName: name,
},
})
}

return devices
}

func readCompatibleValues(root string) ([]string, error) {
var values []string

err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || d.Name() != "compatible" {
return nil
}

data, readErr := os.ReadFile(path)
if readErr != nil {
return readErr
}

entries := parseCompatibleData(data)
values = append(values, entries...)
return nil
})
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}

return values, nil
}

func parseCompatibleData(data []byte) []string {
parts := strings.Split(string(data), "\x00")
values := make([]string, 0, len(parts))

for _, part := range parts {
v := strings.TrimSpace(part)
if v == "" {
continue
}
values = append(values, v)
}

return values
}

func extractAPUSoC(compatible string) (string, bool) {
if !strings.HasPrefix(compatible, "mediatek,") || !strings.HasSuffix(compatible, "-apusys_rv") {
return "", false
}

body := strings.TrimPrefix(compatible, "mediatek,")
soc := strings.TrimSuffix(body, "-apusys_rv")
if soc == "" || strings.Contains(soc, ",") {
return "", false
}

return soc, true
}
Loading
Loading