blob: ea395f96a0f21ef0d7334033ba0de35e57d12701 [file] [log] [blame]
// Copyright 2021 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.
// FirmwareInstall state machine construction and helper
package firmwareservice
import (
"chromiumos/test/provision/cmd/provisionserver/bootstrap/services"
"context"
"errors"
"fmt"
"path"
conf "go.chromium.org/chromiumos/config/go"
"go.chromium.org/chromiumos/config/go/test/api"
"google.golang.org/grpc"
)
const FirmwarePathTmp = "/tmp/fw-provisioning-service/"
// FirmwareService inherits ServiceInterface
type FirmwareService struct {
connection services.ServiceAdapterInterface
mainRwPath *conf.StoragePath
mainRoPath *conf.StoragePath
ecRoPath *conf.StoragePath
pdRoPath *conf.StoragePath
}
const CurlWithRetriesArgsFW = "-S -s -v -# -C - --retry 3 --retry-delay 60"
func NewFirmwareService(dutName string, dutClient api.DutServiceClient, wiringConn *grpc.ClientConn, req *api.InstallFirmwareRequest) (*FirmwareService, error) {
fws, err := newConnectionlessFirmwareService(req)
if err != nil {
return nil, err
}
fws.connection = services.NewServiceAdapter(dutName, dutClient, wiringConn, false /*noReboot*/)
if err := fws.connection.CreateDirectories(context.Background(), []string{FirmwarePathTmp}); err != nil {
return nil, fmt.Errorf("failed to create folder %v: %w", FirmwarePathTmp, err)
}
return fws, nil
}
// NewFirmwareServiceFromExistingConnection is equivalent to the above constructor,
// but recycles a ServiceAdapter. Generally useful for tests.
func NewFirmwareServiceFromExistingConnection(connection services.ServiceAdapterInterface, req *api.InstallFirmwareRequest) (*FirmwareService, error) {
fws, err := newConnectionlessFirmwareService(req)
if err != nil {
return nil, err
}
fws.connection = connection
if err := fws.connection.CreateDirectories(context.Background(), []string{FirmwarePathTmp}); err != nil {
return nil, fmt.Errorf("failed to create folder %v: %w", FirmwarePathTmp, err)
}
return fws, nil
}
// newConnectionlessFirmwareService is a contructor helper function that builds
// FirmwareService given a request, but leaves connection nil.
func newConnectionlessFirmwareService(req *api.InstallFirmwareRequest) (*FirmwareService, error) {
if req.FirmwareConfig == nil {
return nil, errors.New("request.FirmwareConfig is nil")
}
// Firmware may be updated in write-protected mode, where only 'rw' regions
// would be update, or write-protection may be disabled (dangerous) in order
// to update 'ro' regions.
// The only 'rw' firmware is the main one, aka AP firmware.
// Do it with write protection.
mainRwPath := req.FirmwareConfig.MainRwPayload.GetFirmwareImagePath()
// We may need to update 'ro' AP, EC, and PD(non-existent by now?) firmware.
// Disable write protection.
mainRoPath := req.FirmwareConfig.MainRoPayload.GetFirmwareImagePath()
ecRoPath := req.FirmwareConfig.EcRoPayload.GetFirmwareImagePath()
pdRoPath := req.FirmwareConfig.PdRoPayload.GetFirmwareImagePath()
connection := services.ServiceAdapterInterface(nil)
fws := FirmwareService{
connection,
mainRwPath,
mainRoPath,
ecRoPath,
pdRoPath,
}
if !fws.UpdateRo() && !fws.UpdateRw() {
return nil, errors.New("request.FirmwareConfig: no paths to images specified")
}
return &fws, nil
}
func (fws *FirmwareService) UpdateRw() bool {
return fws.mainRwPath != nil
}
func (fws *FirmwareService) UpdateRo() bool {
return (fws.mainRoPath != nil) || (fws.ecRoPath != nil) || (fws.pdRoPath != nil)
}
// GetFirstState returns the first state of this state machine
func (fws *FirmwareService) GetFirstState() services.ServiceState {
if fws.UpdateRw() {
return FirmwareUpdateRwState{
service: *fws,
}
} else if fws.UpdateRo() {
return FirmwareUpdateRoState{
service: *fws,
}
}
return nil
}
// CleanupOnFailure is called if one of service's states failes to Execute() and
// should clean up the temporary files, and undo the execution, if feasible.
func (fws *FirmwareService) CleanupOnFailure(states []services.ServiceState, executionErr error) error {
// TODO(sfrolov): implement cleanup.
return nil
}
// CopyImageToDUT copies the desired image to the DUT, passing through the caching layer.
func (fws *FirmwareService) CopyImageToDUT(ctx context.Context, remotePath *conf.StoragePath, localFilename string) error {
if remotePath.HostType == conf.StoragePath_LOCAL || remotePath.HostType == conf.StoragePath_HOSTTYPE_UNSPECIFIED {
return fmt.Errorf("only GS copying is implemented")
}
if err := fws.connection.CopyData(ctx, remotePath.GetPath(), fws.GetImagePath(localFilename)); err != nil {
return fmt.Errorf("failed to cache %v: %w", remotePath.GetPath(), err)
}
return nil
}
// ExecuteWPFutility executes "futility" flashing utility with write protection.
// futility will be run with "--mode=autoupdate" and "--wp=1".
// futilityImageArg must include an argument to futility that provides a path
// to the AP image to be flashed.
func (fws FirmwareService) ExecuteWPFutility(ctx context.Context, futilityImageArg string) error {
if len(futilityImageArg) == 0 {
return fmt.Errorf("unable to flash: no futility Image args provided")
}
futilityArgs := []string{"update", "--mode=autoupdate", "--wp=1"}
futilityArgs = append(futilityArgs, futilityImageArg)
if _, err := fws.connection.RunCmd(ctx, "futility", futilityArgs); err != nil {
return err
}
return nil
}
// ExecuteFutility executes "futility" flashing utility without write protection
// futility will be run with "--mode=recovery" and "--wp=0"
// futilityImageArgs must include argument(s) to futility that provide path(s)
// to the AP/EC/PD images to be flashed.
func (fws FirmwareService) ExecuteFutility(ctx context.Context, futilityImageArgs []string) error {
if len(futilityImageArgs) == 0 {
return fmt.Errorf("unable to flash: no futility Image args provided")
}
futilityArgs := []string{"update", "--mode=recovery", "--wp=0"}
futilityArgs = append(futilityArgs, futilityImageArgs...)
if _, err := fws.connection.RunCmd(ctx, "futility", futilityArgs); err != nil {
return err
}
return nil
}
// GetImagePath returns joined path of directory with firmware and provided filename.
func (fws *FirmwareService) GetImagePath(filename string) string {
return path.Join(FirmwarePathTmp, filename)
}