blob: 4680ce8e6f53067a7a91d961e35668f34dea2641 [file] [log] [blame]
David Burger7fd1dbe2020-03-26 09:26:55 -06001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2020 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Transforms config from /config/proto/api proto format to platform JSON."""
7
8import argparse
9import json
10import pprint
11import sys
12
13from collections import namedtuple
14
C Shapirofd83a5f2020-03-31 08:56:20 -050015from config.payload import config_bundle_pb2
David Burger7fd1dbe2020-03-26 09:26:55 -060016from config.api import device_brand_pb2
17from config.api.software import brand_config_pb2
18
19Config = namedtuple('Config',
20 ['program',
21 'hw_design',
22 'odm',
23 'hw_design_config',
24 'device_brand',
25 'oem',
26 'sw_config',
27 'brand_config',
28 'build_target'])
29
30
31def ParseArgs(argv):
32 """Parse the available arguments.
33
34 Invalid arguments or -h cause this function to print a message and exit.
35
36 Args:
37 argv: List of string arguments (excluding program name / argv[0])
38
39 Returns:
40 argparse.Namespace object containing the attributes.
41 """
42 parser = argparse.ArgumentParser(
43 description='Converts source proto config into platform JSON config.')
44 parser.add_argument(
45 '-c',
46 '--project_configs',
47 nargs='+',
48 type=str,
49 help='Space delimited list of source protobinary project config files.')
50 parser.add_argument(
51 '-p',
52 '--program_config',
53 type=str,
54 help='Path to the source program-level protobinary file')
55 parser.add_argument(
56 '-o',
57 '--output',
58 type=str,
59 help='Output file that will be generated')
60 return parser.parse_args(argv)
61
62
63def _Set(field, target, target_name):
64 if field:
65 target[target_name] = field
66
67
68def _BuildArc(config):
69 if config.build_target.arc:
70 build_properties = {
71 'device': config.build_target.arc.device,
72 'first-api-level': config.build_target.arc.first_api_level,
73 'marketing-name': config.device_brand.brand_name,
74 'metrics-tag': config.hw_design.name.lower(),
Andrew Lambb47b7dc2020-04-07 10:20:32 -060075 'product': config.build_target.id.value,
David Burger7fd1dbe2020-03-26 09:26:55 -060076 }
77 if config.oem:
78 build_properties['oem'] = config.oem.name
79 return {
80 'build-properties': build_properties
81 }
82
83
84def _BuildFingerprint(hw_topology):
Andrew Lambc2c55462020-04-06 08:43:34 -060085 if hw_topology.HasField('fingerprint'):
David Burger7fd1dbe2020-03-26 09:26:55 -060086 fp = hw_topology.fingerprint.hardware_feature.fingerprint
87 location = fp.Location.DESCRIPTOR.values_by_number[fp.location].name
88 result = {
89 'sensor-location': location.lower().replace('_', '-'),
90 }
91 if fp.board:
92 result['board'] = fp.board
93 return result
94
95
96def _FwBcsPath(payload):
97 if payload and payload.firmware_image_name:
98 return 'bcs://%s.%d.%d.0.tbz2' % (
99 payload.firmware_image_name,
100 payload.version.major,
101 payload.version.minor)
102
103
104def _FwBuildTarget(payload):
105 if payload:
106 return payload.build_target_name
107
108
109def _BuildFirmware(config):
David Burger7fd1dbe2020-03-26 09:26:55 -0600110 fw = config.sw_config.firmware
111 main_ro = fw.main_ro_payload
112 main_rw = fw.main_rw_payload
113 ec_ro = fw.ec_ro_payload
114 pd_ro = fw.pd_ro_payload
115
116 build_targets = {}
117 _Set(_FwBuildTarget(main_ro), build_targets, 'depthcharge')
118 # Default to RO build target if no RW set
119 _Set(_FwBuildTarget(main_rw) or _FwBuildTarget(main_ro),
120 build_targets,
121 'coreboot')
122 _Set(_FwBuildTarget(ec_ro), build_targets, 'ec')
123 _Set(list(fw.ec_extras), build_targets, 'ec_extras')
124 # Default to EC build target if no PD set
125 _Set(_FwBuildTarget(pd_ro) or _FwBuildTarget(ec_ro),
126 build_targets,
127 'libpayload')
128
129 result = {
130 'bcs-overlay': config.build_target.overlay_name,
131 'build-targets': build_targets,
David Burger7fd1dbe2020-03-26 09:26:55 -0600132 }
Andrew Lamb883fa042020-04-06 11:37:22 -0600133
134 _Set(main_ro.firmware_image_name.lower(), result, 'image-name')
135
136 if not any((
137 main_ro.firmware_image_name,
138 main_rw.firmware_image_name,
139 ec_ro.firmware_image_name,
140 pd_ro.firmware_image_name,
141 )):
Andrew Lambb9e660f2020-04-06 11:37:22 -0600142 result['no-firmware'] = True
Andrew Lamb883fa042020-04-06 11:37:22 -0600143
144 _Set(_FwBcsPath(main_ro), result, 'main-ro-image')
145 _Set(_FwBcsPath(main_rw), result, 'main-rw-image')
146 _Set(_FwBcsPath(ec_ro), result, 'ec-ro-image')
147 _Set(_FwBcsPath(pd_ro), result, 'pd-ro-image')
David Burger7fd1dbe2020-03-26 09:26:55 -0600148
149 return result
150
151
152def _BuildFwSigning(config):
153 if not config.sw_config.firmware:
154 return {}
155 # TODO(shapiroc): Source signing config from separate private repo
156 return {
157 'key-id': 'DEFAULT',
158 'signature-id': config.hw_design.name.lower(),
159 }
160
161
162def _File(source, destination):
163 return {
164 'destination': destination,
165 'source': source
166 }
167
168
169def _BuildAudio(config):
170 alsa_path = '/usr/share/alsa/ucm'
171 cras_path = '/etc/cras'
172 project_name = config.hw_design.name.lower()
Andrew Lamb7d536782020-04-07 10:23:55 -0600173 if not config.sw_config.HasField('audio_config'):
David Burger7fd1dbe2020-03-26 09:26:55 -0600174 return {}
175 audio = config.sw_config.audio_config
176 card = audio.card_name
David Burger599ff7b2020-04-06 16:29:31 -0600177 card_with_suffix = audio.card_name
178 if audio.ucm_suffix:
179 card_with_suffix += '.' + audio.ucm_suffix
David Burger7fd1dbe2020-03-26 09:26:55 -0600180 files = []
181 if audio.ucm_file:
David Burger599ff7b2020-04-06 16:29:31 -0600182 files.append(_File(
183 audio.ucm_file,
184 '%s/%s/HiFi.conf' % (alsa_path, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600185 if audio.ucm_master_file:
186 files.append(_File(
David Burger599ff7b2020-04-06 16:29:31 -0600187 audio.ucm_master_file,
188 '%s/%s/%s.conf' % (alsa_path, card_with_suffix, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600189 if audio.card_config_file:
190 files.append(_File(
191 audio.card_config_file, '%s/%s/%s' % (cras_path, project_name, card)))
192 if audio.dsp_file:
193 files.append(
David Burger2e254902020-04-02 16:56:01 -0600194 _File(audio.dsp_file, '%s/%s/dsp.ini' % (cras_path, project_name)))
David Burger599ff7b2020-04-06 16:29:31 -0600195
196 result = {
David Burger7fd1dbe2020-03-26 09:26:55 -0600197 'main': {
198 'cras-config-dir': project_name,
199 'files': files,
200 }
201 }
David Burger599ff7b2020-04-06 16:29:31 -0600202 if audio.ucm_suffix:
David Burger03cdcbd2020-04-13 13:54:48 -0600203 result['main']['ucm-suffix'] = audio.ucm_suffix
David Burger599ff7b2020-04-06 16:29:31 -0600204
205 return result
David Burger7fd1dbe2020-03-26 09:26:55 -0600206
207
Andrew Lamb7806ce92020-04-07 10:22:17 -0600208def _BuildIdentity(hw_scan_config, program, brand_scan_config=None):
David Burger7fd1dbe2020-03-26 09:26:55 -0600209 identity = {}
210 _Set(hw_scan_config.firmware_sku, identity, 'sku-id')
211 _Set(hw_scan_config.smbios_name_match, identity, 'smbios-name-match')
Andrew Lamb7806ce92020-04-07 10:22:17 -0600212 # 'platform-name' is needed to support 'mosys platform name'. Clients should
213 # longer require platform name, but set it here for backwards compatibility.
214 _Set(program.name, identity, 'platform-name')
David Burger7fd1dbe2020-03-26 09:26:55 -0600215 # ARM architecture
216 _Set(hw_scan_config.device_tree_compatible_match, identity,
217 'device-tree-compatible-match')
218
219 if brand_scan_config:
220 _Set(brand_scan_config.whitelabel_tag, identity, 'whitelabel-tag')
221
222 return identity
223
224
225def _Lookup(id_value, id_map):
226 if id_value.value:
227 key = id_value.value
228 if key in id_map:
229 return id_map[id_value.value]
230 error = 'Failed to lookup %s with value: %s' % (
231 id_value.__class__.__name__.replace('Id', ''), key)
232 print(error)
233 print('Check the config contents provided:')
234 pp = pprint.PrettyPrinter(indent=4)
235 pp.pprint(id_map)
236 raise Exception(error)
237
238
239def _TransformBuildConfigs(config):
240 partners = dict([(x.id.value, x) for x in config.partners.value])
241 programs = dict([(x.id.value, x) for x in config.programs.value])
David Burger7fd1dbe2020-03-26 09:26:55 -0600242 sw_configs = list(config.software_configs)
243 brand_configs = dict([(x.brand_id.value, x) for x in config.brand_configs])
244
C Shapiroa0b766c2020-03-31 08:35:28 -0500245 if len(config.build_targets) != 1:
246 # Artifact of sharing the config_bundle for analysis and transforms.
247 # Integrated analysis of multiple programs/projects it the only time
248 # having multiple build targets would be valid.
249 raise Exception('Single build_target required for transform')
250
David Burger7fd1dbe2020-03-26 09:26:55 -0600251 results = {}
252 for hw_design in config.designs.value:
253 if config.device_brands.value:
254 device_brands = [x for x in config.device_brands.value
255 if x.design_id.value == hw_design.id.value]
256 else:
257 device_brands = [device_brand_pb2.DeviceBrand()]
258
259 for device_brand in device_brands:
260 # Brand config can be empty since platform JSON config allows it
261 brand_config = brand_config_pb2.BrandConfig()
262 if device_brand.id.value in brand_configs:
263 brand_config = brand_configs[device_brand.id.value]
264
265 for hw_design_config in hw_design.configs:
266 design_id = hw_design_config.id.value
267 sw_config_matches = [x for x in sw_configs
268 if x.design_config_id.value == design_id]
269 if len(sw_config_matches) == 1:
270 sw_config = sw_config_matches[0]
271 elif len(sw_config_matches) > 1:
272 raise Exception('Multiple software configs found for: %s' % design_id)
273 else:
274 raise Exception('Software config is required for: %s' % design_id)
275
276 transformed_config = _TransformBuildConfig(Config(
277 program=_Lookup(hw_design.program_id, programs),
278 hw_design=hw_design,
279 odm=_Lookup(hw_design.odm_id, partners),
280 hw_design_config=hw_design_config,
281 device_brand=device_brand,
282 oem=_Lookup(device_brand.oem_id, partners),
283 sw_config=sw_config,
284 brand_config=brand_config,
C Shapiroa0b766c2020-03-31 08:35:28 -0500285 build_target=config.build_targets[0]))
David Burger7fd1dbe2020-03-26 09:26:55 -0600286
287 config_json = json.dumps(transformed_config,
288 sort_keys=True,
289 indent=2,
290 separators=(',', ': '))
291
292 if config_json not in results:
293 results[config_json] = transformed_config
294
295 return list(results.values())
296
297
298def _TransformBuildConfig(config):
299 """Transforms Config instance into target platform JSON schema.
300
301 Args:
302 config: Config namedtuple
303
304 Returns:
305 Unique config payload based on the platform JSON schema.
306 """
307 result = {
308 'identity': _BuildIdentity(
309 config.sw_config.id_scan_config,
Andrew Lamb7806ce92020-04-07 10:22:17 -0600310 config.program,
David Burger7fd1dbe2020-03-26 09:26:55 -0600311 config.brand_config.scan_config),
312 'name': config.hw_design.name.lower(),
313 }
314
315 _Set(_BuildArc(config), result, 'arc')
316 _Set(_BuildAudio(config), result, 'audio')
317 _Set(config.device_brand.brand_code, result, 'brand-code')
318 _Set(_BuildFirmware(config), result, 'firmware')
319 _Set(_BuildFwSigning(config), result, 'firmware-signing')
320 _Set(_BuildFingerprint(
321 config.hw_design_config.hardware_topology), result, 'fingerprint')
322 power_prefs = config.sw_config.power_config.preferences
323 power_prefs_map = dict(
324 (x.replace('_', '-'),
325 power_prefs[x]) for x in power_prefs)
326 _Set(power_prefs_map, result, 'power')
327
328 return result
329
330
331def WriteOutput(configs, output=None):
332 """Writes a list of configs to platform JSON format.
333
334 Args:
335 configs: List of config dicts defined in cros_config_schema.yaml
336 output: Target file output (if None, prints to stdout)
337 """
338 json_output = json.dumps(
339 {'chromeos': {
340 'configs': configs,
341 }},
342 sort_keys=True,
343 indent=2,
344 separators=(',', ': '))
345 if output:
346 with open(output, 'w') as output_stream:
347 # Using print function adds proper trailing newline.
348 print(json_output, file=output_stream)
349 else:
350 print(json_output)
351
352
353def _ReadConfig(path):
354 """Reads a binary proto from a file.
355
356 Args:
357 path: Path to the binary proto.
358 """
359 config = config_bundle_pb2.ConfigBundle()
360 with open(path, 'rb') as f:
361 config.ParseFromString(f.read())
362 return config
363
364
365def _MergeConfigs(configs):
366 result = config_bundle_pb2.ConfigBundle()
367 for config in configs:
368 result.MergeFrom(config)
369
370 return result
371
372
373def Main(project_configs,
374 program_config,
375 output):
376 """Transforms source proto config into platform JSON.
377
378 Args:
379 project_configs: List of source project configs to transform.
380 program_config: Program config for the given set of projects.
381 output: Output file that will be generated by the transform.
382 """
383 WriteOutput(
384 _TransformBuildConfigs(
385 _MergeConfigs(
386 [_ReadConfig(program_config)] +
387 [_ReadConfig(config) for config in project_configs],)
388 ,),
389 output)
390
391
392def main(argv=None):
393 """Main program which parses args and runs
394
395 Args:
396 argv: List of command line arguments, if None uses sys.argv.
397 """
398 if argv is None:
399 argv = sys.argv[1:]
400 opts = ParseArgs(argv)
401 Main(opts.project_configs, opts.program_config, opts.output)
402
403
404if __name__ == '__main__':
405 sys.exit(main(sys.argv[1:]))