From 4332b302719a97738e4b9da76bb5226bdd1da5ae Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Tue, 21 Apr 2026 15:44:56 +0200 Subject: [PATCH 1/6] feat(hardware_info): Add support for Qualcomm NPU devices and FastRPC bus Introduce the FastRPC bus type and restrict it to NPU devices. Add Qualcomm hardware detection by checking for fastrpc nodes. Include NodeGlob field for NPU devices and update disk info gathering to silently skip nonexistent directories. Signed-off-by: Sinan KARAKAYA --- pkg/engines/busses.go | 31 +++++++ pkg/engines/busses_test.go | 9 ++ pkg/engines/devices_test.go | 23 +++++ pkg/engines/types.go | 4 +- pkg/hardware_info/disk/disk_info.go | 9 ++ pkg/hardware_info/hardware-info.go | 24 ++++++ pkg/hardware_info/qualcomm/qualcomm.go | 94 +++++++++++++++++++++ pkg/hardware_info/qualcomm/qualcomm_test.go | 86 +++++++++++++++++++ pkg/selector/fastrpc/fastrpc.go | 58 +++++++++++++ pkg/selector/fastrpc/fastrpc_test.go | 50 +++++++++++ pkg/selector/fastrpc_test.go | 52 ++++++++++++ pkg/selector/select_stack.go | 19 +++++ pkg/types/detected_devices.go | 12 +++ pkg/types/hw_info.go | 2 + 14 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 pkg/hardware_info/qualcomm/qualcomm.go create mode 100644 pkg/hardware_info/qualcomm/qualcomm_test.go create mode 100644 pkg/selector/fastrpc/fastrpc.go create mode 100644 pkg/selector/fastrpc/fastrpc_test.go create mode 100644 pkg/selector/fastrpc_test.go create mode 100644 pkg/types/detected_devices.go diff --git a/pkg/engines/busses.go b/pkg/engines/busses.go index 1f2b2869..46f79c80 100644 --- a/pkg/engines/busses.go +++ b/pkg/engines/busses.go @@ -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: @@ -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 +} diff --git a/pkg/engines/busses_test.go b/pkg/engines/busses_test.go index a8abd686..6155aa99 100644 --- a/pkg/engines/busses_test.go +++ b/pkg/engines/busses_test.go @@ -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) + }) } diff --git a/pkg/engines/devices_test.go b/pkg/engines/devices_test.go index 168b3565..5531036b 100644 --- a/pkg/engines/devices_test.go +++ b/pkg/engines/devices_test.go @@ -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) { diff --git a/pkg/engines/types.go b/pkg/engines/types.go index 803c120a..05c7da14 100644 --- a/pkg/engines/types.go +++ b/pkg/engines/types.go @@ -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"` @@ -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"` diff --git a/pkg/hardware_info/disk/disk_info.go b/pkg/hardware_info/disk/disk_info.go index 1781be2a..7858efde 100644 --- a/pkg/hardware_info/disk/disk_info.go +++ b/pkg/hardware_info/disk/disk_info.go @@ -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" @@ -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) diff --git a/pkg/hardware_info/hardware-info.go b/pkg/hardware_info/hardware-info.go index b6e9d278..a28b01ed 100644 --- a/pkg/hardware_info/hardware-info.go +++ b/pkg/hardware_info/hardware-info.go @@ -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" ) @@ -40,6 +42,13 @@ func Get(friendlyNames bool) (*types.HwInfo, error) { } hwInfo.PciDevices = pciDevices + platforms, devices, err := qualcomm.Info() + if err != nil { + return nil, fmt.Errorf("getting qualcomm devices: %v", err) + } + hwInfo.Platforms = platforms + hwInfo.Devices = devices + return &hwInfo, nil } @@ -97,6 +106,21 @@ 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)) + platforms, devices := qualcomm.DetectFromNodes(nodes) + hwInfo.Platforms = platforms + 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) diff --git a/pkg/hardware_info/qualcomm/qualcomm.go b/pkg/hardware_info/qualcomm/qualcomm.go new file mode 100644 index 00000000..27ca2742 --- /dev/null +++ b/pkg/hardware_info/qualcomm/qualcomm.go @@ -0,0 +1,94 @@ +package qualcomm + +import ( + "fmt" + "path/filepath" + "sort" + "strings" + + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +var devRoot = "/dev" + +func Info() ([]types.PlatformInfo, []types.DetectedDevice, error) { + fastrpcNodes, err := globSorted(filepath.Join(devRoot, "fastrpc-*")) + if err != nil { + return nil, nil, fmt.Errorf("detecting fastrpc nodes: %v", err) + } + + cdspNodes, err := globSorted(filepath.Join(devRoot, "fastrpc-cdsp*")) + if err != nil { + return nil, nil, fmt.Errorf("detecting cdsp fastrpc nodes: %v", err) + } + + var platforms []types.PlatformInfo + if len(fastrpcNodes) > 0 { + platforms = append(platforms, types.PlatformInfo{ + Vendor: "qualcomm", + Name: "dragonwing", + }) + } + + var devices []types.DetectedDevice + if len(cdspNodes) > 0 { + devices = append(devices, types.DetectedDevice{ + Type: "npu", + Bus: "fastrpc", + Nodes: cdspNodes, + }) + } + + return platforms, devices, nil +} + +func DetectFromNodes(nodes []string) ([]types.PlatformInfo, []types.DetectedDevice) { + var fastrpcNodes []string + var cdspNodes []string + + for _, node := range nodes { + node = strings.TrimSpace(node) + if node == "" { + continue + } + + base := filepath.Base(node) + if strings.HasPrefix(base, "fastrpc-") { + fastrpcNodes = append(fastrpcNodes, node) + } + if strings.HasPrefix(base, "fastrpc-cdsp") { + cdspNodes = append(cdspNodes, node) + } + } + + sort.Strings(fastrpcNodes) + sort.Strings(cdspNodes) + + var platforms []types.PlatformInfo + if len(fastrpcNodes) > 0 { + platforms = append(platforms, types.PlatformInfo{ + Vendor: "qualcomm", + Name: "dragonwing", + }) + } + + var devices []types.DetectedDevice + if len(cdspNodes) > 0 { + devices = append(devices, types.DetectedDevice{ + Type: "npu", + Bus: "fastrpc", + Nodes: cdspNodes, + }) + } + + return platforms, devices +} + +func globSorted(pattern string) ([]string, error) { + matches, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + sort.Strings(matches) + return matches, nil +} diff --git a/pkg/hardware_info/qualcomm/qualcomm_test.go b/pkg/hardware_info/qualcomm/qualcomm_test.go new file mode 100644 index 00000000..cf7914c1 --- /dev/null +++ b/pkg/hardware_info/qualcomm/qualcomm_test.go @@ -0,0 +1,86 @@ +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) + } + + platforms, devices, err := Info() + if err != nil { + t.Fatal(err) + } + + expectedPlatforms := []types.PlatformInfo{{ + Vendor: "qualcomm", + Name: "dragonwing", + }} + if !reflect.DeepEqual(platforms, expectedPlatforms) { + t.Fatalf("platforms=%+v expected=%+v", platforms, expectedPlatforms) + } + + expectedDevices := []types.DetectedDevice{{ + Type: "npu", + Bus: "fastrpc", + Nodes: []string{ + filepath.Join(tmp, "fastrpc-cdsp"), + 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", + } + + platforms, devices := DetectFromNodes(nodes) + if len(platforms) != 1 { + t.Fatalf("expected 1 platform, got %d", len(platforms)) + } + if platforms[0].Vendor != "qualcomm" || platforms[0].Name != "dragonwing" { + t.Fatalf("unexpected platform %+v", platforms[0]) + } + + if len(devices) != 1 { + t.Fatalf("expected 1 device, got %d", len(devices)) + } + if devices[0].Type != "npu" || devices[0].Bus != "fastrpc" { + t.Fatalf("unexpected device %+v", devices[0]) + } + + expectedNodes := []string{"/dev/fastrpc-cdsp", "/dev/fastrpc-cdsp1-secure"} + if !reflect.DeepEqual(devices[0].Nodes, expectedNodes) { + t.Fatalf("nodes=%v expected=%v", devices[0].Nodes, expectedNodes) + } +} diff --git a/pkg/selector/fastrpc/fastrpc.go b/pkg/selector/fastrpc/fastrpc.go new file mode 100644 index 00000000..c3823311 --- /dev/null +++ b/pkg/selector/fastrpc/fastrpc.go @@ -0,0 +1,58 @@ +package fastrpc + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/canonical/go-snapctl" + "github.com/canonical/inference-snaps-cli/pkg/engines" + "github.com/canonical/inference-snaps-cli/pkg/selector/weights" + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +const defaultNpuNodeGlob = "/dev/fastrpc-cdsp*" + +func Match(manifestDevice engines.Device, hostDevices []types.DetectedDevice) (int, []string) { + nodeGlob := defaultNpuNodeGlob + if manifestDevice.NodeGlob != nil { + nodeGlob = *manifestDevice.NodeGlob + } + + for _, hostDevice := range hostDevices { + if hostDevice.Bus != "fastrpc" { + continue + } + if manifestDevice.Type != "" && hostDevice.Type != manifestDevice.Type { + continue + } + + for _, node := range hostDevice.Nodes { + matched, err := filepath.Match(nodeGlob, node) + if err != nil { + return 0, []string{fmt.Sprintf("invalid node-glob %q: %v", nodeGlob, err)} + } + if !matched { + continue + } + + score := weights.PciDevice + weights.PciDeviceType + for _, connection := range manifestDevice.SnapConnections { + if testing.Testing() { + continue + } + connected, err := snapctl.IsConnected(connection).Run() + if err != nil { + return 0, []string{fmt.Sprintf("checking snap connection %q: %v", connection, err)} + } + if !connected { + return 0, []string{fmt.Sprintf("%q is not connected", connection)} + } + } + + return score, nil + } + } + + return 0, []string{fmt.Sprintf("device node matching %q not found", nodeGlob)} +} diff --git a/pkg/selector/fastrpc/fastrpc_test.go b/pkg/selector/fastrpc/fastrpc_test.go new file mode 100644 index 00000000..33065305 --- /dev/null +++ b/pkg/selector/fastrpc/fastrpc_test.go @@ -0,0 +1,50 @@ +package fastrpc + +import ( + "testing" + + "github.com/canonical/inference-snaps-cli/pkg/engines" + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +func TestMatch(t *testing.T) { + hostDevices := []types.DetectedDevice{ + { + Type: "npu", + Bus: "fastrpc", + Nodes: []string{ + "/dev/fastrpc-cdsp", + "/dev/fastrpc-cdsp1-secure", + }, + }, + } + + t.Run("default pattern", func(t *testing.T) { + device := engines.Device{Type: "npu", Bus: "fastrpc"} + score, issues := Match(device, hostDevices) + if score == 0 || len(issues) > 0 { + t.Fatalf("expected a positive score with no issues, score=%d issues=%v", score, issues) + } + }) + + t.Run("custom node glob", func(t *testing.T) { + nodeGlob := "/dev/fastrpc-cdsp1*" + device := engines.Device{Type: "npu", Bus: "fastrpc", NodeGlob: &nodeGlob} + score, issues := Match(device, hostDevices) + if score == 0 || len(issues) > 0 { + t.Fatalf("expected a positive score with no issues, score=%d issues=%v", score, issues) + } + }) + + t.Run("no matching node", func(t *testing.T) { + nodeGlob := "/dev/fastrpc-gdsp*" + device := engines.Device{Type: "npu", Bus: "fastrpc", NodeGlob: &nodeGlob} + score, issues := Match(device, hostDevices) + if score != 0 { + t.Fatalf("expected score=0, got %d", score) + } + if len(issues) == 0 { + t.Fatal("expected compatibility issues") + } + }) +} diff --git a/pkg/selector/fastrpc_test.go b/pkg/selector/fastrpc_test.go new file mode 100644 index 00000000..dd2132ac --- /dev/null +++ b/pkg/selector/fastrpc_test.go @@ -0,0 +1,52 @@ +package selector + +import ( + "testing" + + "github.com/canonical/inference-snaps-cli/pkg/engines" + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +func TestFastRpcNpuSelection(t *testing.T) { + hwInfo := &types.HwInfo{ + Memory: types.MemoryInfo{TotalRam: 8 * 1024 * 1024 * 1024}, + Disk: map[string]types.DirStats{ + "/var/lib/snapd/snaps": { + Avail: 20 * 1024 * 1024 * 1024, + }, + }, + Devices: []types.DetectedDevice{ + { + Type: "npu", + Bus: "fastrpc", + Nodes: []string{ + "/dev/fastrpc-cdsp", + }, + }, + }, + } + + manifest := engines.Manifest{ + Name: "qualcomm-npu", + Description: "qualcomm dragonwing npu", + Vendor: "qualcomm", + Grade: "stable", + Devices: engines.Devices{ + Anyof: []engines.Device{{ + Type: "npu", + Bus: "fastrpc", + }}, + }, + } + + score, report, err := checkEngine(hwInfo, manifest) + if err != nil { + t.Fatal(err) + } + if !report.EngineCompatible() { + t.Fatalf("expected engine to be compatible: %+v", report) + } + if score <= 0 { + t.Fatalf("expected positive score, got %d", score) + } +} diff --git a/pkg/selector/select_stack.go b/pkg/selector/select_stack.go index 671f52c1..bd84a5db 100644 --- a/pkg/selector/select_stack.go +++ b/pkg/selector/select_stack.go @@ -8,6 +8,7 @@ import ( "github.com/canonical/inference-snaps-cli/pkg/constants" "github.com/canonical/inference-snaps-cli/pkg/engines" "github.com/canonical/inference-snaps-cli/pkg/selector/cpu" + "github.com/canonical/inference-snaps-cli/pkg/selector/fastrpc" "github.com/canonical/inference-snaps-cli/pkg/selector/pci" "github.com/canonical/inference-snaps-cli/pkg/types" "github.com/canonical/inference-snaps-cli/pkg/utils" @@ -165,6 +166,15 @@ func scoreDevicesAll(hardwareInfo *types.HwInfo, devices []engines.Device) int { compatible = false devices[i].CompatibilityIssues = append(devices[i].CompatibilityIssues, "usb device matching not implemented") + } else if devices[i].Bus == "fastrpc" { + fastRpcScore, fastRpcIssues := fastrpc.Match(devices[i], hardwareInfo.Devices) + if len(fastRpcIssues) > 0 { + compatible = false + devices[i].CompatibilityIssues = append(devices[i].CompatibilityIssues, fastRpcIssues...) + } else { + compatibilityScore += fastRpcScore + } + } else if devices[i].Bus == "" || devices[i].Bus == "pci" { // Fallback to PCI as default bus pciScore, pciIssues := pci.Match(devices[i], hardwareInfo.PciDevices) @@ -204,6 +214,15 @@ func scoreDevicesAny(hardwareInfo *types.HwInfo, devices []engines.Device) int { compatible = false device.CompatibilityIssues = append(device.CompatibilityIssues, "usb device matching not implemented") + } else if device.Bus == "fastrpc" { + fastRpcScore, fastRpcIssues := fastrpc.Match(device, hardwareInfo.Devices) + if len(fastRpcIssues) > 0 { + devices[i].CompatibilityIssues = append(device.CompatibilityIssues, fastRpcIssues...) + } else { + devicesFound++ + compatibilityScore += fastRpcScore + } + } else if device.Bus == "" || device.Bus == "pci" { // Fallback to PCI as default bus pciScore, pciIssues := pci.Match(device, hardwareInfo.PciDevices) diff --git a/pkg/types/detected_devices.go b/pkg/types/detected_devices.go new file mode 100644 index 00000000..7db5cfc5 --- /dev/null +++ b/pkg/types/detected_devices.go @@ -0,0 +1,12 @@ +package types + +type PlatformInfo struct { + Vendor string `json:"vendor" yaml:"vendor"` + Name string `json:"name" yaml:"name"` +} + +type DetectedDevice struct { + Type string `json:"type" yaml:"type"` + Bus string `json:"bus" yaml:"bus"` + Nodes []string `json:"nodes,omitempty" yaml:"nodes,omitempty"` +} diff --git a/pkg/types/hw_info.go b/pkg/types/hw_info.go index 9548c730..f31b35ba 100644 --- a/pkg/types/hw_info.go +++ b/pkg/types/hw_info.go @@ -5,4 +5,6 @@ type HwInfo struct { Memory MemoryInfo `json:"memory,omitempty" yaml:"memory,omitempty"` Disk map[string]DirStats `json:"disk,omitempty" yaml:"disk,omitempty"` PciDevices []PciDevice `json:"pci,omitempty" yaml:"pci"` + Platforms []PlatformInfo `json:"platforms,omitempty" yaml:"platforms,omitempty"` + Devices []DetectedDevice `json:"devices,omitempty" yaml:"devices,omitempty"` } From f6cb7886ef7471ef13341b5a5b6e1c528e0cd51f Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Fri, 24 Apr 2026 16:23:06 +0200 Subject: [PATCH 2/6] refator(hw-detection: Flatted QC NPU nodes, moved Platforms to DetectedDevices Flattened NPU nodes (ADSP, CDSP, GDSP) to be considered as distinct devices rather than relying on DetectedDevice.Nodes. PlatformsInfo was moved from Devices to DetectedDevices, to avoid having to keep a relationship between each devices and their respective platform's metadata. Signed-off-by: Sinan KARAKAYA Co-authored-by: Copilot --- pkg/hardware_info/hardware-info.go | 6 +- pkg/hardware_info/qualcomm/qualcomm.go | 82 +++++++------------ pkg/hardware_info/qualcomm/qualcomm_test.go | 91 ++++++++++++++------- pkg/selector/fastrpc/fastrpc.go | 65 ++++++++++----- pkg/selector/fastrpc/fastrpc_test.go | 16 +++- pkg/selector/fastrpc_test.go | 6 +- pkg/types/detected_devices.go | 3 +- pkg/types/hw_info.go | 1 - 8 files changed, 154 insertions(+), 116 deletions(-) diff --git a/pkg/hardware_info/hardware-info.go b/pkg/hardware_info/hardware-info.go index a28b01ed..7db51d09 100644 --- a/pkg/hardware_info/hardware-info.go +++ b/pkg/hardware_info/hardware-info.go @@ -42,11 +42,10 @@ func Get(friendlyNames bool) (*types.HwInfo, error) { } hwInfo.PciDevices = pciDevices - platforms, devices, err := qualcomm.Info() + devices, err := qualcomm.Info() if err != nil { return nil, fmt.Errorf("getting qualcomm devices: %v", err) } - hwInfo.Platforms = platforms hwInfo.Devices = devices return &hwInfo, nil @@ -114,8 +113,7 @@ func GetFromRawData(t *testing.T, device string, friendlyNames bool, testDir str } nodes := strings.Fields(string(nodesData)) - platforms, devices := qualcomm.DetectFromNodes(nodes) - hwInfo.Platforms = platforms + devices := qualcomm.DetectFromNodes(nodes) hwInfo.Devices = devices } else if !os.IsNotExist(err) { t.Fatalf("error checking file '%s': %v\n", fastrpcNodesFile, err) diff --git a/pkg/hardware_info/qualcomm/qualcomm.go b/pkg/hardware_info/qualcomm/qualcomm.go index 27ca2742..643d4aa7 100644 --- a/pkg/hardware_info/qualcomm/qualcomm.go +++ b/pkg/hardware_info/qualcomm/qualcomm.go @@ -11,40 +11,17 @@ import ( var devRoot = "/dev" -func Info() ([]types.PlatformInfo, []types.DetectedDevice, error) { - fastrpcNodes, err := globSorted(filepath.Join(devRoot, "fastrpc-*")) +func Info() ([]types.DetectedDevice, error) { + nodes, err := globSorted(filepath.Join(devRoot, "fastrpc-*")) if err != nil { - return nil, nil, fmt.Errorf("detecting fastrpc nodes: %v", err) + return nil, fmt.Errorf("detecting fastrpc nodes: %v", err) } - cdspNodes, err := globSorted(filepath.Join(devRoot, "fastrpc-cdsp*")) - if err != nil { - return nil, nil, fmt.Errorf("detecting cdsp fastrpc nodes: %v", err) - } - - var platforms []types.PlatformInfo - if len(fastrpcNodes) > 0 { - platforms = append(platforms, types.PlatformInfo{ - Vendor: "qualcomm", - Name: "dragonwing", - }) - } - - var devices []types.DetectedDevice - if len(cdspNodes) > 0 { - devices = append(devices, types.DetectedDevice{ - Type: "npu", - Bus: "fastrpc", - Nodes: cdspNodes, - }) - } - - return platforms, devices, nil + return DetectFromNodes(nodes), nil } -func DetectFromNodes(nodes []string) ([]types.PlatformInfo, []types.DetectedDevice) { - var fastrpcNodes []string - var cdspNodes []string +func DetectFromNodes(nodes []string) []types.DetectedDevice { + var devices []types.DetectedDevice for _, node := range nodes { node = strings.TrimSpace(node) @@ -53,35 +30,35 @@ func DetectFromNodes(nodes []string) ([]types.PlatformInfo, []types.DetectedDevi } base := filepath.Base(node) - if strings.HasPrefix(base, "fastrpc-") { - fastrpcNodes = append(fastrpcNodes, node) - } - if strings.HasPrefix(base, "fastrpc-cdsp") { - cdspNodes = append(cdspNodes, node) + if !strings.HasPrefix(base, "fastrpc-") { + continue } - } - - sort.Strings(fastrpcNodes) - sort.Strings(cdspNodes) - - var platforms []types.PlatformInfo - if len(fastrpcNodes) > 0 { - platforms = append(platforms, types.PlatformInfo{ - Vendor: "qualcomm", - Name: "dragonwing", - }) - } - var devices []types.DetectedDevice - if len(cdspNodes) > 0 { devices = append(devices, types.DetectedDevice{ - Type: "npu", - Bus: "fastrpc", - Nodes: cdspNodes, + Type: detectNpuType(base), + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: node, + SoC: "dragonwing", + }, }) } - return platforms, devices + sort.Slice(devices, func(i, j int) bool { + return devices[i].PlatformInfo.Name < devices[j].PlatformInfo.Name + }) + + 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) { @@ -89,6 +66,7 @@ func globSorted(pattern string) ([]string, error) { if err != nil { return nil, err } + sort.Strings(matches) return matches, nil } diff --git a/pkg/hardware_info/qualcomm/qualcomm_test.go b/pkg/hardware_info/qualcomm/qualcomm_test.go index cf7914c1..820f99fa 100644 --- a/pkg/hardware_info/qualcomm/qualcomm_test.go +++ b/pkg/hardware_info/qualcomm/qualcomm_test.go @@ -30,27 +30,40 @@ func TestInfo(t *testing.T) { t.Fatal(err) } - platforms, devices, err := Info() + devices, err := Info() if err != nil { t.Fatal(err) } - expectedPlatforms := []types.PlatformInfo{{ - Vendor: "qualcomm", - Name: "dragonwing", - }} - if !reflect.DeepEqual(platforms, expectedPlatforms) { - t.Fatalf("platforms=%+v expected=%+v", platforms, expectedPlatforms) - } - - expectedDevices := []types.DetectedDevice{{ - Type: "npu", - Bus: "fastrpc", - Nodes: []string{ - filepath.Join(tmp, "fastrpc-cdsp"), - filepath.Join(tmp, "fastrpc-cdsp1"), + expectedDevices := []types.DetectedDevice{ + { + Type: "NPU - adsp-secure", + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: filepath.Join(tmp, "fastrpc-adsp-secure"), + SoC: "dragonwing", + }, + }, + { + Type: "NPU - cdsp", + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: filepath.Join(tmp, "fastrpc-cdsp"), + SoC: "dragonwing", + }, + }, + { + Type: "NPU - cdsp1", + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: filepath.Join(tmp, "fastrpc-cdsp1"), + SoC: "dragonwing", + }, }, - }} + } if !reflect.DeepEqual(devices, expectedDevices) { t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) } @@ -64,23 +77,39 @@ func TestDetectFromNodes(t *testing.T) { "/dev/fastrpc-cdsp", } - platforms, devices := DetectFromNodes(nodes) - if len(platforms) != 1 { - t.Fatalf("expected 1 platform, got %d", len(platforms)) - } - if platforms[0].Vendor != "qualcomm" || platforms[0].Name != "dragonwing" { - t.Fatalf("unexpected platform %+v", platforms[0]) - } + devices := DetectFromNodes(nodes) - if len(devices) != 1 { - t.Fatalf("expected 1 device, got %d", len(devices)) - } - if devices[0].Type != "npu" || devices[0].Bus != "fastrpc" { - t.Fatalf("unexpected device %+v", devices[0]) + expectedDevices := []types.DetectedDevice{ + { + Type: "NPU - adsp", + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: "/dev/fastrpc-adsp", + SoC: "dragonwing", + }, + }, + { + Type: "NPU - cdsp", + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: "/dev/fastrpc-cdsp", + SoC: "dragonwing", + }, + }, + { + Type: "NPU - cdsp1-secure", + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: "/dev/fastrpc-cdsp1-secure", + SoC: "dragonwing", + }, + }, } - expectedNodes := []string{"/dev/fastrpc-cdsp", "/dev/fastrpc-cdsp1-secure"} - if !reflect.DeepEqual(devices[0].Nodes, expectedNodes) { - t.Fatalf("nodes=%v expected=%v", devices[0].Nodes, expectedNodes) + if !reflect.DeepEqual(devices, expectedDevices) { + t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) } } diff --git a/pkg/selector/fastrpc/fastrpc.go b/pkg/selector/fastrpc/fastrpc.go index c3823311..a75e357b 100644 --- a/pkg/selector/fastrpc/fastrpc.go +++ b/pkg/selector/fastrpc/fastrpc.go @@ -3,6 +3,7 @@ package fastrpc import ( "fmt" "path/filepath" + "strings" "testing" "github.com/canonical/go-snapctl" @@ -23,36 +24,56 @@ func Match(manifestDevice engines.Device, hostDevices []types.DetectedDevice) (i if hostDevice.Bus != "fastrpc" { continue } - if manifestDevice.Type != "" && hostDevice.Type != manifestDevice.Type { + if !deviceTypeMatch(manifestDevice.Type, hostDevice.Type) { + continue + } + if hostDevice.PlatformInfo == nil || hostDevice.PlatformInfo.Name == "" { continue } - for _, node := range hostDevice.Nodes { - matched, err := filepath.Match(nodeGlob, node) - if err != nil { - return 0, []string{fmt.Sprintf("invalid node-glob %q: %v", nodeGlob, err)} - } - if !matched { + matched, err := filepath.Match(nodeGlob, hostDevice.PlatformInfo.Name) + if err != nil { + return 0, []string{fmt.Sprintf("invalid node-glob %q: %v", nodeGlob, err)} + } + if !matched { + continue + } + + score := weights.PciDevice + weights.PciDeviceType + for _, connection := range manifestDevice.SnapConnections { + if testing.Testing() { continue } - - score := weights.PciDevice + weights.PciDeviceType - for _, connection := range manifestDevice.SnapConnections { - if testing.Testing() { - continue - } - connected, err := snapctl.IsConnected(connection).Run() - if err != nil { - return 0, []string{fmt.Sprintf("checking snap connection %q: %v", connection, err)} - } - if !connected { - return 0, []string{fmt.Sprintf("%q is not connected", connection)} - } + connected, err := snapctl.IsConnected(connection).Run() + if err != nil { + return 0, []string{fmt.Sprintf("checking snap connection %q: %v", connection, err)} + } + if !connected { + return 0, []string{fmt.Sprintf("%q is not connected", connection)} } - - return score, nil } + + return score, nil } return 0, []string{fmt.Sprintf("device node matching %q not found", nodeGlob)} } + +func deviceTypeMatch(manifestType, hostType string) bool { + if manifestType == "" { + return true + } + + manifest := strings.ToLower(strings.TrimSpace(manifestType)) + host := strings.ToLower(strings.TrimSpace(hostType)) + + if manifest == host { + return true + } + + if manifest == "npu" && strings.HasPrefix(host, "npu") { + return true + } + + return false +} diff --git a/pkg/selector/fastrpc/fastrpc_test.go b/pkg/selector/fastrpc/fastrpc_test.go index 33065305..1c94b53e 100644 --- a/pkg/selector/fastrpc/fastrpc_test.go +++ b/pkg/selector/fastrpc/fastrpc_test.go @@ -12,9 +12,19 @@ func TestMatch(t *testing.T) { { Type: "npu", Bus: "fastrpc", - Nodes: []string{ - "/dev/fastrpc-cdsp", - "/dev/fastrpc-cdsp1-secure", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: "/dev/fastrpc-cdsp", + SoC: "dragonwing", + }, + }, + { + Type: "npu", + Bus: "fastrpc", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: "/dev/fastrpc-cdsp1-secure", + SoC: "dragonwing", }, }, } diff --git a/pkg/selector/fastrpc_test.go b/pkg/selector/fastrpc_test.go index dd2132ac..e753160a 100644 --- a/pkg/selector/fastrpc_test.go +++ b/pkg/selector/fastrpc_test.go @@ -19,8 +19,10 @@ func TestFastRpcNpuSelection(t *testing.T) { { Type: "npu", Bus: "fastrpc", - Nodes: []string{ - "/dev/fastrpc-cdsp", + PlatformInfo: &types.PlatformInfo{ + Vendor: "qualcomm", + Name: "/dev/fastrpc-cdsp", + SoC: "dragonwing", }, }, }, diff --git a/pkg/types/detected_devices.go b/pkg/types/detected_devices.go index 7db5cfc5..b657e52f 100644 --- a/pkg/types/detected_devices.go +++ b/pkg/types/detected_devices.go @@ -3,10 +3,11 @@ package types type PlatformInfo struct { Vendor string `json:"vendor" yaml:"vendor"` Name string `json:"name" yaml:"name"` + SoC string `json:"soc,omitempty" yaml:"soc,omitempty"` } type DetectedDevice struct { Type string `json:"type" yaml:"type"` Bus string `json:"bus" yaml:"bus"` - Nodes []string `json:"nodes,omitempty" yaml:"nodes,omitempty"` + PlatformInfo *PlatformInfo `json:"platform_info,omitempty" yaml:"platform_info,omitempty"` } diff --git a/pkg/types/hw_info.go b/pkg/types/hw_info.go index f31b35ba..a8acc817 100644 --- a/pkg/types/hw_info.go +++ b/pkg/types/hw_info.go @@ -5,6 +5,5 @@ type HwInfo struct { Memory MemoryInfo `json:"memory,omitempty" yaml:"memory,omitempty"` Disk map[string]DirStats `json:"disk,omitempty" yaml:"disk,omitempty"` PciDevices []PciDevice `json:"pci,omitempty" yaml:"pci"` - Platforms []PlatformInfo `json:"platforms,omitempty" yaml:"platforms,omitempty"` Devices []DetectedDevice `json:"devices,omitempty" yaml:"devices,omitempty"` } From edc7d10b010524c0c4cb74db096543ab455687d4 Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Mon, 27 Apr 2026 11:14:24 +0200 Subject: [PATCH 3/6] refactor(DetectedDevice): renamed PlatformInfo->DeviceMetadata and SoC->VendorName Co-authored-by: Copilot --- pkg/hardware_info/qualcomm/qualcomm.go | 6 +++--- pkg/hardware_info/qualcomm/qualcomm_test.go | 24 ++++++++++----------- pkg/selector/fastrpc/fastrpc.go | 4 ++-- pkg/selector/fastrpc/fastrpc_test.go | 8 +++---- pkg/selector/fastrpc_test.go | 4 ++-- pkg/types/detected_devices.go | 14 ++++++------ 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/pkg/hardware_info/qualcomm/qualcomm.go b/pkg/hardware_info/qualcomm/qualcomm.go index 643d4aa7..0923b0cb 100644 --- a/pkg/hardware_info/qualcomm/qualcomm.go +++ b/pkg/hardware_info/qualcomm/qualcomm.go @@ -37,16 +37,16 @@ func DetectFromNodes(nodes []string) []types.DetectedDevice { devices = append(devices, types.DetectedDevice{ Type: detectNpuType(base), Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: node, - SoC: "dragonwing", + VendorName: "dragonwing", }, }) } sort.Slice(devices, func(i, j int) bool { - return devices[i].PlatformInfo.Name < devices[j].PlatformInfo.Name + return devices[i].Metadata.Name < devices[j].Metadata.Name }) return devices diff --git a/pkg/hardware_info/qualcomm/qualcomm_test.go b/pkg/hardware_info/qualcomm/qualcomm_test.go index 820f99fa..7bdc8a9f 100644 --- a/pkg/hardware_info/qualcomm/qualcomm_test.go +++ b/pkg/hardware_info/qualcomm/qualcomm_test.go @@ -39,28 +39,28 @@ func TestInfo(t *testing.T) { { Type: "NPU - adsp-secure", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: filepath.Join(tmp, "fastrpc-adsp-secure"), - SoC: "dragonwing", + VendorName: "dragonwing", }, }, { Type: "NPU - cdsp", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: filepath.Join(tmp, "fastrpc-cdsp"), - SoC: "dragonwing", + VendorName: "dragonwing", }, }, { Type: "NPU - cdsp1", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: filepath.Join(tmp, "fastrpc-cdsp1"), - SoC: "dragonwing", + VendorName: "dragonwing", }, }, } @@ -83,28 +83,28 @@ func TestDetectFromNodes(t *testing.T) { { Type: "NPU - adsp", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: "/dev/fastrpc-adsp", - SoC: "dragonwing", + VendorName: "dragonwing", }, }, { Type: "NPU - cdsp", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: "/dev/fastrpc-cdsp", - SoC: "dragonwing", + VendorName: "dragonwing", }, }, { Type: "NPU - cdsp1-secure", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: "/dev/fastrpc-cdsp1-secure", - SoC: "dragonwing", + VendorName: "dragonwing", }, }, } diff --git a/pkg/selector/fastrpc/fastrpc.go b/pkg/selector/fastrpc/fastrpc.go index a75e357b..dafe7c0a 100644 --- a/pkg/selector/fastrpc/fastrpc.go +++ b/pkg/selector/fastrpc/fastrpc.go @@ -27,11 +27,11 @@ func Match(manifestDevice engines.Device, hostDevices []types.DetectedDevice) (i if !deviceTypeMatch(manifestDevice.Type, hostDevice.Type) { continue } - if hostDevice.PlatformInfo == nil || hostDevice.PlatformInfo.Name == "" { + if hostDevice.Metadata == nil || hostDevice.Metadata.Name == "" { continue } - matched, err := filepath.Match(nodeGlob, hostDevice.PlatformInfo.Name) + matched, err := filepath.Match(nodeGlob, hostDevice.Metadata.Name) if err != nil { return 0, []string{fmt.Sprintf("invalid node-glob %q: %v", nodeGlob, err)} } diff --git a/pkg/selector/fastrpc/fastrpc_test.go b/pkg/selector/fastrpc/fastrpc_test.go index 1c94b53e..4dd9871d 100644 --- a/pkg/selector/fastrpc/fastrpc_test.go +++ b/pkg/selector/fastrpc/fastrpc_test.go @@ -12,19 +12,19 @@ func TestMatch(t *testing.T) { { Type: "npu", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: "/dev/fastrpc-cdsp", - SoC: "dragonwing", + VendorName: "dragonwing", }, }, { Type: "npu", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: "/dev/fastrpc-cdsp1-secure", - SoC: "dragonwing", + VendorName: "dragonwing", }, }, } diff --git a/pkg/selector/fastrpc_test.go b/pkg/selector/fastrpc_test.go index e753160a..a25d113f 100644 --- a/pkg/selector/fastrpc_test.go +++ b/pkg/selector/fastrpc_test.go @@ -19,10 +19,10 @@ func TestFastRpcNpuSelection(t *testing.T) { { Type: "npu", Bus: "fastrpc", - PlatformInfo: &types.PlatformInfo{ + Metadata: &types.DeviceMetadata{ Vendor: "qualcomm", Name: "/dev/fastrpc-cdsp", - SoC: "dragonwing", + VendorName: "dragonwing", }, }, }, diff --git a/pkg/types/detected_devices.go b/pkg/types/detected_devices.go index b657e52f..96b70985 100644 --- a/pkg/types/detected_devices.go +++ b/pkg/types/detected_devices.go @@ -1,13 +1,13 @@ package types -type PlatformInfo struct { - Vendor string `json:"vendor" yaml:"vendor"` - Name string `json:"name" yaml:"name"` - SoC string `json:"soc,omitempty" yaml:"soc,omitempty"` +type DeviceMetadata struct { + Vendor string `json:"vendor" yaml:"vendor"` + Name string `json:"name" yaml:"name"` + VendorName string `json:"vendor_name,omitempty" yaml:"vendor_name,omitempty"` } type DetectedDevice struct { - Type string `json:"type" yaml:"type"` - Bus string `json:"bus" yaml:"bus"` - PlatformInfo *PlatformInfo `json:"platform_info,omitempty" yaml:"platform_info,omitempty"` + Type string `json:"type" yaml:"type"` + Bus string `json:"bus" yaml:"bus"` + Metadata *DeviceMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"` } From 659de4b8d07a1d978ab3195056ae088c8ba66220 Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Mon, 27 Apr 2026 15:39:19 +0200 Subject: [PATCH 4/6] refactor(hw-detection): correct naming and values for DetectedDevice metadata Co-authored-by: Copilot --- pkg/hardware_info/qualcomm/qualcomm.go | 7 +++-- pkg/hardware_info/qualcomm/qualcomm_test.go | 30 +++++++++------------ pkg/selector/fastrpc/fastrpc.go | 4 +-- pkg/selector/fastrpc/fastrpc_test.go | 10 +++---- pkg/selector/fastrpc_test.go | 5 ++-- pkg/types/detected_devices.go | 5 ++-- 6 files changed, 25 insertions(+), 36 deletions(-) diff --git a/pkg/hardware_info/qualcomm/qualcomm.go b/pkg/hardware_info/qualcomm/qualcomm.go index 0923b0cb..08d7bb93 100644 --- a/pkg/hardware_info/qualcomm/qualcomm.go +++ b/pkg/hardware_info/qualcomm/qualcomm.go @@ -38,15 +38,14 @@ func DetectFromNodes(nodes []string) []types.DetectedDevice { Type: detectNpuType(base), Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: node, - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: node, }, }) } sort.Slice(devices, func(i, j int) bool { - return devices[i].Metadata.Name < devices[j].Metadata.Name + return devices[i].Metadata.ProductName < devices[j].Metadata.ProductName }) return devices diff --git a/pkg/hardware_info/qualcomm/qualcomm_test.go b/pkg/hardware_info/qualcomm/qualcomm_test.go index 7bdc8a9f..d189e526 100644 --- a/pkg/hardware_info/qualcomm/qualcomm_test.go +++ b/pkg/hardware_info/qualcomm/qualcomm_test.go @@ -40,27 +40,24 @@ func TestInfo(t *testing.T) { Type: "NPU - adsp-secure", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: filepath.Join(tmp, "fastrpc-adsp-secure"), - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: filepath.Join(tmp, "fastrpc-adsp-secure"), }, }, { Type: "NPU - cdsp", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: filepath.Join(tmp, "fastrpc-cdsp"), - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: filepath.Join(tmp, "fastrpc-cdsp"), }, }, { Type: "NPU - cdsp1", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: filepath.Join(tmp, "fastrpc-cdsp1"), - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: filepath.Join(tmp, "fastrpc-cdsp1"), }, }, } @@ -84,27 +81,24 @@ func TestDetectFromNodes(t *testing.T) { Type: "NPU - adsp", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: "/dev/fastrpc-adsp", - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-adsp", }, }, { Type: "NPU - cdsp", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: "/dev/fastrpc-cdsp", - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp", }, }, { Type: "NPU - cdsp1-secure", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: "/dev/fastrpc-cdsp1-secure", - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp1-secure", }, }, } diff --git a/pkg/selector/fastrpc/fastrpc.go b/pkg/selector/fastrpc/fastrpc.go index dafe7c0a..ba0111be 100644 --- a/pkg/selector/fastrpc/fastrpc.go +++ b/pkg/selector/fastrpc/fastrpc.go @@ -27,11 +27,11 @@ func Match(manifestDevice engines.Device, hostDevices []types.DetectedDevice) (i if !deviceTypeMatch(manifestDevice.Type, hostDevice.Type) { continue } - if hostDevice.Metadata == nil || hostDevice.Metadata.Name == "" { + if hostDevice.Metadata == nil || hostDevice.Metadata.ProductName == "" { continue } - matched, err := filepath.Match(nodeGlob, hostDevice.Metadata.Name) + matched, err := filepath.Match(nodeGlob, hostDevice.Metadata.ProductName) if err != nil { return 0, []string{fmt.Sprintf("invalid node-glob %q: %v", nodeGlob, err)} } diff --git a/pkg/selector/fastrpc/fastrpc_test.go b/pkg/selector/fastrpc/fastrpc_test.go index 4dd9871d..1c849e1d 100644 --- a/pkg/selector/fastrpc/fastrpc_test.go +++ b/pkg/selector/fastrpc/fastrpc_test.go @@ -13,18 +13,16 @@ func TestMatch(t *testing.T) { Type: "npu", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: "/dev/fastrpc-cdsp", - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp", }, }, { Type: "npu", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: "/dev/fastrpc-cdsp1-secure", - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp1-secure", }, }, } diff --git a/pkg/selector/fastrpc_test.go b/pkg/selector/fastrpc_test.go index a25d113f..7f5796c7 100644 --- a/pkg/selector/fastrpc_test.go +++ b/pkg/selector/fastrpc_test.go @@ -20,9 +20,8 @@ func TestFastRpcNpuSelection(t *testing.T) { Type: "npu", Bus: "fastrpc", Metadata: &types.DeviceMetadata{ - Vendor: "qualcomm", - Name: "/dev/fastrpc-cdsp", - VendorName: "dragonwing", + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp", }, }, }, diff --git a/pkg/types/detected_devices.go b/pkg/types/detected_devices.go index 96b70985..6595cadc 100644 --- a/pkg/types/detected_devices.go +++ b/pkg/types/detected_devices.go @@ -1,9 +1,8 @@ package types type DeviceMetadata struct { - Vendor string `json:"vendor" yaml:"vendor"` - Name string `json:"name" yaml:"name"` - VendorName string `json:"vendor_name,omitempty" yaml:"vendor_name,omitempty"` + VendorName string `json:"vendor_name" yaml:"vendor_name"` + ProductName string `json:"product_name,omitempty" yaml:"product_name,omitempty"` } type DetectedDevice struct { From 850f228a7d5e17337c4368899caeb87f814dbc8c Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Fri, 24 Apr 2026 10:07:09 +0200 Subject: [PATCH 5/6] feat(hw-detection): added support for mediatek npu --- pkg/hardware_info/hardware-info.go | 24 ++- pkg/hardware_info/mediatek/mediatek.go | 141 ++++++++++++ pkg/hardware_info/mediatek/mediatek_test.go | 115 ++++++++++ .../testflinger/qualcomm-dragonwing-npu.yaml | 202 ++++++++++++++++++ 4 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 pkg/hardware_info/mediatek/mediatek.go create mode 100644 pkg/hardware_info/mediatek/mediatek_test.go create mode 100644 test_data/testflinger/qualcomm-dragonwing-npu.yaml diff --git a/pkg/hardware_info/hardware-info.go b/pkg/hardware_info/hardware-info.go index 7db51d09..85bc8744 100644 --- a/pkg/hardware_info/hardware-info.go +++ b/pkg/hardware_info/hardware-info.go @@ -9,6 +9,7 @@ import ( "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" @@ -42,12 +43,18 @@ func Get(friendlyNames bool) (*types.HwInfo, error) { } hwInfo.PciDevices = pciDevices - devices, err := qualcomm.Info() + QCDevices, err := qualcomm.Info() if err != nil { return nil, fmt.Errorf("getting qualcomm devices: %v", err) } - hwInfo.Devices = devices + hwInfo.Devices = QCDevices + MediatekDevices, err = mediatek.Info() + if err != nil { + return nil, fmt.Errorf("getting mediatek devices: %v", err) + } + hwInfo.Devices = append(hwInfo.Devices, MediatekDevices...) + return &hwInfo, nil } @@ -119,6 +126,19 @@ func GetFromRawData(t *testing.T, device string, friendlyNames bool, testDir str 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) diff --git a/pkg/hardware_info/mediatek/mediatek.go b/pkg/hardware_info/mediatek/mediatek.go new file mode 100644 index 00000000..a5fd905d --- /dev/null +++ b/pkg/hardware_info/mediatek/mediatek.go @@ -0,0 +1,141 @@ +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.PlatformInfo, []types.DetectedDevice, error) { + compatibleValues, err := readCompatibleValues(deviceTreeRoot) + if err != nil { + return nil, nil, fmt.Errorf("detecting mediatek apusys: %v", err) + } + + platforms, devices := DetectFromCompatibles(compatibleValues) + return platforms, devices, nil +} + +func DetectFromCompatibles(compatibleValues []string) ([]types.PlatformInfo, []types.DetectedDevice) { + platSet := make(map[string]struct{}) + matchSet := 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{}{} + matchSet[value] = struct{}{} + } + + if len(matchSet) == 0 { + return nil, nil + } + + platformNames := make([]string, 0, len(platSet)) + for name := range platSet { + platformNames = append(platformNames, name) + } + sort.Strings(platformNames) + + platforms := make([]types.PlatformInfo, 0, len(platformNames)) + for _, name := range platformNames { + platforms = append(platforms, types.PlatformInfo{ + Vendor: "mediatek", + Name: name, + }) + } + + matches := make([]string, 0, len(matchSet)) + for value := range matchSet { + matches = append(matches, value) + } + sort.Strings(matches) + + devices := []types.DetectedDevice{{ + Type: "npu", + Bus: "mdla", + Nodes: matches, + }} + + return platforms, 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 +} diff --git a/pkg/hardware_info/mediatek/mediatek_test.go b/pkg/hardware_info/mediatek/mediatek_test.go new file mode 100644 index 00000000..afdbd568 --- /dev/null +++ b/pkg/hardware_info/mediatek/mediatek_test.go @@ -0,0 +1,115 @@ +package mediatek + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +func TestDetectFromCompatibles(t *testing.T) { + platforms, devices := DetectFromCompatibles([]string{ + "arm,v8", + "mediatek,mt8195-apusys_rv", + "mediatek,mt8188-apusys_rv", + "mediatek,mt8189-apusys_rv", + "mediatek,mt9999-apusys_rv", + "mediatek,mt8188-apusys_rv", // duplicate should be deduped + }) + + expectedPlatforms := []types.PlatformInfo{ + {Vendor: "mediatek", Name: "genio-1200"}, + {Vendor: "mediatek", Name: "genio-510-700"}, + {Vendor: "mediatek", Name: "genio-520-720"}, + {Vendor: "mediatek", Name: "mediatek-mt9999"}, + } + if !reflect.DeepEqual(platforms, expectedPlatforms) { + t.Fatalf("platforms=%+v expected=%+v", platforms, expectedPlatforms) + } + + expectedDevices := []types.DetectedDevice{{ + Type: "npu", + Bus: "mdla", + Nodes: []string{ + "mediatek,mt8188-apusys_rv", + "mediatek,mt8189-apusys_rv", + "mediatek,mt8195-apusys_rv", + "mediatek,mt9999-apusys_rv", + }, + }} + if !reflect.DeepEqual(devices, expectedDevices) { + t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) + } +} + +func TestInfo(t *testing.T) { + tmp := t.TempDir() + originalRoot := deviceTreeRoot + deviceTreeRoot = tmp + t.Cleanup(func() { + deviceTreeRoot = originalRoot + }) + + goodDir := filepath.Join(tmp, "soc", "apu") + if err := os.MkdirAll(goodDir, 0o755); err != nil { + t.Fatal(err) + } + + // Device-tree compatible entries are NUL-separated. + if err := os.WriteFile(filepath.Join(goodDir, "compatible"), []byte("arm,v8\x00mediatek,mt8188-apusys_rv\x00"), 0o644); err != nil { + t.Fatal(err) + } + + otherDir := filepath.Join(tmp, "soc", "cpu") + if err := os.MkdirAll(otherDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(otherDir, "compatible"), []byte("arm,cortex-a76\x00"), 0o644); err != nil { + t.Fatal(err) + } + + platforms, devices, err := Info() + if err != nil { + t.Fatal(err) + } + + expectedPlatforms := []types.PlatformInfo{{Vendor: "mediatek", Name: "genio-510-700"}} + if !reflect.DeepEqual(platforms, expectedPlatforms) { + t.Fatalf("platforms=%+v expected=%+v", platforms, expectedPlatforms) + } + + expectedDevices := []types.DetectedDevice{{ + Type: "npu", + Bus: "mdla", + Nodes: []string{"mediatek,mt8188-apusys_rv"}, + }} + if !reflect.DeepEqual(devices, expectedDevices) { + t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) + } +} + +func TestInfoNoMatch(t *testing.T) { + tmp := t.TempDir() + originalRoot := deviceTreeRoot + deviceTreeRoot = tmp + t.Cleanup(func() { + deviceTreeRoot = originalRoot + }) + + if err := os.MkdirAll(filepath.Join(tmp, "soc"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(tmp, "soc", "compatible"), []byte("arm,v8\x00"), 0o644); err != nil { + t.Fatal(err) + } + + platforms, devices, err := Info() + if err != nil { + t.Fatal(err) + } + if len(platforms) != 0 || len(devices) != 0 { + t.Fatalf("expected no mediatek npu match, got platforms=%+v devices=%+v", platforms, devices) + } +} \ No newline at end of file diff --git a/test_data/testflinger/qualcomm-dragonwing-npu.yaml b/test_data/testflinger/qualcomm-dragonwing-npu.yaml new file mode 100644 index 00000000..46eb367e --- /dev/null +++ b/test_data/testflinger/qualcomm-dragonwing-npu.yaml @@ -0,0 +1,202 @@ +job_queue: qualcomm-rb4-qcs8275-c38153 + +provision_data: + skip: true + +test_data: + test_cmds: | + set -euxo pipefail + + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update + sudo apt-get install -y git jq curl ca-certificates + + WORKDIR="$HOME/inference-snaps-cli" + rm -rf "$WORKDIR" + git clone https://github.com/canonical/inference-snaps-cli.git "$WORKDIR" + cd "$WORKDIR" + + # Optional: apply your unpushed local changes as a base64-encoded git patch. + # Replace __LOCAL_PATCH_B64__ with: git diff --binary | base64 -w0 + LOCAL_PATCH_B64='ZGlmZiAtLWdpdCBhL3BrZy9lbmdpbmVzL2J1c3Nlcy5nbyBiL3BrZy9lbmdpbmVzL2J1c3Nlcy5nbwppbmRleCAxZjJiMjg2Li40NmY3OWM4IDEwMDY0NAotLS0gYS9wa2cvZW5naW5lcy9idXNzZXMuZ28KKysrIGIvcGtnL2VuZ2luZXMvYnVzc2VzLmdvCkBAIC0xMiw2ICsxMiw4IEBAIGZ1bmMgKGRldmljZSBEZXZpY2UpIHZhbGlkYXRlQnVzKGV4dHJhRmllbGRzIFtdc3RyaW5nKSBlcnJvciB7CiAJCXJldHVybiBkZXZpY2UudmFsaWRhdGVQY2koZXh0cmFGaWVsZHMpCiAJY2FzZSAidXNiIjoKIAkJcmV0dXJuIGRldmljZS52YWxpZGF0ZVVzYihleHRyYUZpZWxkcykKKwljYXNlICJmYXN0cnBjIjoKKwkJcmV0dXJuIGRldmljZS52YWxpZGF0ZUZhc3RScGMoZXh0cmFGaWVsZHMpCiAJY2FzZSAiIjogLy8gZGVmYXVsdCB0byBwY2kgYnVzCiAJCXJldHVybiBkZXZpY2UudmFsaWRhdGVQY2koZXh0cmFGaWVsZHMpCiAJZGVmYXVsdDoKQEAgLTQ5LDMgKzUxLDMyIEBAIGZ1bmMgKGRldmljZSBEZXZpY2UpIHZhbGlkYXRlUGNpKGV4dHJhRmllbGRzIFtdc3RyaW5nKSBlcnJvciB7CiAKIAlyZXR1cm4gbmlsCiB9CisKK2Z1bmMgKGRldmljZSBEZXZpY2UpIHZhbGlkYXRlRmFzdFJwYyhleHRyYUZpZWxkcyBbXXN0cmluZykgZXJyb3IgeworCWlmIGRldmljZS5UeXBlICE9ICIiICYmIGRldmljZS5UeXBlICE9ICJucHUiIHsKKwkJcmV0dXJuIGZtdC5FcnJvcmYoImZhc3RycGMgYnVzIG9ubHkgc3VwcG9ydHMgbnB1IGRldmljZXMiKQorCX0KKworCXZhbGlkRmllbGRzIDo9IFtdc3RyaW5neworCQkiVHlwZSIsCisJCSJCdXMiLAorCQkiTm9kZUdsb2IiLAorCQkiU25hcENvbm5lY3Rpb25zIiwKKwl9CisJdmFsaWRGaWVsZHMgPSBhcHBlbmQodmFsaWRGaWVsZHMsIGV4dHJhRmllbGRzLi4uKQorCisJdCA6PSByZWZsZWN0LlR5cGVPZihkZXZpY2UpCisJdiA6PSByZWZsZWN0LlZhbHVlT2YoZGV2aWNlKQorCisJZm9yIGkgOj0gMDsgaSA8IHQuTnVtRmllbGQoKTsgaSsrIHsKKwkJZmllbGROYW1lIDo9IHQuRmllbGQoaSkuTmFtZQorCQlmaWVsZFZhbHVlIDo9IHYuRmllbGRCeU5hbWUoZmllbGROYW1lKQorCQlpZiBmaWVsZFZhbHVlLklzVmFsaWQoKSAmJiAhZmllbGRWYWx1ZS5Jc1plcm8oKSB7CisJCQlpZiAhc2xpY2VzLkNvbnRhaW5zKHZhbGlkRmllbGRzLCBmaWVsZE5hbWUpIHsKKwkJCQlyZXR1cm4gZm10LkVycm9yZigiZmFzdHJwYzogaW52YWxpZCBmaWVsZDogJXMiLCBmaWVsZE5hbWUpCisJCQl9CisJCX0KKwl9CisKKwlyZXR1cm4gbmlsCit9CmRpZmYgLS1naXQgYS9wa2cvZW5naW5lcy9idXNzZXNfdGVzdC5nbyBiL3BrZy9lbmdpbmVzL2J1c3Nlc190ZXN0LmdvCmluZGV4IGE4YWJkNjguLjYxNTVhYTkgMTAwNjQ0Ci0tLSBhL3BrZy9lbmdpbmVzL2J1c3Nlc190ZXN0LmdvCisrKyBiL3BrZy9lbmdpbmVzL2J1c3Nlc190ZXN0LmdvCkBAIC00Miw0ICs0MiwxMyBAQCBmdW5jIFRlc3REZXZpY2VCdXModCAqdGVzdGluZy5UKSB7CiAJCX0KIAkJdC5Mb2coZXJyKQogCX0pCisKKwl0LlJ1bigiRmFzdFJQQyBidXMgd2l0aCBub24tTlBVIHR5cGUiLCBmdW5jKHQgKnRlc3RpbmcuVCkgeworCQlkZXZpY2UuQnVzID0gImZhc3RycGMiCisJCWVyciA6PSBkZXZpY2UudmFsaWRhdGUoKQorCQlpZiBlcnIgPT0gbmlsIHsKKwkJCXQuRmF0YWwoIkZhc3RSUEMgYnVzIHNob3VsZCBiZSBpbnZhbGlkIGZvciBHUFUgdHlwZSIpCisJCX0KKwkJdC5Mb2coZXJyKQorCX0pCiB9CmRpZmYgLS1naXQgYS9wa2cvZW5naW5lcy9kZXZpY2VzX3Rlc3QuZ28gYi9wa2cvZW5naW5lcy9kZXZpY2VzX3Rlc3QuZ28KaW5kZXggMTY4YjM1Ni4uNTUzMTAzNiAxMDA2NDQKLS0tIGEvcGtnL2VuZ2luZXMvZGV2aWNlc190ZXN0LmdvCisrKyBiL3BrZy9lbmdpbmVzL2RldmljZXNfdGVzdC5nbwpAQCAtMTMwLDYgKzEzMCwyOSBAQCBmdW5jIFRlc3REZXZpY2VOcHUodCAqdGVzdGluZy5UKSB7CiAJCX0KIAkJdC5Mb2coZXJyKQogCX0pCisKKwl0LlJ1bigiTlBVIGZhc3RycGMgdmFsaWQgZmllbGRzIiwgZnVuYyh0ICp0ZXN0aW5nLlQpIHsKKwkJZGV2aWNlID0gRGV2aWNle1R5cGU6ICJucHUiLCBCdXM6ICJmYXN0cnBjIn0KKwkJbm9kZUdsb2IgOj0gIi9kZXYvZmFzdHJwYy1jZHNwKiIKKwkJZGV2aWNlLk5vZGVHbG9iID0gJm5vZGVHbG9iCisKKwkJZXJyIDo9IGRldmljZS52YWxpZGF0ZSgpCisJCWlmIGVyciAhPSBuaWwgeworCQkJdC5GYXRhbGYoIk5QVSBmYXN0cnBjIGZpZWxkcyBzaG91bGQgYmUgdmFsaWQ6ICV2IiwgZXJyKQorCQl9CisJfSkKKworCXQuUnVuKCJOUFUgZmFzdHJwYyBpbnZhbGlkIGZpZWxkcyIsIGZ1bmModCAqdGVzdGluZy5UKSB7CisJCWRldmljZSA9IERldmljZXtUeXBlOiAibnB1IiwgQnVzOiAiZmFzdHJwYyJ9CisJCWhleFZhbHVlIDo9IHR5cGVzLkhleEludCgweEFBKQorCQlkZXZpY2UuVmVuZG9ySWQgPSAmaGV4VmFsdWUKKworCQllcnIgOj0gZGV2aWNlLnZhbGlkYXRlKCkKKwkJaWYgZXJyID09IG5pbCB7CisJCQl0LkZhdGFsKCJOUFUgZmFzdHJwYyBmaWVsZHMgc2hvdWxkIGJlIGludmFsaWQiKQorCQl9CisJCXQuTG9nKGVycikKKwl9KQogfQogCiBmdW5jIFRlc3REZXZpY2VUeXBlbGVzcyh0ICp0ZXN0aW5nLlQpIHsKZGlmZiAtLWdpdCBhL3BrZy9lbmdpbmVzL3R5cGVzLmdvIGIvcGtnL2VuZ2luZXMvdHlwZXMuZ28KaW5kZXggODAzYzEyMC4uMDVjN2RhMSAxMDA2NDQKLS0tIGEvcGtnL2VuZ2luZXMvdHlwZXMuZ28KKysrIGIvcGtnL2VuZ2luZXMvdHlwZXMuZ28KQEAgLTQ1LDcgKzQ1LDcgQEAgdHlwZSBEZXZpY2VzIHN0cnVjdCB7CiB0eXBlIERldmljZSBzdHJ1Y3QgewogCS8vIEdlbmVyYWwKIAlUeXBlIHN0cmluZyBgeWFtbDoidHlwZSxvbWl0ZW1wdHkiIGpzb246InR5cGUsb21pdGVtcHR5ImAgLy8gY3B1LCBncHUsIG5wdSBvciBuaWwKLQlCdXMgIHN0cmluZyBgeWFtbDoiYnVzLG9taXRlbXB0eSIganNvbjoiYnVzLG9taXRlbXB0eSJgICAgLy8gcGNpLCB1c2Igb3IgbmlsCisJQnVzICBzdHJpbmcgYHlhbWw6ImJ1cyxvbWl0ZW1wdHkiIGpzb246ImJ1cyxvbWl0ZW1wdHkiYCAgIC8vIHBjaSwgdXNiLCBmYXN0cnBjIG9yIG5pbAogCiAJLy8gQ1BVcwogCUFyY2hpdGVjdHVyZSAqc3RyaW5nIGB5YW1sOiJhcmNoaXRlY3R1cmUsb21pdGVtcHR5IiBqc29uOiJhcmNoaXRlY3R1cmUsb21pdGVtcHR5ImAKQEAgLTY5LDcgKzY5LDcgQEAgdHlwZSBEZXZpY2Ugc3RydWN0IHsKIAlDb21wdXRlQ2FwYWJpbGl0eSAqc3RyaW5nIGB5YW1sOiJjb21wdXRlLWNhcGFiaWxpdHksb21pdGVtcHR5IiBqc29uOiJjb21wdXRlLWNhcGFiaWxpdHksb21pdGVtcHR5ImAKIAogCS8vIE5QVQotCS8vIG5vIGFkZGl0aW9uYWwgcHJvcGVydGllcyBmb3Igbm93CisJTm9kZUdsb2IgKnN0cmluZyBgeWFtbDoibm9kZS1nbG9iLG9taXRlbXB0eSIganNvbjoibm9kZS1nbG9iLG9taXRlbXB0eSJgCiAKIAkvLyBEcml2ZXJzCiAJU25hcENvbm5lY3Rpb25zIFtdc3RyaW5nIGB5YW1sOiJzbmFwLWNvbm5lY3Rpb25zLG9taXRlbXB0eSIganNvbjoic25hcC1jb25uZWN0aW9ucyxvbWl0ZW1wdHkiYApkaWZmIC0tZ2l0IGEvcGtnL2hhcmR3YXJlX2luZm8vZGlzay9kaXNrX2luZm8uZ28gYi9wa2cvaGFyZHdhcmVfaW5mby9kaXNrL2Rpc2tfaW5mby5nbwppbmRleCAxNzgxYmUyLi43ODU4ZWZkIDEwMDY0NAotLS0gYS9wa2cvaGFyZHdhcmVfaW5mby9kaXNrL2Rpc2tfaW5mby5nbworKysgYi9wa2cvaGFyZHdhcmVfaW5mby9kaXNrL2Rpc2tfaW5mby5nbwpAQCAtMiw2ICsyLDcgQEAgcGFja2FnZSBkaXNrCiAKIGltcG9ydCAoCiAJImZtdCIKKwkib3MiCiAKIAkiZ2l0aHViLmNvbS9jYW5vbmljYWwvaW5mZXJlbmNlLXNuYXBzLWNsaS9wa2cvY29uc3RhbnRzIgogCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy90eXBlcyIKQEAgLTE2LDYgKzE3LDE0IEBAIGZ1bmMgSW5mbygpIChtYXBbc3RyaW5nXXR5cGVzLkRpclN0YXRzLCBlcnJvcikgewogCXZhciBpbmZvID0gbWFrZShtYXBbc3RyaW5nXXR5cGVzLkRpclN0YXRzKQogCiAJZm9yIF8sIGRpciA6PSByYW5nZSBkaXJlY3RvcmllcyB7CisJCS8vIFNraXAgZGlyZWN0b3JpZXMgdGhhdCBkb24ndCBleGlzdAorCQlpZiBfLCBlcnIgOj0gb3MuU3RhdChkaXIpOyBlcnIgIT0gbmlsIHsKKwkJCWlmIG9zLklzTm90RXhpc3QoZXJyKSB7CisJCQkJY29udGludWUKKwkJCX0KKwkJCXJldHVybiBuaWwsIGZtdC5FcnJvcmYoImNoZWNraW5nIGRpcmVjdG9yeSAlczogJXYiLCBkaXIsIGVycikKKwkJfQorCQkKIAkJZGlySW5mbywgZXJyIDo9IHN0YXRGcyhkaXIpCiAJCWlmIGVyciAhPSBuaWwgewogCQkJcmV0dXJuIG5pbCwgZm10LkVycm9yZigiZ2V0dGluZyBkaXJlY3RvcnkgaW5mbyBmb3IgJXM6ICV2IiwgZGlyLCBlcnIpCmRpZmYgLS1naXQgYS9wa2cvaGFyZHdhcmVfaW5mby9oYXJkd2FyZS1pbmZvLmdvIGIvcGtnL2hhcmR3YXJlX2luZm8vaGFyZHdhcmUtaW5mby5nbwppbmRleCBiNmU5ZDI3Li5hMjhiMDFlIDEwMDY0NAotLS0gYS9wa2cvaGFyZHdhcmVfaW5mby9oYXJkd2FyZS1pbmZvLmdvCisrKyBiL3BrZy9oYXJkd2FyZV9pbmZvL2hhcmR3YXJlLWluZm8uZ28KQEAgLTQsMTIgKzQsMTQgQEAgaW1wb3J0ICgKIAkiZW5jb2RpbmcvanNvbiIKIAkiZm10IgogCSJvcyIKKwkic3RyaW5ncyIKIAkidGVzdGluZyIKIAogCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy9oYXJkd2FyZV9pbmZvL2NwdSIKIAkiZ2l0aHViLmNvbS9jYW5vbmljYWwvaW5mZXJlbmNlLXNuYXBzLWNsaS9wa2cvaGFyZHdhcmVfaW5mby9kaXNrIgogCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy9oYXJkd2FyZV9pbmZvL21lbW9yeSIKIAkiZ2l0aHViLmNvbS9jYW5vbmljYWwvaW5mZXJlbmNlLXNuYXBzLWNsaS9wa2cvaGFyZHdhcmVfaW5mby9wY2kiCisJImdpdGh1Yi5jb20vY2Fub25pY2FsL2luZmVyZW5jZS1zbmFwcy1jbGkvcGtnL2hhcmR3YXJlX2luZm8vcXVhbGNvbW0iCiAJImdpdGh1Yi5jb20vY2Fub25pY2FsL2luZmVyZW5jZS1zbmFwcy1jbGkvcGtnL3R5cGVzIgogKQogCkBAIC00MCw2ICs0MiwxMyBAQCBmdW5jIEdldChmcmllbmRseU5hbWVzIGJvb2wpICgqdHlwZXMuSHdJbmZvLCBlcnJvcikgewogCX0KIAlod0luZm8uUGNpRGV2aWNlcyA9IHBjaURldmljZXMKIAorCXBsYXRmb3JtcywgZGV2aWNlcywgZXJyIDo9IHF1YWxjb21tLkluZm8oKQorCWlmIGVyciAhPSBuaWwgeworCQlyZXR1cm4gbmlsLCBmbXQuRXJyb3JmKCJnZXR0aW5nIHF1YWxjb21tIGRldmljZXM6ICV2IiwgZXJyKQorCX0KKwlod0luZm8uUGxhdGZvcm1zID0gcGxhdGZvcm1zCisJaHdJbmZvLkRldmljZXMgPSBkZXZpY2VzCisKIAlyZXR1cm4gJmh3SW5mbywgbmlsCiB9CiAKQEAgLTk3LDYgKzEwNiwyMSBAQCBmdW5jIEdldEZyb21SYXdEYXRhKHQgKnRlc3RpbmcuVCwgZGV2aWNlIHN0cmluZywgZnJpZW5kbHlOYW1lcyBib29sLCB0ZXN0RGlyIHN0cgogCX0KIAlod0luZm8uUGNpRGV2aWNlcyA9IHBjaURldmljZXMKIAorCWZhc3RycGNOb2Rlc0ZpbGUgOj0gZGV2aWNlUGF0aCArICJmYXN0cnBjLW5vZGVzLnR4dCIKKwlpZiBfLCBlcnIgOj0gb3MuU3RhdChmYXN0cnBjTm9kZXNGaWxlKTsgZXJyID09IG5pbCB7CisJCW5vZGVzRGF0YSwgZXJyIDo9IG9zLlJlYWRGaWxlKGZhc3RycGNOb2Rlc0ZpbGUpCisJCWlmIGVyciAhPSBuaWwgeworCQkJdC5GYXRhbChlcnIpCisJCX0KKworCQlub2RlcyA6PSBzdHJpbmdzLkZpZWxkcyhzdHJpbmcobm9kZXNEYXRhKSkKKwkJcGxhdGZvcm1zLCBkZXZpY2VzIDo9IHF1YWxjb21tLkRldGVjdEZyb21Ob2Rlcyhub2RlcykKKwkJaHdJbmZvLlBsYXRmb3JtcyA9IHBsYXRmb3JtcworCQlod0luZm8uRGV2aWNlcyA9IGRldmljZXMKKwl9IGVsc2UgaWYgIW9zLklzTm90RXhpc3QoZXJyKSB7CisJCXQuRmF0YWxmKCJlcnJvciBjaGVja2luZyBmaWxlICclcyc6ICV2XG4iLCBmYXN0cnBjTm9kZXNGaWxlLCBlcnIpCisJfQorCiAJLy8gQWRkaXRpb25hbCBwcm9wZXJ0aWVzIC0gd2UgYXBwZW5kIHRoZXNlIGRpcmVjdGx5IGZyb20gYSBmaWxlLCBhcyB3ZSBjYW4gbm90IHJ1biB0aGUgdmVuZG9yIHNwZWNpZmljIHRvb2xzIG9uIHRoZSBtYWNoaW5lCiAJYWRkUHJvcHNGaWxlIDo9IGRldmljZVBhdGggKyAiYWRkaXRpb25hbC1wcm9wZXJ0aWVzLmpzb24iCiAJXywgZXJyID0gb3MuU3RhdChhZGRQcm9wc0ZpbGUpCmRpZmYgLS1naXQgYS9wa2cvaGFyZHdhcmVfaW5mby9xdWFsY29tbS9xdWFsY29tbS5nbyBiL3BrZy9oYXJkd2FyZV9pbmZvL3F1YWxjb21tL3F1YWxjb21tLmdvCm5ldyBmaWxlIG1vZGUgMTAwNjQ0CmluZGV4IDAwMDAwMDAuLjI3Y2EyNzQKLS0tIC9kZXYvbnVsbAorKysgYi9wa2cvaGFyZHdhcmVfaW5mby9xdWFsY29tbS9xdWFsY29tbS5nbwpAQCAtMCwwICsxLDk0IEBACitwYWNrYWdlIHF1YWxjb21tCisKK2ltcG9ydCAoCisJImZtdCIKKwkicGF0aC9maWxlcGF0aCIKKwkic29ydCIKKwkic3RyaW5ncyIKKworCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy90eXBlcyIKKykKKwordmFyIGRldlJvb3QgPSAiL2RldiIKKworZnVuYyBJbmZvKCkgKFtddHlwZXMuUGxhdGZvcm1JbmZvLCBbXXR5cGVzLkRldGVjdGVkRGV2aWNlLCBlcnJvcikgeworCWZhc3RycGNOb2RlcywgZXJyIDo9IGdsb2JTb3J0ZWQoZmlsZXBhdGguSm9pbihkZXZSb290LCAiZmFzdHJwYy0qIikpCisJaWYgZXJyICE9IG5pbCB7CisJCXJldHVybiBuaWwsIG5pbCwgZm10LkVycm9yZigiZGV0ZWN0aW5nIGZhc3RycGMgbm9kZXM6ICV2IiwgZXJyKQorCX0KKworCWNkc3BOb2RlcywgZXJyIDo9IGdsb2JTb3J0ZWQoZmlsZXBhdGguSm9pbihkZXZSb290LCAiZmFzdHJwYy1jZHNwKiIpKQorCWlmIGVyciAhPSBuaWwgeworCQlyZXR1cm4gbmlsLCBuaWwsIGZtdC5FcnJvcmYoImRldGVjdGluZyBjZHNwIGZhc3RycGMgbm9kZXM6ICV2IiwgZXJyKQorCX0KKworCXZhciBwbGF0Zm9ybXMgW110eXBlcy5QbGF0Zm9ybUluZm8KKwlpZiBsZW4oZmFzdHJwY05vZGVzKSA+IDAgeworCQlwbGF0Zm9ybXMgPSBhcHBlbmQocGxhdGZvcm1zLCB0eXBlcy5QbGF0Zm9ybUluZm97CisJCQlWZW5kb3I6ICJxdWFsY29tbSIsCisJCQlOYW1lOiAgICJkcmFnb253aW5nIiwKKwkJfSkKKwl9CisKKwl2YXIgZGV2aWNlcyBbXXR5cGVzLkRldGVjdGVkRGV2aWNlCisJaWYgbGVuKGNkc3BOb2RlcykgPiAwIHsKKwkJZGV2aWNlcyA9IGFwcGVuZChkZXZpY2VzLCB0eXBlcy5EZXRlY3RlZERldmljZXsKKwkJCVR5cGU6ICAibnB1IiwKKwkJCUJ1czogICAiZmFzdHJwYyIsCisJCQlOb2RlczogY2RzcE5vZGVzLAorCQl9KQorCX0KKworCXJldHVybiBwbGF0Zm9ybXMsIGRldmljZXMsIG5pbAorfQorCitmdW5jIERldGVjdEZyb21Ob2Rlcyhub2RlcyBbXXN0cmluZykgKFtddHlwZXMuUGxhdGZvcm1JbmZvLCBbXXR5cGVzLkRldGVjdGVkRGV2aWNlKSB7CisJdmFyIGZhc3RycGNOb2RlcyBbXXN0cmluZworCXZhciBjZHNwTm9kZXMgW11zdHJpbmcKKworCWZvciBfLCBub2RlIDo9IHJhbmdlIG5vZGVzIHsKKwkJbm9kZSA9IHN0cmluZ3MuVHJpbVNwYWNlKG5vZGUpCisJCWlmIG5vZGUgPT0gIiIgeworCQkJY29udGludWUKKwkJfQorCisJCWJhc2UgOj0gZmlsZXBhdGguQmFzZShub2RlKQorCQlpZiBzdHJpbmdzLkhhc1ByZWZpeChiYXNlLCAiZmFzdHJwYy0iKSB7CisJCQlmYXN0cnBjTm9kZXMgPSBhcHBlbmQoZmFzdHJwY05vZGVzLCBub2RlKQorCQl9CisJCWlmIHN0cmluZ3MuSGFzUHJlZml4KGJhc2UsICJmYXN0cnBjLWNkc3AiKSB7CisJCQljZHNwTm9kZXMgPSBhcHBlbmQoY2RzcE5vZGVzLCBub2RlKQorCQl9CisJfQorCisJc29ydC5TdHJpbmdzKGZhc3RycGNOb2RlcykKKwlzb3J0LlN0cmluZ3MoY2RzcE5vZGVzKQorCisJdmFyIHBsYXRmb3JtcyBbXXR5cGVzLlBsYXRmb3JtSW5mbworCWlmIGxlbihmYXN0cnBjTm9kZXMpID4gMCB7CisJCXBsYXRmb3JtcyA9IGFwcGVuZChwbGF0Zm9ybXMsIHR5cGVzLlBsYXRmb3JtSW5mb3sKKwkJCVZlbmRvcjogInF1YWxjb21tIiwKKwkJCU5hbWU6ICAgImRyYWdvbndpbmciLAorCQl9KQorCX0KKworCXZhciBkZXZpY2VzIFtddHlwZXMuRGV0ZWN0ZWREZXZpY2UKKwlpZiBsZW4oY2RzcE5vZGVzKSA+IDAgeworCQlkZXZpY2VzID0gYXBwZW5kKGRldmljZXMsIHR5cGVzLkRldGVjdGVkRGV2aWNleworCQkJVHlwZTogICJucHUiLAorCQkJQnVzOiAgICJmYXN0cnBjIiwKKwkJCU5vZGVzOiBjZHNwTm9kZXMsCisJCX0pCisJfQorCisJcmV0dXJuIHBsYXRmb3JtcywgZGV2aWNlcworfQorCitmdW5jIGdsb2JTb3J0ZWQocGF0dGVybiBzdHJpbmcpIChbXXN0cmluZywgZXJyb3IpIHsKKwltYXRjaGVzLCBlcnIgOj0gZmlsZXBhdGguR2xvYihwYXR0ZXJuKQorCWlmIGVyciAhPSBuaWwgeworCQlyZXR1cm4gbmlsLCBlcnIKKwl9CisJc29ydC5TdHJpbmdzKG1hdGNoZXMpCisJcmV0dXJuIG1hdGNoZXMsIG5pbAorfQpkaWZmIC0tZ2l0IGEvcGtnL2hhcmR3YXJlX2luZm8vcXVhbGNvbW0vcXVhbGNvbW1fdGVzdC5nbyBiL3BrZy9oYXJkd2FyZV9pbmZvL3F1YWxjb21tL3F1YWxjb21tX3Rlc3QuZ28KbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uY2Y3OTE0YwotLS0gL2Rldi9udWxsCisrKyBiL3BrZy9oYXJkd2FyZV9pbmZvL3F1YWxjb21tL3F1YWxjb21tX3Rlc3QuZ28KQEAgLTAsMCArMSw4NiBAQAorcGFja2FnZSBxdWFsY29tbQorCitpbXBvcnQgKAorCSJvcyIKKwkicGF0aC9maWxlcGF0aCIKKwkicmVmbGVjdCIKKwkidGVzdGluZyIKKworCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy90eXBlcyIKKykKKworZnVuYyBUZXN0SW5mbyh0ICp0ZXN0aW5nLlQpIHsKKwl0bXAgOj0gdC5UZW1wRGlyKCkKKwlvcmlnaW5hbFJvb3QgOj0gZGV2Um9vdAorCWRldlJvb3QgPSB0bXAKKwl0LkNsZWFudXAoZnVuYygpIHsKKwkJZGV2Um9vdCA9IG9yaWdpbmFsUm9vdAorCX0pCisKKwlfLCBlcnIgOj0gb3MuQ3JlYXRlKGZpbGVwYXRoLkpvaW4odG1wLCAiZmFzdHJwYy1hZHNwLXNlY3VyZSIpKQorCWlmIGVyciAhPSBuaWwgeworCQl0LkZhdGFsKGVycikKKwl9CisJXywgZXJyID0gb3MuQ3JlYXRlKGZpbGVwYXRoLkpvaW4odG1wLCAiZmFzdHJwYy1jZHNwIikpCisJaWYgZXJyICE9IG5pbCB7CisJCXQuRmF0YWwoZXJyKQorCX0KKwlfLCBlcnIgPSBvcy5DcmVhdGUoZmlsZXBhdGguSm9pbih0bXAsICJmYXN0cnBjLWNkc3AxIikpCisJaWYgZXJyICE9IG5pbCB7CisJCXQuRmF0YWwoZXJyKQorCX0KKworCXBsYXRmb3JtcywgZGV2aWNlcywgZXJyIDo9IEluZm8oKQorCWlmIGVyciAhPSBuaWwgeworCQl0LkZhdGFsKGVycikKKwl9CisKKwlleHBlY3RlZFBsYXRmb3JtcyA6PSBbXXR5cGVzLlBsYXRmb3JtSW5mb3t7CisJCVZlbmRvcjogInF1YWxjb21tIiwKKwkJTmFtZTogICAiZHJhZ29ud2luZyIsCisJfX0KKwlpZiAhcmVmbGVjdC5EZWVwRXF1YWwocGxhdGZvcm1zLCBleHBlY3RlZFBsYXRmb3JtcykgeworCQl0LkZhdGFsZigicGxhdGZvcm1zPSUrdiBleHBlY3RlZD0lK3YiLCBwbGF0Zm9ybXMsIGV4cGVjdGVkUGxhdGZvcm1zKQorCX0KKworCWV4cGVjdGVkRGV2aWNlcyA6PSBbXXR5cGVzLkRldGVjdGVkRGV2aWNle3sKKwkJVHlwZTogIm5wdSIsCisJCUJ1czogICJmYXN0cnBjIiwKKwkJTm9kZXM6IFtdc3RyaW5neworCQkJZmlsZXBhdGguSm9pbih0bXAsICJmYXN0cnBjLWNkc3AiKSwKKwkJCWZpbGVwYXRoLkpvaW4odG1wLCAiZmFzdHJwYy1jZHNwMSIpLAorCQl9LAorCX19CisJaWYgIXJlZmxlY3QuRGVlcEVxdWFsKGRldmljZXMsIGV4cGVjdGVkRGV2aWNlcykgeworCQl0LkZhdGFsZigiZGV2aWNlcz0lK3YgZXhwZWN0ZWQ9JSt2IiwgZGV2aWNlcywgZXhwZWN0ZWREZXZpY2VzKQorCX0KK30KKworZnVuYyBUZXN0RGV0ZWN0RnJvbU5vZGVzKHQgKnRlc3RpbmcuVCkgeworCW5vZGVzIDo9IFtdc3RyaW5neworCQkiL2Rldi9mYXN0cnBjLWNkc3AxLXNlY3VyZSIsCisJCSIvZGV2L2Zhc3RycGMtYWRzcCIsCisJCSIvZGV2L3NvbWV0aGluZy1lbHNlIiwKKwkJIi9kZXYvZmFzdHJwYy1jZHNwIiwKKwl9CisKKwlwbGF0Zm9ybXMsIGRldmljZXMgOj0gRGV0ZWN0RnJvbU5vZGVzKG5vZGVzKQorCWlmIGxlbihwbGF0Zm9ybXMpICE9IDEgeworCQl0LkZhdGFsZigiZXhwZWN0ZWQgMSBwbGF0Zm9ybSwgZ290ICVkIiwgbGVuKHBsYXRmb3JtcykpCisJfQorCWlmIHBsYXRmb3Jtc1swXS5WZW5kb3IgIT0gInF1YWxjb21tIiB8fCBwbGF0Zm9ybXNbMF0uTmFtZSAhPSAiZHJhZ29ud2luZyIgeworCQl0LkZhdGFsZigidW5leHBlY3RlZCBwbGF0Zm9ybSAlK3YiLCBwbGF0Zm9ybXNbMF0pCisJfQorCisJaWYgbGVuKGRldmljZXMpICE9IDEgeworCQl0LkZhdGFsZigiZXhwZWN0ZWQgMSBkZXZpY2UsIGdvdCAlZCIsIGxlbihkZXZpY2VzKSkKKwl9CisJaWYgZGV2aWNlc1swXS5UeXBlICE9ICJucHUiIHx8IGRldmljZXNbMF0uQnVzICE9ICJmYXN0cnBjIiB7CisJCXQuRmF0YWxmKCJ1bmV4cGVjdGVkIGRldmljZSAlK3YiLCBkZXZpY2VzWzBdKQorCX0KKworCWV4cGVjdGVkTm9kZXMgOj0gW11zdHJpbmd7Ii9kZXYvZmFzdHJwYy1jZHNwIiwgIi9kZXYvZmFzdHJwYy1jZHNwMS1zZWN1cmUifQorCWlmICFyZWZsZWN0LkRlZXBFcXVhbChkZXZpY2VzWzBdLk5vZGVzLCBleHBlY3RlZE5vZGVzKSB7CisJCXQuRmF0YWxmKCJub2Rlcz0ldiBleHBlY3RlZD0ldiIsIGRldmljZXNbMF0uTm9kZXMsIGV4cGVjdGVkTm9kZXMpCisJfQorfQpkaWZmIC0tZ2l0IGEvcGtnL3NlbGVjdG9yL2Zhc3RycGMvZmFzdHJwYy5nbyBiL3BrZy9zZWxlY3Rvci9mYXN0cnBjL2Zhc3RycGMuZ28KbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uYzM4MjMzMQotLS0gL2Rldi9udWxsCisrKyBiL3BrZy9zZWxlY3Rvci9mYXN0cnBjL2Zhc3RycGMuZ28KQEAgLTAsMCArMSw1OCBAQAorcGFja2FnZSBmYXN0cnBjCisKK2ltcG9ydCAoCisJImZtdCIKKwkicGF0aC9maWxlcGF0aCIKKwkidGVzdGluZyIKKworCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9nby1zbmFwY3RsIgorCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy9lbmdpbmVzIgorCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy9zZWxlY3Rvci93ZWlnaHRzIgorCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy90eXBlcyIKKykKKworY29uc3QgZGVmYXVsdE5wdU5vZGVHbG9iID0gIi9kZXYvZmFzdHJwYy1jZHNwKiIKKworZnVuYyBNYXRjaChtYW5pZmVzdERldmljZSBlbmdpbmVzLkRldmljZSwgaG9zdERldmljZXMgW110eXBlcy5EZXRlY3RlZERldmljZSkgKGludCwgW11zdHJpbmcpIHsKKwlub2RlR2xvYiA6PSBkZWZhdWx0TnB1Tm9kZUdsb2IKKwlpZiBtYW5pZmVzdERldmljZS5Ob2RlR2xvYiAhPSBuaWwgeworCQlub2RlR2xvYiA9ICptYW5pZmVzdERldmljZS5Ob2RlR2xvYgorCX0KKworCWZvciBfLCBob3N0RGV2aWNlIDo9IHJhbmdlIGhvc3REZXZpY2VzIHsKKwkJaWYgaG9zdERldmljZS5CdXMgIT0gImZhc3RycGMiIHsKKwkJCWNvbnRpbnVlCisJCX0KKwkJaWYgbWFuaWZlc3REZXZpY2UuVHlwZSAhPSAiIiAmJiBob3N0RGV2aWNlLlR5cGUgIT0gbWFuaWZlc3REZXZpY2UuVHlwZSB7CisJCQljb250aW51ZQorCQl9CisKKwkJZm9yIF8sIG5vZGUgOj0gcmFuZ2UgaG9zdERldmljZS5Ob2RlcyB7CisJCQltYXRjaGVkLCBlcnIgOj0gZmlsZXBhdGguTWF0Y2gobm9kZUdsb2IsIG5vZGUpCisJCQlpZiBlcnIgIT0gbmlsIHsKKwkJCQlyZXR1cm4gMCwgW11zdHJpbmd7Zm10LlNwcmludGYoImludmFsaWQgbm9kZS1nbG9iICVxOiAldiIsIG5vZGVHbG9iLCBlcnIpfQorCQkJfQorCQkJaWYgIW1hdGNoZWQgeworCQkJCWNvbnRpbnVlCisJCQl9CisKKwkJCXNjb3JlIDo9IHdlaWdodHMuUGNpRGV2aWNlICsgd2VpZ2h0cy5QY2lEZXZpY2VUeXBlCisJCQlmb3IgXywgY29ubmVjdGlvbiA6PSByYW5nZSBtYW5pZmVzdERldmljZS5TbmFwQ29ubmVjdGlvbnMgeworCQkJCWlmIHRlc3RpbmcuVGVzdGluZygpIHsKKwkJCQkJY29udGludWUKKwkJCQl9CisJCQkJY29ubmVjdGVkLCBlcnIgOj0gc25hcGN0bC5Jc0Nvbm5lY3RlZChjb25uZWN0aW9uKS5SdW4oKQorCQkJCWlmIGVyciAhPSBuaWwgeworCQkJCQlyZXR1cm4gMCwgW11zdHJpbmd7Zm10LlNwcmludGYoImNoZWNraW5nIHNuYXAgY29ubmVjdGlvbiAlcTogJXYiLCBjb25uZWN0aW9uLCBlcnIpfQorCQkJCX0KKwkJCQlpZiAhY29ubmVjdGVkIHsKKwkJCQkJcmV0dXJuIDAsIFtdc3RyaW5ne2ZtdC5TcHJpbnRmKCIlcSBpcyBub3QgY29ubmVjdGVkIiwgY29ubmVjdGlvbil9CisJCQkJfQorCQkJfQorCisJCQlyZXR1cm4gc2NvcmUsIG5pbAorCQl9CisJfQorCisJcmV0dXJuIDAsIFtdc3RyaW5ne2ZtdC5TcHJpbnRmKCJkZXZpY2Ugbm9kZSBtYXRjaGluZyAlcSBub3QgZm91bmQiLCBub2RlR2xvYil9Cit9CmRpZmYgLS1naXQgYS9wa2cvc2VsZWN0b3IvZmFzdHJwYy9mYXN0cnBjX3Rlc3QuZ28gYi9wa2cvc2VsZWN0b3IvZmFzdHJwYy9mYXN0cnBjX3Rlc3QuZ28KbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uMzMwNjUzMAotLS0gL2Rldi9udWxsCisrKyBiL3BrZy9zZWxlY3Rvci9mYXN0cnBjL2Zhc3RycGNfdGVzdC5nbwpAQCAtMCwwICsxLDUwIEBACitwYWNrYWdlIGZhc3RycGMKKworaW1wb3J0ICgKKwkidGVzdGluZyIKKworCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy9lbmdpbmVzIgorCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy90eXBlcyIKKykKKworZnVuYyBUZXN0TWF0Y2godCAqdGVzdGluZy5UKSB7CisJaG9zdERldmljZXMgOj0gW110eXBlcy5EZXRlY3RlZERldmljZXsKKwkJeworCQkJVHlwZTogIm5wdSIsCisJCQlCdXM6ICAiZmFzdHJwYyIsCisJCQlOb2RlczogW11zdHJpbmd7CisJCQkJIi9kZXYvZmFzdHJwYy1jZHNwIiwKKwkJCQkiL2Rldi9mYXN0cnBjLWNkc3AxLXNlY3VyZSIsCisJCQl9LAorCQl9LAorCX0KKworCXQuUnVuKCJkZWZhdWx0IHBhdHRlcm4iLCBmdW5jKHQgKnRlc3RpbmcuVCkgeworCQlkZXZpY2UgOj0gZW5naW5lcy5EZXZpY2V7VHlwZTogIm5wdSIsIEJ1czogImZhc3RycGMifQorCQlzY29yZSwgaXNzdWVzIDo9IE1hdGNoKGRldmljZSwgaG9zdERldmljZXMpCisJCWlmIHNjb3JlID09IDAgfHwgbGVuKGlzc3VlcykgPiAwIHsKKwkJCXQuRmF0YWxmKCJleHBlY3RlZCBhIHBvc2l0aXZlIHNjb3JlIHdpdGggbm8gaXNzdWVzLCBzY29yZT0lZCBpc3N1ZXM9JXYiLCBzY29yZSwgaXNzdWVzKQorCQl9CisJfSkKKworCXQuUnVuKCJjdXN0b20gbm9kZSBnbG9iIiwgZnVuYyh0ICp0ZXN0aW5nLlQpIHsKKwkJbm9kZUdsb2IgOj0gIi9kZXYvZmFzdHJwYy1jZHNwMSoiCisJCWRldmljZSA6PSBlbmdpbmVzLkRldmljZXtUeXBlOiAibnB1IiwgQnVzOiAiZmFzdHJwYyIsIE5vZGVHbG9iOiAmbm9kZUdsb2J9CisJCXNjb3JlLCBpc3N1ZXMgOj0gTWF0Y2goZGV2aWNlLCBob3N0RGV2aWNlcykKKwkJaWYgc2NvcmUgPT0gMCB8fCBsZW4oaXNzdWVzKSA+IDAgeworCQkJdC5GYXRhbGYoImV4cGVjdGVkIGEgcG9zaXRpdmUgc2NvcmUgd2l0aCBubyBpc3N1ZXMsIHNjb3JlPSVkIGlzc3Vlcz0ldiIsIHNjb3JlLCBpc3N1ZXMpCisJCX0KKwl9KQorCisJdC5SdW4oIm5vIG1hdGNoaW5nIG5vZGUiLCBmdW5jKHQgKnRlc3RpbmcuVCkgeworCQlub2RlR2xvYiA6PSAiL2Rldi9mYXN0cnBjLWdkc3AqIgorCQlkZXZpY2UgOj0gZW5naW5lcy5EZXZpY2V7VHlwZTogIm5wdSIsIEJ1czogImZhc3RycGMiLCBOb2RlR2xvYjogJm5vZGVHbG9ifQorCQlzY29yZSwgaXNzdWVzIDo9IE1hdGNoKGRldmljZSwgaG9zdERldmljZXMpCisJCWlmIHNjb3JlICE9IDAgeworCQkJdC5GYXRhbGYoImV4cGVjdGVkIHNjb3JlPTAsIGdvdCAlZCIsIHNjb3JlKQorCQl9CisJCWlmIGxlbihpc3N1ZXMpID09IDAgeworCQkJdC5GYXRhbCgiZXhwZWN0ZWQgY29tcGF0aWJpbGl0eSBpc3N1ZXMiKQorCQl9CisJfSkKK30KZGlmZiAtLWdpdCBhL3BrZy9zZWxlY3Rvci9mYXN0cnBjX3Rlc3QuZ28gYi9wa2cvc2VsZWN0b3IvZmFzdHJwY190ZXN0LmdvCm5ldyBmaWxlIG1vZGUgMTAwNjQ0CmluZGV4IDAwMDAwMDAuLmRkMjEzMmEKLS0tIC9kZXYvbnVsbAorKysgYi9wa2cvc2VsZWN0b3IvZmFzdHJwY190ZXN0LmdvCkBAIC0wLDAgKzEsNTIgQEAKK3BhY2thZ2Ugc2VsZWN0b3IKKworaW1wb3J0ICgKKwkidGVzdGluZyIKKworCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy9lbmdpbmVzIgorCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy90eXBlcyIKKykKKworZnVuYyBUZXN0RmFzdFJwY05wdVNlbGVjdGlvbih0ICp0ZXN0aW5nLlQpIHsKKwlod0luZm8gOj0gJnR5cGVzLkh3SW5mb3sKKwkJTWVtb3J5OiB0eXBlcy5NZW1vcnlJbmZve1RvdGFsUmFtOiA4ICogMTAyNCAqIDEwMjQgKiAxMDI0fSwKKwkJRGlzazogbWFwW3N0cmluZ110eXBlcy5EaXJTdGF0c3sKKwkJCSIvdmFyL2xpYi9zbmFwZC9zbmFwcyI6IHsKKwkJCQlBdmFpbDogMjAgKiAxMDI0ICogMTAyNCAqIDEwMjQsCisJCQl9LAorCQl9LAorCQlEZXZpY2VzOiBbXXR5cGVzLkRldGVjdGVkRGV2aWNleworCQkJeworCQkJCVR5cGU6ICJucHUiLAorCQkJCUJ1czogICJmYXN0cnBjIiwKKwkJCQlOb2RlczogW11zdHJpbmd7CisJCQkJCSIvZGV2L2Zhc3RycGMtY2RzcCIsCisJCQkJfSwKKwkJCX0sCisJCX0sCisJfQorCisJbWFuaWZlc3QgOj0gZW5naW5lcy5NYW5pZmVzdHsKKwkJTmFtZTogICAgICAgICJxdWFsY29tbS1ucHUiLAorCQlEZXNjcmlwdGlvbjogInF1YWxjb21tIGRyYWdvbndpbmcgbnB1IiwKKwkJVmVuZG9yOiAgICAgICJxdWFsY29tbSIsCisJCUdyYWRlOiAgICAgICAic3RhYmxlIiwKKwkJRGV2aWNlczogZW5naW5lcy5EZXZpY2VzeworCQkJQW55b2Y6IFtdZW5naW5lcy5EZXZpY2V7eworCQkJCVR5cGU6ICJucHUiLAorCQkJCUJ1czogICJmYXN0cnBjIiwKKwkJCX19LAorCQl9LAorCX0KKworCXNjb3JlLCByZXBvcnQsIGVyciA6PSBjaGVja0VuZ2luZShod0luZm8sIG1hbmlmZXN0KQorCWlmIGVyciAhPSBuaWwgeworCQl0LkZhdGFsKGVycikKKwl9CisJaWYgIXJlcG9ydC5FbmdpbmVDb21wYXRpYmxlKCkgeworCQl0LkZhdGFsZigiZXhwZWN0ZWQgZW5naW5lIHRvIGJlIGNvbXBhdGlibGU6ICUrdiIsIHJlcG9ydCkKKwl9CisJaWYgc2NvcmUgPD0gMCB7CisJCXQuRmF0YWxmKCJleHBlY3RlZCBwb3NpdGl2ZSBzY29yZSwgZ290ICVkIiwgc2NvcmUpCisJfQorfQpkaWZmIC0tZ2l0IGEvcGtnL3NlbGVjdG9yL3NlbGVjdF9zdGFjay5nbyBiL3BrZy9zZWxlY3Rvci9zZWxlY3Rfc3RhY2suZ28KaW5kZXggNjcxZjUyYy4uYmQ4NGE1ZCAxMDA2NDQKLS0tIGEvcGtnL3NlbGVjdG9yL3NlbGVjdF9zdGFjay5nbworKysgYi9wa2cvc2VsZWN0b3Ivc2VsZWN0X3N0YWNrLmdvCkBAIC04LDYgKzgsNyBAQCBpbXBvcnQgKAogCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy9jb25zdGFudHMiCiAJImdpdGh1Yi5jb20vY2Fub25pY2FsL2luZmVyZW5jZS1zbmFwcy1jbGkvcGtnL2VuZ2luZXMiCiAJImdpdGh1Yi5jb20vY2Fub25pY2FsL2luZmVyZW5jZS1zbmFwcy1jbGkvcGtnL3NlbGVjdG9yL2NwdSIKKwkiZ2l0aHViLmNvbS9jYW5vbmljYWwvaW5mZXJlbmNlLXNuYXBzLWNsaS9wa2cvc2VsZWN0b3IvZmFzdHJwYyIKIAkiZ2l0aHViLmNvbS9jYW5vbmljYWwvaW5mZXJlbmNlLXNuYXBzLWNsaS9wa2cvc2VsZWN0b3IvcGNpIgogCSJnaXRodWIuY29tL2Nhbm9uaWNhbC9pbmZlcmVuY2Utc25hcHMtY2xpL3BrZy90eXBlcyIKIAkiZ2l0aHViLmNvbS9jYW5vbmljYWwvaW5mZXJlbmNlLXNuYXBzLWNsaS9wa2cvdXRpbHMiCkBAIC0xNjUsNiArMTY2LDE1IEBAIGZ1bmMgc2NvcmVEZXZpY2VzQWxsKGhhcmR3YXJlSW5mbyAqdHlwZXMuSHdJbmZvLCBkZXZpY2VzIFtdZW5naW5lcy5EZXZpY2UpIGludCB7CiAJCQljb21wYXRpYmxlID0gZmFsc2UKIAkJCWRldmljZXNbaV0uQ29tcGF0aWJpbGl0eUlzc3VlcyA9IGFwcGVuZChkZXZpY2VzW2ldLkNvbXBhdGliaWxpdHlJc3N1ZXMsICJ1c2IgZGV2aWNlIG1hdGNoaW5nIG5vdCBpbXBsZW1lbnRlZCIpCiAKKwkJfSBlbHNlIGlmIGRldmljZXNbaV0uQnVzID09ICJmYXN0cnBjIiB7CisJCQlmYXN0UnBjU2NvcmUsIGZhc3RScGNJc3N1ZXMgOj0gZmFzdHJwYy5NYXRjaChkZXZpY2VzW2ldLCBoYXJkd2FyZUluZm8uRGV2aWNlcykKKwkJCWlmIGxlbihmYXN0UnBjSXNzdWVzKSA+IDAgeworCQkJCWNvbXBhdGlibGUgPSBmYWxzZQorCQkJCWRldmljZXNbaV0uQ29tcGF0aWJpbGl0eUlzc3VlcyA9IGFwcGVuZChkZXZpY2VzW2ldLkNvbXBhdGliaWxpdHlJc3N1ZXMsIGZhc3RScGNJc3N1ZXMuLi4pCisJCQl9IGVsc2UgeworCQkJCWNvbXBhdGliaWxpdHlTY29yZSArPSBmYXN0UnBjU2NvcmUKKwkJCX0KKwogCQl9IGVsc2UgaWYgZGV2aWNlc1tpXS5CdXMgPT0gIiIgfHwgZGV2aWNlc1tpXS5CdXMgPT0gInBjaSIgewogCQkJLy8gRmFsbGJhY2sgdG8gUENJIGFzIGRlZmF1bHQgYnVzCiAJCQlwY2lTY29yZSwgcGNpSXNzdWVzIDo9IHBjaS5NYXRjaChkZXZpY2VzW2ldLCBoYXJkd2FyZUluZm8uUGNpRGV2aWNlcykKQEAgLTIwNCw2ICsyMTQsMTUgQEAgZnVuYyBzY29yZURldmljZXNBbnkoaGFyZHdhcmVJbmZvICp0eXBlcy5Id0luZm8sIGRldmljZXMgW11lbmdpbmVzLkRldmljZSkgaW50IHsKIAkJCWNvbXBhdGlibGUgPSBmYWxzZQogCQkJZGV2aWNlLkNvbXBhdGliaWxpdHlJc3N1ZXMgPSBhcHBlbmQoZGV2aWNlLkNvbXBhdGliaWxpdHlJc3N1ZXMsICJ1c2IgZGV2aWNlIG1hdGNoaW5nIG5vdCBpbXBsZW1lbnRlZCIpCiAKKwkJfSBlbHNlIGlmIGRldmljZS5CdXMgPT0gImZhc3RycGMiIHsKKwkJCWZhc3RScGNTY29yZSwgZmFzdFJwY0lzc3VlcyA6PSBmYXN0cnBjLk1hdGNoKGRldmljZSwgaGFyZHdhcmVJbmZvLkRldmljZXMpCisJCQlpZiBsZW4oZmFzdFJwY0lzc3VlcykgPiAwIHsKKwkJCQlkZXZpY2VzW2ldLkNvbXBhdGliaWxpdHlJc3N1ZXMgPSBhcHBlbmQoZGV2aWNlLkNvbXBhdGliaWxpdHlJc3N1ZXMsIGZhc3RScGNJc3N1ZXMuLi4pCisJCQl9IGVsc2UgeworCQkJCWRldmljZXNGb3VuZCsrCisJCQkJY29tcGF0aWJpbGl0eVNjb3JlICs9IGZhc3RScGNTY29yZQorCQkJfQorCiAJCX0gZWxzZSBpZiBkZXZpY2UuQnVzID09ICIiIHx8IGRldmljZS5CdXMgPT0gInBjaSIgewogCQkJLy8gRmFsbGJhY2sgdG8gUENJIGFzIGRlZmF1bHQgYnVzCiAJCQlwY2lTY29yZSwgcGNpSXNzdWVzIDo9IHBjaS5NYXRjaChkZXZpY2UsIGhhcmR3YXJlSW5mby5QY2lEZXZpY2VzKQpkaWZmIC0tZ2l0IGEvcGtnL3R5cGVzL2RldGVjdGVkX2RldmljZXMuZ28gYi9wa2cvdHlwZXMvZGV0ZWN0ZWRfZGV2aWNlcy5nbwpuZXcgZmlsZSBtb2RlIDEwMDY0NAppbmRleCAwMDAwMDAwLi43ZGI1Y2ZjCi0tLSAvZGV2L251bGwKKysrIGIvcGtnL3R5cGVzL2RldGVjdGVkX2RldmljZXMuZ28KQEAgLTAsMCArMSwxMiBAQAorcGFja2FnZSB0eXBlcworCit0eXBlIFBsYXRmb3JtSW5mbyBzdHJ1Y3QgeworCVZlbmRvciBzdHJpbmcgYGpzb246InZlbmRvciIgeWFtbDoidmVuZG9yImAKKwlOYW1lICAgc3RyaW5nIGBqc29uOiJuYW1lIiB5YW1sOiJuYW1lImAKK30KKwordHlwZSBEZXRlY3RlZERldmljZSBzdHJ1Y3QgeworCVR5cGUgIHN0cmluZyAgIGBqc29uOiJ0eXBlIiB5YW1sOiJ0eXBlImAKKwlCdXMgICBzdHJpbmcgICBganNvbjoiYnVzIiB5YW1sOiJidXMiYAorCU5vZGVzIFtdc3RyaW5nIGBqc29uOiJub2RlcyxvbWl0ZW1wdHkiIHlhbWw6Im5vZGVzLG9taXRlbXB0eSJgCit9CmRpZmYgLS1naXQgYS9wa2cvdHlwZXMvaHdfaW5mby5nbyBiL3BrZy90eXBlcy9od19pbmZvLmdvCmluZGV4IDk1NDhjNzMuLmYzMWIzNWIgMTAwNjQ0Ci0tLSBhL3BrZy90eXBlcy9od19pbmZvLmdvCisrKyBiL3BrZy90eXBlcy9od19pbmZvLmdvCkBAIC01LDQgKzUsNiBAQCB0eXBlIEh3SW5mbyBzdHJ1Y3QgewogCU1lbW9yeSAgICAgTWVtb3J5SW5mbyAgICAgICAgICBganNvbjoibWVtb3J5LG9taXRlbXB0eSIgeWFtbDoibWVtb3J5LG9taXRlbXB0eSJgCiAJRGlzayAgICAgICBtYXBbc3RyaW5nXURpclN0YXRzIGBqc29uOiJkaXNrLG9taXRlbXB0eSIgeWFtbDoiZGlzayxvbWl0ZW1wdHkiYAogCVBjaURldmljZXMgW11QY2lEZXZpY2UgICAgICAgICBganNvbjoicGNpLG9taXRlbXB0eSIgeWFtbDoicGNpImAKKwlQbGF0Zm9ybXMgIFtdUGxhdGZvcm1JbmZvICAgICAgYGpzb246InBsYXRmb3JtcyxvbWl0ZW1wdHkiIHlhbWw6InBsYXRmb3JtcyxvbWl0ZW1wdHkiYAorCURldmljZXMgICAgW11EZXRlY3RlZERldmljZSAgICBganNvbjoiZGV2aWNlcyxvbWl0ZW1wdHkiIHlhbWw6ImRldmljZXMsb21pdGVtcHR5ImAKIH0KZGlmZiAtLWdpdCBhL3Rlc3RfZGF0YS90ZXN0Zmxpbmdlci9xdWFsY29tbS1kcmFnb253aW5nLW5wdS55YW1sIGIvdGVzdF9kYXRhL3Rlc3RmbGluZ2VyL3F1YWxjb21tLWRyYWdvbndpbmctbnB1LnlhbWwKbmV3IGZpbGUgbW9kZSAxMDA2NDQKaW5kZXggMDAwMDAwMC4uYjU3NWZhYQotLS0gL2Rldi9udWxsCisrKyBiL3Rlc3RfZGF0YS90ZXN0Zmxpbmdlci9xdWFsY29tbS1kcmFnb253aW5nLW5wdS55YW1sCkBAIC0wLDAgKzEsMjAyIEBACitqb2JfcXVldWU6IGRyYWdvbndpbmctZXZhbHVhdGlvbi1raXQtaXEtOTA3NS1ldmsKKworcHJvdmlzaW9uX2RhdGE6CisgIHNraXA6IHRydWUKKwordGVzdF9kYXRhOgorICB0ZXN0X2NtZHM6IHwKKyAgICBzZXQgLWV1eG8gcGlwZWZhaWwKKworICAgIGV4cG9ydCBERUJJQU5fRlJPTlRFTkQ9bm9uaW50ZXJhY3RpdmUKKyAgICBzdWRvIGFwdC1nZXQgdXBkYXRlCisgICAgc3VkbyBhcHQtZ2V0IGluc3RhbGwgLXkgZ2l0IGpxIGN1cmwgY2EtY2VydGlmaWNhdGVzCisKKyAgICBXT1JLRElSPSIkSE9NRS9pbmZlcmVuY2Utc25hcHMtY2xpIgorICAgIHJtIC1yZiAiJFdPUktESVIiCisgICAgZ2l0IGNsb25lIGh0dHBzOi8vZ2l0aHViLmNvbS9jYW5vbmljYWwvaW5mZXJlbmNlLXNuYXBzLWNsaS5naXQgIiRXT1JLRElSIgorICAgIGNkICIkV09SS0RJUiIKKworICAgICMgT3B0aW9uYWw6IGFwcGx5IHlvdXIgdW5wdXNoZWQgbG9jYWwgY2hhbmdlcyBhcyBhIGJhc2U2NC1lbmNvZGVkIGdpdCBwYXRjaC4KKyAgICAjIFJlcGxhY2UgX19MT0NBTF9QQVRDSF9CNjRfXyB3aXRoOiBnaXQgZGlmZiAtLWJpbmFyeSB8IGJhc2U2NCAtdzAKKyAgICBMT0NBTF9QQVRDSF9CNjQ9J19fTE9DQUxfUEFUQ0hfQjY0X18nCisKKyAgICBpZiBbIC1uICIkTE9DQUxfUEFUQ0hfQjY0IiBdICYmIFsgIiRMT0NBTF9QQVRDSF9CNjQiICE9ICdfX0xPQ0FMX1BBVENIX0I2NF9fJyBdOyB0aGVuCisgICAgICBwcmludGYgJyVzJyAiJExPQ0FMX1BBVENIX0I2NCIgfCBiYXNlNjQgLWQgPiAvdG1wL2xvY2FsLnBhdGNoCisKKyAgICAgICMgRW5zdXJlIHBhcmVudCBkaXJlY3RvcmllcyBmb3IgbmV3L3JlbmFtZWQgZmlsZXMgZXhpc3QgYmVmb3JlIGFwcGx5IC0tY2hlY2suCisgICAgICBhd2sgJy9eXCtcK1wrIGJcLy8geyBwcmludCBzdWJzdHIoJDIsIDMpIH0nIC90bXAvbG9jYWwucGF0Y2ggXAorICAgICAgICB8IGdyZXAgLXYgJ14vZGV2L251bGwkJyBcCisgICAgICAgIHwgeGFyZ3MgLXIgLW4xIGRpcm5hbWUgXAorICAgICAgICB8IHNvcnQgLXUgXAorICAgICAgICB8IHhhcmdzIC1yIG1rZGlyIC1wCisKKyAgICAgIGdpdCBhcHBseSAtLWNoZWNrIC90bXAvbG9jYWwucGF0Y2gKKyAgICAgIGdpdCBhcHBseSAvdG1wL2xvY2FsLnBhdGNoCisgICAgZWxzZQorICAgICAgZWNobyAiTm8gbG9jYWwgcGF0Y2ggZW1iZWRkZWQ7IHJ1bm5pbmcgYWdhaW5zdCByZXBvc2l0b3J5IEhFQUQiCisgICAgZmkKKworICAgICMgVHJ5IHRvIHVzZSBleGlzdGluZyBHbyBmaXJzdCAoc28gdGVzdHMgY2FuIHdvcmsgZXZlbiB3aXRoIG9sZGVyIHZlcnNpb25zKQorICAgIGlmIGNvbW1hbmQgLXYgZ28gJj4gL2Rldi9udWxsOyB0aGVuCisgICAgICBlY2hvICJHbyBhbHJlYWR5IGluc3RhbGxlZDogJChnbyB2ZXJzaW9uKSIKKyAgICAgIGV4cG9ydCBQQVRIPSIkKGdvIGVudiBHT1JPT1QpL2JpbjokUEFUSCIKKyAgICBlbHNlCisgICAgICBlY2hvICJHbyBub3QgZm91bmQsIGF0dGVtcHRpbmcgdG8gaW5zdGFsbC4uLiIKKyAgICAgIEdPX1ZFUlNJT05fUkFXPSIkKGF3ayAnL15nbyAve3ByaW50ICQyOyBleGl0fScgZ28ubW9kKSIKKyAgICAgIGNhc2UgIiRHT19WRVJTSU9OX1JBVyIgaW4KKyAgICAgICAgKi4qLiopIEdPX1ZFUlNJT049IiRHT19WRVJTSU9OX1JBVyIgOzsKKyAgICAgICAgKi4qKSBHT19WRVJTSU9OPSIke0dPX1ZFUlNJT05fUkFXfS4wIiA7OworICAgICAgICAqKSBlY2hvICJVbnN1cHBvcnRlZCBnby5tb2QgdmVyc2lvbiBmb3JtYXQ6ICRHT19WRVJTSU9OX1JBVyI7IGV4aXQgMSA7OworICAgICAgZXNhYworCisgICAgICBjYXNlICIkKHVuYW1lIC1tKSIgaW4KKyAgICAgICAgeDg2XzY0KSBHT19BUkNIPSJhbWQ2NCIgOzsKKyAgICAgICAgYWFyY2g2NHxhcm02NCkgR09fQVJDSD0iYXJtNjQiIDs7CisgICAgICAgICopIGVjaG8gIlVuc3VwcG9ydGVkIGFyY2hpdGVjdHVyZTogJCh1bmFtZSAtbSkiOyBleGl0IDEgOzsKKyAgICAgIGVzYWMKKworICAgICAgR09fVEFSQkFMTD0iZ28ke0dPX1ZFUlNJT059LmxpbnV4LSR7R09fQVJDSH0udGFyLmd6IgorICAgICAgCisgICAgICAjIFRyeSBtdWx0aXBsZSBkb3dubG9hZCBVUkxzIHdpdGggZmFsbGJhY2sgc3RyYXRlZ3kKKyAgICAgIGRlY2xhcmUgLWEgVVJMUz0oCisgICAgICAgICJodHRwczovL2dvLmRldi9kbC8ke0dPX1RBUkJBTEx9IgorICAgICAgICAiaHR0cHM6Ly9nb2xhbmcuZ29vZ2xlLmNuL2RsLyR7R09fVEFSQkFMTH0iCisgICAgICAgICJodHRwczovL21pcnJvci5naHByb3h5LmNvbS9odHRwczovL2dpdGh1Yi5jb20vZ29sYW5nL2dvL3JlbGVhc2VzL2Rvd25sb2FkL2dvJHtHT19WRVJTSU9OfS8ke0dPX1RBUkJBTEx9IgorICAgICAgKQorICAgICAgCisgICAgICBET1dOTE9BREVEPTAKKyAgICAgIGZvciBVUkwgaW4gIiR7VVJMU1tAXX0iOyBkbworICAgICAgICBlY2hvICJBdHRlbXB0aW5nIHRvIGRvd25sb2FkIGZyb206ICRVUkwiCisgICAgICAgIGlmIGN1cmwgLS1jb25uZWN0LXRpbWVvdXQgNSAtLW1heC10aW1lIDYwIC1mc1NMbyAiL3RtcC8ke0dPX1RBUkJBTEx9IiAiJFVSTCI7IHRoZW4KKyAgICAgICAgICBlY2hvICJTdWNjZXNzZnVsbHkgZG93bmxvYWRlZCBHbyBmcm9tICRVUkwiCisgICAgICAgICAgRE9XTkxPQURFRD0xCisgICAgICAgICAgYnJlYWsKKyAgICAgICAgZmkKKyAgICAgICAgZWNobyAiRmFpbGVkIHRvIGRvd25sb2FkIGZyb20gJFVSTCwgdHJ5aW5nIG5leHQuLi4iCisgICAgICBkb25lCisgICAgICAKKyAgICAgIGlmIFsgJERPV05MT0FERUQgLWVxIDAgXTsgdGhlbgorICAgICAgICBlY2hvICJXQVJOSU5HOiBDb3VsZCBub3QgZG93bmxvYWQgR28gJEdPX1ZFUlNJT04uIEF0dGVtcHRpbmcgdG8gdXNlIHN5c3RlbSBHby4uLiIKKyAgICAgICAgaWYgc3VkbyBhcHQtZ2V0IGluc3RhbGwgLXkgZ29sYW5nLWdvOyB0aGVuCisgICAgICAgICAgZWNobyAiSW5zdGFsbGVkIHN5c3RlbSBHbyB2aWEgYXB0IgorICAgICAgICAgIGV4cG9ydCBQQVRIPSIkKGdvIGVudiBHT1JPT1QpL2JpbjokUEFUSCIKKyAgICAgICAgZWxzZQorICAgICAgICAgIGVjaG8gIkVSUk9SOiBDb3VsZCBub3QgaW5zdGFsbCBHbyBieSBhbnkgbWV0aG9kIgorICAgICAgICAgIGV4aXQgMQorICAgICAgICBmaQorICAgICAgZWxzZQorICAgICAgICBzdWRvIHJtIC1yZiAvdXNyL2xvY2FsL2dvCisgICAgICAgIHN1ZG8gdGFyIC1DIC91c3IvbG9jYWwgLXh6ZiAiL3RtcC8ke0dPX1RBUkJBTEx9IgorICAgICAgICBleHBvcnQgUEFUSD0iL3Vzci9sb2NhbC9nby9iaW46JFBBVEgiCisgICAgICBmaQorICAgIGZpCisKKyAgICBnbyB2ZXJzaW9uCisKKyAgICBnbyBidWlsZCAtbyBpbmZlcmVuY2Utc25hcHMtY2xpIC4vY21kL2NsaQorCisgICAgIyBTdHJpY3QgZGlhZ25vc3RpYzogdmVyaWZ5IHRoaXMgam9iIGlzIHJlYWxseSBleGVjdXRpbmcgb24gdGhlIGV4cGVjdGVkIERVVC4KKyAgICBlY2hvICI9PT0gU1lTVEVNIERJQUdOT1NUSUNTID09PSIKKyAgICBlY2hvICJIb3N0OiAkKGhvc3RuYW1lKSIKKyAgICBlY2hvICJLZXJuZWw6ICQodW5hbWUgLXIpIgorICAgIGVjaG8gIkFyY2g6ICQodW5hbWUgLW0pIgorICAgIFsgLWYgL2V0Yy9vcy1yZWxlYXNlIF0gJiYgY2F0IC9ldGMvb3MtcmVsZWFzZQorICAgIGlmIFsgLWYgL3N5cy9maXJtd2FyZS9kZXZpY2V0cmVlL2Jhc2UvbW9kZWwgXTsgdGhlbgorICAgICAgZWNobyAiRGV2aWNlLXRyZWUgbW9kZWw6IgorICAgICAgdHIgLWQgJ1wwMDAnIDwgL3N5cy9maXJtd2FyZS9kZXZpY2V0cmVlL2Jhc2UvbW9kZWwgfHwgdHJ1ZQorICAgIGZpCisgICAgaWYgWyAtZiAvcHJvYy9kZXZpY2UtdHJlZS9tb2RlbCBdOyB0aGVuCisgICAgICBlY2hvICJQcm9jIGRldmljZS10cmVlIG1vZGVsOiIKKyAgICAgIHRyIC1kICdcMDAwJyA8IC9wcm9jL2RldmljZS10cmVlL21vZGVsIHx8IHRydWUKKyAgICBmaQorICAgIGVjaG8gIj09PT09PT09PT09PT09PT09PT09PT09PT09IgorICAgIGVjaG8gIiIKKworICAgICMgSGFyZCBmYWlsIGlmIHRoaXMgaXMgbm90IGFuIEFSTSBEVVQuIEVhcmxpZXIgZmFpbHVyZXMgc2hvd2VkIHg4Nl82NC9rdm0gcnVucy4KKyAgICBjYXNlICIkKHVuYW1lIC1tKSIgaW4KKyAgICAgIGFhcmNoNjR8YXJtNjQpIDs7CisgICAgICAqKQorICAgICAgICBlY2hvICJFUlJPUjogSm9iIGlzIG5vdCBydW5uaW5nIG9uIGFuIEFSTSBRdWFsY29tbSBEVVQgKGFyY2g9JCh1bmFtZSAtbSkpLiIKKyAgICAgICAgZWNobyAiRVJST1I6IFRoaXMgaW5kaWNhdGVzIHdyb25nIHF1ZXVlIGFsbG9jYXRpb24gb3Igd3JvbmcgZXhlY3V0aW9uIGVudmlyb25tZW50LiIKKyAgICAgICAgZXhpdCAyCisgICAgICAgIDs7CisgICAgZXNhYworCisgICAgIyAxKSBXYWl0IGZvciByZWFsIEZhc3RSUEMgbm9kZXMgdG8gYXBwZWFyIChubyBtb2NrIG5vZGVzKS4KKyAgICBlY2hvICJXYWl0aW5nIGZvciAvZGV2L2Zhc3RycGMtKiBub2RlcyAodXAgdG8gOTBzKS4uLiIKKyAgICBGQVNUUlBDX0ZPVU5EPTAKKyAgICBmb3IgXyBpbiAkKHNlcSAxIDMwKTsgZG8KKyAgICAgIGlmIGxzIC0xIC9kZXYvZmFzdHJwYy0qID4vZGV2L251bGwgMj4mMTsgdGhlbgorICAgICAgICBGQVNUUlBDX0ZPVU5EPTEKKyAgICAgICAgYnJlYWsKKyAgICAgIGZpCisgICAgICBzbGVlcCAzCisgICAgZG9uZQorCisgICAgaWYgWyAiJEZBU1RSUENfRk9VTkQiIC1uZSAxIF07IHRoZW4KKyAgICAgIGVjaG8gIkVSUk9SOiBObyAvZGV2L2Zhc3RycGMtKiBub2RlcyBmb3VuZCBhZnRlciB3YWl0aW5nLiIKKyAgICAgIGVjaG8gIkxpc3RpbmcgL2RldiBlbnRyaWVzIHRoYXQgbWlnaHQgYmUgcmVsZXZhbnQ6IgorICAgICAgbHMgLTEgL2RldiB8IGdyZXAgLUVpICdmYXN0cnBjfGNkc3B8YWRzcHxxY29tfGRzcCcgfHwgdHJ1ZQorICAgICAgZWNobyAiTG9hZGVkIG1vZHVsZXMgbWVudGlvbmluZyBmYXN0cnBjOiIKKyAgICAgIGlmIGNvbW1hbmQgLXYgbHNtb2QgPi9kZXYvbnVsbCAyPiYxOyB0aGVuCisgICAgICAgIGxzbW9kIHwgZ3JlcCAtaSBmYXN0cnBjIHx8IHRydWUKKyAgICAgIGVsc2UKKyAgICAgICAgZ3JlcCAtaSBmYXN0cnBjIC9wcm9jL21vZHVsZXMgfHwgdHJ1ZQorICAgICAgZmkKKyAgICAgIGV4aXQgMworICAgIGZpCisKKyAgICBlY2hvICJGYXN0UlBDIG5vZGVzIGZvdW5kOiIKKyAgICBscyAtMSAvZGV2L2Zhc3RycGMtKgorICAgIGxzIC0xIC9kZXYvZmFzdHJwYy1jZHNwKiB8fCB0cnVlCisKKyAgICAjIDIpIFZlcmlmeSBtYWNoaW5lIGluZm8gY29udGFpbnMgUXVhbGNvbW0gRHJhZ29ud2luZyBhbmQgTlBVL0Zhc3RSUEMgZW50cmllcy4KKyAgICBlY2hvICIiCisgICAgZWNobyAiPT09IE1BQ0hJTkUgSU5GTyBURVNUID09PSIKKyAgICAjIFNvbWUgRFVUIGltYWdlcyBkbyBub3QgaW5jbHVkZSBzbmFwZCBwcmVpbnN0YWxsZWQ7IGVuc3VyZSBleHBlY3RlZCBwYXRoIGV4aXN0cy4KKyAgICBzdWRvIG1rZGlyIC1wIC92YXIvbGliL3NuYXBkL3NuYXBzCisgICAgLi9pbmZlcmVuY2Utc25hcHMtY2xpIHNob3ctbWFjaGluZSAtLWZvcm1hdD1qc29uID4gL3RtcC9oYXJkd2FyZS1pbmZvLmpzb24KKyAgICBlY2hvICJNYWNoaW5lIGluZm8gKGZpcnN0IDgwIGxpbmVzKToiCisgICAgaGVhZCAtODAgL3RtcC9oYXJkd2FyZS1pbmZvLmpzb24KKworICAgICMgU3RyaWN0IGFzc2VydGlvbnM6IG9uIGEgcmVhbCBEVVQgd2l0aCBGYXN0UlBDIG5vZGVzLCB0aGVzZSBtdXN0IGJlIHByZXNlbnQuCisgICAganEgLWUgJy5wbGF0Zm9ybXMgfCBtYXAoc2VsZWN0KC52ZW5kb3IgPT0gInF1YWxjb21tIiBhbmQgLm5hbWUgPT0gImRyYWdvbndpbmciKSkgfCBsZW5ndGggPiAwJyAvdG1wL2hhcmR3YXJlLWluZm8uanNvbgorICAgIGpxIC1lICcuZGV2aWNlcyB8IG1hcChzZWxlY3QoLnR5cGUgPT0gIm5wdSIgYW5kIC5idXMgPT0gImZhc3RycGMiKSkgfCBsZW5ndGggPiAwJyAvdG1wL2hhcmR3YXJlLWluZm8uanNvbgorICAgIGpxIC1lICcuZGV2aWNlc1tdIHwgc2VsZWN0KC50eXBlID09ICJucHUiIGFuZCAuYnVzID09ICJmYXN0cnBjIikgfCAubm9kZXMgfCBsZW5ndGggPiAwJyAvdG1wL2hhcmR3YXJlLWluZm8uanNvbgorICAgIGVjaG8gIk1hY2hpbmUgZGV0ZWN0aW9uIGNoZWNrcyBwYXNzZWQuIgorCisgICAgIyAzKSBWZXJpZnkgc2VsZWN0b3IgY2FuIG1hdGNoIGFuIE5QVSBlbmdpbmUgdXNpbmcgdGhlIG5ldyBmYXN0cnBjIGJ1cy4KKyAgICBlY2hvICIiCisgICAgZWNobyAiPT09IFNFTEVDVE9SIE1BVENISU5HIFRFU1QgPT09IgorICAgIG1rZGlyIC1wIC90bXAvZW5naW5lcy9xdWFsY29tbS1ucHUtc21va2UKKyAgICBjYXQgPiAvdG1wL2VuZ2luZXMvcXVhbGNvbW0tbnB1LXNtb2tlL2VuZ2luZS55YW1sIDw8ICdFTkdJTkVfRU9GJworICAgIG5hbWU6IHF1YWxjb21tLW5wdS1zbW9rZQorICAgIGRlc2NyaXB0aW9uOiBTbW9rZSB0ZXN0IGVuZ2luZSBmb3IgUXVhbGNvbW0gRHJhZ29ud2luZyBOUFUgZGV0ZWN0aW9uCisgICAgdmVuZG9yOiBDYW5vbmljYWwKKyAgICBncmFkZTogc3RhYmxlCisgICAgZGV2aWNlczoKKyAgICAgIGFueW9mOgorICAgICAgICAtIHR5cGU6IG5wdQorICAgICAgICAgIGJ1czogZmFzdHJwYworICAgIEVOR0lORV9FT0YKKworICAgIGVjaG8gIlJ1bm5pbmcgc2VsZWN0b3Igd2l0aCBOUFUgZW5naW5lLi4uIgorICAgIC4vaW5mZXJlbmNlLXNuYXBzLWNsaSBzaG93LW1hY2hpbmUgLS1mb3JtYXQ9anNvbiB8IC4vaW5mZXJlbmNlLXNuYXBzLWNsaSBkZWJ1ZyBzZWxlY3QtZW5naW5lIC0tZW5naW5lcyAvdG1wL2VuZ2luZXMgPiAvdG1wL3NlbGVjdC1lbmdpbmUub3V0IDI+JjEKKyAgICBlY2hvICJTZWxlY3RvciBvdXRwdXQ6IgorICAgIGNhdCAvdG1wL3NlbGVjdC1lbmdpbmUub3V0CisgICAgCisgICAgZ3JlcCAtcSAicXVhbGNvbW0tbnB1LXNtb2tlIiAvdG1wL3NlbGVjdC1lbmdpbmUub3V0CisgICAgZWNobyAiU2VsZWN0b3IgbWF0Y2hlZCBOUFUgZW5naW5lLiIKKworICAgIGVjaG8gIiIKKyAgICBlY2hvICI9PT0gVEVTVCBTVU1NQVJZID09PSIKKyAgICBlY2hvICJHbyB0b29sY2hhaW4gY29uZmlndXJlZCIKKyAgICBlY2hvICJpbmZlcmVuY2Utc25hcHMtY2xpIGJ1aWx0IHN1Y2Nlc3NmdWxseSIKKyAgICBlY2hvICJNYWNoaW5lIGRldGVjdGlvbiB2YWxpZGF0ZWQgb24gcmVhbCBGYXN0UlBDIG5vZGVzIgorICAgIGVjaG8gIlNlbGVjdG9yIG1hdGNoaW5nIHZhbGlkYXRlZCIKKyAgICBlY2hvICIiCisgICAgZWNobyAiUXVhbGNvbW0gRHJhZ29ud2luZyBOUFUgZGV0ZWN0aW9uIGltcGxlbWVudGF0aW9uIHZhbGlkYXRlZC4iCityZXNlcnZlX2RhdGE6CisgIHNzaF9rZXlzOgorICAgIC0gImxwOnNpbmFuLWsiCisgIHRpbWVvdXQ6IDVoClwgTm8gbmV3bGluZSBhdCBlbmQgb2YgZmlsZQo=' + + if [ -n "$LOCAL_PATCH_B64" ] && [ "$LOCAL_PATCH_B64" != '__LOCAL_PATCH_B64__' ]; then + printf '%s' "$LOCAL_PATCH_B64" | base64 -d > /tmp/local.patch + + # Ensure parent directories for new/renamed files exist before apply --check. + awk '/^\+\+\+ b\// { print substr($2, 3) }' /tmp/local.patch \ + | grep -v '^/dev/null$' \ + | xargs -r -n1 dirname \ + | sort -u \ + | xargs -r mkdir -p + + git apply --check /tmp/local.patch + git apply /tmp/local.patch + else + echo "No local patch embedded; running against repository HEAD" + fi + + # Try to use existing Go first (so tests can work even with older versions) + if command -v go &> /dev/null; then + echo "Go already installed: $(go version)" + export PATH="$(go env GOROOT)/bin:$PATH" + else + echo "Go not found, attempting to install..." + GO_VERSION_RAW="$(awk '/^go /{print $2; exit}' go.mod)" + case "$GO_VERSION_RAW" in + *.*.*) GO_VERSION="$GO_VERSION_RAW" ;; + *.*) GO_VERSION="${GO_VERSION_RAW}.0" ;; + *) echo "Unsupported go.mod version format: $GO_VERSION_RAW"; exit 1 ;; + esac + + case "$(uname -m)" in + x86_64) GO_ARCH="amd64" ;; + aarch64|arm64) GO_ARCH="arm64" ;; + *) echo "Unsupported architecture: $(uname -m)"; exit 1 ;; + esac + + GO_TARBALL="go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + + # Try multiple download URLs with fallback strategy + declare -a URLS=( + "https://go.dev/dl/${GO_TARBALL}" + "https://golang.google.cn/dl/${GO_TARBALL}" + "https://mirror.ghproxy.com/https://github.com/golang/go/releases/download/go${GO_VERSION}/${GO_TARBALL}" + ) + + DOWNLOADED=0 + for URL in "${URLS[@]}"; do + echo "Attempting to download from: $URL" + if curl --connect-timeout 5 --max-time 60 -fsSLo "/tmp/${GO_TARBALL}" "$URL"; then + echo "Successfully downloaded Go from $URL" + DOWNLOADED=1 + break + fi + echo "Failed to download from $URL, trying next..." + done + + if [ $DOWNLOADED -eq 0 ]; then + echo "WARNING: Could not download Go $GO_VERSION. Attempting to use system Go..." + if sudo apt-get install -y golang-go; then + echo "Installed system Go via apt" + export PATH="$(go env GOROOT)/bin:$PATH" + else + echo "ERROR: Could not install Go by any method" + exit 1 + fi + else + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf "/tmp/${GO_TARBALL}" + export PATH="/usr/local/go/bin:$PATH" + fi + fi + + go version + + go build -o inference-snaps-cli ./cmd/cli + + # Strict diagnostic: verify this job is really executing on the expected DUT. + echo "=== SYSTEM DIAGNOSTICS ===" + echo "Host: $(hostname)" + echo "Kernel: $(uname -r)" + echo "Arch: $(uname -m)" + [ -f /etc/os-release ] && cat /etc/os-release + if [ -f /sys/firmware/devicetree/base/model ]; then + echo "Device-tree model:" + tr -d '\000' < /sys/firmware/devicetree/base/model || true + fi + if [ -f /proc/device-tree/model ]; then + echo "Proc device-tree model:" + tr -d '\000' < /proc/device-tree/model || true + fi + echo "==========================" + echo "" + + # Hard fail if this is not an ARM DUT. Earlier failures showed x86_64/kvm runs. + case "$(uname -m)" in + aarch64|arm64) ;; + *) + echo "ERROR: Job is not running on an ARM Qualcomm DUT (arch=$(uname -m))." + echo "ERROR: This indicates wrong queue allocation or wrong execution environment." + exit 2 + ;; + esac + + # 1) Wait for real FastRPC nodes to appear (no mock nodes). + echo "Waiting for /dev/fastrpc-* nodes (up to 90s)..." + FASTRPC_FOUND=0 + for _ in $(seq 1 30); do + if ls -1 /dev/fastrpc-* >/dev/null 2>&1; then + FASTRPC_FOUND=1 + break + fi + sleep 3 + done + + if [ "$FASTRPC_FOUND" -ne 1 ]; then + echo "ERROR: No /dev/fastrpc-* nodes found after waiting." + echo "Listing /dev entries that might be relevant:" + ls -1 /dev | grep -Ei 'fastrpc|cdsp|adsp|qcom|dsp' || true + echo "Loaded modules mentioning fastrpc:" + if command -v lsmod >/dev/null 2>&1; then + lsmod | grep -i fastrpc || true + else + grep -i fastrpc /proc/modules || true + fi + exit 3 + fi + + echo "FastRPC nodes found:" + ls -1 /dev/fastrpc-* + ls -1 /dev/fastrpc-cdsp* || true + + # 2) Verify machine info contains Qualcomm Dragonwing and NPU/FastRPC entries. + echo "" + echo "=== MACHINE INFO TEST ===" + # Some DUT images do not include snapd preinstalled; ensure expected path exists. + sudo mkdir -p /var/lib/snapd/snaps + ./inference-snaps-cli show-machine --format=json > /tmp/hardware-info.json + echo "Machine info (first 80 lines):" + head -80 /tmp/hardware-info.json + + # Strict assertions: on a real DUT with FastRPC nodes, these must be present. + jq -e '.platforms | map(select(.vendor == "qualcomm" and .name == "dragonwing")) | length > 0' /tmp/hardware-info.json + jq -e '.devices | map(select(.type == "npu" and .bus == "fastrpc")) | length > 0' /tmp/hardware-info.json + jq -e '.devices[] | select(.type == "npu" and .bus == "fastrpc") | .nodes | length > 0' /tmp/hardware-info.json + echo "Machine detection checks passed." + + # 3) Verify selector can match an NPU engine using the new fastrpc bus. + echo "" + echo "=== SELECTOR MATCHING TEST ===" + mkdir -p /tmp/engines/qualcomm-npu-smoke + cat > /tmp/engines/qualcomm-npu-smoke/engine.yaml << 'ENGINE_EOF' + name: qualcomm-npu-smoke + description: Smoke test engine for Qualcomm Dragonwing NPU detection + vendor: Canonical + grade: stable + devices: + anyof: + - type: npu + bus: fastrpc + ENGINE_EOF + + echo "Running selector with NPU engine..." + ./inference-snaps-cli show-machine --format=json | ./inference-snaps-cli debug select-engine --engines /tmp/engines > /tmp/select-engine.out 2>&1 + echo "Selector output:" + cat /tmp/select-engine.out + + grep -q "qualcomm-npu-smoke" /tmp/select-engine.out + echo "Selector matched NPU engine." + + echo "" + echo "=== TEST SUMMARY ===" + echo "Go toolchain configured" + echo "inference-snaps-cli built successfully" + echo "Machine detection validated on real FastRPC nodes" + echo "Selector matching validated" + echo "" + echo "Qualcomm Dragonwing NPU detection implementation validated." +reserve_data: + ssh_keys: + - "lp:sinan-k" + timeout: 5h \ No newline at end of file From 47530d01cc1b4b8cd9f5f76ac8631f7fc3c74611 Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Mon, 27 Apr 2026 16:35:01 +0200 Subject: [PATCH 6/6] refactor(mediatek): updated DetectedDevice struct based on QC refactor Co-authored-by: Copilot --- pkg/hardware_info/hardware-info.go | 10 ++-- pkg/hardware_info/mediatek/mediatek.go | 42 +++++-------- pkg/hardware_info/mediatek/mediatek_test.go | 65 ++++++++++++--------- 3 files changed, 57 insertions(+), 60 deletions(-) diff --git a/pkg/hardware_info/hardware-info.go b/pkg/hardware_info/hardware-info.go index 85bc8744..b1e991b3 100644 --- a/pkg/hardware_info/hardware-info.go +++ b/pkg/hardware_info/hardware-info.go @@ -43,18 +43,18 @@ func Get(friendlyNames bool) (*types.HwInfo, error) { } hwInfo.PciDevices = pciDevices - QCDevices, err := qualcomm.Info() + qcDevices, err := qualcomm.Info() if err != nil { return nil, fmt.Errorf("getting qualcomm devices: %v", err) } - hwInfo.Devices = QCDevices + hwInfo.Devices = qcDevices - MediatekDevices, err = mediatek.Info() + mediatekDevices, err := mediatek.Info() if err != nil { return nil, fmt.Errorf("getting mediatek devices: %v", err) } - hwInfo.Devices = append(hwInfo.Devices, MediatekDevices...) - + hwInfo.Devices = append(hwInfo.Devices, mediatekDevices...) + return &hwInfo, nil } diff --git a/pkg/hardware_info/mediatek/mediatek.go b/pkg/hardware_info/mediatek/mediatek.go index a5fd905d..2b07670f 100644 --- a/pkg/hardware_info/mediatek/mediatek.go +++ b/pkg/hardware_info/mediatek/mediatek.go @@ -19,19 +19,18 @@ var socToPlatform = map[string]string{ "mt8189": "genio-520-720", } -func Info() ([]types.PlatformInfo, []types.DetectedDevice, error) { +func Info() ([]types.DetectedDevice, error) { compatibleValues, err := readCompatibleValues(deviceTreeRoot) if err != nil { - return nil, nil, fmt.Errorf("detecting mediatek apusys: %v", err) + return nil, fmt.Errorf("detecting mediatek apusys: %v", err) } - platforms, devices := DetectFromCompatibles(compatibleValues) - return platforms, devices, nil + devices := DetectFromCompatibles(compatibleValues) + return devices, nil } -func DetectFromCompatibles(compatibleValues []string) ([]types.PlatformInfo, []types.DetectedDevice) { +func DetectFromCompatibles(compatibleValues []string) []types.DetectedDevice { platSet := make(map[string]struct{}) - matchSet := make(map[string]struct{}) for _, value := range compatibleValues { soc, ok := extractAPUSoC(value) @@ -45,11 +44,10 @@ func DetectFromCompatibles(compatibleValues []string) ([]types.PlatformInfo, []t } platSet[platformName] = struct{}{} - matchSet[value] = struct{}{} } - if len(matchSet) == 0 { - return nil, nil + if len(platSet) == 0 { + return nil } platformNames := make([]string, 0, len(platSet)) @@ -58,27 +56,19 @@ func DetectFromCompatibles(compatibleValues []string) ([]types.PlatformInfo, []t } sort.Strings(platformNames) - platforms := make([]types.PlatformInfo, 0, len(platformNames)) + devices := make([]types.DetectedDevice, 0, len(platformNames)) for _, name := range platformNames { - platforms = append(platforms, types.PlatformInfo{ - Vendor: "mediatek", - Name: name, + devices = append(devices, types.DetectedDevice{ + Type: "npu", + Bus: "mdla", + Metadata: &types.DeviceMetadata{ + VendorName: "mediatek", + ProductName: name, + }, }) } - matches := make([]string, 0, len(matchSet)) - for value := range matchSet { - matches = append(matches, value) - } - sort.Strings(matches) - - devices := []types.DetectedDevice{{ - Type: "npu", - Bus: "mdla", - Nodes: matches, - }} - - return platforms, devices + return devices } func readCompatibleValues(root string) ([]string, error) { diff --git a/pkg/hardware_info/mediatek/mediatek_test.go b/pkg/hardware_info/mediatek/mediatek_test.go index afdbd568..b996c29d 100644 --- a/pkg/hardware_info/mediatek/mediatek_test.go +++ b/pkg/hardware_info/mediatek/mediatek_test.go @@ -10,7 +10,7 @@ import ( ) func TestDetectFromCompatibles(t *testing.T) { - platforms, devices := DetectFromCompatibles([]string{ + devices := DetectFromCompatibles([]string{ "arm,v8", "mediatek,mt8195-apusys_rv", "mediatek,mt8188-apusys_rv", @@ -19,24 +19,33 @@ func TestDetectFromCompatibles(t *testing.T) { "mediatek,mt8188-apusys_rv", // duplicate should be deduped }) - expectedPlatforms := []types.PlatformInfo{ - {Vendor: "mediatek", Name: "genio-1200"}, - {Vendor: "mediatek", Name: "genio-510-700"}, - {Vendor: "mediatek", Name: "genio-520-720"}, - {Vendor: "mediatek", Name: "mediatek-mt9999"}, - } - if !reflect.DeepEqual(platforms, expectedPlatforms) { - t.Fatalf("platforms=%+v expected=%+v", platforms, expectedPlatforms) - } - expectedDevices := []types.DetectedDevice{{ Type: "npu", Bus: "mdla", - Nodes: []string{ - "mediatek,mt8188-apusys_rv", - "mediatek,mt8189-apusys_rv", - "mediatek,mt8195-apusys_rv", - "mediatek,mt9999-apusys_rv", + Metadata: &types.DeviceMetadata{ + VendorName: "mediatek", + ProductName: "genio-1200", + }, + }, { + Type: "npu", + Bus: "mdla", + Metadata: &types.DeviceMetadata{ + VendorName: "mediatek", + ProductName: "genio-510-700", + }, + }, { + Type: "npu", + Bus: "mdla", + Metadata: &types.DeviceMetadata{ + VendorName: "mediatek", + ProductName: "genio-520-720", + }, + }, { + Type: "npu", + Bus: "mdla", + Metadata: &types.DeviceMetadata{ + VendorName: "mediatek", + ProductName: "mediatek-mt9999", }, }} if !reflect.DeepEqual(devices, expectedDevices) { @@ -70,20 +79,18 @@ func TestInfo(t *testing.T) { t.Fatal(err) } - platforms, devices, err := Info() + devices, err := Info() if err != nil { t.Fatal(err) } - expectedPlatforms := []types.PlatformInfo{{Vendor: "mediatek", Name: "genio-510-700"}} - if !reflect.DeepEqual(platforms, expectedPlatforms) { - t.Fatalf("platforms=%+v expected=%+v", platforms, expectedPlatforms) - } - expectedDevices := []types.DetectedDevice{{ - Type: "npu", - Bus: "mdla", - Nodes: []string{"mediatek,mt8188-apusys_rv"}, + Type: "npu", + Bus: "mdla", + Metadata: &types.DeviceMetadata{ + VendorName: "mediatek", + ProductName: "genio-510-700", + }, }} if !reflect.DeepEqual(devices, expectedDevices) { t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) @@ -105,11 +112,11 @@ func TestInfoNoMatch(t *testing.T) { t.Fatal(err) } - platforms, devices, err := Info() + devices, err := Info() if err != nil { t.Fatal(err) } - if len(platforms) != 0 || len(devices) != 0 { - t.Fatalf("expected no mediatek npu match, got platforms=%+v devices=%+v", platforms, devices) + if len(devices) != 0 { + t.Fatalf("expected no mediatek npu match, got devices=%+v", devices) } -} \ No newline at end of file +}