blob: 6f56805f75efbf7aaea296de6f1a65a5466806f3 [file] [log] [blame]
# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""cros ap: firmware AP related commands."""
import argparse
import logging
import os
from pathlib import Path
from chromite.cli import command
from chromite.lib import build_target_lib
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.lib import path_util
from chromite.lib.firmware import dut
from chromite.lib.firmware import firmware_config
from chromite.lib.firmware import firmware_lib
@command.command_decorator("ap")
class APCommand(command.CommandGroup):
"""Execute an AP-related command."""
@APCommand.subcommand("build", dryrun=True)
class BuildSubcommand(command.CliCommand):
"""Build the AP Firmware for the requested build target."""
def __init__(self, options) -> None:
super().__init__(options)
self.build_target = build_target_lib.BuildTarget(
self.options.build_target
)
@classmethod
def AddParser(cls, parser) -> None:
"""Adds AP Build specific CLI arguments to parser."""
parser.add_argument(
"-b",
"--build-target",
dest="build_target",
default=cros_build_lib.GetDefaultBoard(),
required=not bool(cros_build_lib.GetDefaultBoard()),
help="The build target whose AP firmware should be built.",
)
parser.add_argument(
"--fw-name",
"--variant",
dest="fw_name",
help="Sets the FW_NAME environment variable. Set to build only the "
"specified variant's firmware.",
)
parser.epilog = """
To build the AP Firmware for foo:
cros ap build -b foo
To build the AP Firmware only for foo-variant:
cros ap build -b foo --fw-name foo-variant
"""
def Run(self) -> None:
commandline.RunInsideChroot(self)
try:
firmware_lib.build(
self.build_target,
fw_name=self.options.fw_name,
dry_run=self.options.dryrun,
)
except firmware_lib.Error as e:
cros_build_lib.Die(e)
@APCommand.subcommand("read", dryrun=True)
class ReadSubcommand(command.CliCommand):
"""Read the AP Firmware from a device."""
@classmethod
def ProcessOptions(cls, parser, options) -> None:
"""Post process options."""
if options.device is None:
parser.error("Specify device using --device argument.")
options.output_path = Path(options.output)
@classmethod
def AddParser(cls, parser) -> None:
"""Adds AP Read specific CLI arguments to parser."""
cls.AddDeviceArgument(
parser,
schemes=[
commandline.DeviceScheme.SSH,
commandline.DeviceScheme.SERVO,
],
)
parser.add_argument(
"-b",
"--build-target",
default=cros_build_lib.GetDefaultBoard(),
dest="build_target",
help="The name of the build target.",
)
parser.add_argument(
"-r" "--region", dest="region", type=str, help="Region to read."
)
parser.add_argument(
"-o",
"--output",
type="str_path",
required=True,
help="Output file.",
)
parser.epilog = """Command to read the AP firmware from a DUT.
To read image of device.cros via SSH:
cros ap read -b volteer -o /tmp/volteer-image.bin -d ssh://device.cros
If you don't have ssh access from within the chroot, you may set up ssh tunnel:
ssh -L 2222:localhost:22 device.cros
cros ap read -b volteer -o /tmp/volteer-image.bin -d ssh://localhost:2222
To read image from DUT via SERVO on port 1234:
cros ap read -b volteer -o /tmp/volteer-image.bin -d servo:port:1234
To read a specific region from DUT via SERVO on default port(9999):
cros ap read -b volteer -r region -o /tmp/volteer-image.bin -d servo:port
"""
def Run(self) -> None:
if not cros_build_lib.IsInsideChroot():
logging.notice(
"Command will run in chroot, "
"and the output file path will be inside."
)
commandline.RunInsideChroot(self)
build_target = build_target_lib.BuildTarget(self.options.build_target)
ip = None
if self.options.device:
port = self.options.device.port
if self.options.device.scheme == commandline.DeviceScheme.SSH:
ip = self.options.device.hostname
port = port or self.options.device.port
else:
ip = os.getenv("IP")
region = None
if hasattr(self.options, "region"):
region = self.options.region
if ip:
firmware_lib.ssh_read(
self.options.output,
self.options.verbose,
ip,
port,
self.options.dryrun,
region,
)
else:
dut_ctl = dut.DutControl(port)
servo = dut_ctl.get_servo()
ap_config = firmware_config.get_config(build_target.name, servo)
flashrom_cmd = [
"flashrom",
"-p",
ap_config.programmer,
"-r",
self.options.output,
]
if self.options.verbose:
flashrom_cmd += ["-V"]
if region:
flashrom_cmd += ["-i", self.options.region]
if not dut_ctl.servo_run(
ap_config.dut_control_on,
ap_config.dut_control_off,
flashrom_cmd,
self.options.verbose,
self.options.dryrun,
):
logging.error(
"Unable to read, verify servo connection "
"is correct and servod is running in the background."
)
@APCommand.subcommand("flash", dryrun=True)
class FlashSubcommand(command.CliCommand):
"""Update the AP Firmware on a device."""
@classmethod
def ProcessOptions(cls, parser, options) -> None:
"""Post process options."""
if not os.path.exists(options.image):
parser.error(
f"{options.image} does not exist, verify the path of your "
"build and try again."
)
if options.fast:
parser.error(
"Flags such as --fast must be passed directly after --\n"
"For futility use: cros ap flash ${OTHER_ARGS} -- --fast\n"
"For flashrom use: cros ap flash --flashrom ${OTHER_ARGS} -- -n"
)
@classmethod
def AddParser(cls, parser) -> None:
"""Adds AP Flash specific CLI arguments to parser."""
cls.AddDeviceArgument(
parser,
schemes=[
commandline.DeviceScheme.SSH,
commandline.DeviceScheme.SERVO,
],
)
parser.add_argument(
"-i",
"--image",
required=True,
type="str_path",
help="/path/to/BIOS_image.bin",
)
parser.add_argument(
"-b",
"--build-target",
default=cros_build_lib.GetDefaultBoard(),
dest="build_target",
help="The name of the build target.",
)
parser.add_argument(
"--flashrom",
action="store_true",
help="Use flashrom to flash instead of futility.",
)
parser.add_argument(
"--flash-contents",
type=str,
help="Assume flash contents to be the specified file. "
"Only available when using --flashrom.",
)
parser.add_argument(
"--fast",
action="store_true",
help="Deprecated. Pass your arbitrary flags after --.",
)
parser.add_argument(
"extra_options",
nargs=argparse.REMAINDER,
help="Pass additional options to flashrom/futility.",
)
parser.epilog = """
Command to flash the AP firmware onto a DUT.
To flash your zork DUT with an IP of 1.1.1.1 via SSH:
cros ap flash -b zork -i /path/to/image.bin -d ssh://1.1.1.1
To flash your volteer DUT via SERVO on the default port (9999):
cros ap flash -d servo:port -b volteer -i /path/to/image.bin
To flash your volteer DUT via SERVO on port 1234:
cros ap flash -d servo:port:1234 -b volteer -i /path/to/image.bin
To pass additional options to futility or flashrom, provide them after `--`,
e.g.:
cros ap flash -b zork -i /path/to/image.bin -d ssh://1.1.1.1 -- --force
"""
def Run(self) -> None:
commandline.RunInsideChroot(self)
passthrough_args = self.options.extra_options
if passthrough_args and passthrough_args[0] == "--":
del passthrough_args[0]
build_target = build_target_lib.BuildTarget(self.options.build_target)
try:
firmware_lib.deploy(
build_target,
self.options.image,
self.options.device,
flashrom=self.options.flashrom,
verbose=self.options.verbose,
dryrun=self.options.dryrun,
flash_contents=self.options.flash_contents,
passthrough_args=passthrough_args,
)
except firmware_lib.Error as e:
cros_build_lib.Die(e)
def TranslateToChrootArgv(self):
"""Get reexec args for cros ap flash."""
argv = super().TranslateToChrootArgv()
image = Path(self.options.image)
if image.exists():
# The image path is an outside the SDK path, translate it.
chroot_path = path_util.ToChrootPath(image)
if "-i" in argv:
# Update -i some/path.
argv[argv.index("-i") + 1] = chroot_path
elif "--image" in argv:
# Update --image some/path.
argv[argv.index("--image") + 1] = chroot_path
else:
# Update --image=some/path.
for arg in argv[:]:
if arg.startswith("--image="):
argv.remove(arg)
argv.extend(["--image", chroot_path])
break
return argv
@APCommand.subcommand("clean", dryrun=True)
class CleanSubcommand(command.CliCommand):
"""Clean up dependencies and artifacts for the requested build target."""
def __init__(self, options) -> None:
super().__init__(options)
self.build_target = build_target_lib.BuildTarget(
self.options.build_target
)
@classmethod
def AddParser(cls, parser) -> None:
"""Adds AP Clean specific CLI arguments to parser."""
parser.add_argument(
"-b",
"--build-target",
default=cros_build_lib.GetDefaultBoard(),
required=not bool(cros_build_lib.GetDefaultBoard()),
help="The build target whose artifacts should be cleaned.",
)
parser.epilog = """
This command removes firmware-related packages, including everything in
`/build/${build_target}/firmware`.
"""
def Run(self) -> None:
if not cros_build_lib.IsInsideChroot():
logging.notice(
"Command will run in chroot, "
"and the output file path will be inside."
)
commandline.RunInsideChroot(self)
try:
firmware_lib.clean(self.build_target, self.options.dryrun)
except firmware_lib.Error as e:
cros_build_lib.Die(e)