diff --git a/pkg/engines/busses.go b/pkg/engines/busses.go index 1f2b2869..26158f38 100644 --- a/pkg/engines/busses.go +++ b/pkg/engines/busses.go @@ -12,6 +12,10 @@ func (device Device) validateBus(extraFields []string) error { return device.validatePci(extraFields) case "usb": return device.validateUsb(extraFields) + case "fastrpc": + return device.validateFastRpc(extraFields) + case "drpai": + return device.validateDrpAi(extraFields) case "": // default to pci bus return device.validatePci(extraFields) default: @@ -49,3 +53,61 @@ 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 +} + +func (device Device) validateDrpAi(extraFields []string) error { + if device.Type != "" && device.Type != "npu" { + return fmt.Errorf("drpai 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("drpai: invalid field: %s", fieldName) + } + } + } + + return nil +} diff --git a/pkg/engines/busses_test.go b/pkg/engines/busses_test.go index a8abd686..c77e2929 100644 --- a/pkg/engines/busses_test.go +++ b/pkg/engines/busses_test.go @@ -42,4 +42,22 @@ 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) + }) + + t.Run("DRP-AI bus with non-NPU type", func(t *testing.T) { + device.Bus = "drpai" + err := device.validate() + if err == nil { + t.Fatal("DRP-AI 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..8817eb08 100644 --- a/pkg/engines/devices_test.go +++ b/pkg/engines/devices_test.go @@ -130,6 +130,52 @@ 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) + }) + + t.Run("NPU drpai valid fields", func(t *testing.T) { + device = Device{Type: "npu", Bus: "drpai"} + nodeGlob := "/dev/drpai*" + device.NodeGlob = &nodeGlob + + err := device.validate() + if err != nil { + t.Fatalf("NPU drpai fields should be valid: %v", err) + } + }) + + t.Run("NPU drpai invalid fields", func(t *testing.T) { + device = Device{Type: "npu", Bus: "drpai"} + hexValue := types.HexInt(0xAA) + device.VendorId = &hexValue + + err := device.validate() + if err == nil { + t.Fatal("NPU drpai 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 a87bd62c..f2502b5a 100644 --- a/pkg/hardware_info/hardware-info.go +++ b/pkg/hardware_info/hardware-info.go @@ -4,12 +4,16 @@ import ( "encoding/json" "fmt" "os" + "strings" "testing" "github.com/canonical/inference-snaps-cli/pkg/hardware_info/cpu" "github.com/canonical/inference-snaps-cli/pkg/hardware_info/disk" + "github.com/canonical/inference-snaps-cli/pkg/hardware_info/mediatek" "github.com/canonical/inference-snaps-cli/pkg/hardware_info/memory" "github.com/canonical/inference-snaps-cli/pkg/hardware_info/pci" + "github.com/canonical/inference-snaps-cli/pkg/hardware_info/qualcomm" + "github.com/canonical/inference-snaps-cli/pkg/hardware_info/renesas" "github.com/canonical/inference-snaps-cli/pkg/types" ) @@ -40,6 +44,24 @@ func Get(friendlyNames bool) (*types.HwInfo, []string, error) { } hwInfo.PciDevices = pciDevices + qualcommDevices, err := qualcomm.Info() + if err != nil { + return nil, nil, fmt.Errorf("getting qualcomm devices: %v", err) + } + hwInfo.Devices = append(hwInfo.Devices, qualcommDevices...) + + mediatekDevices, err := mediatek.Info() + if err != nil { + return nil, nil, fmt.Errorf("getting mediatek devices: %v", err) + } + hwInfo.Devices = append(hwInfo.Devices, mediatekDevices...) + + renesasDevices, err := renesas.Info() + if err != nil { + return nil, nil, fmt.Errorf("getting renesas devices: %v", err) + } + hwInfo.Devices = append(hwInfo.Devices, renesasDevices...) + return &hwInfo, warnings, nil } @@ -100,6 +122,47 @@ func GetFromRawData(t *testing.T, device string, friendlyNames bool, testDir str } hwInfo.PciDevices = pciDevices + fastrpcNodesFile := devicePath + "fastrpc-nodes.txt" + if _, err := os.Stat(fastrpcNodesFile); err == nil { + nodesData, err := os.ReadFile(fastrpcNodesFile) + if err != nil { + t.Fatal(err) + } + + nodes := strings.Fields(string(nodesData)) + devices := qualcomm.DetectFromNodes(nodes) + hwInfo.Devices = devices + } else if !os.IsNotExist(err) { + t.Fatalf("error checking file '%s': %v\n", fastrpcNodesFile, err) + } + + mediatekCompatiblesFile := devicePath + "mediatek-compatible.txt" + if _, err := os.Stat(mediatekCompatiblesFile); err == nil { + compatiblesData, err := os.ReadFile(mediatekCompatiblesFile) + if err != nil { + t.Fatal(err) + } + + devices := mediatek.DetectFromCompatibles(strings.Fields(string(compatiblesData))) + hwInfo.Devices = append(hwInfo.Devices, devices...) + } else if !os.IsNotExist(err) { + t.Fatalf("error checking file '%s': %v\n", mediatekCompatiblesFile, err) + } + + drpAiNodesFile := devicePath + "drpai-nodes.txt" + if _, err := os.Stat(drpAiNodesFile); err == nil { + nodesData, err := os.ReadFile(drpAiNodesFile) + if err != nil { + t.Fatal(err) + } + + nodes := strings.Fields(string(nodesData)) + devices := renesas.DetectFromNodes(nodes) + hwInfo.Devices = append(hwInfo.Devices, devices...) + } else if !os.IsNotExist(err) { + t.Fatalf("error checking file '%s': %v\n", drpAiNodesFile, 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..2b07670f --- /dev/null +++ b/pkg/hardware_info/mediatek/mediatek.go @@ -0,0 +1,131 @@ +package mediatek + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +var deviceTreeRoot = "/sys/firmware/devicetree/base" + +var socToPlatform = map[string]string{ + "mt8195": "genio-1200", + "mt8188": "genio-510-700", + "mt8189": "genio-520-720", +} + +func Info() ([]types.DetectedDevice, error) { + compatibleValues, err := readCompatibleValues(deviceTreeRoot) + if err != nil { + return nil, fmt.Errorf("detecting mediatek apusys: %v", err) + } + + devices := DetectFromCompatibles(compatibleValues) + return devices, nil +} + +func DetectFromCompatibles(compatibleValues []string) []types.DetectedDevice { + platSet := make(map[string]struct{}) + + for _, value := range compatibleValues { + soc, ok := extractAPUSoC(value) + if !ok { + continue + } + + platformName, found := socToPlatform[soc] + if !found { + platformName = "mediatek-" + soc + } + + platSet[platformName] = struct{}{} + } + + if len(platSet) == 0 { + return nil + } + + platformNames := make([]string, 0, len(platSet)) + for name := range platSet { + platformNames = append(platformNames, name) + } + sort.Strings(platformNames) + + devices := make([]types.DetectedDevice, 0, len(platformNames)) + for _, name := range platformNames { + devices = append(devices, types.DetectedDevice{ + Type: "npu", + Bus: "mdla", + Metadata: &types.DeviceMetadata{ + VendorName: "mediatek", + ProductName: name, + }, + }) + } + + return devices +} + +func readCompatibleValues(root string) ([]string, error) { + var values []string + + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() || d.Name() != "compatible" { + return nil + } + + data, readErr := os.ReadFile(path) + if readErr != nil { + return readErr + } + + entries := parseCompatibleData(data) + values = append(values, entries...) + return nil + }) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + + return values, nil +} + +func parseCompatibleData(data []byte) []string { + parts := strings.Split(string(data), "\x00") + values := make([]string, 0, len(parts)) + + for _, part := range parts { + v := strings.TrimSpace(part) + if v == "" { + continue + } + values = append(values, v) + } + + return values +} + +func extractAPUSoC(compatible string) (string, bool) { + if !strings.HasPrefix(compatible, "mediatek,") || !strings.HasSuffix(compatible, "-apusys_rv") { + return "", false + } + + body := strings.TrimPrefix(compatible, "mediatek,") + soc := strings.TrimSuffix(body, "-apusys_rv") + if soc == "" || strings.Contains(soc, ",") { + return "", false + } + + return soc, true +} diff --git a/pkg/hardware_info/mediatek/mediatek_test.go b/pkg/hardware_info/mediatek/mediatek_test.go new file mode 100644 index 00000000..b996c29d --- /dev/null +++ b/pkg/hardware_info/mediatek/mediatek_test.go @@ -0,0 +1,122 @@ +package mediatek + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +func TestDetectFromCompatibles(t *testing.T) { + 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 + }) + + expectedDevices := []types.DetectedDevice{{ + Type: "npu", + Bus: "mdla", + 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) { + 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) + } + + devices, err := Info() + if err != nil { + t.Fatal(err) + } + + expectedDevices := []types.DetectedDevice{{ + 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) + } +} + +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) + } + + devices, err := Info() + if err != nil { + t.Fatal(err) + } + if len(devices) != 0 { + t.Fatalf("expected no mediatek npu match, got devices=%+v", devices) + } +} diff --git a/pkg/hardware_info/qualcomm/qualcomm.go b/pkg/hardware_info/qualcomm/qualcomm.go new file mode 100644 index 00000000..08d7bb93 --- /dev/null +++ b/pkg/hardware_info/qualcomm/qualcomm.go @@ -0,0 +1,71 @@ +package qualcomm + +import ( + "fmt" + "path/filepath" + "sort" + "strings" + + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +var devRoot = "/dev" + +func Info() ([]types.DetectedDevice, error) { + nodes, err := globSorted(filepath.Join(devRoot, "fastrpc-*")) + if err != nil { + return nil, fmt.Errorf("detecting fastrpc nodes: %v", err) + } + + return DetectFromNodes(nodes), nil +} + +func DetectFromNodes(nodes []string) []types.DetectedDevice { + var devices []types.DetectedDevice + + for _, node := range nodes { + node = strings.TrimSpace(node) + if node == "" { + continue + } + + base := filepath.Base(node) + if !strings.HasPrefix(base, "fastrpc-") { + continue + } + + devices = append(devices, types.DetectedDevice{ + Type: detectNpuType(base), + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: node, + }, + }) + } + + sort.Slice(devices, func(i, j int) bool { + return devices[i].Metadata.ProductName < devices[j].Metadata.ProductName + }) + + return devices +} + +func detectNpuType(nodeBaseName string) string { + const prefix = "fastrpc-" + if strings.HasPrefix(nodeBaseName, prefix) { + suffix := strings.TrimPrefix(nodeBaseName, prefix) + return fmt.Sprintf("NPU - %s", suffix) + } + return "NPU" +} + +func globSorted(pattern string) ([]string, error) { + matches, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + + sort.Strings(matches) + return matches, nil +} diff --git a/pkg/hardware_info/qualcomm/qualcomm_test.go b/pkg/hardware_info/qualcomm/qualcomm_test.go new file mode 100644 index 00000000..d189e526 --- /dev/null +++ b/pkg/hardware_info/qualcomm/qualcomm_test.go @@ -0,0 +1,109 @@ +package qualcomm + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +func TestInfo(t *testing.T) { + tmp := t.TempDir() + originalRoot := devRoot + devRoot = tmp + t.Cleanup(func() { + devRoot = originalRoot + }) + + _, err := os.Create(filepath.Join(tmp, "fastrpc-adsp-secure")) + if err != nil { + t.Fatal(err) + } + _, err = os.Create(filepath.Join(tmp, "fastrpc-cdsp")) + if err != nil { + t.Fatal(err) + } + _, err = os.Create(filepath.Join(tmp, "fastrpc-cdsp1")) + if err != nil { + t.Fatal(err) + } + + devices, err := Info() + if err != nil { + t.Fatal(err) + } + + expectedDevices := []types.DetectedDevice{ + { + Type: "NPU - adsp-secure", + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: filepath.Join(tmp, "fastrpc-adsp-secure"), + }, + }, + { + Type: "NPU - cdsp", + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: filepath.Join(tmp, "fastrpc-cdsp"), + }, + }, + { + Type: "NPU - cdsp1", + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: filepath.Join(tmp, "fastrpc-cdsp1"), + }, + }, + } + if !reflect.DeepEqual(devices, expectedDevices) { + t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) + } +} + +func TestDetectFromNodes(t *testing.T) { + nodes := []string{ + "/dev/fastrpc-cdsp1-secure", + "/dev/fastrpc-adsp", + "/dev/something-else", + "/dev/fastrpc-cdsp", + } + + devices := DetectFromNodes(nodes) + + expectedDevices := []types.DetectedDevice{ + { + Type: "NPU - adsp", + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-adsp", + }, + }, + { + Type: "NPU - cdsp", + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp", + }, + }, + { + Type: "NPU - cdsp1-secure", + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp1-secure", + }, + }, + } + + if !reflect.DeepEqual(devices, expectedDevices) { + t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) + } +} diff --git a/pkg/hardware_info/renesas/renesas.go b/pkg/hardware_info/renesas/renesas.go new file mode 100644 index 00000000..e6f14c67 --- /dev/null +++ b/pkg/hardware_info/renesas/renesas.go @@ -0,0 +1,62 @@ +package renesas + +import ( + "fmt" + "path/filepath" + "sort" + "strings" + + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +var devRoot = "/dev" + +func Info() ([]types.DetectedDevice, error) { + nodes, err := globSorted(filepath.Join(devRoot, "drpai*")) + if err != nil { + return nil, fmt.Errorf("detecting drpai nodes: %v", err) + } + + return DetectFromNodes(nodes), nil +} + +func DetectFromNodes(nodes []string) []types.DetectedDevice { + var devices []types.DetectedDevice + + for _, node := range nodes { + node = strings.TrimSpace(node) + if node == "" { + continue + } + + base := filepath.Base(node) + if !strings.HasPrefix(base, "drpai") { + continue + } + + devices = append(devices, types.DetectedDevice{ + Type: "npu", + Bus: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: node, + }, + }) + } + + sort.Slice(devices, func(i, j int) bool { + return devices[i].Metadata.ProductName < devices[j].Metadata.ProductName + }) + + return 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/renesas/renesas_test.go b/pkg/hardware_info/renesas/renesas_test.go new file mode 100644 index 00000000..dd38843b --- /dev/null +++ b/pkg/hardware_info/renesas/renesas_test.go @@ -0,0 +1,92 @@ +package renesas + +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, "drpai1")) + if err != nil { + t.Fatal(err) + } + _, err = os.Create(filepath.Join(tmp, "drpai0")) + if err != nil { + t.Fatal(err) + } + _, err = os.Create(filepath.Join(tmp, "something-else")) + if err != nil { + t.Fatal(err) + } + + devices, err := Info() + if err != nil { + t.Fatal(err) + } + + expectedDevices := []types.DetectedDevice{ + { + Type: "npu", + Bus: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: filepath.Join(tmp, "drpai0"), + }, + }, + { + Type: "npu", + Bus: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: filepath.Join(tmp, "drpai1"), + }, + }, + } + if !reflect.DeepEqual(devices, expectedDevices) { + t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) + } +} + +func TestDetectFromNodes(t *testing.T) { + nodes := []string{ + "/dev/drpai1", + "/dev/something-else", + "/dev/drpai0", + } + + devices := DetectFromNodes(nodes) + + expectedDevices := []types.DetectedDevice{ + { + Type: "npu", + Bus: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: "/dev/drpai0", + }, + }, + { + Type: "npu", + Bus: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: "/dev/drpai1", + }, + }, + } + + if !reflect.DeepEqual(devices, expectedDevices) { + t.Fatalf("devices=%+v expected=%+v", devices, expectedDevices) + } +} diff --git a/pkg/selector/drpai/drpai.go b/pkg/selector/drpai/drpai.go new file mode 100644 index 00000000..203e6585 --- /dev/null +++ b/pkg/selector/drpai/drpai.go @@ -0,0 +1,59 @@ +package drpai + +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/drpai*" + +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 != "drpai" { + continue + } + if manifestDevice.Type != "" && manifestDevice.Type != hostDevice.Type { + continue + } + if hostDevice.Metadata == nil || hostDevice.Metadata.ProductName == "" { + continue + } + + matched, err := filepath.Match(nodeGlob, hostDevice.Metadata.ProductName) + 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/drpai/drpai_test.go b/pkg/selector/drpai/drpai_test.go new file mode 100644 index 00000000..5201e2ed --- /dev/null +++ b/pkg/selector/drpai/drpai_test.go @@ -0,0 +1,77 @@ +package drpai + +import ( + "strings" + "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: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: "/dev/drpai0", + }, + }, + } + + device := engines.Device{Type: "npu", Bus: "drpai"} + score, issues := Match(device, hostDevices) + if len(issues) > 0 { + t.Fatalf("expected match, got issues=%v", issues) + } + if score <= 0 { + t.Fatalf("expected positive score, got %d", score) + } +} + +func TestMatchWithNodeGlob(t *testing.T) { + hostDevices := []types.DetectedDevice{ + { + Type: "npu", + Bus: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: "/dev/drpai0", + }, + }, + } + + nodeGlob := "/dev/drpai1" + device := engines.Device{Type: "npu", Bus: "drpai", NodeGlob: &nodeGlob} + score, issues := Match(device, hostDevices) + if score != 0 { + t.Fatalf("expected zero score, got %d", score) + } + if len(issues) == 0 || !strings.Contains(issues[0], "device node matching") { + t.Fatalf("expected node matching issue, got %v", issues) + } +} + +func TestMatchInvalidNodeGlob(t *testing.T) { + hostDevices := []types.DetectedDevice{ + { + Type: "npu", + Bus: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: "/dev/drpai0", + }, + }, + } + + nodeGlob := "[" + device := engines.Device{Type: "npu", Bus: "drpai", NodeGlob: &nodeGlob} + score, issues := Match(device, hostDevices) + if score != 0 { + t.Fatalf("expected zero score, got %d", score) + } + if len(issues) == 0 || !strings.Contains(issues[0], "invalid node-glob") { + t.Fatalf("expected invalid node glob issue, got %v", issues) + } +} diff --git a/pkg/selector/drpai_test.go b/pkg/selector/drpai_test.go new file mode 100644 index 00000000..6008f9e2 --- /dev/null +++ b/pkg/selector/drpai_test.go @@ -0,0 +1,53 @@ +package selector + +import ( + "testing" + + "github.com/canonical/inference-snaps-cli/pkg/engines" + "github.com/canonical/inference-snaps-cli/pkg/types" +) + +func TestDrpAiNpuSelection(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: "drpai", + Metadata: &types.DeviceMetadata{ + VendorName: "renesas", + ProductName: "/dev/drpai0", + }, + }, + }, + } + + manifest := engines.Manifest{ + Name: "renesas-npu", + Description: "renesas drp-ai npu", + Vendor: "renesas", + Grade: "stable", + Devices: engines.Devices{ + Anyof: []engines.Device{{ + Type: "npu", + Bus: "drpai", + }}, + }, + } + + 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/fastrpc/fastrpc.go b/pkg/selector/fastrpc/fastrpc.go new file mode 100644 index 00000000..ba0111be --- /dev/null +++ b/pkg/selector/fastrpc/fastrpc.go @@ -0,0 +1,79 @@ +package fastrpc + +import ( + "fmt" + "path/filepath" + "strings" + "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 !deviceTypeMatch(manifestDevice.Type, hostDevice.Type) { + continue + } + if hostDevice.Metadata == nil || hostDevice.Metadata.ProductName == "" { + continue + } + + matched, err := filepath.Match(nodeGlob, hostDevice.Metadata.ProductName) + 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)} +} + +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 new file mode 100644 index 00000000..1c849e1d --- /dev/null +++ b/pkg/selector/fastrpc/fastrpc_test.go @@ -0,0 +1,58 @@ +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", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: "/dev/fastrpc-cdsp", + }, + }, + { + Type: "npu", + Bus: "fastrpc", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: "/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..7f5796c7 --- /dev/null +++ b/pkg/selector/fastrpc_test.go @@ -0,0 +1,53 @@ +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", + Metadata: &types.DeviceMetadata{ + VendorName: "qualcomm", + ProductName: "/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..57c13ca0 100644 --- a/pkg/selector/select_stack.go +++ b/pkg/selector/select_stack.go @@ -8,6 +8,8 @@ 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/drpai" + "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 +167,24 @@ 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 == "drpai" { + drpAiScore, drpAiIssues := drpai.Match(devices[i], hardwareInfo.Devices) + if len(drpAiIssues) > 0 { + compatible = false + devices[i].CompatibilityIssues = append(devices[i].CompatibilityIssues, drpAiIssues...) + } else { + compatibilityScore += drpAiScore + } + } 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 +224,24 @@ 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 == "drpai" { + drpAiScore, drpAiIssues := drpai.Match(device, hardwareInfo.Devices) + if len(drpAiIssues) > 0 { + devices[i].CompatibilityIssues = append(device.CompatibilityIssues, drpAiIssues...) + } else { + devicesFound++ + compatibilityScore += drpAiScore + } + } 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..6595cadc --- /dev/null +++ b/pkg/types/detected_devices.go @@ -0,0 +1,12 @@ +package types + +type DeviceMetadata struct { + VendorName string `json:"vendor_name" yaml:"vendor_name"` + ProductName string `json:"product_name,omitempty" yaml:"product_name,omitempty"` +} + +type DetectedDevice struct { + Type string `json:"type" yaml:"type"` + Bus string `json:"bus" yaml:"bus"` + Metadata *DeviceMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"` +} diff --git a/pkg/types/hw_info.go b/pkg/types/hw_info.go index 9548c730..a8acc817 100644 --- a/pkg/types/hw_info.go +++ b/pkg/types/hw_info.go @@ -5,4 +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"` + Devices []DetectedDevice `json:"devices,omitempty" yaml:"devices,omitempty"` } 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