blob: 44c38c2f431b66fd2fdc286804f4729224b4c1e1 [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
Prathmesh Prabhu72f8a002020-04-10 09:57:53 -070015from chromiumos.config.payload import config_bundle_pb2
16from chromiumos.config.api import device_brand_pb2
17from chromiumos.config.api.software import brand_config_pb2
David Burger7fd1dbe2020-03-26 09:26:55 -060018
19Config = namedtuple('Config',
20 ['program',
21 'hw_design',
22 'odm',
23 'hw_design_config',
24 'device_brand',
C Shapiro2f0bb5d2020-04-14 10:07:47 -050025 'device_signer_config',
David Burger7fd1dbe2020-03-26 09:26:55 -060026 'oem',
27 'sw_config',
28 'brand_config',
29 'build_target'])
30
31
32def ParseArgs(argv):
33 """Parse the available arguments.
34
35 Invalid arguments or -h cause this function to print a message and exit.
36
37 Args:
38 argv: List of string arguments (excluding program name / argv[0])
39
40 Returns:
41 argparse.Namespace object containing the attributes.
42 """
43 parser = argparse.ArgumentParser(
44 description='Converts source proto config into platform JSON config.')
45 parser.add_argument(
46 '-c',
47 '--project_configs',
48 nargs='+',
49 type=str,
50 help='Space delimited list of source protobinary project config files.')
51 parser.add_argument(
52 '-p',
53 '--program_config',
54 type=str,
55 help='Path to the source program-level protobinary file')
56 parser.add_argument(
57 '-o',
58 '--output',
59 type=str,
60 help='Output file that will be generated')
61 return parser.parse_args(argv)
62
63
64def _Set(field, target, target_name):
65 if field:
66 target[target_name] = field
67
68
69def _BuildArc(config):
70 if config.build_target.arc:
71 build_properties = {
72 'device': config.build_target.arc.device,
73 'first-api-level': config.build_target.arc.first_api_level,
74 'marketing-name': config.device_brand.brand_name,
75 'metrics-tag': config.hw_design.name.lower(),
Andrew Lambb47b7dc2020-04-07 10:20:32 -060076 'product': config.build_target.id.value,
David Burger7fd1dbe2020-03-26 09:26:55 -060077 }
78 if config.oem:
79 build_properties['oem'] = config.oem.name
80 return {
81 'build-properties': build_properties
82 }
83
84
85def _BuildFingerprint(hw_topology):
Andrew Lambc2c55462020-04-06 08:43:34 -060086 if hw_topology.HasField('fingerprint'):
David Burger7fd1dbe2020-03-26 09:26:55 -060087 fp = hw_topology.fingerprint.hardware_feature.fingerprint
88 location = fp.Location.DESCRIPTOR.values_by_number[fp.location].name
89 result = {
90 'sensor-location': location.lower().replace('_', '-'),
91 }
92 if fp.board:
93 result['board'] = fp.board
94 return result
95
96
97def _FwBcsPath(payload):
98 if payload and payload.firmware_image_name:
99 return 'bcs://%s.%d.%d.0.tbz2' % (
100 payload.firmware_image_name,
101 payload.version.major,
102 payload.version.minor)
103
104
105def _FwBuildTarget(payload):
106 if payload:
107 return payload.build_target_name
108
109
110def _BuildFirmware(config):
David Burger7fd1dbe2020-03-26 09:26:55 -0600111 fw = config.sw_config.firmware
112 main_ro = fw.main_ro_payload
113 main_rw = fw.main_rw_payload
114 ec_ro = fw.ec_ro_payload
115 pd_ro = fw.pd_ro_payload
116
117 build_targets = {}
118 _Set(_FwBuildTarget(main_ro), build_targets, 'depthcharge')
119 # Default to RO build target if no RW set
120 _Set(_FwBuildTarget(main_rw) or _FwBuildTarget(main_ro),
121 build_targets,
122 'coreboot')
123 _Set(_FwBuildTarget(ec_ro), build_targets, 'ec')
124 _Set(list(fw.ec_extras), build_targets, 'ec_extras')
125 # Default to EC build target if no PD set
126 _Set(_FwBuildTarget(pd_ro) or _FwBuildTarget(ec_ro),
127 build_targets,
128 'libpayload')
129
130 result = {
131 'bcs-overlay': config.build_target.overlay_name,
132 'build-targets': build_targets,
David Burger7fd1dbe2020-03-26 09:26:55 -0600133 }
Andrew Lamb883fa042020-04-06 11:37:22 -0600134
135 _Set(main_ro.firmware_image_name.lower(), result, 'image-name')
136
137 if not any((
138 main_ro.firmware_image_name,
139 main_rw.firmware_image_name,
140 ec_ro.firmware_image_name,
141 pd_ro.firmware_image_name,
142 )):
Andrew Lambb9e660f2020-04-06 11:37:22 -0600143 result['no-firmware'] = True
Andrew Lamb883fa042020-04-06 11:37:22 -0600144
145 _Set(_FwBcsPath(main_ro), result, 'main-ro-image')
146 _Set(_FwBcsPath(main_rw), result, 'main-rw-image')
147 _Set(_FwBcsPath(ec_ro), result, 'ec-ro-image')
148 _Set(_FwBcsPath(pd_ro), result, 'pd-ro-image')
David Burger7fd1dbe2020-03-26 09:26:55 -0600149
Andrew Lambf39fbe82020-04-13 16:14:33 -0600150 _Set(
151 config.hw_design_config.hardware_features.fw_config.value,
152 result,
153 'firmware-config',
154 )
155
David Burger7fd1dbe2020-03-26 09:26:55 -0600156 return result
157
158
159def _BuildFwSigning(config):
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500160 if config.sw_config.firmware and config.device_signer_config:
161 return {
162 'key-id': config.device_signer_config.key_id,
163 'signature-id': config.hw_design.name.lower(),
164 }
165 return {}
David Burger7fd1dbe2020-03-26 09:26:55 -0600166
167
168def _File(source, destination):
169 return {
170 'destination': destination,
171 'source': source
172 }
173
174
175def _BuildAudio(config):
176 alsa_path = '/usr/share/alsa/ucm'
177 cras_path = '/etc/cras'
178 project_name = config.hw_design.name.lower()
Andrew Lamb7d536782020-04-07 10:23:55 -0600179 if not config.sw_config.HasField('audio_config'):
David Burger7fd1dbe2020-03-26 09:26:55 -0600180 return {}
181 audio = config.sw_config.audio_config
182 card = audio.card_name
David Burger599ff7b2020-04-06 16:29:31 -0600183 card_with_suffix = audio.card_name
184 if audio.ucm_suffix:
185 card_with_suffix += '.' + audio.ucm_suffix
David Burger7fd1dbe2020-03-26 09:26:55 -0600186 files = []
187 if audio.ucm_file:
David Burger599ff7b2020-04-06 16:29:31 -0600188 files.append(_File(
189 audio.ucm_file,
190 '%s/%s/HiFi.conf' % (alsa_path, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600191 if audio.ucm_master_file:
192 files.append(_File(
David Burger599ff7b2020-04-06 16:29:31 -0600193 audio.ucm_master_file,
194 '%s/%s/%s.conf' % (alsa_path, card_with_suffix, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600195 if audio.card_config_file:
196 files.append(_File(
197 audio.card_config_file, '%s/%s/%s' % (cras_path, project_name, card)))
198 if audio.dsp_file:
199 files.append(
David Burger2e254902020-04-02 16:56:01 -0600200 _File(audio.dsp_file, '%s/%s/dsp.ini' % (cras_path, project_name)))
David Burger599ff7b2020-04-06 16:29:31 -0600201
202 result = {
David Burger7fd1dbe2020-03-26 09:26:55 -0600203 'main': {
204 'cras-config-dir': project_name,
205 'files': files,
206 }
207 }
David Burger599ff7b2020-04-06 16:29:31 -0600208 if audio.ucm_suffix:
David Burger03cdcbd2020-04-13 13:54:48 -0600209 result['main']['ucm-suffix'] = audio.ucm_suffix
David Burger599ff7b2020-04-06 16:29:31 -0600210
211 return result
David Burger7fd1dbe2020-03-26 09:26:55 -0600212
213
David Burger8aa8fa32020-04-14 08:30:34 -0600214def _BuildCamera(hw_topology):
215 if hw_topology.HasField('camera'):
216 camera = hw_topology.camera.hardware_feature.camera
217 result = {}
218 if camera.count.value:
219 result['count'] = camera.count.value
220 return result
221
222
Andrew Lamb7806ce92020-04-07 10:22:17 -0600223def _BuildIdentity(hw_scan_config, program, brand_scan_config=None):
David Burger7fd1dbe2020-03-26 09:26:55 -0600224 identity = {}
225 _Set(hw_scan_config.firmware_sku, identity, 'sku-id')
226 _Set(hw_scan_config.smbios_name_match, identity, 'smbios-name-match')
Andrew Lamb7806ce92020-04-07 10:22:17 -0600227 # 'platform-name' is needed to support 'mosys platform name'. Clients should
228 # longer require platform name, but set it here for backwards compatibility.
229 _Set(program.name, identity, 'platform-name')
David Burger7fd1dbe2020-03-26 09:26:55 -0600230 # ARM architecture
231 _Set(hw_scan_config.device_tree_compatible_match, identity,
232 'device-tree-compatible-match')
233
234 if brand_scan_config:
235 _Set(brand_scan_config.whitelabel_tag, identity, 'whitelabel-tag')
236
237 return identity
238
239
240def _Lookup(id_value, id_map):
241 if id_value.value:
242 key = id_value.value
243 if key in id_map:
244 return id_map[id_value.value]
245 error = 'Failed to lookup %s with value: %s' % (
246 id_value.__class__.__name__.replace('Id', ''), key)
247 print(error)
248 print('Check the config contents provided:')
249 pp = pprint.PrettyPrinter(indent=4)
250 pp.pprint(id_map)
251 raise Exception(error)
252
253
254def _TransformBuildConfigs(config):
255 partners = dict([(x.id.value, x) for x in config.partners.value])
256 programs = dict([(x.id.value, x) for x in config.programs.value])
David Burger7fd1dbe2020-03-26 09:26:55 -0600257 sw_configs = list(config.software_configs)
258 brand_configs = dict([(x.brand_id.value, x) for x in config.brand_configs])
259
C Shapiroa0b766c2020-03-31 08:35:28 -0500260 if len(config.build_targets) != 1:
261 # Artifact of sharing the config_bundle for analysis and transforms.
262 # Integrated analysis of multiple programs/projects it the only time
263 # having multiple build targets would be valid.
264 raise Exception('Single build_target required for transform')
265
David Burger7fd1dbe2020-03-26 09:26:55 -0600266 results = {}
267 for hw_design in config.designs.value:
268 if config.device_brands.value:
269 device_brands = [x for x in config.device_brands.value
270 if x.design_id.value == hw_design.id.value]
271 else:
272 device_brands = [device_brand_pb2.DeviceBrand()]
273
274 for device_brand in device_brands:
275 # Brand config can be empty since platform JSON config allows it
276 brand_config = brand_config_pb2.BrandConfig()
277 if device_brand.id.value in brand_configs:
278 brand_config = brand_configs[device_brand.id.value]
279
280 for hw_design_config in hw_design.configs:
281 design_id = hw_design_config.id.value
282 sw_config_matches = [x for x in sw_configs
283 if x.design_config_id.value == design_id]
284 if len(sw_config_matches) == 1:
285 sw_config = sw_config_matches[0]
286 elif len(sw_config_matches) > 1:
287 raise Exception('Multiple software configs found for: %s' % design_id)
288 else:
289 raise Exception('Software config is required for: %s' % design_id)
290
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500291 program = _Lookup(hw_design.program_id, programs)
292 signer_configs = dict(
293 [(x.brand_id.value, x) for x in program.device_signer_configs])
294 device_signer_config = None
295 if signer_configs:
296 device_signer_config = _Lookup(device_brand.id, signer_configs)
297
David Burger7fd1dbe2020-03-26 09:26:55 -0600298 transformed_config = _TransformBuildConfig(Config(
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500299 program=program,
David Burger7fd1dbe2020-03-26 09:26:55 -0600300 hw_design=hw_design,
301 odm=_Lookup(hw_design.odm_id, partners),
302 hw_design_config=hw_design_config,
303 device_brand=device_brand,
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500304 device_signer_config=device_signer_config,
David Burger7fd1dbe2020-03-26 09:26:55 -0600305 oem=_Lookup(device_brand.oem_id, partners),
306 sw_config=sw_config,
307 brand_config=brand_config,
C Shapiroa0b766c2020-03-31 08:35:28 -0500308 build_target=config.build_targets[0]))
David Burger7fd1dbe2020-03-26 09:26:55 -0600309
310 config_json = json.dumps(transformed_config,
311 sort_keys=True,
312 indent=2,
313 separators=(',', ': '))
314
315 if config_json not in results:
316 results[config_json] = transformed_config
317
318 return list(results.values())
319
320
321def _TransformBuildConfig(config):
322 """Transforms Config instance into target platform JSON schema.
323
324 Args:
325 config: Config namedtuple
326
327 Returns:
328 Unique config payload based on the platform JSON schema.
329 """
330 result = {
331 'identity': _BuildIdentity(
332 config.sw_config.id_scan_config,
Andrew Lamb7806ce92020-04-07 10:22:17 -0600333 config.program,
David Burger7fd1dbe2020-03-26 09:26:55 -0600334 config.brand_config.scan_config),
335 'name': config.hw_design.name.lower(),
336 }
337
338 _Set(_BuildArc(config), result, 'arc')
339 _Set(_BuildAudio(config), result, 'audio')
340 _Set(config.device_brand.brand_code, result, 'brand-code')
David Burger8aa8fa32020-04-14 08:30:34 -0600341 _Set(_BuildCamera(
342 config.hw_design_config.hardware_topology), result, 'camera')
David Burger7fd1dbe2020-03-26 09:26:55 -0600343 _Set(_BuildFirmware(config), result, 'firmware')
344 _Set(_BuildFwSigning(config), result, 'firmware-signing')
345 _Set(_BuildFingerprint(
346 config.hw_design_config.hardware_topology), result, 'fingerprint')
347 power_prefs = config.sw_config.power_config.preferences
348 power_prefs_map = dict(
349 (x.replace('_', '-'),
350 power_prefs[x]) for x in power_prefs)
351 _Set(power_prefs_map, result, 'power')
352
353 return result
354
355
356def WriteOutput(configs, output=None):
357 """Writes a list of configs to platform JSON format.
358
359 Args:
360 configs: List of config dicts defined in cros_config_schema.yaml
361 output: Target file output (if None, prints to stdout)
362 """
363 json_output = json.dumps(
364 {'chromeos': {
365 'configs': configs,
366 }},
367 sort_keys=True,
368 indent=2,
369 separators=(',', ': '))
370 if output:
371 with open(output, 'w') as output_stream:
372 # Using print function adds proper trailing newline.
373 print(json_output, file=output_stream)
374 else:
375 print(json_output)
376
377
378def _ReadConfig(path):
379 """Reads a binary proto from a file.
380
381 Args:
382 path: Path to the binary proto.
383 """
384 config = config_bundle_pb2.ConfigBundle()
385 with open(path, 'rb') as f:
386 config.ParseFromString(f.read())
387 return config
388
389
390def _MergeConfigs(configs):
391 result = config_bundle_pb2.ConfigBundle()
392 for config in configs:
393 result.MergeFrom(config)
394
395 return result
396
397
398def Main(project_configs,
399 program_config,
400 output):
401 """Transforms source proto config into platform JSON.
402
403 Args:
404 project_configs: List of source project configs to transform.
405 program_config: Program config for the given set of projects.
406 output: Output file that will be generated by the transform.
407 """
408 WriteOutput(
409 _TransformBuildConfigs(
410 _MergeConfigs(
411 [_ReadConfig(program_config)] +
412 [_ReadConfig(config) for config in project_configs],)
413 ,),
414 output)
415
416
417def main(argv=None):
418 """Main program which parses args and runs
419
420 Args:
421 argv: List of command line arguments, if None uses sys.argv.
422 """
423 if argv is None:
424 argv = sys.argv[1:]
425 opts = ParseArgs(argv)
426 Main(opts.project_configs, opts.program_config, opts.output)
427
428
429if __name__ == '__main__':
430 sys.exit(main(sys.argv[1:]))