| // Copyright 2022 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Package hpsutil contains functionality used by the HPS tast tests. |
| package hpsutil |
| |
| import ( |
| "context" |
| "io/ioutil" |
| "os" |
| "path" |
| "path/filepath" |
| "regexp" |
| "strings" |
| "time" |
| |
| "chromiumos/tast/common/testexec" |
| "chromiumos/tast/errors" |
| "chromiumos/tast/testing" |
| ) |
| |
| const ( |
| // P2PowerCycleFilename is the name of the script that power-cycles the p2 board. |
| P2PowerCycleFilename = "proto2-power-cycle.py" |
| |
| // devboardDevice is the argument passed to the hps tool for SantaP2. |
| devboardDevice = "--mcp" |
| // builtinDevice is the argument passed to the hps tool for the builtin HPS module. |
| // builtinDevice = "--bus=/dev/i2c-15" |
| builtinDevice = "--bus=/dev/i2c-hps-controller" |
| |
| usbhidPath = "/sys/bus/usb/drivers/usbhid" |
| |
| // Stage1 verification done by Stage0 takes approximately 700ms. |
| // We want to use slightly longer pause to make this non-flaky. |
| firmwareVerificationDelay = time.Second * 2 |
| |
| // Paths to different firmware blobs. |
| hpsFirmwarePath = "/usr/lib/firmware/hps/" |
| stage1Name = "mcu_stage1.bin" |
| fpgaBitstreamName = "fpga_bitstream.bin" |
| fpgaApplicationName = "fpga_application.bin" |
| ) |
| |
| // DeviceType specifies which device to flash. |
| type DeviceType int |
| |
| const ( |
| // DeviceTypeDevboard is Santa p2 board plugged using USB. |
| DeviceTypeDevboard = iota |
| |
| // DeviceTypeBuiltin is the built-in HPS connected over i2c bus. |
| DeviceTypeBuiltin |
| ) |
| |
| func unbindUsbHid(hctx *HpsContext) error { |
| if hctx.DutConn != nil { |
| return remoteUnbindUsbHid(hctx) |
| } |
| return localUnbindUsbHid(hctx.Ctx) |
| } |
| |
| func remoteUnbindUsbHid(hctx *HpsContext) error { |
| dconn := hctx.DutConn |
| ctx := hctx.Ctx |
| list, err := dconn.CommandContext(ctx, "ls", usbhidPath).Output() |
| if err != nil { |
| return err |
| } |
| files := strings.Split(string(list), "\n") |
| |
| usbHidRegex := regexp.MustCompile(`^\d+`) |
| for _, file := range files { |
| matched := usbHidRegex.MatchString(file) |
| if matched { |
| testing.ContextLog(ctx, "usbhid: Unbinding ", file) |
| _, err := dconn.CommandContext(hctx.Ctx, "echo", file, ">>", |
| path.Join(usbhidPath, "unbind")).Output() |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| func localUnbindUsbHid(ctx context.Context) error { |
| // Unbind the USB to stop getting USB `claim interface: Resource busy` errors. |
| // This needs to be done once after SantaP2 is re-plugged. |
| files, err := ioutil.ReadDir(usbhidPath) |
| if err != nil { |
| return errors.Wrap(err, "unable to list files in usbhid") |
| } |
| |
| usbHidRegex := regexp.MustCompile(`^\d+`) |
| |
| for _, file := range files { |
| matched := usbHidRegex.MatchString(file.Name()) |
| if matched { |
| testing.ContextLog(ctx, "usbhid: Unbinding ", file.Name()) |
| err := ioutil.WriteFile(path.Join(usbhidPath, "unbind"), []byte(file.Name()), 0300) |
| if err != nil { |
| return errors.Wrapf(err, "unable to unbind %s", file.Name()) |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| func flashWithRetries(hctx *HpsContext, powerCycleFunc func(*HpsContext) error, cmd []string) error { |
| var err error |
| if err = RunHpsTool(hctx, cmd...); err != nil { |
| for i := 1; i < 3; i++ { |
| // Rarely it doesn't work on the first try, retry after delay. |
| testing.ContextLog(hctx.Ctx, "Failed. Retry ", i) |
| if err = powerCycleFunc(hctx); err != nil { |
| return err |
| } |
| if err = testing.Sleep(hctx.Ctx, time.Second*3); err != nil { |
| return errors.Wrap(err, "sleep failed") |
| } |
| if err = RunHpsTool(hctx, cmd...); err != nil { |
| RunStatus(hctx) |
| continue |
| } |
| break |
| } |
| } |
| RunStatus(hctx) |
| return err |
| } |
| |
| // EnsureLatestFirmware flashes all firmware to the HPS board. |
| // |
| // Prerequisites for Santa: |
| // 1. ./scripts/p2-stage0-run |
| // 2. cd mcu_rom/hps-factory && cargo run -- --mcp --one-time-init |
| func EnsureLatestFirmware(hctx *HpsContext) error { |
| testing.ContextLog(hctx.Ctx, "Started HPS firmware update") |
| |
| if err := hctx.PowerCycle(); err != nil { |
| return err |
| } |
| |
| // stage0: Expect it to be initialized by hpsd automatically. |
| // Nothing should break even if we don't update it. |
| // |
| // In order to update it manually run ./scripts/p2-stage0-run in hps-firmware on gLinux box. |
| |
| // Flash stage1. |
| // http://b/191716856: Read (retries exceeded) failed |
| powerCycleFunc := func(hctx *HpsContext) error { |
| return hctx.PowerCycle() |
| } |
| binFilepaths, err := decompressBin(hctx.Ctx) |
| if err != nil { |
| return errors.Wrap(err, "Decompress bin files failed") |
| } |
| stage1Path := binFilepaths[stage1Name] |
| fpgaBitstreamPath := binFilepaths[fpgaBitstreamName] |
| fpgaApplicationPath := binFilepaths[fpgaApplicationName] |
| tmpDir := filepath.Dir(stage1Path) |
| defer os.RemoveAll(tmpDir) |
| |
| if err := flashWithRetries(hctx, powerCycleFunc, []string{"dl", "0", stage1Path}); err != nil { |
| return errors.Wrap(err, "Need to update stage0 or replug the device?") |
| } |
| |
| // Reset stage0. |
| if err := RunHpsTool(hctx, "cmd", "reset"); err != nil { |
| return errors.Wrap(err, "Run this command in hps-firmware to fix dev board: cd hps-firmware2/mcu_rom/hps-factory && cargo run -- --mcp --one-time-init") |
| } |
| |
| if err := testing.Sleep(hctx.Ctx, firmwareVerificationDelay); err != nil { |
| return errors.Wrap(err, "sleep failed") |
| } |
| |
| if err := RunStatus(hctx); err != nil { |
| return err |
| } |
| |
| // Need to start hps in order to be able to do `dl 1`. |
| powerCyclePlusCmdLaunchFunc := func(hctx *HpsContext) error { |
| if err := hctx.PowerCycle(); err != nil { |
| return err |
| } |
| if err := RunHpsTool(hctx, "cmd", "launch"); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // Need to call `cmd launch` before flashing. |
| if err := powerCyclePlusCmdLaunchFunc(hctx); err != nil { |
| return err |
| } |
| |
| if err := flashWithRetries(hctx, powerCyclePlusCmdLaunchFunc, []string{"dl", "1", fpgaBitstreamPath}); err != nil { |
| return err |
| } |
| |
| // b/209533097: dml@ recommends to add a power-cycle here to work-around the SPI data corruption. |
| if err := powerCyclePlusCmdLaunchFunc(hctx); err != nil { |
| return err |
| } |
| |
| if err := flashWithRetries(hctx, powerCyclePlusCmdLaunchFunc, []string{"dl", "2", fpgaApplicationPath}); err != nil { |
| return err |
| } |
| |
| testing.ContextLog(hctx.Ctx, "Finished HPS firmware update") |
| return nil |
| } |
| |
| func decompressBin(ctx context.Context) (map[string]string, error) { |
| tmpDir, err := ioutil.TempDir("", "hps_firmware") |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to create test directory under /tmp") |
| } |
| testing.ContextLog(ctx, "tmp dir: ", tmpDir) |
| |
| firmwareTmpPath := strings.TrimSpace(string(tmpDir)) |
| |
| files, err := ioutil.ReadDir(hpsFirmwarePath) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to list files under firmware dir") |
| } |
| |
| paths := make(map[string]string) |
| for _, file := range files { |
| if !strings.Contains(file.Name(), ".xz") { |
| continue |
| } |
| |
| originPath := filepath.Join(hpsFirmwarePath, file.Name()) |
| filePrefix := strings.Replace(file.Name(), ".xz", "", -1) |
| |
| if _, err := testexec.CommandContext(ctx, "cp", originPath, firmwareTmpPath).Output(); err != nil { |
| return nil, errors.Wrap(err, "copying file failed") |
| } |
| if _, err := testexec.CommandContext(ctx, "unxz", filepath.Join(firmwareTmpPath, file.Name())).Output(); err != nil { |
| return nil, errors.Wrap(err, "failed to unzip the xz file") |
| } |
| paths[filePrefix] = filepath.Join(firmwareTmpPath, filePrefix) |
| } |
| return paths, nil |
| } |