• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020 The ChromiumOS Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Constraint checks related to firmware configuration."""
5
6import itertools
7import pathlib
8
9from checker import constraint_suite
10from common import proto_utils
11
12from chromiumos.config.payload import config_bundle_pb2
13from chromiumos.config.api import topology_pb2
14
15
16def _topo_to_string(topo):
17  return '{}:{}'.format(topology_pb2.Topology.Type.Name(topo.type), topo.id)
18
19
20class FirmwareConfigurationConstraintSuite(constraint_suite.ConstraintSuite):
21  """Constraint checks related to firmware configuration."""
22
23  def check_firmware_configuration_masks(
24      self,
25      program_config: config_bundle_pb2.ConfigBundle,
26      project_config: config_bundle_pb2.ConfigBundle,
27      factory_dir: pathlib.Path,
28  ):
29    """Checks firmware configuration masks are valid.
30
31    1. Check that the FirmwareConfigurationSegments defined in the program do
32    not overlap.
33    2. Check that each mask defined in a FirmwareConfiguration aligns with a
34    segment.
35    """
36    del factory_dir
37    fw_cfg_segments = {
38        program.id.value: program.firmware_configuration_segments
39        for program in program_config.program_list
40    }
41    for segments in fw_cfg_segments.values():
42      for segment_a, segment_b in itertools.combinations(segments, 2):
43        overlap = segment_a.mask & segment_b.mask
44        self.assertFalse(
45            overlap,
46            msg='Overlap in masks {} and {}: {:b} & {:b} = {:b}'.format(
47                segment_a.name, segment_b.name, segment_a.mask, segment_b.mask,
48                overlap))
49
50    # For every topology that defines a FirmwareConfiguration, check the mask
51    # aligns with a segment.
52    for design in project_config.design_list:
53      segments = fw_cfg_segments[design.program_id.value]
54      for config in design.configs:
55        for topology in proto_utils.get_all_fields(config.hardware_topology):
56          mask = topology.hardware_feature.fw_config.mask
57          for seg in segments:
58            overlap = mask & seg.mask
59            if overlap:
60              self.assertEqual(
61                  overlap, seg.mask,
62                  'Topology {} with fw_config mask 0x{:08X} did not specify the '
63                  'complete fw_config field "{}" with mask 0x{:08X}'.format(
64                      _topo_to_string(topology),
65                      topology.hardware_feature.fw_config.mask, seg.name,
66                      seg.mask))
67              # Remove the valid seg.mask to keep track of any extra mask in
68              # the topology value
69              mask -= overlap
70          # After looping through all valid fw_config masks, ensure that topo's
71          # value is empty
72          self.assertEqual(
73              mask, 0,
74              'Topology {} specifies fw_mask that is not known 0x{:08X}'.format(
75                  _topo_to_string(topology), mask))
76
77  def check_firmware_configuration_value_collision(
78      self,
79      program_config: config_bundle_pb2.ConfigBundle,
80      project_config: config_bundle_pb2.ConfigBundle,
81      factory_dir: pathlib.Path,
82  ):
83    """Checks that a given firmware value is only used by a single topology.
84
85    More precisely: For a given project, each FirmwareConfiguration.value is
86    used by exactly one (Topology.id, Topology.type) pair.
87
88    For example, the following is a violation:
89
90        thermal: <
91          id: "DEFAULT_THERMAL"
92          type: THERMAL
93          hardware_feature: <
94            fw_config: <
95              value: 11
96            >
97          >
98        >
99        screen: <
100          id: "DEFAULT_SCREEN"
101          type: SCREEN
102          hardware_feature: <
103            fw_config: <
104              value: 11
105            >
106          >
107        >
108
109    because both ("DEFAULT_THERMAL", THERMAL) and ("DEFAULT_SCREEN", SCREEN) use
110    value 11.
111    """
112    del program_config, factory_dir
113
114    # Map from FirmwareConfiguration.value -> (Topology.id, Topology.type).
115    value_to_topo = {}
116
117    for design in project_config.design_list:
118      for config in design.configs:
119        for topology in proto_utils.get_all_fields(config.hardware_topology):
120          fw_value = topology.hardware_feature.fw_config.value
121          if not fw_value:
122            continue
123
124          # Topologies must set id and type.
125          self.assertTrue(topology.id)
126          self.assertTrue(topology.type)
127          topo_key = (topology.id, topology.type)
128
129          prev_topo_key = value_to_topo.get(fw_value)
130          if prev_topo_key:
131            # We only need to ensure that the types are the same. Two different
132            # types cannot both set/control the same FW_CONFIG field value.
133            # It is allowed for two topology values of the same type to control
134            # the same FW_CONFIG field value (thus having the same FW_CONFIG
135            # value).
136            self.assertEqual(
137                topo_key[1],
138                prev_topo_key[1],
139                msg=('Topologies ({id1}, {type1}) and ({id2}, {type2}) both use'
140                     ' firmware value {fw_value}'
141                     ' but have different types').format(
142                         id1=topo_key[0],
143                         type1=topology_pb2.Topology.Type.Name(topo_key[1]),
144                         id2=prev_topo_key[0],
145                         type2=topology_pb2.Topology.Type.Name(
146                             prev_topo_key[1]),
147                         fw_value=fw_value))
148          else:
149            value_to_topo[fw_value] = topo_key
150