blob: daceae3f0fc377fea53c32362d9622c5aa4cec93 [file] [log] [blame]
# Copyright 2018 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.
"""Base impl of the Chrome OS Configuration access library."""
from __future__ import print_function
from collections import namedtuple, OrderedDict
import os
# Represents a single touch firmware file which needs to be installed:
# source: source filename of firmware file. This is installed in a
# directory in the root filesystem
# dest: destination filename of firmware file in the root filesystem. This is
# in /opt/google/touch/firmware
# symlink: name of symbolic link to put in LIB_FIRMWARE to point to the touch
# firmware. This is where Linux finds the firmware at runtime.
TouchFile = namedtuple('TouchFile', ['source', 'dest', 'symlink'])
# Represents a single file which needs to be installed:
# source: Source filename within ${FILESDIR}
# dest: Destination filename in the root filesystem
BaseFile = namedtuple('BaseFile', ['source', 'dest'])
# Represents information needed to create firmware for a model:
# model: Name of model (e.g 'reef'). Also used as the signature ID for signing
# shared_model: Name of model containing the shared firmware used by this
# model, or None if this model has its own firmware images
# key_id: Key ID used to sign firmware for this model (e.g. 'REEF')
# have_image: True if we need to generate a setvars.sh file for this model.
# If this is False it indicates that the model will never be detected at
# run-time since it is a zero-touch whitelabel model. The signature ID
# will be obtained from the customization_id in VPD when needed. Signing
# instructions should still be generated for this model.
# bios_build_target: Build target to use to build the BIOS, or None if none
# ec_build_target: Build target to use to build the EC, or None if none
# main_image_uri: URI to use to obtain main firmware image (e.g.
# 'bcs://Caroline.2017.21.1.tbz2')
# ec_image_uri: URI to use to obtain the EC (Embedded Controller) firmware
# image
# pd_image_uri: URI to use to obtain the PD (Power Delivery controller)
# firmware image
# extra: List of extra files to include in the firmware update, each a string
# create_bios_rw_image: True to create a RW BIOS image
# tools: List of tools to include in the firmware update
# sig_id: Signature ID to put in the setvars.sh file. This is normally the
# same as the model, since that is what we use for signature ID. But for
# zero-touch whitelabel this is 'sig-id-in-customization-id' since we do
# not know the signature ID until we look up in VPD.
FirmwareInfo = namedtuple('FirmwareInfo', [
'model', 'shared_model', 'key_id', 'have_image', 'bios_build_target',
'ec_build_target', 'main_image_uri', 'main_rw_image_uri', 'ec_image_uri',
'pd_image_uri', 'extra', 'create_bios_rw_image', 'tools', 'sig_id'
])
class PathComponent(object):
"""A component in a directory/file tree
Properties:
name: Name this component
children: Dict of children:
key: Name of child
value: PathComponent object for child
"""
def __init__(self, name):
self.name = name
self.children = dict()
def AddPath(self, path):
parts = path.split('/', 1)
part = parts[0]
rest = parts[1] if len(parts) > 1 else ''
child = self.children.get(part)
if not child:
child = PathComponent(part)
self.children[part] = child
if rest:
child.AddPath(rest)
def ShowTree(self, base_path, path='', indent=0):
"""Show a tree of file paths
This shows a component and all its children. Nodes can either be directories
or files. Each file is shown with its size, or 'missing' if not found.
Args:
base_path: Base path where the actual files can be found
path: Path of this component relative to the root (e.g. 'etc/cras/)
indent: Indent level we are up to (0 = first)
"""
path = os.path.join(path, self.name)
fname = os.path.join(base_path, path)
if os.path.isdir(fname):
status = ''
elif os.path.exists(fname):
status = os.stat(fname).st_size
else:
status = 'missing'
print('%-10s%s%s%s' % (status, ' ' * indent, str(self.name),
self.children and '/' or ''))
for child in sorted(self.children.keys()):
self.children[child].ShowTree(base_path, path, indent + 1)
class DeviceConfig(object):
"""Configuration for a unique Device/SKU/Product combination.
Provides an abstraction layer between DTS/JSON for accessing config for a
unique Device/SKU/Product instance.
"""
def GetName(self):
"""Returns the name of the config.
Returns:
Name of he config
"""
pass
def GetProperties(self, path):
"""Returns a map of properties at the given config path.
Args:
path: Path to the config desired.
Returns:
A map of properties at the given config path.
"""
pass
def GetProperty(self, path, name):
"""Returns the name value at a given path.
Args:
path: Path to the config desired.
name: Property desired.
Returns:
Requested value or empty string if not present.
"""
pass
def GetFirmwareConfig(self):
"""Returns a map hierarchy of the firmware config."""
pass
def GetFirmwareUris(self):
"""Returns a list of (string) firmware URIs.
Generates and returns a list of firmeware URIs for this device. These URIs
can be used to pull down remote firmware packages.
Returns:
A list of (string) full firmware URIs, or an empty list on failure.
"""
firmware = self.GetFirmwareConfig()
if not firmware:
return []
if 'bcs-overlay' not in firmware:
return []
# Strip "overlay-" from bcs_overlay
bcs_overlay = firmware['bcs-overlay'][8:]
ebuild_name = bcs_overlay.split('-')[0]
valid_images = [p for n, p in firmware.iteritems()
if n.endswith('-image') and p.startswith('bcs://')]
# Strip "bcs://" from bcs_from images (to get the file names only)
file_names = [p[6:] for p in valid_images]
uri_format = ('gs://chromeos-binaries/HOME/bcs-{bcs}/overlay-{bcs}/'
'chromeos-base/chromeos-firmware-{ebuild_name}/{fname}')
uris = [uri_format.format(
bcs=bcs_overlay,
model=self.GetName(),
fname=fname,
ebuild_name=ebuild_name) for fname in file_names]
return sorted(uris)
def GetTouchFirmwareFiles(self):
"""Get a list of unique touch firmware files
Returns:
List of TouchFile objects representing the touch firmware referenced
by this model
"""
pass
def GetArcFiles(self):
"""Get a list of arc++ files for this device
Returns:
List of BaseFile objects representing the arc++ files needed.
"""
pass
def GetAudioFiles(self):
"""Get a list of audio files
Returns:
List of BaseFile objects representing the audio files referenced
by this device.
"""
pass
def GetThermalFiles(self):
"""Get a list of thermal files
Returns:
List of BaseFile objects representing the thermal files referenced
by this device.
"""
pass
def GetFirmwareInfo(self):
"""Gets the FirmewareInfo instance for a given device.
Returns:
Returns the FirmwareInfo instance.
"""
pass
def GetWallpaperFiles(self):
"""Get a set of wallpaper files used for this model"""
pass
class CrosConfigBaseImpl(object):
"""The ChromeOS Configuration API for the host."""
def GetConfig(self, name):
"""Gets a (DeviceConfig) instance by name.
Returns:
(DeviceConfig) instance if found, else None
"""
for device in self.GetDeviceConfigs():
if device.GetName() == name:
return device
return None
def GetDeviceConfigs(self):
"""Returns a list of (DeviceConfig) instances.
Returns:
A list of (DeviceConfig) instances.
"""
pass
def GetFullConfig(self):
"""Returns a full dict of every config returned from every API.
Returns:
Dictionary that maps method call onto return config.
"""
result = {}
result['GetFirmwareUris'] = self.GetFirmwareUris()
result['GetTouchFirmwareFiles'] = self.GetTouchFirmwareFiles()
result['GetArcFiles'] = self.GetArcFiles()
result['GetAudioFiles'] = self.GetAudioFiles()
result['GetThermalFiles'] = self.GetThermalFiles()
result['GetFirmwareInfo'] = self.GetFirmwareInfo()
result['GetWallpaperFiles'] = self.GetWallpaperFiles()
return result
def GetFirmwareUris(self):
"""Returns a list of (string) firmware URIs.
Generates and returns a list of firmeware URIs for all device. These URIs
can be used to pull down remote firmware packages.
Returns:
A list of (string) full firmware URIs, or an empty list on failure.
"""
uris = set()
for device in self.GetDeviceConfigs():
uris.update(set(device.GetFirmwareUris()))
return sorted(list(uris))
def GetTouchFirmwareFiles(self):
"""Get a list of unique touch firmware files for all devices
These files may come from ${FILESDIR} or from a tar file in BCS.
Returns:
List of TouchFile objects representing all the touch firmware referenced
by all devices
"""
file_set = set()
for device in self.GetDeviceConfigs():
for files in device.GetTouchFirmwareFiles():
file_set.add(files)
return sorted(file_set, key=lambda files: files.source)
def GetBcsUri(self, overlay, path):
"""Form a valid BCS URI for downloading files.
Args:
overlay: Name of overlay (e.g. 'reef-private')
path: Path to file in overlay (e.g. 'chromeos-base/'
'chromeos-touch-firmware-reef/chromeos-touch-firmware-reef-1.0-r9.tbz2')
Returns:
Valid BCS URI to download from
"""
if not overlay.startswith('overlay'):
return None
# Strip "overlay-" from bcs_overlay.
bcs_overlay = overlay[8:]
return (
'gs://chromeos-binaries/HOME/bcs-%(bcs)s/overlay-%(bcs)s/%(path)s' % {
'bcs': bcs_overlay,
'path': path
})
def GetArcFiles(self):
"""Get a list of unique Arc++ files for all devices
Returns:
List of BaseFile objects representing all the arc++ files referenced
by all devices
"""
file_set = set()
for device in self.GetDeviceConfigs():
for files in device.GetArcFiles():
file_set.add(files)
return sorted(file_set, key=lambda files: files.source)
def GetAudioFiles(self):
"""Get a list of unique audio files for all models
Returns:
List of BaseFile objects representing all the audio files referenced
by all models
"""
file_set = set()
for device in self.GetDeviceConfigs():
for files in device.GetAudioFiles():
file_set.add(files)
return sorted(file_set, key=lambda files: files.source)
def GetFirmwareBuildTargets(self, target_type):
"""Returns a list of all firmware build-targets of the given target type.
Args:
target_type: A string type for the build-targets to return
Returns:
A list of all build-targets of the given type, for all models.
"""
firmware_filter = os.getenv('FW_NAME')
build_targets = []
for device in self.GetDeviceConfigs():
device_targets = device.GetProperties('/firmware/build-targets')
# Skip nodes with no build targets
if not device_targets:
continue
# TODO(teravest): Add a name field and use here instead of coreboot.
key = device_targets['coreboot']
if firmware_filter and key != firmware_filter:
continue
if target_type in device_targets:
build_targets.append(device_targets[target_type])
if target_type == 'ec':
for ec_extra in ('base', 'cr50'):
if ec_extra in device_targets:
build_targets.append(device_targets[ec_extra])
return sorted(set(build_targets))
def GetFirmwareBuildCombinations(self, components):
"""Get named firmware build combinations for all devices.
Args:
components: List of firmware components to get target combinations for.
Returns:
OrderedDict containing firmware combinations
key: combination name
value: list of firmware targets for specified types
Raises:
ValueError if a collision is encountered for named combinations.
"""
firmware_filter = os.getenv('FW_NAME')
combos = OrderedDict()
for device in self.GetDeviceConfigs():
device_targets = device.GetProperties('/firmware/build-targets')
# Skip device_targetss with no build targets
if not device_targets:
continue
targets = [device_targets[c] for c in components]
# Always name firmware combinations after the 'coreboot' name.
# TODO(teravest): Add a 'name' field.
key = device_targets['coreboot']
if firmware_filter and key != firmware_filter:
continue
if key in combos and targets != combos[key]:
raise ValueError('Colliding firmware combinations found for key %s: '
'%s, %s' % (key, targets, combos[key]))
combos[key] = targets
return OrderedDict(sorted(combos.iteritems()))
def GetThermalFiles(self):
"""Get a list of unique thermal files for all models
Returns:
List of BaseFile objects representing all the audio files referenced
by all devices
"""
file_set = set()
for device in self.GetDeviceConfigs():
for files in device.GetThermalFiles():
file_set.add(files)
return sorted(file_set, key=lambda files: files.source)
def ShowTree(self, base_path, tree):
print('%-10s%s' % ('Size', 'Path'))
tree.ShowTree(base_path)
def GetFileTree(self):
"""Get a tree of all files installed by the config
This looks at all available config that installs files in the root and
returns them as a tree structure. This can be passed to ShowTree(), which
is the only feature currently implemented which uses this tree.
Returns:
PathComponent object containing the root component
"""
paths = set()
for item in self.GetAudioFiles():
paths.add(item.dest)
for item in self.GetTouchFirmwareFiles():
paths.add(item.dest)
paths.add(item.symlink)
root = PathComponent('')
for path in paths:
root.AddPath(path[1:])
return root
def GetModelList(self):
"""Return a list of models
Returns:
List of model names, each a string
"""
return sorted(set([device.GetName() for device in self.GetDeviceConfigs()]))
def GetFirmwareScript(self):
"""Obtain the packer script to use. Always updater4.sh
Returns:
Filename of packer script to use (e.g. 'updater4.sh')
"""
return 'updater4.sh'
def GetFirmwareInfo(self):
firmware_info = OrderedDict()
for name in self.GetModelList():
for device in self.GetDeviceConfigs():
if device.GetName() == name:
firmware_info.update(device.GetFirmwareInfo())
return firmware_info
def GetWallpaperFiles(self):
"""Get a list of wallpaper files used for all models"""
wallpapers = set()
for device in self.GetDeviceConfigs():
wallpapers |= device.GetWallpaperFiles()
return sorted(wallpapers)