| # Copyright 2017 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. |
| |
| """Chrome OS Configuration access library. |
| |
| Provides build-time access to the master configuration on the host. It is used |
| for reading from the master configuration. Consider using cros_config_host.py |
| for CLI access to this library. |
| """ |
| |
| from __future__ import print_function |
| |
| from collections import namedtuple, OrderedDict |
| import os |
| import sys |
| |
| from . import validate_config |
| |
| # 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']) |
| |
| UNIBOARD_DTB_INSTALL_PATH = 'usr/share/chromeos-config/config.dtb' |
| |
| # We support two configuration file format |
| (FORMAT_FDT, FORMAT_YAML) = range(2) |
| |
| 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, self.name, |
| self.children and '/' or '')) |
| for child in sorted(self.children.keys()): |
| self.children[child].ShowTree(base_path, path, indent + 1) |
| |
| |
| def GetFilename(node_path, props, fname_template): |
| """Create a filename based on the given template and properties |
| |
| Args: |
| node_path: Path of the node generating this filename (for error |
| reporting only) |
| props: Dict of properties which can be used in the template: |
| key: Variable name |
| value: Value of that variable |
| fname_template: Filename template |
| """ |
| template = fname_template.replace('$', '') |
| try: |
| return template.format(props, **props) |
| except KeyError as e: |
| raise ValueError(("node '%s': Format string '%s' has properties '%s' " + |
| "but lacks '%s'") % |
| (node_path, template, props.keys(), e.message)) |
| |
| def GetPropFilename(node_path, props, fname_prop): |
| """Create a filename based on the given template and properties |
| |
| Args: |
| node_path: Path of the node generating this filename (for error |
| reporting only) |
| props: Dict of properties which can be used in the template: |
| key: Variable name |
| value: Value of that variable |
| fname_prop: Name of property containing the template |
| """ |
| template = props[fname_prop] |
| return GetFilename(node_path, props, template) |
| |
| |
| class CrosConfigImpl(object): |
| """The ChromeOS Configuration API for the host. |
| |
| CrosConfigImpl is the top level API for accessing ChromeOS Configs from the |
| host. |
| |
| Properties: |
| models: All models in the CrosConfigImpl tree, in the form of a dictionary: |
| <model name: string, model: CrosConfigImpl.Node> |
| root: Root node (CrosConigImpl.Node object) |
| validator: Validator for the config (CrosConfigValidator object) |
| """ |
| def __init__(self, infile): |
| self.infile = infile |
| self.models = OrderedDict() |
| self.validator = validate_config.GetValidator() |
| |
| def _GetProperty(self, absolute_path, property_name): |
| """Internal function to read a property from anywhere in the tree |
| |
| Args: |
| absolute_path: within the root node (e.g. '/chromeos/family/firmware') |
| property_name: Name of property to get |
| |
| Returns: |
| Property object, or None if not found |
| """ |
| return self.root.PathProperty(absolute_path, property_name) |
| |
| def GetFamilyNode(self, relative_path): |
| return self.family.PathNode(relative_path) |
| |
| def GetFamilyProperty(self, relative_path, property_name): |
| """Read a property from a family node |
| |
| Args: |
| relative_path: Relative path within the family (e.g. '/firmware') |
| property_name: Name of property to get |
| |
| Returns: |
| Property object, or None if not found |
| """ |
| return self.family.PathProperty(relative_path, property_name) |
| |
| def GetFirmwareUris(self): |
| """Returns a list of (string) firmware URIs. |
| |
| Generates and returns a list of firmeware URIs for all model. 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 model in self.models.values(): |
| uris.update(set(model.GetFirmwareUris())) |
| return sorted(list(uris)) |
| |
| def GetTouchFirmwareFiles(self): |
| """Get a list of unique touch firmware files for all models |
| |
| 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 models |
| """ |
| file_set = set() |
| for model in self.models.values(): |
| for files in model.GetTouchFirmwareFiles().values(): |
| 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 GetBspTarFiles(self): |
| """Get a list of tarfiles needed by the BSP ebuild |
| |
| It is possible to upload tarfiles to BCS which contain firmware used by the |
| BSP ebuild. These need to be unpacked so that the files are available to be |
| installed by the BSP ebuild. |
| |
| The files are stored in distdir by portage, so we can locate them there. |
| |
| Returns: |
| List of tarfile filenames, each a string |
| """ |
| tarfile_set = set() |
| for model in self.models.values(): |
| for fname in model.GetBspTarFiles().values(): |
| tarfile_set.add(fname) |
| |
| return sorted(tarfile_set) |
| |
| def GetArcFiles(self): |
| """Get a list of unique Arc++ files for all models |
| |
| Returns: |
| List of BaseFile objects representing all the arc++ files referenced |
| by all models |
| """ |
| file_set = set() |
| for model in self.models.values(): |
| for files in model.GetArcFiles().values(): |
| 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 model in self.models.values(): |
| for files in model.GetAudioFiles().values(): |
| 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. |
| """ |
| build_targets = [model.PathProperty('/firmware/build-targets', target_type) |
| for model in self.models.values()] |
| if target_type == 'ec': |
| build_targets += [model.PathProperty('/firmware/build-targets', 'cr50') |
| for model in self.models.values()] |
| # De-duplicate |
| build_targets_dedup = {target.value if target else None |
| for target in build_targets if target} |
| return list(build_targets_dedup) |
| |
| 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 models |
| """ |
| file_set = set() |
| for model in self.models.values(): |
| for files in model.GetThermalFiles().values(): |
| 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 containin 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 |
| |
| @staticmethod |
| def GetTargetDirectories(): |
| """Gets a dict of directory targets for each PropFile property |
| |
| Returns: |
| Dict: |
| key: Property name |
| value: Ansolute path for this property |
| """ |
| validator = validate_config.GetValidator() |
| return validator.GetTargetDirectories() |
| |
| def GetModelList(self): |
| """Return a list of models |
| |
| Returns: |
| List of model names, each a string |
| """ |
| return sorted(self.models.keys()) |
| |
| def GetFirmwareScript(self): |
| """Obtain the packer script to use for this family |
| |
| Returns: |
| Filename of packer script to use (e.g. 'updater4.sh') |
| """ |
| return self.GetFamilyProperty('/firmware', 'script').value |
| |
| def GetFirmwareInfo(self): |
| firmware_info = OrderedDict() |
| for name in self.GetModelList(): |
| firmware_info.update(self.models[name].GetFirmwareInfo()) |
| return firmware_info |
| |
| def GetTouchBspUris(self): |
| """Get the touch firmware BSP file URI |
| |
| Returns: |
| URI of touch firmware file to use, or None if none |
| """ |
| file_set = set() |
| for model in self.models.values(): |
| for files in model.GetTouchBspUris().values(): |
| file_set.add(files) |
| |
| return file_set |
| |
| def GetBspUris(self): |
| """Gets a list of URIs containing files required by the BSP |
| |
| This looks through the subsystems which support BCS (Binary Component |
| Server) storage and returns a list of URIs that the config needs. Each is |
| a tar file which is downloaded from BCS using the SRC_URI mechanism in the |
| ebuild. Once it is downloaded, individual files within the archive can |
| be accessed and installed. |
| |
| Returns: |
| List of URIs found (which may be empty) |
| """ |
| return list(self.GetTouchBspUris()) |
| |
| class Node(object): |
| """Represents a single node in the CrosConfig tree, including Model. |
| |
| A node can have several subnodes nodes, as well as several properties. Both |
| can be accessed via .subnodes and .properties respectively. A few helpers |
| are also provided to make node traversal a little easier. |
| |
| Properties: |
| name: The name of the this node. |
| subnodes: Child nodes, in the form of a dictionary: |
| <node name: string, child node: CrosConfigImpl.Node> |
| properties: All properties attached to this node in the form of a |
| dictionary: <name: string, property: CrosConfigImpl.Property> |
| """ |
| def __init__(self, cros_config): |
| self.cros_config = cros_config |
| self.subnodes = OrderedDict() |
| self.properties = OrderedDict() |
| self.default = None |
| self.submodels = {} |
| |
| def GetPath(self): |
| """Get the full path to a node, implemented by subclasses. |
| |
| Returns: |
| path to node as a string |
| """ |
| return '' |
| |
| def FollowShare(self): |
| """Follow a node's shares property |
| |
| Some nodes support sharing the properties of another node, e.g. firmware |
| and whitelabel. This follows that share if it can find it. We don't need |
| to be too careful to ignore invalid properties (e.g. whitelabel can only |
| be in a model node) since validation takes care of that. |
| |
| Returns: |
| Node that the share points to, or None if none |
| """ |
| # It's confusing that arc-properties-type shows up as a "shares" property |
| # here, but it has the same semantics as the other auto-follow properties |
| # and validation makes sure it can't appear in places where it shouldn't. |
| share_prop = [i for i in ['arc-properties-type', 'shares', 'whitelabel'] |
| if i in self.properties] |
| if share_prop: |
| return self.FollowPhandle(share_prop[0]) |
| return None |
| |
| def PathNode(self, relative_path): |
| """Returns the CrosConfigImpl.Node at the relative path. |
| |
| This method is useful for accessing a nested child object at a relative |
| path from a Node (or Model). The path must be separated with '/' |
| delimiters. Return None if the path is invalid. |
| |
| Args: |
| relative_path: A relative path string separated by '/', '/thermal' |
| |
| Returns: |
| A CrosConfigImpl.Node at the path, or None if it doesn't exist. |
| """ |
| if not relative_path: |
| return self |
| path_parts = [path for path in relative_path.split('/') if path] |
| if not path_parts: |
| return self |
| part = path_parts[0] |
| if part in self.subnodes: |
| sub_node = self.subnodes[part] |
| |
| # Handle a 'shares' property, which means we can grab nodes / properties |
| # from the associated node. |
| else: |
| shared = self.FollowShare() |
| if shared and part in shared.subnodes: |
| sub_node = shared.subnodes[part] |
| else: |
| return None |
| return sub_node.PathNode('/'.join(path_parts[1:])) |
| |
| def Property(self, property_name): |
| """Get a property from a node |
| |
| This is a trivial function but it does provide some insulation against our |
| internal data structures. |
| |
| Args: |
| property_name: Name of property to find |
| |
| Returns: |
| CrosConfigImpl.Property object that waws found, or None if none |
| """ |
| return self.properties.get(property_name) |
| |
| def GetStr(self, property_name): |
| """Get a string value from a property |
| |
| Args: |
| property_name: Name of property to access |
| |
| Returns: |
| String value of property, or '' if not present |
| """ |
| prop = self.Property(property_name) |
| return prop.value if prop else '' |
| |
| def GetStrList(self, property_name): |
| """Get a string-list value from a property |
| |
| Args: |
| property_name: Name of property to access |
| |
| Returns: |
| List of strings representing the value of the property, or [] if not |
| present |
| """ |
| prop = self.Property(property_name) |
| if not prop: |
| return [] |
| return prop.value |
| |
| def GetBool(self, property_name): |
| """Get a boolean value from a property |
| |
| Args: |
| property_name: Name of property to access |
| |
| Returns: |
| True if the property is present, False if not |
| """ |
| return property_name in self.properties |
| |
| def PathProperty(self, relative_path, property_name): |
| """Returns the value of a property relatative to this node |
| |
| This function honours the 'shared' property, by following the phandle and |
| searching there, at any component of the path. It also honours the |
| 'default' property which is defined for nodes. |
| |
| Args: |
| relative_path: A relative path string separated by '/', e.g. '/thermal' |
| property_name: Name of property to look up, e.g 'dptf-dv' |
| |
| Returns: |
| String value of property, or None if not found |
| """ |
| child_node = self.PathNode(relative_path) |
| if not child_node: |
| shared = self.FollowShare() |
| if shared: |
| child_node = shared.PathNode(relative_path) |
| if child_node: |
| prop = child_node.properties.get(property_name) |
| if not prop: |
| shared = child_node.FollowShare() |
| if shared: |
| prop = shared.properties.get(property_name) |
| if prop: |
| return prop |
| if self.default: |
| return self.default.PathProperty(relative_path, property_name) |
| return None |
| |
| def FollowPhandle(self, _prop_name): |
| """Follow a property's phandle, implemented by subclasses |
| |
| Args: |
| _prop_name: Property name to check |
| |
| Returns: |
| Node that the property's phandle points to, or None if none |
| """ |
| return None |
| |
| @staticmethod |
| def MergeProperties(props, node, ignore=''): |
| if node: |
| for name, prop in node.properties.iteritems(): |
| if (name not in props and not name.endswith('phandle') and |
| name != ignore): |
| props[name] = prop.value |
| |
| def GetMergedProperties(self, node, phandle_prop): |
| """Obtain properties in two nodes linked by a phandle |
| |
| This is used to create a dict of the properties in a main node along with |
| those found in a linked node. The link is provided by a property in the |
| main node containing a single phandle pointing to the linked node. |
| |
| The result is a dict that combines both nodes' properties, with the |
| linked node filling in anything missing. The main node's properties take |
| precedence. |
| |
| Phandle properties and 'reg' properties are not included. The 'default' |
| node is checked as well. |
| |
| Args: |
| node: Main node to obtain properties from |
| phandle_prop: Name of the phandle property to follow to get more |
| properties |
| |
| Returns: |
| dict containing property names and values from both nodes: |
| key: property name |
| value: property value |
| """ |
| # First get all the property keys/values from the main node |
| props = OrderedDict((prop.name, prop.value) |
| for prop in node.properties.values() |
| if prop.name not in [phandle_prop, 'bcs-type', 'reg']) |
| |
| # Follow the phandle and add any new ones we find |
| self.MergeProperties(props, node.FollowPhandle(phandle_prop), 'bcs-type') |
| if self.default: |
| # Get the path of this node relative to its model. For example: |
| # '/chromeos/models/pyro/thermal' will return '/thermal' in subpath. |
| # Once crbug.com/775229 is completed, we will be able to do this in a |
| # nicer way. |
| _, _, _, _, subpath = node.GetPath().split('/', 4) |
| default_node = self.default.PathNode(subpath) |
| if default_node: |
| self.MergeProperties(props, default_node, phandle_prop) |
| self.MergeProperties(props, default_node.FollowPhandle(phandle_prop)) |
| return props |
| |
| def ScanSubnodes(self): |
| """Collect a list of submodels""" |
| if (self.name in self.cros_config.models and |
| 'submodels' in self.subnodes.keys()): |
| for name, subnode in self.subnodes['submodels'].subnodes.iteritems(): |
| self.submodels[name] = subnode |
| |
| def SubmodelPathProperty(self, submodel_name, relative_path, property_name): |
| """Reads a property from a submodel. |
| |
| Args: |
| submodel_name: Submodel to read from |
| relative_path: A relative path string separated by '/'. |
| property_name: Name of property to read |
| |
| Returns: |
| Value of property as a string, or None if not found |
| """ |
| submodel = self.submodels.get(submodel_name) |
| if not submodel: |
| return None |
| return submodel.PathProperty(relative_path, property_name) |
| |
| def GetFirmwareUris(self): |
| """Returns a list of (string) firmware URIs. |
| |
| Generates and returns a list of firmeware URIs for this model. 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.PathNode('/firmware') |
| if not firmware or firmware.GetBool('no-firmware'): |
| return [] |
| props = self.GetMergedProperties(firmware, 'shares') |
| |
| if 'bcs-overlay' not in props: |
| return [] |
| # Strip "overlay-" from bcs_overlay |
| bcs_overlay = props['bcs-overlay'][8:] |
| ebuild_name = bcs_overlay.split('-')[0] |
| valid_images = [p for n, p in props.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}') |
| return [uri_format.format(bcs=bcs_overlay, model=self.name, fname=fname, |
| ebuild_name=ebuild_name) |
| for fname in file_names] |
| |
| def SetupModelProps(self, props): |
| props['model'] = self.name |
| props['MODEL'] = self.name.upper() |
| |
| def GetTouchFirmwareFiles(self): |
| """Get a list of unique touch firmware files |
| |
| Returns: |
| List of TouchFile objects representing the touch firmware referenced |
| by this model |
| """ |
| files = {} |
| for device_name, props, dirname, tarball in self.GetTouchBspInfo(): |
| # Add a special property for the capitalised model name |
| self.SetupModelProps(props) |
| fw_prop_name = 'firmware-bin' |
| fw_target_dir = self.cros_config.validator.GetModelTargetDir( |
| '/touch/ANY', fw_prop_name) |
| if not fw_target_dir: |
| raise ValueError(("node '%s': Property '%s' does not have a " + |
| "target directory (internal error)") % |
| (device_name, fw_prop_name)) |
| sym_prop_name = 'firmware-sym' |
| sym_target_dir = self.cros_config.validator.GetModelTargetDir( |
| '/touch/ANY', 'firmware-symlink') |
| if not sym_target_dir: |
| raise ValueError(("node '%s': Property '%s' does not have a " + |
| "target directory (internal error)") % |
| (device_name, sym_prop_name)) |
| src = GetPropFilename(self.GetPath(), props, fw_prop_name) |
| dest = src |
| sym_fname = GetPropFilename(self.GetPath(), props, 'firmware-symlink') |
| if tarball: |
| root, _ = os.path.splitext(os.path.basename(tarball)) |
| src_dir = os.path.join(root, fw_target_dir[1:]) |
| src = os.path.join(src_dir, os.path.basename(src)) |
| else: |
| src_dir = dirname |
| src = os.path.join(src_dir, src) |
| files[device_name] = TouchFile( |
| src, |
| os.path.join(fw_target_dir, dest), |
| os.path.join(sym_target_dir, sym_fname)) |
| |
| return files |
| |
| def GetTouchBspInfo(self, need_filesdir=True): |
| distdir = os.getenv('DISTDIR') |
| if not distdir: |
| raise ValueError('Cannot locate tar files unless DISTDIR is defined') |
| filesdir = os.getenv('FILESDIR') |
| if not filesdir and need_filesdir: |
| raise ValueError('Cannot locate BSP files unless FILESDIR is defined') |
| touch = self.PathNode('/touch') |
| if touch: |
| for device in touch.subnodes.values(): |
| props = self.GetMergedProperties(device, 'touch-type') |
| touch_type = device.FollowPhandle('touch-type') |
| bcs = device.FollowPhandle('bcs-type') |
| if not bcs and touch_type: |
| bcs = touch_type.FollowPhandle('bcs-type') |
| if bcs: |
| self.MergeProperties(props, bcs) |
| tarball = GetPropFilename(touch.GetPath(), props, 'tarball') |
| yield [device.name, props, distdir, tarball] |
| elif filesdir: |
| yield [device.name, props, filesdir, None] |
| |
| def GetBspTarFiles(self): |
| """Get a dict of tarfiles needed by the BSP ebuild for this model |
| |
| Returns: |
| Dict of tarfile filenames: |
| key: touch device which needs this tarfile |
| value: filename of tarfile |
| """ |
| files = {} |
| for device_name, _, distdir, tarball in self.GetTouchBspInfo(False): |
| if tarball: |
| fname = os.path.join(distdir, os.path.basename(tarball)) |
| files[device_name] = fname |
| return files |
| |
| def GetTouchBspUris(self): |
| """Get a dict of URIs needed by the BSP ebuild for this model |
| |
| Returns: |
| Dict of URIs: |
| key: touch device which needs this URI |
| value: URI (string) |
| """ |
| files = {} |
| for device_name, props, _, tarball in self.GetTouchBspInfo(False): |
| if tarball: |
| files[device_name] = self.cros_config.GetBcsUri(props['overlay'], |
| tarball) |
| return files |
| |
| def AllPathNodes(self, relative_path): |
| """List all path nodes which match the relative path (including submodels) |
| |
| This looks in the model and all its submodels for this relative path. |
| |
| Args: |
| relative_path: A relative path string separated by '/', '/thermal' |
| |
| Returns: |
| Dict of: |
| key: Name of this model/submodel |
| value: Node object for this model/submodel |
| """ |
| path_nodes = {} |
| node = self.PathNode(relative_path) |
| if node: |
| path_nodes[self.name] = node |
| for submodel_node in self.submodels.values(): |
| node = submodel_node.PathNode(relative_path) |
| if node: |
| path_nodes[submodel_node.name] = node |
| return path_nodes |
| |
| def GetArcFiles(self): |
| """Get a dict of arc++ files |
| |
| Returns: |
| Dict of BaseFile objects representing the arc++ files referenced |
| by this model: |
| key: property |
| value: BaseFile object |
| """ |
| files = {} |
| prop = 'hw-features' |
| arc = self.PathNode('/arc') |
| target_dir = self.cros_config.validator.GetModelTargetDir('/arc', prop) |
| if arc: |
| files['base'] = BaseFile( |
| arc.properties[prop].value, |
| os.path.join(target_dir, arc.properties[prop].value)) |
| return files |
| |
| def GetAudioFiles(self): |
| """Get a list of audio files |
| |
| Returns: |
| Dict of BaseFile objects representing the audio files referenced |
| by this model: |
| key: (model, property) |
| value: BaseFile object |
| """ |
| card = None # To keep pylint happy since we use it in this function: |
| name = '' |
| def _AddAudioFile(prop_name, dest_template, dirname=''): |
| """Helper to add a single audio file |
| |
| If present in the configuration, this adds an audio file containing the |
| source and destination file. |
| """ |
| if prop_name in props: |
| target_dir = self.cros_config.validator.GetModelTargetDir( |
| '/audio/ANY', prop_name) |
| if not target_dir: |
| raise ValueError(("node '%s': Property '%s' does not have a " + |
| "target directory (internal error)") % |
| (card.name, prop_name)) |
| files[name, prop_name] = BaseFile( |
| GetPropFilename(self.GetPath(), props, prop_name), |
| os.path.join( |
| target_dir, |
| dirname, |
| GetFilename(self.GetPath(), props, dest_template))) |
| |
| files = {} |
| audio_nodes = self.AllPathNodes('/audio') |
| for name, audio in audio_nodes.iteritems(): |
| for card in audio.subnodes.values(): |
| # First get all the property keys/values from the current node |
| props = self.GetMergedProperties(card, 'audio-type') |
| self.SetupModelProps(props) |
| |
| cras_dir = props.get('cras-config-dir') |
| if not cras_dir: |
| raise ValueError(("node '%s': Should have a cras-config-dir") % |
| (card.GetPath().path)) |
| _AddAudioFile('volume', '{card}', cras_dir) |
| _AddAudioFile('dsp-ini', 'dsp.ini', cras_dir) |
| |
| # Allow renaming this file to something other than HiFi.conf |
| _AddAudioFile('hifi-conf', |
| os.path.join('{card}.{ucm-suffix}', |
| os.path.basename(props['hifi-conf']))) |
| _AddAudioFile('alsa-conf', |
| '{card}.{ucm-suffix}/{card}.{ucm-suffix}.conf') |
| |
| # Non-Intel platforms don't use topology-bin |
| topology = props.get('topology-bin') |
| if topology: |
| _AddAudioFile('topology-bin', |
| os.path.basename(props.get('topology-bin'))) |
| |
| return files |
| |
| def GetThermalFiles(self): |
| """Get a dict of thermal files |
| |
| Returns: |
| Dict of BaseFile objects representing the thermal files referenced |
| by this model: |
| key: property |
| value: BaseFile object |
| """ |
| files = {} |
| prop = 'dptf-dv' |
| thermal = self.PathNode('/thermal') |
| target_dir = self.cros_config.validator.GetModelTargetDir('/thermal', |
| prop) |
| if thermal: |
| files['base'] = BaseFile( |
| thermal.properties[prop].value, |
| os.path.join(target_dir, thermal.properties[prop].value)) |
| return files |
| |
| def GetFirmwareInfo(self): |
| whitelabel = self.FollowPhandle('whitelabel') |
| base_model = whitelabel if whitelabel else self |
| firmware_node = self.PathNode('/firmware') |
| base_firmware_node = base_model.PathNode('/firmware') |
| |
| # If this model shares firmware with another model, get our |
| # images from there. |
| image_node = base_firmware_node.FollowPhandle('shares') |
| if image_node: |
| # Override the node - use the shared firmware instead. |
| node = image_node |
| shared_model = image_node.name |
| else: |
| node = base_firmware_node |
| shared_model = None |
| key_id = firmware_node.GetStr('key-id') |
| if firmware_node.GetBool('no-firmware'): |
| return {} |
| |
| have_image = True |
| if (whitelabel and base_firmware_node and |
| base_firmware_node.Property('sig-id-in-customization-id')): |
| # For zero-touch whitelabel devices, we don't need to generate anything |
| # since the device will never report this model at runtime. The |
| # signature ID will come from customization_id. |
| have_image = False |
| |
| # Firmware configuration supports both sharing the same fw image across |
| # multiple models and pinning specific models to different fw revisions. |
| # For context, see: |
| # https://chromium.googlesource.com/chromiumos/platform2/+/master/chromeos-config/README.md |
| # |
| # In order to support this, the firmware build target needs to be |
| # decoupled from models (since it can be shared). This was supported |
| # in the config with 'build-targets', which drives the actual firmware |
| # build/targets. |
| # |
| # This takes advantage of that same config to determine what the target |
| # FW image will be named in the build output. This allows a many to |
| # many mapping between models and firmware images. |
| build_node = node.PathNode('build-targets') |
| if build_node: |
| bios_build_target = build_node.GetStr('coreboot') |
| ec_build_target = build_node.GetStr('ec') |
| else: |
| bios_build_target, ec_build_target = None, None |
| |
| main_image_uri = node.GetStr('main-image') |
| main_rw_image_uri = node.GetStr('main-rw-image') |
| ec_image_uri = node.GetStr('ec-image') |
| pd_image_uri = node.GetStr('pd-image') |
| create_bios_rw_image = node.GetBool('create-bios-rw-image') |
| extra = node.GetStrList('extra') |
| |
| tools = node.GetStrList('tools') |
| |
| whitelabels = self.PathNode('/whitelabels') |
| if whitelabels or firmware_node.GetBool('sig-id-in-customization-id'): |
| sig_id = 'sig-id-in-customization-id' |
| else: |
| sig_id = self.name |
| |
| info = FirmwareInfo( |
| self.name, 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) |
| |
| # Handle the alternative schema, where whitelabels are in a single model |
| # and have whitelabel tags to distinguish them. |
| result = OrderedDict() |
| result[self.name] = info |
| if whitelabels: |
| for whitelabel in whitelabels.subnodes.values(): |
| key_id = whitelabel.GetStr('key-id') |
| whitelabel_name = '%s-%s' % (base_model.name, whitelabel.name) |
| result[whitelabel_name] = info._replace( |
| model=whitelabel_name, key_id=key_id, have_image=False, |
| sig_id=whitelabel_name) |
| return result |
| |
| class Property(object): |
| """Represents a single property in a ChromeOS Configuration.""" |
| def __init__(self): |
| pass |
| |
| def GetPhandle(self): |
| """Get the value of a property as a phandle, implemented by subclasses. |
| |
| Returns: |
| Property's phandle as an integer (> 0) |
| """ |
| return None |
| |
| |
| import libcros_config_host_fdt |
| import v2.libcros_config_host_json |
| |
| def CrosConfig(fname=None, config_format=None): |
| """Create a new CrosConfigImpl object |
| |
| This is in a separate function to allow us to (in the future) support YAML, |
| which will have a different means of creating the impl class. |
| """ |
| if not fname: |
| if 'SYSROOT' not in os.environ: |
| raise ValueError('No master configuration is available outside the ' |
| 'ebuild environemnt. You must specify one') |
| fname = os.path.join(os.environ['SYSROOT'], UNIBOARD_DTB_INSTALL_PATH) |
| if not config_format: |
| if fname and ('.yaml' in fname or '.json' in fname): |
| config_format = FORMAT_YAML |
| else: |
| config_format = FORMAT_FDT |
| # Allow files for backward compatibility with the old API. |
| # TODO(sjg@chromum.org): Remove this when the firmware updater is updated. |
| if isinstance(fname, file): |
| infile = fname |
| infile.seek(0) |
| elif fname == '-': |
| infile = sys.stdin |
| else: |
| infile = open(fname) |
| |
| if config_format == FORMAT_FDT: |
| return libcros_config_host_fdt.CrosConfigFdt(infile) |
| elif config_format == FORMAT_YAML: |
| # TODO(sjg@chromium.org): Move this to use CrosConfigImpl |
| return libcros_config_host_json.CrosConfigJson(infile) |
| else: |
| raise ValueError("Invalid config format '%s' requested" % config_format) |