blob: 8921492ab431677dd883d70ddf91ce5573e9fce7 [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
Andrew Lambf8954ee2020-04-21 10:24:40 -0600136 _Set(fw_build_config.build_targets.depthcharge, build_targets, 'depthcharge')
137 _Set(fw_build_config.build_targets.coreboot, build_targets, 'coreboot')
138 _Set(fw_build_config.build_targets.ec, build_targets, 'ec')
139 _Set(
140 list(fw_build_config.build_targets.ec_extras), build_targets, 'ec_extras')
141 _Set(fw_build_config.build_targets.libpayload, build_targets, 'libpayload')
David Burger7fd1dbe2020-03-26 09:26:55 -0600142
143 result = {
144 'bcs-overlay': config.build_target.overlay_name,
145 'build-targets': build_targets,
David Burger7fd1dbe2020-03-26 09:26:55 -0600146 }
Andrew Lamb883fa042020-04-06 11:37:22 -0600147
148 _Set(main_ro.firmware_image_name.lower(), result, 'image-name')
149
150 if not any((
151 main_ro.firmware_image_name,
152 main_rw.firmware_image_name,
153 ec_ro.firmware_image_name,
154 pd_ro.firmware_image_name,
155 )):
Andrew Lambb9e660f2020-04-06 11:37:22 -0600156 result['no-firmware'] = True
Andrew Lamb883fa042020-04-06 11:37:22 -0600157
158 _Set(_FwBcsPath(main_ro), result, 'main-ro-image')
159 _Set(_FwBcsPath(main_rw), result, 'main-rw-image')
160 _Set(_FwBcsPath(ec_ro), result, 'ec-ro-image')
161 _Set(_FwBcsPath(pd_ro), result, 'pd-ro-image')
David Burger7fd1dbe2020-03-26 09:26:55 -0600162
Andrew Lambf39fbe82020-04-13 16:14:33 -0600163 _Set(
164 config.hw_design_config.hardware_features.fw_config.value,
165 result,
166 'firmware-config',
167 )
168
David Burger7fd1dbe2020-03-26 09:26:55 -0600169 return result
170
171
172def _BuildFwSigning(config):
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500173 if config.sw_config.firmware and config.device_signer_config:
174 return {
175 'key-id': config.device_signer_config.key_id,
176 'signature-id': config.hw_design.name.lower(),
177 }
178 return {}
David Burger7fd1dbe2020-03-26 09:26:55 -0600179
180
181def _File(source, destination):
182 return {
183 'destination': destination,
184 'source': source
185 }
186
187
188def _BuildAudio(config):
189 alsa_path = '/usr/share/alsa/ucm'
190 cras_path = '/etc/cras'
191 project_name = config.hw_design.name.lower()
Andrew Lamb7d536782020-04-07 10:23:55 -0600192 if not config.sw_config.HasField('audio_config'):
David Burger7fd1dbe2020-03-26 09:26:55 -0600193 return {}
194 audio = config.sw_config.audio_config
195 card = audio.card_name
David Burger599ff7b2020-04-06 16:29:31 -0600196 card_with_suffix = audio.card_name
197 if audio.ucm_suffix:
198 card_with_suffix += '.' + audio.ucm_suffix
David Burger7fd1dbe2020-03-26 09:26:55 -0600199 files = []
200 if audio.ucm_file:
David Burger599ff7b2020-04-06 16:29:31 -0600201 files.append(_File(
202 audio.ucm_file,
203 '%s/%s/HiFi.conf' % (alsa_path, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600204 if audio.ucm_master_file:
205 files.append(_File(
David Burger599ff7b2020-04-06 16:29:31 -0600206 audio.ucm_master_file,
207 '%s/%s/%s.conf' % (alsa_path, card_with_suffix, card_with_suffix)))
David Burger7fd1dbe2020-03-26 09:26:55 -0600208 if audio.card_config_file:
209 files.append(_File(
210 audio.card_config_file, '%s/%s/%s' % (cras_path, project_name, card)))
211 if audio.dsp_file:
212 files.append(
David Burger2e254902020-04-02 16:56:01 -0600213 _File(audio.dsp_file, '%s/%s/dsp.ini' % (cras_path, project_name)))
David Burger599ff7b2020-04-06 16:29:31 -0600214
215 result = {
David Burger7fd1dbe2020-03-26 09:26:55 -0600216 'main': {
217 'cras-config-dir': project_name,
218 'files': files,
219 }
220 }
David Burger599ff7b2020-04-06 16:29:31 -0600221 if audio.ucm_suffix:
David Burger03cdcbd2020-04-13 13:54:48 -0600222 result['main']['ucm-suffix'] = audio.ucm_suffix
David Burger599ff7b2020-04-06 16:29:31 -0600223
224 return result
David Burger7fd1dbe2020-03-26 09:26:55 -0600225
226
David Burger8aa8fa32020-04-14 08:30:34 -0600227def _BuildCamera(hw_topology):
228 if hw_topology.HasField('camera'):
229 camera = hw_topology.camera.hardware_feature.camera
230 result = {}
231 if camera.count.value:
232 result['count'] = camera.count.value
233 return result
234
235
Andrew Lamb7806ce92020-04-07 10:22:17 -0600236def _BuildIdentity(hw_scan_config, program, brand_scan_config=None):
David Burger7fd1dbe2020-03-26 09:26:55 -0600237 identity = {}
238 _Set(hw_scan_config.firmware_sku, identity, 'sku-id')
239 _Set(hw_scan_config.smbios_name_match, identity, 'smbios-name-match')
Andrew Lamb7806ce92020-04-07 10:22:17 -0600240 # 'platform-name' is needed to support 'mosys platform name'. Clients should
241 # longer require platform name, but set it here for backwards compatibility.
242 _Set(program.name, identity, 'platform-name')
David Burger7fd1dbe2020-03-26 09:26:55 -0600243 # ARM architecture
244 _Set(hw_scan_config.device_tree_compatible_match, identity,
245 'device-tree-compatible-match')
246
247 if brand_scan_config:
248 _Set(brand_scan_config.whitelabel_tag, identity, 'whitelabel-tag')
249
250 return identity
251
252
253def _Lookup(id_value, id_map):
254 if id_value.value:
255 key = id_value.value
256 if key in id_map:
257 return id_map[id_value.value]
258 error = 'Failed to lookup %s with value: %s' % (
259 id_value.__class__.__name__.replace('Id', ''), key)
260 print(error)
261 print('Check the config contents provided:')
262 pp = pprint.PrettyPrinter(indent=4)
263 pp.pprint(id_map)
264 raise Exception(error)
265
266
C Shapiro90fda252020-04-17 14:34:57 -0500267def _TransformBuildConfigs(config, bluetooth_files={}):
David Burger7fd1dbe2020-03-26 09:26:55 -0600268 partners = dict([(x.id.value, x) for x in config.partners.value])
269 programs = dict([(x.id.value, x) for x in config.programs.value])
David Burger7fd1dbe2020-03-26 09:26:55 -0600270 sw_configs = list(config.software_configs)
271 brand_configs = dict([(x.brand_id.value, x) for x in config.brand_configs])
272
C Shapiroa0b766c2020-03-31 08:35:28 -0500273 if len(config.build_targets) != 1:
274 # Artifact of sharing the config_bundle for analysis and transforms.
275 # Integrated analysis of multiple programs/projects it the only time
276 # having multiple build targets would be valid.
277 raise Exception('Single build_target required for transform')
278
David Burger7fd1dbe2020-03-26 09:26:55 -0600279 results = {}
280 for hw_design in config.designs.value:
281 if config.device_brands.value:
282 device_brands = [x for x in config.device_brands.value
283 if x.design_id.value == hw_design.id.value]
284 else:
285 device_brands = [device_brand_pb2.DeviceBrand()]
286
287 for device_brand in device_brands:
288 # Brand config can be empty since platform JSON config allows it
289 brand_config = brand_config_pb2.BrandConfig()
290 if device_brand.id.value in brand_configs:
291 brand_config = brand_configs[device_brand.id.value]
292
293 for hw_design_config in hw_design.configs:
294 design_id = hw_design_config.id.value
295 sw_config_matches = [x for x in sw_configs
296 if x.design_config_id.value == design_id]
297 if len(sw_config_matches) == 1:
298 sw_config = sw_config_matches[0]
299 elif len(sw_config_matches) > 1:
300 raise Exception('Multiple software configs found for: %s' % design_id)
301 else:
302 raise Exception('Software config is required for: %s' % design_id)
303
C Shapiro2f0bb5d2020-04-14 10:07:47 -0500304 program = _Lookup(hw_design.program_id, programs)
305 signer_configs = dict(
306 [(x.brand_id.value, x) for x in program.device_signer_configs])
307 device_signer_config = None
308 if signer_configs:
309 device_signer_config = _Lookup(device_brand.id, signer_configs)
310
C Shapiro90fda252020-04-17 14:34:57 -0500311 transformed_config = _TransformBuildConfig(
312 Config(
313 program=program,
314 hw_design=hw_design,
315 odm=_Lookup(hw_design.odm_id, partners),
316 hw_design_config=hw_design_config,
317 device_brand=device_brand,
318 device_signer_config=device_signer_config,
319 oem=_Lookup(device_brand.oem_id, partners),
320 sw_config=sw_config,
321 brand_config=brand_config,
322 build_target=config.build_targets[0]),
323 bluetooth_files)
David Burger7fd1dbe2020-03-26 09:26:55 -0600324
325 config_json = json.dumps(transformed_config,
326 sort_keys=True,
327 indent=2,
328 separators=(',', ': '))
329
330 if config_json not in results:
331 results[config_json] = transformed_config
332
333 return list(results.values())
334
335
C Shapiro90fda252020-04-17 14:34:57 -0500336def _TransformBuildConfig(config, bluetooth_files):
David Burger7fd1dbe2020-03-26 09:26:55 -0600337 """Transforms Config instance into target platform JSON schema.
338
339 Args:
340 config: Config namedtuple
C Shapiro90fda252020-04-17 14:34:57 -0500341 bluetooth_files: Map to look up the generated bluetooth config files.
David Burger7fd1dbe2020-03-26 09:26:55 -0600342
343 Returns:
344 Unique config payload based on the platform JSON schema.
345 """
346 result = {
347 'identity': _BuildIdentity(
348 config.sw_config.id_scan_config,
Andrew Lamb7806ce92020-04-07 10:22:17 -0600349 config.program,
David Burger7fd1dbe2020-03-26 09:26:55 -0600350 config.brand_config.scan_config),
351 'name': config.hw_design.name.lower(),
352 }
353
354 _Set(_BuildArc(config), result, 'arc')
355 _Set(_BuildAudio(config), result, 'audio')
C Shapiro90fda252020-04-17 14:34:57 -0500356 _Set(_BuildBluetooth(config, bluetooth_files), result, 'bluetooth')
David Burger7fd1dbe2020-03-26 09:26:55 -0600357 _Set(config.device_brand.brand_code, result, 'brand-code')
David Burger8aa8fa32020-04-14 08:30:34 -0600358 _Set(_BuildCamera(
359 config.hw_design_config.hardware_topology), result, 'camera')
David Burger7fd1dbe2020-03-26 09:26:55 -0600360 _Set(_BuildFirmware(config), result, 'firmware')
361 _Set(_BuildFwSigning(config), result, 'firmware-signing')
362 _Set(_BuildFingerprint(
363 config.hw_design_config.hardware_topology), result, 'fingerprint')
364 power_prefs = config.sw_config.power_config.preferences
365 power_prefs_map = dict(
366 (x.replace('_', '-'),
367 power_prefs[x]) for x in power_prefs)
368 _Set(power_prefs_map, result, 'power')
369
370 return result
371
372
373def WriteOutput(configs, output=None):
374 """Writes a list of configs to platform JSON format.
375
376 Args:
377 configs: List of config dicts defined in cros_config_schema.yaml
378 output: Target file output (if None, prints to stdout)
379 """
380 json_output = json.dumps(
381 {'chromeos': {
382 'configs': configs,
383 }},
384 sort_keys=True,
385 indent=2,
386 separators=(',', ': '))
387 if output:
388 with open(output, 'w') as output_stream:
389 # Using print function adds proper trailing newline.
390 print(json_output, file=output_stream)
391 else:
392 print(json_output)
393
394
C Shapiro90fda252020-04-17 14:34:57 -0500395def _BluetoothId(project_name, bt_comp):
396 return '_'.join([project_name,
397 bt_comp.vendor_id,
398 bt_comp.product_id,
399 bt_comp.bcd_device])
400
401
402def WriteBluetoothConfigFiles(config, output_dir):
403 """Writes bluetooth conf files for every unique bluetooth chip.
404
405 Args:
406 config: Source ConfigBundle to process.
407 output_dir: Path to the generated output.
408 Returns:
409 dict that maps the bluetooth component id onto the file config.
410 """
411 project_gen_path = re.match(r'.*(generated.*)', output_dir).groups(1)[0]
412 result = {}
413 for hw_design in config.designs.value:
414 project_name = hw_design.name.lower()
415 for design_config in hw_design.configs:
416 bt_comp = design_config.hardware_features.bluetooth.component
417 if bt_comp.vendor_id:
418 bt_id = _BluetoothId(project_name, bt_comp)
419 result[bt_id] = {
420 'build-path': '%s/%s/bluetooth/%s.conf' % (
421 project_name, project_gen_path, bt_id),
422 'system-path': '/etc/bluetooth/%s/main.conf' % bt_id,
423 }
424 bt_content = '''[General]
425DeviceID = bluetooth:%s:%s:%s''' % (bt_comp.vendor_id,
426 bt_comp.product_id,
427 bt_comp.bcd_device)
428
429 output = '%s/bluetooth/%s.conf' % (output_dir, bt_id)
430 with open(output, 'w') as output_stream:
431 # Using print function adds proper trailing newline.
432 print(bt_content, file=output_stream)
433 return result
434
435
David Burger7fd1dbe2020-03-26 09:26:55 -0600436def _ReadConfig(path):
437 """Reads a binary proto from a file.
438
439 Args:
440 path: Path to the binary proto.
441 """
442 config = config_bundle_pb2.ConfigBundle()
443 with open(path, 'rb') as f:
444 config.ParseFromString(f.read())
445 return config
446
447
448def _MergeConfigs(configs):
449 result = config_bundle_pb2.ConfigBundle()
450 for config in configs:
451 result.MergeFrom(config)
452
453 return result
454
455
456def Main(project_configs,
457 program_config,
458 output):
459 """Transforms source proto config into platform JSON.
460
461 Args:
462 project_configs: List of source project configs to transform.
463 program_config: Program config for the given set of projects.
464 output: Output file that will be generated by the transform.
465 """
C Shapiro90fda252020-04-17 14:34:57 -0500466 configs =_MergeConfigs(
467 [_ReadConfig(program_config)] +
468 [_ReadConfig(config) for config in project_configs])
469 bt_files = {}
470 # Extracts output directory through regex versus separate args
471 if output and 'generated' in output:
472 bt_files = WriteBluetoothConfigFiles(configs, os.path.dirname(output))
473 WriteOutput(_TransformBuildConfigs(configs, bt_files), output)
David Burger7fd1dbe2020-03-26 09:26:55 -0600474
475
476def main(argv=None):
477 """Main program which parses args and runs
478
479 Args:
480 argv: List of command line arguments, if None uses sys.argv.
481 """
482 if argv is None:
483 argv = sys.argv[1:]
484 opts = ParseArgs(argv)
485 Main(opts.project_configs, opts.program_config, opts.output)
486
487
488if __name__ == '__main__':
489 sys.exit(main(sys.argv[1:]))