Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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...)
Comment on lines +55 to +66

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
22 changes: 22 additions & 0 deletions pkg/hardware_info/hardware-info.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ 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/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 @@ -40,6 +42,12 @@ func Get(friendlyNames bool) (*types.HwInfo, []string, error) {
}
hwInfo.PciDevices = pciDevices

devices, err := qualcomm.Info()
if err != nil {
return nil, nil, fmt.Errorf("getting qualcomm devices: %v", err)
}
hwInfo.Devices = devices

return &hwInfo, warnings, nil
}

Expand Down Expand Up @@ -100,6 +108,20 @@ 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)
}

// 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
71 changes: 71 additions & 0 deletions pkg/hardware_info/qualcomm/qualcomm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package qualcomm

import (
"fmt"
"path/filepath"
"sort"
"strings"

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

var devRoot = "/dev"

func Info() ([]types.DetectedDevice, error) {
nodes, err := globSorted(filepath.Join(devRoot, "fastrpc-*"))
if err != nil {
return nil, fmt.Errorf("detecting fastrpc nodes: %v", err)
}

return DetectFromNodes(nodes), nil
}

func DetectFromNodes(nodes []string) []types.DetectedDevice {
var devices []types.DetectedDevice

for _, node := range nodes {
node = strings.TrimSpace(node)
if node == "" {
continue
}

base := filepath.Base(node)
if !strings.HasPrefix(base, "fastrpc-") {
continue
}

devices = append(devices, types.DetectedDevice{
Type: detectNpuType(base),
Bus: "fastrpc",
Metadata: &types.DeviceMetadata{
VendorName: "qualcomm",
ProductName: node,
},
})
}

sort.Slice(devices, func(i, j int) bool {
return devices[i].Metadata.ProductName < devices[j].Metadata.ProductName
})

return devices
}

func detectNpuType(nodeBaseName string) string {
const prefix = "fastrpc-"
if strings.HasPrefix(nodeBaseName, prefix) {
suffix := strings.TrimPrefix(nodeBaseName, prefix)
return fmt.Sprintf("NPU - %s", suffix)
}
return "NPU"
}

func globSorted(pattern string) ([]string, error) {
matches, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}

sort.Strings(matches)
return matches, nil
}
109 changes: 109 additions & 0 deletions pkg/hardware_info/qualcomm/qualcomm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package qualcomm

import (
"os"
"path/filepath"
"reflect"
"testing"

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

func TestInfo(t *testing.T) {
tmp := t.TempDir()
originalRoot := devRoot
devRoot = tmp
t.Cleanup(func() {
devRoot = originalRoot
})

_, err := os.Create(filepath.Join(tmp, "fastrpc-adsp-secure"))
if err != nil {
t.Fatal(err)
}
_, err = os.Create(filepath.Join(tmp, "fastrpc-cdsp"))
if err != nil {
t.Fatal(err)
}
_, err = os.Create(filepath.Join(tmp, "fastrpc-cdsp1"))
if err != nil {
t.Fatal(err)
}

devices, err := Info()
if err != nil {
t.Fatal(err)
}

expectedDevices := []types.DetectedDevice{
{
Type: "NPU - adsp-secure",
Bus: "fastrpc",
Metadata: &types.DeviceMetadata{
VendorName: "qualcomm",
ProductName: filepath.Join(tmp, "fastrpc-adsp-secure"),
},
},
{
Type: "NPU - cdsp",
Bus: "fastrpc",
Metadata: &types.DeviceMetadata{
VendorName: "qualcomm",
ProductName: filepath.Join(tmp, "fastrpc-cdsp"),
},
},
{
Type: "NPU - cdsp1",
Bus: "fastrpc",
Metadata: &types.DeviceMetadata{
VendorName: "qualcomm",
ProductName: filepath.Join(tmp, "fastrpc-cdsp1"),
},
},
}
if !reflect.DeepEqual(devices, expectedDevices) {
t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices)
}
}

func TestDetectFromNodes(t *testing.T) {
nodes := []string{
"/dev/fastrpc-cdsp1-secure",
"/dev/fastrpc-adsp",
"/dev/something-else",
"/dev/fastrpc-cdsp",
}

devices := DetectFromNodes(nodes)

expectedDevices := []types.DetectedDevice{
{
Type: "NPU - adsp",
Bus: "fastrpc",
Metadata: &types.DeviceMetadata{
VendorName: "qualcomm",
ProductName: "/dev/fastrpc-adsp",
},
},
{
Type: "NPU - cdsp",
Bus: "fastrpc",
Metadata: &types.DeviceMetadata{
VendorName: "qualcomm",
ProductName: "/dev/fastrpc-cdsp",
},
},
{
Type: "NPU - cdsp1-secure",
Bus: "fastrpc",
Metadata: &types.DeviceMetadata{
VendorName: "qualcomm",
ProductName: "/dev/fastrpc-cdsp1-secure",
},
},
}

if !reflect.DeepEqual(devices, expectedDevices) {
t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices)
}
}
Loading
Loading