| # 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. |
| |
| """Crome 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 fdt, validate_config |
| |
| # Represents a single touch firmware file which needs to be installed: |
| # firmware: source filename of firmware file. This is installed in a |
| # directory in the root filesystem |
| # 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', ['firmware', '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']) |
| |
| # Known directories for installation |
| # TODO(sjg@chromium.org): Move these to the schema |
| LIB_FIRMWARE = '/lib/firmware' |
| |
| UNIBOARD_DTB_INSTALL_PATH = 'usr/share/chromeos-config/config.dtb' |
| |
| |
| 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 CrosConfig(object): |
| """The ChromeOS Configuration API for the host. |
| |
| CrosConfig is the top level API for accessing ChromeOS Configs from the host. |
| |
| Properties: |
| models: All models in the CrosConfig tree, in the form of a dictionary: |
| <model name: string, model: CrosConfig.Model> |
| phandle_to_node: Map of phandles to the assocated CrosConfig.Node: |
| key: Integer phandle value (>= 1) |
| value: Associated CrosConfig.Node object |
| """ |
| def __init__(self, infile=None): |
| if not infile: |
| 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) |
| with open(fname) as infile: |
| self._fdt = fdt.Fdt(infile) |
| else: |
| self._fdt = fdt.Fdt(infile) |
| self._fdt.Scan() |
| self.phandle_to_node = {} |
| self.models = OrderedDict() |
| self.root = CrosConfig.MakeNode(self, self._fdt.GetRoot()) |
| self.validator = validate_config.GetValidator() |
| self.family = self.root.subnodes['chromeos'].subnodes['family'] |
| |
| @staticmethod |
| def MakeNode(cros_config, fdt_node): |
| """Make a new Node in the tree |
| |
| This create a new Node or Model object in the tree and recursively adds all |
| subnodes to it. Any phandles found update the phandle_to_node map. |
| |
| Args: |
| cros_config: CrosConfig object |
| fdt_node: fdt.Node object containing the device-tree node |
| """ |
| if fdt_node.parent and fdt_node.parent.name == 'models': |
| node = CrosConfig.Model(cros_config, fdt_node) |
| cros_config.models[node.name] = node |
| else: |
| node = CrosConfig.Node(cros_config, fdt_node) |
| if 'phandle' in node.properties: |
| phandle = fdt_node.props['phandle'].GetPhandle() |
| cros_config.phandle_to_node[phandle] = node |
| for subnode in fdt_node.subnodes.values(): |
| node.subnodes[subnode.name] = CrosConfig.MakeNode(cros_config, subnode) |
| node.ScanSubnodes() |
| return node |
| |
| 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 |
| |
| 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.firmware) |
| |
| 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(): |
| # TODO(sjg@chromium.org): Move these constant paths into cros_config so |
| # that ebuilds do not need to specify them. crbug.com/769575 |
| paths.add(os.path.join('/opt/google/touch/firmware', item.firmware)) |
| paths.add(os.path.join(LIB_FIRMWARE, 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 |
| |
| 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: CrosConfig.Node> |
| properties: All properties attached to this node in the form of a |
| dictionary: <name: string, property: CrosConfig.Property> |
| """ |
| def __init__(self, cros_config, fdt_node): |
| self.cros_config = cros_config |
| self._fdt_node = fdt_node |
| self.name = fdt_node.name |
| # Subnodes are set up in Model.ScanSubnodes() |
| self.subnodes = OrderedDict() |
| self.properties = OrderedDict((n, CrosConfig.Property(p)) |
| for n, p in fdt_node.props.iteritems()) |
| self.default = None |
| |
| def ScanSubnodes(self): |
| """Do any post-processing needed after the node's subnodes are present""" |
| pass |
| |
| 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 |
| """ |
| share_prop = [i for i in ['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 CrosConfig.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 CrosConfig.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: |
| CrosConfig.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 |
| |
| Args: |
| prop_name: Property name to check |
| |
| Returns: |
| Node that the property's phandle points to, or None if none |
| """ |
| prop = self.properties.get(prop_name) |
| if not prop: |
| return None |
| return self.cros_config.phandle_to_node[prop.GetPhandle()] |
| |
| @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, _, 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. |
| |
| Args: |
| phandle_prop: Name of the phandle property to follow |
| |
| 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 current node |
| props = OrderedDict((prop.name, prop.value) |
| for prop in self.properties.values() |
| if prop.name not in [phandle_prop, 'reg']) |
| |
| # Follow the phandle and add any new ones we find |
| self.MergeProperties(props, self.FollowPhandle(phandle_prop)) |
| return props |
| |
| class Model(Node): |
| """Represents a ChromeOS Configuration Model. |
| |
| A CrosConfig.Model is a subclass of CrosConfig.Node, meaning it can be |
| traversed in the same manner. It also exposes helper functions |
| specific to ChromeOS Config models. |
| """ |
| def __init__(self, cros_config, fdt_node): |
| super(CrosConfig.Model, self).__init__(cros_config, fdt_node) |
| self.default = self.FollowPhandle('default') |
| self.submodels = {} |
| |
| def ScanSubnodes(self): |
| """Collect a list of submodels""" |
| if '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 GetMergedProperties(self, node, phandle_prop): |
| props = node.GetMergedProperties(None, phandle_prop) |
| 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._fdt_node.path.split('/', 4) |
| default_node = self.default.PathNode(subpath) |
| self.MergeProperties(props, default_node, phandle_prop) |
| self.MergeProperties(props, default_node.FollowPhandle(phandle_prop)) |
| return props |
| |
| 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: |
| 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 |
| """ |
| touch = self.PathNode('/touch') |
| files = {} |
| if touch: |
| for device in touch.subnodes.values(): |
| props = self.GetMergedProperties(device, 'touch-type') |
| |
| # Add a special property for the capitalised model name |
| self.SetupModelProps(props) |
| files[device.name] = TouchFile( |
| GetPropFilename(self._fdt_node.path, props, 'firmware-bin'), |
| GetPropFilename(self._fdt_node.path, props, 'firmware-symlink')) |
| 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 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._fdt_node.path, props, prop_name), |
| os.path.join( |
| target_dir, |
| dirname, |
| GetFilename(self._fdt_node.path, 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._fdt_node.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') |
| |
| 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. |
| |
| Properties: |
| name: The name of the property. |
| value: The value of the property. |
| type: The FDT type of the property (for now). |
| """ |
| def __init__(self, fdt_prop): |
| self._fdt_prop = fdt_prop |
| self.name = fdt_prop.name |
| self.value = fdt_prop.value |
| # TODO(athilenius): Transform these int types to something more useful |
| self.type = fdt_prop.type |
| |
| def GetPhandle(self): |
| """Get the value of a property as a phandle |
| |
| Returns: |
| Property's phandle as an integer (> 0) |
| """ |
| return self._fdt_prop.GetPhandle() |