diff --git a/cmake/AsicConfigV3ConfigCli.cmake b/cmake/AsicConfigV3ConfigCli.cmake new file mode 100644 index 0000000000000..7c070d586d93b --- /dev/null +++ b/cmake/AsicConfigV3ConfigCli.cmake @@ -0,0 +1,37 @@ +# CMake to build libraries and binaries in fboss/lib/asic_config_v3 +# Data-driven ASIC config generation + +include(FBPythonBinary) + +set( + ASIC_CONFIG_V3_PY_SRCS + "fboss/lib/asic_config_v3/__init__.py" + "fboss/lib/asic_config_v3/base_generator.py" + "fboss/lib/asic_config_v3/gen.py" + "fboss/lib/asic_config_v3/generators/__init__.py" + "fboss/lib/platform_mapping_v2/asic_vendor_config.py" + "fboss/lib/platform_mapping_v2/gen.py" + "fboss/lib/platform_mapping_v2/helpers.py" + "fboss/lib/platform_mapping_v2/platform_mapping_v2.py" + "fboss/lib/platform_mapping_v2/port_profile_mapping.py" + "fboss/lib/platform_mapping_v2/profile_settings.py" + "fboss/lib/platform_mapping_v2/read_files_utils.py" + "fboss/lib/platform_mapping_v2/si_settings.py" + "fboss/lib/platform_mapping_v2/static_mapping.py" +) + +add_fb_thrift_python_executable( + fboss-asic-config-v3-gen + MAIN_MODULE fboss.lib.asic_config_v3.gen:generate_all_asic_configs + SOURCES ${ASIC_CONFIG_V3_PY_SRCS} + DEPENDS + platform_config_python + switch_config_python + transceiver_python + phy_python + platform_mapping_config_python + fboss_common_python + python-pyyaml::python-pyyaml +) + +install_fb_python_executable(fboss-asic-config-v3-gen) diff --git a/fboss/lib/asic_config_v2/run-helper.sh b/fboss/lib/asic_config_v2/run-helper.sh index 80ba38234ea30..02491b608cb6e 100755 --- a/fboss/lib/asic_config_v2/run-helper.sh +++ b/fboss/lib/asic_config_v2/run-helper.sh @@ -1,2 +1,5 @@ #!/bin/bash -python3 fboss/lib/oss/run-helper.py --target fboss-asic-config-gen "$@" +python3 fboss/lib/oss/run-helper.py \ + --target fboss-asic-config-gen.GEN_PY_EXE \ + --extra-cmake-defines='{"RANGE_V3_TESTS": "OFF"}' \ + "$@" diff --git a/fboss/lib/asic_config_v3/__init__.py b/fboss/lib/asic_config_v3/__init__.py new file mode 100644 index 0000000000000..35302f75f52a5 --- /dev/null +++ b/fboss/lib/asic_config_v3/__init__.py @@ -0,0 +1 @@ +# pyre-strict diff --git a/fboss/lib/asic_config_v3/base_generator.py b/fboss/lib/asic_config_v3/base_generator.py new file mode 100644 index 0000000000000..f151cfd37ce4a --- /dev/null +++ b/fboss/lib/asic_config_v3/base_generator.py @@ -0,0 +1,82 @@ +# pyre-strict + +import copy +import os +from abc import ABC, abstractmethod +from typing import Any + +# Resolve config paths relative to the repo root rather than this file so the +# generator can run from the same checkout layout the bundled getdeps build +# expects. +_FBOSS_DIR: str = os.getcwd() + "/fboss" +MODULE_DIR: str = f"{_FBOSS_DIR}/lib/asic_config_v3" + + +def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]: + """Recursively merge ``override`` on top of ``base``. + + Dict values present in both are merged recursively. For any other value + type (scalars, lists, or a type mismatch between the two sides) the + override replaces the base entry outright. Returns a new dict; the inputs + are not mutated. + """ + result = copy.deepcopy(base) + for key, ov_value in override.items(): + if ( + key in result + and isinstance(result[key], dict) + and isinstance(ov_value, dict) + ): + result[key] = _deep_merge(result[key], ov_value) + else: + result[key] = copy.deepcopy(ov_value) + return result + + +class BaseAsicConfigGenerator(ABC): + """Abstract base class for ASIC config generators. + + Subclasses implement ``generate()`` to produce vendor-specific output + such as YAML or JSON. + """ + + def __init__( + self, + platform_name: str, + variant: str, + platform_config: dict[str, Any], + ) -> None: + self.platform_name = platform_name + self.variant = variant + self.platform_config = platform_config + + vendor = platform_config.get("vendor") + asic = platform_config.get("asic") + if not vendor: + raise ValueError("platform asic_config.json must define 'vendor'") + if not asic: + raise ValueError("platform asic_config.json must define 'asic'") + self.asic_vendor: str = vendor + self.asic_name: str = asic + + # The platform JSON may declare a top-level ``defaults`` block inherited + # by every variant. The effective variant config is produced by deep- + # merging the variant-specific entries on top of ``defaults``. Dict + # values are merged recursively; scalars and lists are replaced. + defaults = platform_config.get("defaults", {}) + variant_override = platform_config.get("variants", {}).get(variant, {}) + self.variant_config: dict[str, Any] = _deep_merge(defaults, variant_override) + self.asic_config_params: dict[str, Any] = self.variant_config.get( + "asic_config_params", {} + ) + + @abstractmethod + def generate(self) -> str: + """Generate the complete ASIC config and return it as a string.""" + ... + + @property + @abstractmethod + def output_extension(self) -> str: + """File extension for the generated output (e.g. '.yml' or '.json').""" + ... diff --git a/fboss/lib/asic_config_v3/common/ocp_sai_common.json b/fboss/lib/asic_config_v3/common/ocp_sai_common.json new file mode 100644 index 0000000000000..c0af00ae2bd0b --- /dev/null +++ b/fboss/lib/asic_config_v3/common/ocp_sai_common.json @@ -0,0 +1,4 @@ +{ + "_comment": "OCP SAI standard settings (vendor-agnostic).", + "global": {} +} diff --git a/fboss/lib/asic_config_v3/gen.py b/fboss/lib/asic_config_v3/gen.py new file mode 100644 index 0000000000000..aa43a4586a796 --- /dev/null +++ b/fboss/lib/asic_config_v3/gen.py @@ -0,0 +1,109 @@ +# pyre-strict + +import json +import os +import sys + +from fboss.lib.asic_config_v3.base_generator import ( + BaseAsicConfigGenerator, + MODULE_DIR, +) + +OUTPUT_DIR: str = f"{MODULE_DIR}/generated_asic_configs" + +# Add a new (vendor, asic) entry when bringing up a new ASIC family. +_GENERATOR_REGISTRY: dict[tuple[str, str], type[BaseAsicConfigGenerator]] = {} + + +def get_generator( + platform_name: str, variant: str, platform_config: dict +) -> BaseAsicConfigGenerator: + """Instantiate the correct generator based on vendor and ASIC.""" + vendor = platform_config["vendor"] + asic = platform_config["asic"] + key = (vendor, asic) + generator_cls = _GENERATOR_REGISTRY.get(key) + if not generator_cls: + raise ValueError(f"No generator registered for vendor={vendor}, asic={asic}") + return generator_cls(platform_name, variant, platform_config) + + +def discover_platforms() -> dict: + """Return a mapping of platform name to platform config. + + Discovered by scanning ``platforms/*/asic_config.json``. + """ + platforms_dir = os.path.join(MODULE_DIR, "platforms") + platforms = {} + + if not os.path.exists(platforms_dir): + return platforms + + for platform_name in os.listdir(platforms_dir): + platform_path = os.path.join(platforms_dir, platform_name) + if not os.path.isdir(platform_path): + continue + + config_path = os.path.join(platform_path, "asic_config.json") + if not os.path.exists(config_path): + continue + + with open(config_path) as f: + platform_config = json.load(f) + platforms[platform_name] = platform_config + + return platforms + + +def generate_all_asic_configs() -> None: + """Generate ASIC configs for every discovered platform and variant.""" + os.makedirs(OUTPUT_DIR, exist_ok=True) + + for filename in os.listdir(OUTPUT_DIR): + if filename.endswith((".json", ".yml")): + os.remove(os.path.join(OUTPUT_DIR, filename)) + + platforms = discover_platforms() + + for platform_name, platform_config in platforms.items(): + vendor = platform_config.get("vendor", "") + asic = platform_config.get("asic", "") + + if (vendor, asic) not in _GENERATOR_REGISTRY: + print( + f"Skipping {platform_name} (no generator for vendor={vendor}, asic={asic})", + file=sys.stderr, + ) + continue + + variants = platform_config.get("variants", {}) + + for variant_name in variants: + print( + f"Generating ASIC config for {platform_name}/{variant_name}...", + file=sys.stderr, + ) + + try: + generator = get_generator(platform_name, variant_name, platform_config) + output = generator.generate() + + output_filename = ( + f"{platform_name}_{variant_name}{generator.output_extension}" + ) + output_path = os.path.join(OUTPUT_DIR, output_filename) + + print(f"Writing to {output_path}", file=sys.stderr) + with open(output_path, "w", encoding="utf-8") as f: + f.write(output) + + except Exception as e: + print( + f"Error generating config for {platform_name}/{variant_name}: {e}", + file=sys.stderr, + ) + raise + + +if __name__ == "__main__": + generate_all_asic_configs() diff --git a/fboss/lib/asic_config_v3/generators/__init__.py b/fboss/lib/asic_config_v3/generators/__init__.py new file mode 100644 index 0000000000000..35302f75f52a5 --- /dev/null +++ b/fboss/lib/asic_config_v3/generators/__init__.py @@ -0,0 +1 @@ +# pyre-strict diff --git a/fboss/lib/asic_config_v3/run-helper.sh b/fboss/lib/asic_config_v3/run-helper.sh new file mode 100755 index 0000000000000..d42d68d7ddb80 --- /dev/null +++ b/fboss/lib/asic_config_v3/run-helper.sh @@ -0,0 +1,5 @@ +#!/bin/bash +python3 fboss/lib/oss/run-helper.py \ + --target fboss-asic-config-v3-gen.GEN_PY_EXE \ + --extra-cmake-defines='{"RANGE_V3_TESTS": "OFF"}' \ + "$@" diff --git a/fboss/lib/asic_config_v3/schemas/broadcom_xgs_asic_config.schema.json b/fboss/lib/asic_config_v3/schemas/broadcom_xgs_asic_config.schema.json new file mode 100644 index 0000000000000..81677ac2cd1a1 --- /dev/null +++ b/fboss/lib/asic_config_v3/schemas/broadcom_xgs_asic_config.schema.json @@ -0,0 +1,271 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Broadcom XGS ASIC Config", + "description": "Per-ASIC configuration defining the chip's port architecture, output table layout, and SDK / SAI default settings used by the asic_config_v3 generator.", + "type": "object", + "required": [ + "vendor", + "asic", + "port_architecture", + "mmu_size", + "table_names", + "mgmt_port_defaults", + "preamble_support", + "global_defaults", + "sai_overrides", + "flex_counter_settings", + "ctr_eflex_config", + "dlb_defaults", + "port_config_defaults", + "fp_config_defaults", + "tm_thd_config_defaults", + "pass_through_settings", + "conditional_settings" + ], + "additionalProperties": false, + "properties": { + "vendor": { + "type": "string", + "description": "ASIC vendor identifier (e.g. \"broadcom\")." + }, + "asic": { + "type": "string", + "description": "ASIC chip identifier (e.g. \"tomahawk5\", \"tomahawk6\")." + }, + "port_architecture": { + "type": "object", + "description": "Core, lane, and datapath layout consumed by the logical-to-physical port mapping logic.", + "required": [ + "num_cores", + "num_lanes_per_core", + "num_logical_ports_per_datapath", + "num_physical_ports_per_datapath", + "num_lp_ports_on_even_core_default" + ], + "additionalProperties": false, + "properties": { + "num_cores": { + "type": "integer", + "description": "Total number of MAC cores on the ASIC." + }, + "num_lanes_per_core": { + "type": "integer", + "enum": [4, 8], + "description": "Number of physical SerDes lanes per core." + }, + "num_logical_ports_per_datapath": { + "type": "integer", + "description": "Number of logical ports per datapath. Used to derive logical port identifiers." + }, + "num_physical_ports_per_datapath": { + "type": "integer", + "description": "Number of physical ports per datapath. Used to derive physical port identifiers." + }, + "num_lp_ports_on_even_core_default": { + "type": "integer", + "description": "Default number of logical ports placed on even-numbered cores. May be overridden per platform variant." + } + } + }, + "mmu_size": { + "type": "integer", + "description": "Memory Management Unit (MMU) buffer size in bytes. Also used as the MAX_FRAME_SIZE and PORT MTU values." + }, + "table_names": { + "type": "array", + "description": "Ordered list of output YAML table names. Controls which tables appear in the generated config and the order in which they are emitted.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "mgmt_port_defaults": { + "type": "object", + "description": "Default settings for the management (out-of-band) port.", + "required": ["logical_id", "physical_id", "speed", "num_lanes", "fec"], + "additionalProperties": false, + "properties": { + "logical_id": { + "type": "integer", + "description": "Logical port identifier for the management port." + }, + "physical_id": { + "type": "integer", + "description": "Physical port identifier for the management port." + }, + "speed": { + "type": "integer", + "description": "Default port speed in Mbps (e.g. 100000 for 100 GbE)." + }, + "num_lanes": { + "type": "integer", + "description": "Number of physical lanes used by the management port." + }, + "fec": { + "type": "string", + "description": "Forward Error Correction (FEC) mode (e.g. \"PC_FEC_RS528\")." + } + } + }, + "preamble_support": { + "type": "boolean", + "description": "Whether this ASIC supports a preamble section prepended to the output YAML. When false, any preamble_file declared by a platform variant is ignored." + }, + "global_defaults": { + "type": "object", + "description": "ASIC-specific SDK global settings copied verbatim into the \"global\" output table.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "sai_overrides": { + "type": "object", + "description": "ASIC-specific SAI settings layered on top of the vendor sai_common values in the \"global\" output table.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "flex_counter_settings": { + "type": "object", + "description": "Flex counter reservation settings. Routed to an output table via pass_through_settings.", + "additionalProperties": { + "type": "integer" + } + }, + "ctr_eflex_config": { + "type": "object", + "description": "Enhanced flex counter settings emitted into the CTR_EFLEX_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": "integer" + } + }, + "dlb_defaults": { + "type": "object", + "description": "Dynamic Load Balancing (DLB) settings applied conditionally via conditional_settings (typically gated by the generate_dlb_config feature).", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "dlb_ecmp_config_defaults": { + "type": "object", + "description": "Defaults for the DLB_ECMP_CONFIG output table. Applied conditionally via conditional_settings, gated by the generate_dlb_ecmp_config feature.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "pass_through_settings": { + "type": "array", + "description": "Declarative list of data blocks copied to output tables after the layered global settings have been applied.", + "items": { + "type": "object", + "required": ["source", "target_table"], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "description": "Key of the data block in this ASIC config to copy from." + }, + "target_table": { + "type": "string", + "description": "Output table name to copy the settings into." + } + } + } + }, + "port_config_defaults": { + "type": "object", + "description": "Default values for the PORT_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "fp_config_defaults": { + "type": "object", + "description": "Default values for the FP_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "tm_thd_config_defaults": { + "type": "object", + "description": "Default values for the TM_THD_CONFIG output table. Routed via pass_through_settings.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "conditional_settings": { + "type": "array", + "description": "Declarative conditional settings. Each entry is evaluated against the variant config; matching entries are applied to the named output tables.", + "items": { + "$ref": "#/$defs/conditional_setting" + } + } + }, + "$defs": { + "conditional_setting": { + "type": "object", + "required": ["name", "condition"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Identifier for this conditional setting; must be unique within the surrounding list." + }, + "description": { + "type": "string", + "description": "Free-form description of the conditional setting and the configuration it produces." + }, + "condition": { + "type": "object", + "required": ["source", "param", "equals"], + "additionalProperties": false, + "properties": { + "source": { + "type": "string", + "enum": ["asic_config_params", "features"], + "description": "Variant-config section holding the parameter to evaluate." + }, + "param": { + "type": "string", + "description": "Name of the parameter to compare." + }, + "equals": { + "description": "Expected value; the condition is satisfied when the parameter equals this value." + } + } + }, + "apply": { + "type": "object", + "description": "Inline settings applied when the condition is satisfied. Keyed by target output table name.", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": ["integer", "string"] + } + } + }, + "apply_from": { + "type": "object", + "description": "Reference to a data block elsewhere in this ASIC config. The named source block is copied into the named target table when the condition is satisfied.", + "required": ["source", "target_table"], + "additionalProperties": false, + "properties": { + "source": { + "type": "string" + }, + "target_table": { + "type": "string" + } + } + }, + "skip_from_sai_common": { + "type": "array", + "description": "Keys to omit from the vendor sai_common block when this condition is satisfied.", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/fboss/lib/asic_config_v3/schemas/platform_config.schema.json b/fboss/lib/asic_config_v3/schemas/platform_config.schema.json new file mode 100644 index 0000000000000..386cd710272d3 --- /dev/null +++ b/fboss/lib/asic_config_v3/schemas/platform_config.schema.json @@ -0,0 +1,267 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Platform Config", + "description": "Per-platform configuration declaring the vendor, ASIC, port layout, and one or more configuration variants for the asic_config_v3 generator.", + "type": "object", + "required": ["platform_name", "vendor", "asic", "variants"], + "additionalProperties": true, + "properties": { + "platform_name": { + "type": "string", + "description": "Platform identifier. Must match the directory name under platforms/." + }, + "vendor": { + "type": "string", + "description": "ASIC vendor identifier. Must match a directory name under vendors/." + }, + "asic": { + "type": "string", + "description": "ASIC chip identifier. Must match the basename of a JSON file under vendors//asics/." + }, + "num_ports_per_core": { + "type": "integer", + "description": "Number of front-panel ports per ASIC core. Combined with num_lanes_per_core to derive the number of lanes per port, unless num_lanes_per_port is set explicitly." + }, + "num_lanes_per_port": { + "type": "integer", + "description": "Optional. Explicit override of the lanes-per-port value derived from num_lanes_per_core / num_ports_per_core. Used by platforms that wire only a subset of the ASIC lanes per port." + }, + "defaults": { + "$ref": "#/$defs/variant", + "description": "Shared variant fields inherited by every variant via deep merge. Values declared inside a specific variant override the corresponding entries in this block." + }, + "variants": { + "type": "object", + "description": "Named configuration variants for this platform. Each variant produces a separate output config file.", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/variant" + } + } + }, + "$defs": { + "variant": { + "type": "object", + "description": "A single configuration variant. Fields may be omitted when their value is inherited from the platform-level defaults block.", + "additionalProperties": true, + "properties": { + "asic_config_params": { + "type": "object", + "description": "Parameters controlling generator behavior for this variant.", + "additionalProperties": true, + "properties": { + "config_type": { + "type": "string", + "enum": ["YAML_CONFIG", "JSON_CONFIG"], + "description": "Output configuration format." + }, + "exact_match": { + "type": "boolean", + "description": "When true, enables exact-match forwarding table entries." + }, + "mmu_lossless": { + "type": "boolean", + "description": "When true, enables MMU lossless mode for PFC / RDMA workloads." + }, + "config_gen_type": { + "type": "string", + "enum": ["DEFAULT", "HW_TEST"], + "description": "Configuration generation profile. Controls which vendor configuration profile is applied." + } + } + }, + "port_config": { + "type": "object", + "description": "Front-panel port settings. Fields may be omitted in a variant when inherited from the platform defaults.", + "additionalProperties": false, + "properties": { + "default_speed": { + "type": "integer", + "description": "Default port speed in Mbps (e.g. 400000 for 400 GbE)." + }, + "speed_to_fec": { + "type": "object", + "description": "Mapping from port speed (encoded as a decimal string) to FEC mode.", + "additionalProperties": { + "type": "string" + } + }, + "enable": { + "type": "integer", + "description": "Optional. Value emitted as PC_PORT.ENABLE for front-panel ports. Defaults to 0." + }, + "link_training": { + "type": "integer", + "description": "Optional. When set, emits LINK_TRAINING: on front-panel PC_PORT entries." + } + } + }, + "cpu_port": { + "type": "object", + "description": "CPU port (logical port 0) settings.", + "required": ["speed", "num_lanes"], + "additionalProperties": false, + "properties": { + "speed": { + "type": "integer", + "description": "CPU port speed in Mbps." + }, + "num_lanes": { + "type": "integer", + "description": "Number of lanes used by the CPU port." + } + } + }, + "mgmt_port": { + "type": "object", + "description": "Management port settings. Fields may be omitted in a variant when inherited from the platform defaults.", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the management port is emitted in the generated config." + }, + "enable": { + "type": "integer", + "description": "Optional. Value emitted as PC_PORT.ENABLE for the management port. Defaults to 0." + }, + "in_port_block": { + "type": "boolean", + "description": "Optional. When true, includes the management port in the PORT (MTU) range alongside the front-panel ports." + }, + "speed_variants": { + "type": "object", + "description": "Per-speed management port settings, keyed by port speed (encoded as a decimal string).", + "additionalProperties": { + "$ref": "#/$defs/mgmt_speed_config" + } + } + } + }, + "device_config_overrides": { + "type": "object", + "description": "Optional. Key / value pairs written to the DEVICE_CONFIG output table (for example, clock frequencies).", + "additionalProperties": { + "type": "string" + } + }, + "port_mapping_overrides": { + "type": "object", + "description": "Optional. Overrides for the logical-to-physical port mapping formula.", + "additionalProperties": false, + "properties": { + "core_range": { + "type": "string", + "description": "Comma-separated list of inclusive core ranges to include in the port mapping (e.g. \"0-15,48-63\"). Defaults to all cores on the ASIC." + }, + "num_lp_ports_on_even_core": { + "type": "integer", + "description": "Override for the number of logical ports placed on even-numbered cores." + }, + "num_logical_ports_per_datapath": { + "type": "integer", + "description": "Override for the ASIC-level num_logical_ports_per_datapath value." + }, + "lp_start_step_offset": { + "type": "integer", + "description": "Offset added to num_logical_ports_per_datapath when computing the per-datapath logical-port start. Defaults to 1." + }, + "lp_offset_simple": { + "type": "boolean", + "description": "When true, computes the per-port logical-port offset as i * lanes_per_port on all cores rather than using the default even / odd conditional." + }, + "core_offset_apply_all": { + "type": "boolean", + "description": "When true, applies the +1 core offset to every core rather than only cores 0 and 1. Required for platforms with a linear-stride port layout." + } + } + }, + "mgmt_port_overrides": { + "type": "object", + "description": "Optional. Per-platform overrides of the ASIC-level mgmt_port_defaults.", + "additionalProperties": false, + "properties": { + "logical_id": {"type": "integer"}, + "physical_id": {"type": "integer"}, + "speed": {"type": "integer"}, + "num_lanes": {"type": "integer"}, + "fec": {"type": "string"} + } + }, + "platform_sai_overrides": { + "type": "object", + "description": "Optional. Platform-level SAI settings layered on top of the ASIC sai_overrides. These take precedence within the global-table layering.", + "additionalProperties": { + "type": ["integer", "string"] + } + }, + "conditional_settings": { + "type": "array", + "description": "Optional. Platform-level conditional settings, evaluated after the ASIC-level conditional_settings so that platform values take precedence.", + "items": {"type": "object"} + }, + "features": { + "type": "object", + "description": "Boolean feature toggles enabling optional config sections. All fields are optional; absent flags default to disabled.", + "additionalProperties": false, + "properties": { + "generate_dlb_config": { + "type": "boolean", + "description": "When true, includes Dynamic Load Balancing settings from the ASIC dlb_defaults block." + }, + "generate_low_clock_freq_settings": { + "type": "boolean", + "description": "When true, includes low-clock-frequency device settings." + }, + "generate_autoload_board_settings": { + "type": "boolean", + "description": "When true, emits AUTOLOAD_BOARD_SETTINGS: 0 in the DEVICE_CONFIG output table." + }, + "generate_dlb_ecmp_config": { + "type": "boolean", + "description": "When true, emits the DLB_ECMP_CONFIG output table from the ASIC dlb_ecmp_config_defaults block." + } + } + }, + "preamble_file": { + "type": "string", + "description": "Optional. Path (relative to the asic_config_v3 module root) of a preamble file prepended to the generated YAML. Honored only when the ASIC declares preamble_support: true." + }, + "platform_mapping_name": { + "type": "string", + "description": "Optional. Overrides the platform_mapping_v2 directory consulted for lane-map and polarity data. Defaults to platform_name. Used when multiple variants share an asic_config but consume sibling platform_mapping_v2 directories." + }, + "ctr_eflex_config": { + "type": "object", + "description": "Optional. Variant-level override of the ASIC ctr_eflex_config pass-through block. When present, replaces the ASIC-level value in its entirety.", + "additionalProperties": { + "type": ["integer", "string"] + } + } + } + }, + "mgmt_speed_config": { + "type": "object", + "description": "Per-speed management port configuration.", + "required": ["num_lanes", "fec"], + "additionalProperties": false, + "properties": { + "num_lanes": { + "type": "integer", + "description": "Number of physical lanes used at this speed." + }, + "fec": { + "type": "string", + "description": "FEC mode applied at this speed." + }, + "pmd_firmware": { + "type": "object", + "description": "Optional. PMD firmware overrides applied at this speed.", + "additionalProperties": { + "type": ["integer", "string"] + } + } + } + } + } +} diff --git a/fboss/lib/asic_config_v3/schemas/vendor_common.schema.json b/fboss/lib/asic_config_v3/schemas/vendor_common.schema.json new file mode 100644 index 0000000000000..2dc883cd5e19d --- /dev/null +++ b/fboss/lib/asic_config_v3/schemas/vendor_common.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Vendor Common Config", + "description": "Vendor-level common settings (sdk_common.json and sai_common.json). All key-value pairs under 'global' are pass-through settings copied to the output.", + "type": "object", + "required": ["global"], + "additionalProperties": false, + "properties": { + "global": { + "type": "object", + "description": "Pass-through SDK or SAI settings copied to the 'global' output table.", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/fboss/lib/asic_config_v3/validation/validate_asic_configs.py b/fboss/lib/asic_config_v3/validation/validate_asic_configs.py new file mode 100755 index 0000000000000..b26e834918bd7 --- /dev/null +++ b/fboss/lib/asic_config_v3/validation/validate_asic_configs.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +""" +Validate ASIC config JSON files against their JSON schemas. + +Usage: + python3 validate_asic_configs.py +""" + +import glob +import json +import os +import sys + +import jsonschema + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +MODULE_DIR = os.path.dirname(SCRIPT_DIR) +SCHEMAS_DIR = os.path.join(MODULE_DIR, "schemas") +VENDORS_DIR = os.path.join(MODULE_DIR, "vendors") +PLATFORMS_DIR = os.path.join(MODULE_DIR, "platforms") + +# Each entry pairs a JSON schema with the set of config files validated against it. +VALIDATION_TARGETS = [ + { + "description": "Vendor common configs", + "schema": "vendor_common.schema.json", + "files_glob": os.path.join(VENDORS_DIR, "**", "*_common.json"), + }, + { + "description": "Broadcom XGS ASIC configs", + "schema": "broadcom_xgs_asic_config.schema.json", + "files_glob": os.path.join( + VENDORS_DIR, "broadcom", "xgs", "asics", "tomahawk*.json" + ), + }, + { + "description": "Platform configs", + "schema": "platform_config.schema.json", + "files_glob": os.path.join(PLATFORMS_DIR, "**", "asic_config.json"), + }, +] + + +def validate(schema, config_path): + with open(config_path) as f: + config = json.load(f) + jsonschema.validate(config, schema) + + +def main(): + passed = 0 + failed = 0 + + for target in VALIDATION_TARGETS: + schema_path = os.path.join(SCHEMAS_DIR, target["schema"]) + with open(schema_path) as f: + schema = json.load(f) + + files = sorted(glob.glob(target["files_glob"], recursive=True)) + + print(f"{target['description']} ({target['schema']}):") + + if not files: + print(" No files found.") + continue + + for path in files: + name = os.path.relpath(path, MODULE_DIR) + try: + validate(schema, path) + print(f" {name}: PASSED") + passed += 1 + except jsonschema.ValidationError as e: + print(f" {name}: FAILED - {e.message}") + failed += 1 + + print() + + print(f"{passed} passed, {failed} failed") + sys.exit(1 if failed else 0) + + +if __name__ == "__main__": + main() diff --git a/fboss/lib/oss/run-helper.py b/fboss/lib/oss/run-helper.py index d271185e3752a..574fb1597191a 100644 --- a/fboss/lib/oss/run-helper.py +++ b/fboss/lib/oss/run-helper.py @@ -13,25 +13,36 @@ """ import argparse +import json import os import subprocess import sys from pathlib import Path -from typing import List, Tuple -def get_command_line_args() -> Tuple[str, List[str]]: +def get_command_line_args() -> tuple[str, dict[str, str], list[str]]: parser = argparse.ArgumentParser( description="OSS FBOSS build and run helper script." ) parser.add_argument("--target", type=str, required=True, help="Target to build") + parser.add_argument( + "--extra-cmake-defines", + type=str, + default="{}", + help=("JSON object of additional cmake defines"), + ) args, unknown_args = parser.parse_known_args() - return args.target, unknown_args + extra_defines = json.loads(args.extra_cmake_defines) + if not isinstance(extra_defines, dict): + print("--extra-cmake-defines must be a JSON object", file=sys.stderr) + sys.exit(1) + + return args.target, extra_defines, unknown_args def main() -> None: - target, command_line_args = get_command_line_args() + target, extra_cmake_defines, command_line_args = get_command_line_args() run_path = os.getcwd() parents = Path(__file__).parents @@ -40,14 +51,14 @@ def main() -> None: "Please run the script from the root of the FBOSS repository", file=sys.stderr, ) - exit(1) + sys.exit(1) expected_path = parents[3].absolute().as_posix() if run_path != expected_path: error_string = f"""Please executed the script from the root of the FBOSS repository Expected run path: {expected_path} Current run path: {run_path}""" print(error_string, file=sys.stderr) - exit(1) + sys.exit(1) get_deps_paths = [ expected_path + "/opensource/fbcode_builder/getdeps.py", @@ -60,7 +71,7 @@ def main() -> None: get_deps_path = maybe_path if get_deps_path is None: print("Could not find getdeps.py", file=sys.stderr) - exit(1) + sys.exit(1) else: print(get_deps_path) @@ -70,11 +81,16 @@ def main() -> None: if is_facebook_machine: get_deps_path = f"{proxy_env_vars} {get_deps_path}" + cmake_defines = {"CMAKE_BUILD_TYPE": "MinSizeRel", "CMAKE_CXX_STANDARD": "20"} + cmake_defines.update(extra_cmake_defines) + cmake_defines_json = json.dumps(cmake_defines) + print(f"Starting build for {target}") subprocess.run( f"""{get_deps_path} build """ - + '--allow-system-packages --num-jobs 32 --extra-cmake-defines=\'{"CMAKE_BUILD_TYPE": "MinSizeRel", "CMAKE_CXX_STANDARD": "20"}\' --cmake-target' + + f"--allow-system-packages --num-jobs 32 --extra-cmake-defines='{cmake_defines_json}' --src-dir {expected_path} --cmake-target" + f" {target} fboss", + check=False, shell=True, ) @@ -82,29 +98,32 @@ def main() -> None: show_build_dir_proc = subprocess.run( f"""{get_deps_path} show-build-dir fboss""", + check=False, shell=True, capture_output=True, text=True, ) - fboss_oss_target = ( - show_build_dir_proc.stdout.rstrip() - + f"/{target}" - + " " - + " ".join(command_line_args) - ) + build_dir = show_build_dir_proc.stdout.rstrip() + target_basename = target.removesuffix(".GEN_PY_EXE") + output_path = f"{build_dir}/{target_basename}" + + # Thrift python output is a directory; everything else is a native executable + python_prefix = "python3 " if os.path.isdir(output_path) else "" + fboss_oss_target = f"{python_prefix}{output_path} {' '.join(command_line_args)}" result = subprocess.run( fboss_oss_target, + check=False, shell=True, ) if result.returncode != 0: print(f"Failed to run target {target}", file=sys.stderr) - exit(1) + sys.exit(1) print("Configs have been written to specified output directory") - exit(0) + sys.exit(0) if __name__ == "__main__":