blob: 6d003065a5dfdd8cd24c57ff5fb80cc154692090 [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
C Shapiro90fda252020-04-17 14:34:57 -050011import os
David Burger7fd1dbe2020-03-26 09:26:55 -060012import sys
C Shapiro90fda252020-04-17 14:34:57 -050013import re
David Burger7fd1dbe2020-03-26 09:26:55 -060014
15from collections import namedtuple
16
Prathmesh Prabhu72f8a002020-04-10 09:57:53 -070017from chromiumos.config.payload import config_bundle_pb2
18from chromiumos.config.api import device_brand_pb2
19from chromiumos.config.api.software import brand_config_pb2
David Burger7fd1dbe2020-03-26 09:26:55 -060020
21Config = namedtuple('Config',
22 ['program',
23 'hw_design',
24 'odm',
25 'hw_design_config',
26 'device_brand',
C Shapiro2f0bb5d2020-04-14 10:07:47 -050027 'device_signer_config',
David Burger7fd1dbe2020-03-26 09:26:55 -060028 'oem',
29 'sw_config',
30 'brand_config',
31 'build_target'])
32
33
34def ParseArgs(argv):
35 """Parse the available arguments.
36
37 Invalid arguments or -h cause this function to print a message and exit.
38
39 Args:
40 argv: List of string arguments (excluding program name / argv[0])
41
42 Returns:
43 argparse.Namespace object containing the attributes.
44 """
45 parser = argparse.ArgumentParser(
46 description='Converts source proto config into platform JSON config.')
47 parser.add_argument(
48 '-c',
49 '--project_configs',
50 nargs='+',
51 type=str,
52 help='Space delimited list of source protobinary project config files.')
53 parser.add_argument(
54 '-p',
55 '--program_config',
56 type=str,
57 help='Path to the source program-level protobinary file')
58 parser.add_argument(
59 '-o',
60 '--output',
61 type=str,
62 help='Output file that will be generated')
63 return parser.parse_args(argv)
64
65
66def _Set(field, target, target_name):
67 if field:
68 target[target_name] = field
69
70
71def _BuildArc(config):
72 if config.build_target.arc:
73 build_properties = {
74 'device': config.build_target.arc.device,
75 'first-api-level': config.build_target.arc.first_api_level,
76 'marketing-name': config.device_brand.brand_name,
77 'metrics-tag': config.hw_design.name.lower(),
Andrew Lambb47b7dc2020-04-07 10:20:32 -060078 'product': config.build_target.id.value,
David Burger7fd1dbe2020-03-26 09:26:55 -060079 }
80 if config.oem:
81 build_properties['oem'] = config.oem.name
82 return {
83 'build-properties': build_properties
84 }
85
C Shapiro90fda252020-04-17 14:34:57 -050086def _BuildBluetooth(config, bluetooth_files):
87 bt_flags = config.sw_config.bluetooth_config.flags
88 # Convert to native map (from proto wrapper)
89 bt_flags_map = dict(bt_flags)
90 result = {}
91 if bt_flags_map:
92 result['flags'] = bt_flags_map
93 bt_comp = config.hw_design_config.hardware_features.bluetooth.component
94 if bt_comp.vendor_id:
95 bt_id = _BluetoothId(config.hw_design.name.lower(), bt_comp)
96 if bt_id in bluetooth_files:
97 result['config'] = bluetooth_files[bt_id]
98 return result
99
David Burger7fd1dbe2020-03-26 09:26:55 -0600100
101def _BuildFingerprint(hw_topology):
Andrew Lambc2c55462020-04-06 08:43:34 -0600102 if hw_topology.HasField('fingerprint'):
David Burger7fd1dbe2020-03-26 09:26:55 -0600103 fp = hw_topology.fingerprint.hardware_feature.fingerprint
104 location = fp.Location.DESCRIPTOR.values_by_number[fp.location].name
105 result = {
106 'sensor-location': location.lower().replace('_', '-'),
107 }
108 if fp.board:
109 result['board'] = fp.board
110 return result
111
112
113def _FwBcsPath(payload):
114 if payload and payload.firmware_image_name:
115 return 'bcs://%s.%d.%d.0.tbz2' % (
116 payload.firmware_image_name,
117 payload.version.major,
118 payload.version.minor)
119
120
121def _FwBuildTarget(payload):
122 if payload:
123 return payload.build_target_name
124
125
126def _BuildFirmware(config):
Andrew Lamb3da156d2020-04-16 16:00:56 -0600127 fw_payload_config = config.sw_config.firmware
128 fw_build_config = config.sw_config.firmware_build_config
129 main_ro = fw_payload_config.main_ro_payload
130 main_rw = fw_payload_config.main_rw_payload
131 ec_ro = fw_payload_config.ec_ro_payload
132 pd_ro = fw_payload_config.pd_ro_payload
David Burger7fd1dbe2020-03-26 09:26:55 -0600133
134 build_targets = {}
Andrew Lamb3da156d2020-04-16 16:00:56 -0600135
136 if fw_build_config.HasField('build_targets') and any((
137 _FwBuildTarget(main_ro),
138 _FwBuildTarget(main_rw),
139 _FwBuildTarget(ec_ro),
140 _FwBuildTarget(pd_ro),
141 fw_payload_config.ec_extras,
142 )):
143 raise ValueError(
144 'FirmwareBuildConfig.build_targets cannot be set if build_target_name '
145 'is set on any FirmwarePayload or ec_extras is set. '
146 'FirmwarePayload.build_target_name is deprecated, please use '
147 'FirmwareBuildConfig.build_targets instead.'
148 )
149
150 if fw_build_config.HasField('build_targets'):
151 _Set(fw_build_config.build_targets.depthcharge, build_targets,
152 'depthcharge')
153 _Set(fw_build_config.build_targets.coreboot, build_targets, 'coreboot')
154 _Set(fw_build_config.build_targets.ec, build_targets, 'ec')
155 _Set(
156 list(fw_build_config.build_targets.ec_extras), build_targets,
157 'ec_extras')
158 _Set(fw_build_config.build_targets.libpayload, build_targets, 'libpayload')
159 else:
160 _Set(_FwBuildTarget(main_ro), build_targets, 'depthcharge')
161 # Default to RO build target if no RW set
162 _Set(_FwBuildTarget(main_rw) or _FwBuildTarget(main_ro),
163 build_targets,
164 'coreboot')
165 _Set(_FwBuildTarget(ec_ro), build_targets, 'ec')
166 _Set(list(fw_payload_config.ec_extras), build_targets, 'ec_extras')
167 # Default to EC build target if no PD set
168 _Set(_FwBuildTarget(pd_ro) or _FwBuildTarget(ec_ro),
169 build_targets,
170 'libpayload')
David Burger7fd1dbe2020-03-26 09:26:55 -0600171
172 result = {
173 'bcs-overlay': config.build_target.overlay_name,
174 'build-targets': build_targets,
David Burger7fd1dbe2020-03-26 09:26:55 -0600175 }
Andrew Lamb883fa042020-04-06 11:37:22 -0600176
177 _Set(main_ro.firmware_image_name.lower(), result, 'image-name')
178
179 if not any((
180 main_ro.firmware_image_name,
181 main_rw.firmware_image_name,
182 ec_ro.firmware_image_name,
183 pd_ro.firmware_image_name,
184 )):
Andrew Lambb9e660f2020-04-06 11:37:22 -0600185 result['no-firmware'] = True
Andrew Lamb883fa042020-04-06 11:37:22 -0600186
187 _Set(_FwBcsPath(main_ro), result, 'main-ro-image')
188 _Set(_FwBcsPath(main_rw), result, 'main-rw-image')
189 _Set(_FwBcsPath(ec_ro), result, 'ec-ro-image')
190 _Set(_FwBcsPath(pd_ro), result, 'pd-ro-image')
David Burger7fd1dbe2020-03-26 09:26:55 -0600191
Andrew Lambf39fbe82020-04-13 16:14:33 -0600192 _Set(
193 config.hw_design_config.hardware_features.fw_config.value,
194 result,
195 'firmware-config',
196 )
197
David Burger7fd1dbe2020-03-26 09:26:55 -0600198 return result
199
200
201def _BuildFwSigning(config):
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500202 if config.sw_config.firmware and config.device_signer_config:
203 return {
204 'key-id': config.device_signer_config.key_id,
205 'signature-id': config.hw_design.name.lower(),
206 }
207 return {}
David Burger7fd1dbe2020-03-26 09:26:55 -0600208
209
210def _File(source, destination):
211 return {
212 'destination': destination,
213 'source': source
214 }
215
216
217def _BuildAudio(config):
218 alsa_path = '/usr/share/alsa/ucm'
219 cras_path = '/etc/cras'
220 project_name = config.hw_design.name.lower()
Andrew Lamb7d536782020-04-07 10:23:55 -0600221 if not config.sw_config.HasField('audio_config'):
David Burger7fd1dbe2020-03-26 09:26:55 -0600222 return {}
223 audio = config.sw_config.audio_config
224 card = audio.card_name
David Burger599ff7b2020-04-06 16:29:31 -0600225 card_with_suffix = audio.card_name
226 if audio.ucm_suffix:
227 card_with_suffix += '.' + audio.ucm_suffix
David Burger7fd1dbe2020-03-26 09:26:55 -0600228 files = []
229 if audio.ucm_file:
David Burger599ff7b2020-04-06 16:29:31 -0600230 files.append(_File(
231 audio.ucm_file,
232 '%s/%s/HiFi.conf' % (alsa_path, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600233 if audio.ucm_master_file:
234 files.append(_File(
David Burger599ff7b2020-04-06 16:29:31 -0600235 audio.ucm_master_file,
236 '%s/%s/%s.conf' % (alsa_path, card_with_suffix, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600237 if audio.card_config_file:
238 files.append(_File(
239 audio.card_config_file, '%s/%s/%s' % (cras_path, project_name, card)))
240 if audio.dsp_file:
241 files.append(
David Burger2e254902020-04-02 16:56:01 -0600242 _File(audio.dsp_file, '%s/%s/dsp.ini' % (cras_path, project_name)))
David Burger599ff7b2020-04-06 16:29:31 -0600243
244 result = {
David Burger7fd1dbe2020-03-26 09:26:55 -0600245 'main': {
246 'cras-config-dir': project_name,
247 'files': files,
248 }
249 }
David Burger599ff7b2020-04-06 16:29:31 -0600250 if audio.ucm_suffix:
David Burger03cdcbd2020-04-13 13:54:48 -0600251 result['main']['ucm-suffix'] = audio.ucm_suffix
David Burger599ff7b2020-04-06 16:29:31 -0600252
253 return result
David Burger7fd1dbe2020-03-26 09:26:55 -0600254
255
David Burger8aa8fa32020-04-14 08:30:34 -0600256def _BuildCamera(hw_topology):
257 if hw_topology.HasField('camera'):
258 camera = hw_topology.camera.hardware_feature.camera
259 result = {}
260 if camera.count.value:
261 result['count'] = camera.count.value
262 return result
263
264
Andrew Lamb7806ce92020-04-07 10:22:17 -0600265def _BuildIdentity(hw_scan_config, program, brand_scan_config=None):
David Burger7fd1dbe2020-03-26 09:26:55 -0600266 identity = {}
267 _Set(hw_scan_config.firmware_sku, identity, 'sku-id')
268 _Set(hw_scan_config.smbios_name_match, identity, 'smbios-name-match')
Andrew Lamb7806ce92020-04-07 10:22:17 -0600269 # 'platform-name' is needed to support 'mosys platform name'. Clients should
270 # longer require platform name, but set it here for backwards compatibility.
271 _Set(program.name, identity, 'platform-name')
David Burger7fd1dbe2020-03-26 09:26:55 -0600272 # ARM architecture
273 _Set(hw_scan_config.device_tree_compatible_match, identity,
274 'device-tree-compatible-match')
275
276 if brand_scan_config:
277 _Set(brand_scan_config.whitelabel_tag, identity, 'whitelabel-tag')
278
279 return identity
280
281
282def _Lookup(id_value, id_map):
283 if id_value.value:
284 key = id_value.value
285 if key in id_map:
286 return id_map[id_value.value]
287 error = 'Failed to lookup %s with value: %s' % (
288 id_value.__class__.__name__.replace('Id', ''), key)
289 print(error)
290 print('Check the config contents provided:')
291 pp = pprint.PrettyPrinter(indent=4)
292 pp.pprint(id_map)
293 raise Exception(error)
294
295
C Shapiro90fda252020-04-17 14:34:57 -0500296def _TransformBuildConfigs(config, bluetooth_files={}):
David Burger7fd1dbe2020-03-26 09:26:55 -0600297 partners = dict([(x.id.value, x) for x in config.partners.value])
298 programs = dict([(x.id.value, x) for x in config.programs.value])
David Burger7fd1dbe2020-03-26 09:26:55 -0600299 sw_configs = list(config.software_configs)
300 brand_configs = dict([(x.brand_id.value, x) for x in config.brand_configs])
301
C Shapiroa0b766c2020-03-31 08:35:28 -0500302 if len(config.build_targets) != 1:
303 # Artifact of sharing the config_bundle for analysis and transforms.
304 # Integrated analysis of multiple programs/projects it the only time
305 # having multiple build targets would be valid.
306 raise Exception('Single build_target required for transform')
307
David Burger7fd1dbe2020-03-26 09:26:55 -0600308 results = {}
309 for hw_design in config.designs.value:
310 if config.device_brands.value:
311 device_brands = [x for x in config.device_brands.value
312 if x.design_id.value == hw_design.id.value]
313 else:
314 device_brands = [device_brand_pb2.DeviceBrand()]
315
316 for device_brand in device_brands:
317 # Brand config can be empty since platform JSON config allows it
318 brand_config = brand_config_pb2.BrandConfig()
319 if device_brand.id.value in brand_configs:
320 brand_config = brand_configs[device_brand.id.value]
321
322 for hw_design_config in hw_design.configs:
323 design_id = hw_design_config.id.value
324 sw_config_matches = [x for x in sw_configs
325 if x.design_config_id.value == design_id]
326 if len(sw_config_matches) == 1:
327 sw_config = sw_config_matches[0]
328 elif len(sw_config_matches) > 1:
329 raise Exception('Multiple software configs found for: %s' % design_id)
330 else:
331 raise Exception('Software config is required for: %s' % design_id)
332
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500333 program = _Lookup(hw_design.program_id, programs)
334 signer_configs = dict(
335 [(x.brand_id.value, x) for x in program.device_signer_configs])
336 device_signer_config = None
337 if signer_configs:
338 device_signer_config = _Lookup(device_brand.id, signer_configs)
339
C Shapiro90fda252020-04-17 14:34:57 -0500340 transformed_config = _TransformBuildConfig(
341 Config(
342 program=program,
343 hw_design=hw_design,
344 odm=_Lookup(hw_design.odm_id, partners),
345 hw_design_config=hw_design_config,
346 device_brand=device_brand,
347 device_signer_config=device_signer_config,
348 oem=_Lookup(device_brand.oem_id, partners),
349 sw_config=sw_config,
350 brand_config=brand_config,
351 build_target=config.build_targets[0]),
352 bluetooth_files)
David Burger7fd1dbe2020-03-26 09:26:55 -0600353
354 config_json = json.dumps(transformed_config,
355 sort_keys=True,
356 indent=2,
357 separators=(',', ': '))
358
359 if config_json not in results:
360 results[config_json] = transformed_config
361
362 return list(results.values())
363
364
C Shapiro90fda252020-04-17 14:34:57 -0500365def _TransformBuildConfig(config, bluetooth_files):
David Burger7fd1dbe2020-03-26 09:26:55 -0600366 """Transforms Config instance into target platform JSON schema.
367
368 Args:
369 config: Config namedtuple
C Shapiro90fda252020-04-17 14:34:57 -0500370 bluetooth_files: Map to look up the generated bluetooth config files.
David Burger7fd1dbe2020-03-26 09:26:55 -0600371
372 Returns:
373 Unique config payload based on the platform JSON schema.
374 """
375 result = {
376 'identity': _BuildIdentity(
377 config.sw_config.id_scan_config,
Andrew Lamb7806ce92020-04-07 10:22:17 -0600378 config.program,
David Burger7fd1dbe2020-03-26 09:26:55 -0600379 config.brand_config.scan_config),
380 'name': config.hw_design.name.lower(),
381 }
382
383 _Set(_BuildArc(config), result, 'arc')
384 _Set(_BuildAudio(config), result, 'audio')
C Shapiro90fda252020-04-17 14:34:57 -0500385 _Set(_BuildBluetooth(config, bluetooth_files), result, 'bluetooth')
David Burger7fd1dbe2020-03-26 09:26:55 -0600386 _Set(config.device_brand.brand_code, result, 'brand-code')
David Burger8aa8fa32020-04-14 08:30:34 -0600387 _Set(_BuildCamera(
388 config.hw_design_config.hardware_topology), result, 'camera')
David Burger7fd1dbe2020-03-26 09:26:55 -0600389 _Set(_BuildFirmware(config), result, 'firmware')
390 _Set(_BuildFwSigning(config), result, 'firmware-signing')
391 _Set(_BuildFingerprint(
392 config.hw_design_config.hardware_topology), result, 'fingerprint')
393 power_prefs = config.sw_config.power_config.preferences
394 power_prefs_map = dict(
395 (x.replace('_', '-'),
396 power_prefs[x]) for x in power_prefs)
397 _Set(power_prefs_map, result, 'power')
398
399 return result
400
401
402def WriteOutput(configs, output=None):
403 """Writes a list of configs to platform JSON format.
404
405 Args:
406 configs: List of config dicts defined in cros_config_schema.yaml
407 output: Target file output (if None, prints to stdout)
408 """
409 json_output = json.dumps(
410 {'chromeos': {
411 'configs': configs,
412 }},
413 sort_keys=True,
414 indent=2,
415 separators=(',', ': '))
416 if output:
417 with open(output, 'w') as output_stream:
418 # Using print function adds proper trailing newline.
419 print(json_output, file=output_stream)
420 else:
421 print(json_output)
422
423
C Shapiro90fda252020-04-17 14:34:57 -0500424def _BluetoothId(project_name, bt_comp):
425 return '_'.join([project_name,
426 bt_comp.vendor_id,
427 bt_comp.product_id,
428 bt_comp.bcd_device])
429
430
431def WriteBluetoothConfigFiles(config, output_dir):
432 """Writes bluetooth conf files for every unique bluetooth chip.
433
434 Args:
435 config: Source ConfigBundle to process.
436 output_dir: Path to the generated output.
437 Returns:
438 dict that maps the bluetooth component id onto the file config.
439 """
440 project_gen_path = re.match(r'.*(generated.*)', output_dir).groups(1)[0]
441 result = {}
442 for hw_design in config.designs.value:
443 project_name = hw_design.name.lower()
444 for design_config in hw_design.configs:
445 bt_comp = design_config.hardware_features.bluetooth.component
446 if bt_comp.vendor_id:
447 bt_id = _BluetoothId(project_name, bt_comp)
448 result[bt_id] = {
449 'build-path': '%s/%s/bluetooth/%s.conf' % (
450 project_name, project_gen_path, bt_id),
451 'system-path': '/etc/bluetooth/%s/main.conf' % bt_id,
452 }
453 bt_content = '''[General]
454DeviceID = bluetooth:%s:%s:%s''' % (bt_comp.vendor_id,
455 bt_comp.product_id,
456 bt_comp.bcd_device)
457
458 output = '%s/bluetooth/%s.conf' % (output_dir, bt_id)
459 with open(output, 'w') as output_stream:
460 # Using print function adds proper trailing newline.
461 print(bt_content, file=output_stream)
462 return result
463
464
David Burger7fd1dbe2020-03-26 09:26:55 -0600465def _ReadConfig(path):
466 """Reads a binary proto from a file.
467
468 Args:
469 path: Path to the binary proto.
470 """
471 config = config_bundle_pb2.ConfigBundle()
472 with open(path, 'rb') as f:
473 config.ParseFromString(f.read())
474 return config
475
476
477def _MergeConfigs(configs):
478 result = config_bundle_pb2.ConfigBundle()
479 for config in configs:
480 result.MergeFrom(config)
481
482 return result
483
484
485def Main(project_configs,
486 program_config,
487 output):
488 """Transforms source proto config into platform JSON.
489
490 Args:
491 project_configs: List of source project configs to transform.
492 program_config: Program config for the given set of projects.
493 output: Output file that will be generated by the transform.
494 """
C Shapiro90fda252020-04-17 14:34:57 -0500495 configs =_MergeConfigs(
496 [_ReadConfig(program_config)] +
497 [_ReadConfig(config) for config in project_configs])
498 bt_files = {}
499 # Extracts output directory through regex versus separate args
500 if output and 'generated' in output:
501 bt_files = WriteBluetoothConfigFiles(configs, os.path.dirname(output))
502 WriteOutput(_TransformBuildConfigs(configs, bt_files), output)
David Burger7fd1dbe2020-03-26 09:26:55 -0600503
504
505def main(argv=None):
506 """Main program which parses args and runs
507
508 Args:
509 argv: List of command line arguments, if None uses sys.argv.
510 """
511 if argv is None:
512 argv = sys.argv[1:]
513 opts = ParseArgs(argv)
514 Main(opts.project_configs, opts.program_config, opts.output)
515
516
517if __name__ == '__main__':
518 sys.exit(main(sys.argv[1:]))