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