blob: 7763e8ee5d365e65b1a1c09588890570877198a3 [file] [log] [blame]
# -*- coding: utf-8 -*-
# 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
import sys
# pylint: disable=wrong-import-position
this_dir = os.path.dirname(__file__)
sys.path.insert(0, this_dir)
from cros_config_schema import GetValidSchemaProperties
sys.path.pop(0)
# Represents a single symbolic link 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 target
# firmware. This is where Linux finds the firmware at runtime.
SymlinkedFile = namedtuple('SymlinkedFile', ['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
# 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.
# brand-code: Uniquely identifies a given brand (see go/chromeos-rlz)
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', 'sig_id', 'brand_code'
])
# Represents the firmware image for a model:
# type\: one of ‘ap’, ‘ec’, ‘pd’, ‘rw’.
# build_target: The build target for given firmware image.
# image_uri: The BCS image URI.
FirmwareImage = namedtuple('FirmwareImage',
['type', 'build_target', 'image_uri'])
# Represents the signer data for a device.
# key_id: The key ID of the device.
# sig_id: Teh signature ID of the device.
DeviceSignerInfo = namedtuple('DeviceSignerInfo', ['key_id', 'sig_id'])
class PathComponent(object):
"""A component in a directory/file tree
Attributes:
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(u'%-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
"""
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.
"""
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.
"""
def GetFirmwareConfig(self):
"""Returns a map hierarchy of the firmware config."""
return {}
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.items()
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 SymlinkedFile objects representing the touch firmware referenced
by this model
"""
def GetDetachableBaseFirmwareFiles(self):
"""Get a list of unique detachable base firmware files
Returns:
List of SymlinkedFile objects representing the detachable base firmware
referenced by this model
"""
def GetArcFiles(self):
"""Get a list of arc++ files for this device
Returns:
List of BaseFile objects representing the arc++ files needed.
"""
def GetAudioFiles(self):
"""Get a list of audio files
Returns:
List of BaseFile objects representing the audio files referenced
by this device.
"""
def GetBluetoothFiles(self):
"""Get a list of bluetooth config files
Returns:
List of BaseFile objects representing the bluetooth files referenced
by this device.
"""
def GetCameraFiles(self):
"""Get a list of camera config files
Returns:
List of BaseFile objects representing the camera files referenced
by this device.
"""
def GetThermalFiles(self):
"""Get a list of thermal files
Returns:
List of BaseFile objects representing the thermal files referenced
by this device.
"""
def GetIntelWifiSarFiles(self):
"""Get a list of intel wifi sar files
Returns:
List of BaseFile objects representing the intel wifi sar files referenced
for this device.
"""
def GetFirmwareInfo(self):
"""Gets the FirmewareInfo instance for a given device.
Returns:
Returns the FirmwareInfo instance.
"""
def GetFirmwareConfigs(self):
"""Gets unique firmware configs for all devices.
Returns:
Dictionary of FirmwareImage objects grouped by config name.
"""
def GetFirmwareConfigsByDevice(self):
"""Gets firmware config name for all devices.
Returns:
Dictionary of firmware config names grouped by device.
"""
def GetDeviceSignerInfo(self):
"""Gets firmware signer info for all devices.
Returns:
Dictionary of DeviceSignerInfo grouped by device.
"""
def GetWallpaperFiles(self):
"""Get a set of wallpaper files used for this model"""
def GetAutobrightnessFiles(self):
"""Get a list of autobrightness files
Returns:
List of BaseFile objects representing the autobrightness files referenced
by this device.
"""
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.
"""
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['ListModels'] = self.GetModelList()
result['GetFirmwareUris'] = self.GetFirmwareUris()
result['GetTouchFirmwareFiles'] = self.GetTouchFirmwareFiles()
result['GetDetachableBaseFirmwareFiles'] = \
self.GetDetachableBaseFirmwareFiles()
result['GetArcFiles'] = self.GetArcFiles()
result['GetAudioFiles'] = self.GetAudioFiles()
bluetooth_files = self.GetBluetoothFiles()
if bluetooth_files:
result['GetBluetoothFiles'] = bluetooth_files
result['GetCameraFiles'] = self.GetCameraFiles()
result['GetThermalFiles'] = self.GetThermalFiles()
result['GetIntelWifiSarFiles'] = self.GetIntelWifiSarFiles()
result['GetFirmwareInfo'] = self.GetFirmwareInfo()
for target in ['coreboot', 'ec']:
result['GetFirmwareBuildTargets_%s' % target] = \
self.GetFirmwareBuildTargets(target)
result['GetFirmwareBuildCombinations'] = \
self.GetFirmwareBuildCombinations(['coreboot', 'ec'])
result['GetWallpaperFiles'] = self.GetWallpaperFiles()
result['GetAutobrightnessFiles'] = self.GetAutobrightnessFiles()
schema_properties = GetValidSchemaProperties()
for device in self.GetDeviceConfigs():
value_map = {}
for path in schema_properties:
for schema_property in schema_properties[path]:
prop_value = device.GetProperty(path, schema_property)
# Only dump populated values; this makes it so the config dumps
# don't need to be updated when new schema attributes are added.
if prop_value:
value_map['%s::%s' % (path, schema_property)] = prop_value
result['GetProperty_%s' % device.GetName()] = value_map
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 _GetFiles(self, func_name):
"""Get a list of unique files for all devices.
Args:
func_name: name of method to invoke on a DeviceConfig to retrieve files.
Returns:
list of files sorted by source.
"""
file_set = set()
for device in self.GetDeviceConfigs():
for files in getattr(device, func_name)():
file_set.add(files)
return sorted(file_set, key=lambda files: files.source)
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 SymlinkedFile objects representing all the touch firmware
referenced by all devices
"""
return self._GetFiles('GetTouchFirmwareFiles')
def GetDetachableBaseFirmwareFiles(self):
"""Get a list of unique detachable base firmware files for all devices
These files may come from ${FILESDIR} or from a tar file in BCS.
Returns:
List of SymlinkedFile objects representing all the detachable base
firmware referenced by all devices
"""
return self._GetFiles('GetDetachableBaseFirmwareFiles')
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
"""
return self._GetFiles('GetArcFiles')
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
"""
return self._GetFiles('GetAudioFiles')
def GetBluetoothFiles(self):
"""Get a list of unique bluetooth files for all devices
Returns:
List of BaseFile objects representing all the bluetooth files referenced
by all devices
"""
return self._GetFiles('GetBluetoothFiles')
def GetCameraFiles(self):
"""Get a list of unique camera files for all devices
Returns:
List of BaseFile objects representing all the camera files referenced
by all devices
"""
return self._GetFiles('GetCameraFiles')
def _GetFirmwareGroupingName(self, config):
"""Gets the name of group of firmware build targets
Historically this maps to the name of the coreboot build target.
Args:
config: config object that contains /firmware node
Returns:
A string of the firmware group name
"""
# Use coreboot as key if it exist to support historical use case of
# grouping firmware build targets by coreboot name
key = config.GetProperty('/firmware/build-targets', 'coreboot')
if key:
return key
# Otherwise use the image-name. There are very few cases of having an
# image-name without also having a coreboot image
return config.GetProperty('/firmware', 'image-name')
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
key = self._GetFirmwareGroupingName(device)
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', ):
if ec_extra in device_targets:
build_targets.append(device_targets[ec_extra])
if 'ec_extras' in device_targets:
for extra_target in device_targets['ec_extras']:
build_targets.append(extra_target)
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.get(c) for c in components]
key = self._GetFirmwareGroupingName(device)
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.items()))
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
"""
return self._GetFiles('GetThermalFiles')
def GetIntelWifiSarFiles(self):
"""Get a list of unique intel wifi sar files for all models
Returns:
List of BaseFile objects representing all the intel wifi sar files
referenced by all devices
"""
return self._GetFiles('GetIntelWifiSarFiles')
def ShowTree(self, base_path, tree):
print(u'%-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 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)
def GetAutobrightnessFiles(self):
"""Get a list of unique autobrightness files for all models
Returns:
List of BaseFile objects representing all the autobrightness files
referenced by all devices
"""
return self._GetFiles('GetAutobrightnessFiles')