firmweaver: join CLI tools to new cmd firmweaver - Top level cli "firmweaver coreboot_gpio" and "firmweaver zephyr_dtsi" (aliases: gpio, dtsi) - Add unittests - Update README.md with new build and usage instructions This is needed so both tools can share authentication for google sheets support and usage metrics. BUG=b:475292981 TEST=go test -v -race ./... Change-Id: Ie6293cf9f6c09e1c2650ae3e53de3da05989c0c8 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/firmweaver/+/8318028 Commit-Queue: Kshitij Shah <tij@google.com> Reviewed-by: Karthik Ramasubramanian <kramasub@google.com> Tested-by: Kshitij Shah <tij@google.com>
diff --git a/.gitignore b/.gitignore index ca95764..4fdcb51 100644 --- a/.gitignore +++ b/.gitignore
@@ -1,8 +1,6 @@ # Binaries -ec_fw/ec_fw -ap_fw/gpio_tool - -# Go test binaries +firmweaver +cov.out *.test # IDEs
diff --git a/README.md b/README.md index f97503a..3479249 100644 --- a/README.md +++ b/README.md
@@ -18,47 +18,27 @@ * **Automatic Generation**: Generates firmware objects from hardware sheets. * **Supported Outputs**: - * EC Devicetree (`ec_fw`) - * AP GPIO Configuration (`ap_fw`) + * AP GPIO Configuration (`coreboot_gpio`, alias: `gpio`) + * EC Devicetree (`zephyr_dtsi`, alias: `dtsi`) ## Getting Started -### Building the GPIO Tool +### Building Firmweaver -The GPIO tool is located in the `ap_fw` directory. +Build the `firmweaver` binary from the repository root: -1. Navigate to the `ap_fw` directory: - ```bash - cd ap_fw - ``` -2. Build the tool: - ```bash - go build -o gpio_tool - ``` - This will generate the `gpio_tool` executable in the `ap_fw` directory. - -### Building the EC Devicetree Generator - -The EC Devicetree generator is located in the `ec_fw` directory. See [`ec_fw/README.md`](ec_fw/README.md) for full details. - -1. Navigate to the `ec_fw` directory: - ```bash - cd ec_fw - ``` -2. Build the tool: - ```bash - go build -o ec_fw - ``` - This will generate the `ec_fw` executable in the `ec_fw` directory. +```bash +go build -o firmweaver . +``` ## Usage -### Running the GPIO Tool +### Running the Coreboot GPIO Tool -The `gpio_tool` parses a GPIO map CSV file and generates C configuration code. +The `coreboot_gpio` (alias: `gpio`) parses a GPIO map CSV file and generates C configuration code. ```bash -./ap_fw/gpio_tool --soc <soc_type> --csv <path_to_csv> [--output <path_to_output_c_file>] +./firmweaver coreboot_gpio --soc <soc_type> --csv <path_to_csv> [--output <path_to_output_c_file>] ``` #### Arguments: @@ -69,22 +49,24 @@ #### Example: To generate Intel GPIO configuration from the provided example CSV: ```bash -./ap_fw/gpio_tool --soc intel --csv ap_fw/test/ExampleGpio.csv --output ap_fw/test/gpio_config.c +./firmweaver coreboot_gpio --soc intel --csv coreboot_gpio/test/ExampleGpio.csv --output coreboot_gpio/test/gpio_config.c ``` +*(You can also use the shorthand `./firmweaver gpio ...`)* -### Running the EC Devicetree Generator +### Running the EC Zephyr Devicetree Generator -The `ec_fw` tool parses an EC pinout CSV spreadsheet and generates Zephyr devicetree overlays. +The `zephyr_dtsi` (alias: `dtsi`) parses an EC pinout CSV spreadsheet and generates Zephyr devicetree overlays. ```bash -./ec_fw/ec_fw --chip <chip_name> --csv <path_to_csv> [--pin-col <variant>] [--output <path_to_output_dtsi>] +./firmweaver zephyr_dtsi --chip <chip_name> --csv <path_to_csv> [--pin-col <variant>] [--output <path_to_output_dtsi>] ``` #### Example: To generate Nuvoton NPCX9 Devicetree overlay from the provided example CSV: ```bash -./ec_fw/ec_fw --chip npcx99 --csv ec_fw/test/ExampleEcPinout.csv --pin-col "NPCX99 (NPCX)" --output ec_fw/test/generated.dtsi +./firmweaver zephyr_dtsi --chip npcx99 --csv zephyr_dtsi/test/ExampleEcPinout.csv --pin-col "NPCX99 (NPCX)" --output zephyr_dtsi/test/generated.dtsi ``` +*(You can also use the shorthand `./firmweaver dtsi ...`)* ## Testing
diff --git a/coreboot_gpio/gpio_tool.go b/coreboot_gpio/gpio_tool.go index f9f5597..4a54411 100644 --- a/coreboot_gpio/gpio_tool.go +++ b/coreboot_gpio/gpio_tool.go
@@ -2,15 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package coreboot_gpio import ( "encoding/csv" + "errors" "flag" "fmt" "io" "net/url" "os" + "path/filepath" "strconv" "strings" ) @@ -75,42 +77,35 @@ return gpioPins, nil } -func main() { - soc := flag.String("soc", "", "SoC type (intel, qcom, or mtk)") - csvFile := flag.String("csv", "", "Local file path for GPIO map CSV") - outputFile := flag.String("output", "", "Output file path") - flag.Parse() +// Run executes the AP firmware GPIO configuration generation logic. +func Run(args []string) error { + fs := flag.NewFlagSet("coreboot_gpio", flag.ContinueOnError) + soc := fs.String("soc", "", "SoC type (intel, qcom, or mtk)") + csvFile := fs.String("csv", "", "Local file path for GPIO map CSV") + outputFile := fs.String("output", "", "Output file path") + + if err := fs.Parse(args); err != nil { + if errors.Is(err, flag.ErrHelp) { + return nil + } + return err + } + if len(fs.Args()) > 0 { + return fmt.Errorf("unexpected arguments: %v", fs.Args()) + } if *soc == "" { - fmt.Fprintln(os.Stderr, "Error: --soc flag is required") - flag.Usage() - os.Exit(1) + return fmt.Errorf("--soc flag is required") } if *csvFile == "" { - fmt.Fprintln(os.Stderr, "Error: --csv flag is required") - flag.Usage() - os.Exit(1) - } - - var outputWriter io.Writer - if *outputFile != "" { - f, err := os.Create(*outputFile) - if err != nil { - fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err) - os.Exit(1) - } - defer f.Close() - outputWriter = f - } else { - outputWriter = os.Stdout + return fmt.Errorf("--csv flag is required") } // Open the local CSV file file, err := os.Open(*csvFile) if err != nil { - fmt.Fprintf(os.Stderr, "Error opening CSV file: %v\n", err) - os.Exit(1) + return fmt.Errorf("opening CSV file: %w", err) } defer file.Close() @@ -118,36 +113,63 @@ fmt.Fprintf(os.Stderr, "Reading GPIO map from: %s\n", *csvFile) reader := csv.NewReader(file) + reader.FieldsPerRecord = -1 records, err := reader.ReadAll() if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing CSV: %v\n", err) - os.Exit(1) + return fmt.Errorf("parsing CSV: %w", err) } + var outputWriter io.Writer + var outCloser io.Closer + if *outputFile != "" { + dir := filepath.Dir(*outputFile) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("creating output directory: %w", err) + } + f, err := os.Create(*outputFile) + if err != nil { + return fmt.Errorf("creating output file: %w", err) + } + outCloser = f + outputWriter = f + } else { + outputWriter = os.Stdout + } + defer func() { + if outCloser != nil { + _ = outCloser.Close() + } + }() + switch *soc { case "intel": err := processIntelGPIOs(records, outputWriter) if err != nil { - fmt.Fprintf(os.Stderr, "Error processing Intel GPIOs: %v\n", err) - os.Exit(1) + return fmt.Errorf("processing Intel GPIOs: %w", err) } case "qcom", "mtk": gpioPins, err := parseGenericCSV(records) if err != nil { - fmt.Fprintf(os.Stderr, "Error parsing CSV: %v\n", err) - os.Exit(1) + return fmt.Errorf("parsing CSV: %w", err) } fmt.Fprintln(os.Stderr, "Successfully parsed GPIO pin data.") for _, pin := range gpioPins { fmt.Fprintf(outputWriter, "- Name: %s, Pin: %d, Function: %s\n", pin.Name, pin.Pin, pin.Function) } default: - fmt.Fprintf(os.Stderr, "Error: Unsupported SoC '%s'. Must be one of 'intel', 'qcom', or 'mtk'.\n", *soc) - flag.Usage() - os.Exit(1) + return fmt.Errorf("unsupported SoC '%s'. Must be one of 'intel', 'qcom', or 'mtk'", *soc) + } + + if outCloser != nil { + if err := outCloser.Close(); err != nil { + return fmt.Errorf("closing output file: %w", err) + } + outCloser = nil } if *outputFile != "" { fmt.Fprintf(os.Stderr, "Parsed data written to %s\n", *outputFile) } + + return nil }
diff --git a/coreboot_gpio/gpio_tool_test.go b/coreboot_gpio/gpio_tool_test.go new file mode 100644 index 0000000..a3711d6 --- /dev/null +++ b/coreboot_gpio/gpio_tool_test.go
@@ -0,0 +1,51 @@ +// Copyright 2026 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package coreboot_gpio + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestGpioTool_RunValidation(t *testing.T) { + if err := Run([]string{"-h"}); err != nil { + t.Errorf("Run('-h') unexpected error: %v", err) + } + if err := Run([]string{"--help"}); err != nil { + t.Errorf("Run(--help) expected nil, got %v", err) + } + if err := Run([]string{}); err == nil { + t.Errorf("expected error with no flags") + } + if err := Run([]string{"--soc=intel"}); err == nil { + t.Errorf("expected error without --csv") + } + if err := Run([]string{"--soc=unsupported", "--csv=test/ExampleGpio.csv"}); err == nil { + t.Errorf("expected error with unsupported SoC") + } +} + +func TestGpioTool_RunSuccess(t *testing.T) { + out := filepath.Join(t.TempDir(), "gpio.c") + err := Run([]string{ + "--soc=intel", + "--csv=test/ExampleGpio.csv", + "--output=" + out, + }) + if err != nil { + t.Fatalf("Run failed: %v", err) + } + + data, err := os.ReadFile(out) + if err != nil { + t.Fatalf("failed to read output file: %v", err) + } + content := string(data) + if !strings.Contains(content, "PAD_CFG") { + t.Errorf("expected generated C file to contain PAD_CFG") + } +}
diff --git a/coreboot_gpio/intel_gpio_config.go b/coreboot_gpio/intel_gpio_config.go index 936f1c2..f5a02d3 100644 --- a/coreboot_gpio/intel_gpio_config.go +++ b/coreboot_gpio/intel_gpio_config.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package coreboot_gpio import ( "fmt" @@ -190,7 +190,7 @@ // writeCBoilerplate writes the header of the generated C file. func writeCBoilerplate(w io.Writer) { - fmt.Fprintln(w, "// Generated by gpio_tool.go") + fmt.Fprintln(w, "// Generated by coreboot_gpio") fmt.Fprintln(w, "") fmt.Fprintln(w, "static const struct pad_config gpio_table[] = {") }
diff --git a/coreboot_gpio/test/gpio_config.c b/coreboot_gpio/test/gpio_config.c index 4a5fe3e..8846b87 100644 --- a/coreboot_gpio/test/gpio_config.c +++ b/coreboot_gpio/test/gpio_config.c
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Generated by gpio_tool.go +// Generated by coreboot_gpio static const struct pad_config gpio_table[] = { /* GPP_A02: ESPI_SOC_IO2_R */
diff --git a/main.go b/main.go new file mode 100644 index 0000000..596f77b --- /dev/null +++ b/main.go
@@ -0,0 +1,65 @@ +// Copyright 2026 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "os" + + "firmweaver/coreboot_gpio" + "firmweaver/zephyr_dtsi" +) + +func printUsage(w io.Writer) { + fmt.Fprintln(w, `Firmweaver + +Usage: + firmweaver <command> [flags...] + +Available Commands: + coreboot_gpio (alias: gpio) Generate AP SoC GPIO configurations + zephyr_dtsi (alias: dtsi) Generate EC Devicetree (DTSI) pinmux configurations`) +} + +func main() { + if err := run(os.Args[1:]); err != nil { + if errors.Is(err, flag.ErrHelp) { + os.Exit(0) + } + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func run(args []string) error { + if len(args) == 0 { + printUsage(os.Stderr) + return fmt.Errorf("subcommand required: 'coreboot_gpio' or 'zephyr_dtsi'") + } + + switch args[0] { + case "coreboot_gpio", "gpio": + err := coreboot_gpio.Run(args[1:]) + if errors.Is(err, flag.ErrHelp) { + return nil + } + return err + case "zephyr_dtsi", "dtsi": + err := zephyr_dtsi.Run(args[1:]) + if errors.Is(err, flag.ErrHelp) { + return nil + } + return err + case "-h", "--help", "help": + printUsage(os.Stdout) + return nil + default: + printUsage(os.Stderr) + return fmt.Errorf("unknown command %q", args[0]) + } +}
diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..888a40d --- /dev/null +++ b/main_test.go
@@ -0,0 +1,45 @@ +// Copyright 2026 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package main + +import ( + "testing" +) + +func TestRun_SubcommandRouting(t *testing.T) { + if err := run([]string{}); err == nil { + t.Error("run([]) expected error for missing subcommand, got nil") + } + if err := run([]string{"help"}); err != nil { + t.Errorf("run('help') unexpected error: %v", err) + } + if err := run([]string{"-h"}); err != nil { + t.Errorf("run('-h') unexpected error: %v", err) + } + if err := run([]string{"--help"}); err != nil { + t.Errorf("run('--help') unexpected error: %v", err) + } + if err := run([]string{"zephyr_dtsi", "-h"}); err != nil { + t.Errorf("run('zephyr_dtsi -h') unexpected error: %v", err) + } + if err := run([]string{"zephyr_dtsi", "--help"}); err != nil { + t.Errorf("run('zephyr_dtsi --help') unexpected error: %v", err) + } + if err := run([]string{"dtsi", "-h"}); err != nil { + t.Errorf("run('dtsi -h') unexpected error: %v", err) + } + if err := run([]string{"coreboot_gpio", "-h"}); err != nil { + t.Errorf("run('coreboot_gpio -h') unexpected error: %v", err) + } + if err := run([]string{"coreboot_gpio", "--help"}); err != nil { + t.Errorf("run('coreboot_gpio --help') unexpected error: %v", err) + } + if err := run([]string{"gpio", "-h"}); err != nil { + t.Errorf("run('gpio -h') unexpected error: %v", err) + } + if err := run([]string{"invalid"}); err == nil { + t.Error("run('invalid') expected error for unknown command, got nil") + } +}
diff --git a/zephyr_dtsi/README.md b/zephyr_dtsi/README.md index 4bcbf4f..0dd96e5 100644 --- a/zephyr_dtsi/README.md +++ b/zephyr_dtsi/README.md
@@ -1,6 +1,6 @@ -# EC Firmware Devicetree Generator (`ec_fw`) +# EC Firmware Devicetree Generator (`zephyr_dtsi`) -`ec_fw` is a tool for generating Zephyr EC Devicetree configuration files (`generated.dtsi`) for Chromium OS EC board bringup. +`zephyr_dtsi` is a tool for generating Zephyr EC Devicetree configuration files (`generated.dtsi`) for Chromium OS EC board bringup. It ingests hardware pinout CSV spreadsheets prepared by board design teams and outputs a self-contained Zephyr Devicetree overlay containing named GPIOs, ADC channels, PWM channels, I2C ports, TACH channels, and Keyboard matrix configurations. @@ -28,7 +28,7 @@ ## CLI Reference ```bash -go run ./ec_fw --chip=<chip_name> --csv=<path_to_csv> [options] +./firmweaver dtsi --chip=<chip_name> --csv=<path_to_csv> [options] ``` ### Options @@ -46,29 +46,29 @@ ## Usage Examples ### 1. Generating Devicetree Using the Bundled Test Example -To test generating a `generated.dtsi` file from the example test CSV included in `ec_fw/test/`: +To test generating a `generated.dtsi` file from the example test CSV included in `zephyr_dtsi/test/`: For NPCX (using `npcx99` chip): ```bash -go run ./ec_fw --chip=npcx99 \ - --csv=ec_fw/test/ExampleEcPinout.csv \ +./firmweaver dtsi --chip=npcx99 \ + --csv=zephyr_dtsi/test/ExampleEcPinout.csv \ --pin-col="NPCX99 (NPCX)" \ - --output=ec_fw/test/generated_npcx.dtsi + --output=zephyr_dtsi/test/generated_npcx.dtsi ``` For ITE (using `it82002` chip): ```bash -go run ./ec_fw --chip=it82002 \ - --csv=ec_fw/test/ExampleEcPinout.csv \ +./firmweaver dtsi --chip=it82002 \ + --csv=zephyr_dtsi/test/ExampleEcPinout.csv \ --pin-col="IT82002 (ITE)" \ - --output=ec_fw/test/generated_ite.dtsi + --output=zephyr_dtsi/test/generated_ite.dtsi ``` ### 2. Generating Devicetree for Board Variants To generate a `generated.dtsi` file for the Fatcat **RVP** (NPCX chip): ```bash -go run ./ec_fw --chip=npcx99 \ +./firmweaver dtsi --chip=npcx99 \ --csv=/path/to/fatcat.csv \ --pin-col="NPCx9 RVP" \ --output=/path/to/platform/ec/zephyr/program/fatcat/generated_npcx.dtsi @@ -77,18 +77,18 @@ To generate for the Fatcat **ITE** variant: ```bash -go run ./ec_fw --chip=it82002 \ +./firmweaver dtsi --chip=it82002 \ --csv=/path/to/fatcat.csv \ --pin-col="IT82002 Ref" \ --output=/path/to/platform/ec/zephyr/program/fatcat/generated_ite.dtsi ``` ### 3. Building and Running Binary -Build the standalone `ec_fw` binary from the repository root: +Build the standalone `zephyr_dtsi` binary from the repository root: ```bash -go build -o ec_fw ./ec_fw -./ec_fw --chip=it82202ax --csv=/path/to/hylia.csv --output=generated.dtsi +go build -o firmweaver . +./firmweaver dtsi --chip=it82202ax --csv=/path/to/hylia.csv --output=generated.dtsi ``` ### 4. Adding Custom Chip Pin Maps @@ -106,12 +106,12 @@ ``` 2. Pass the directory containing your JSON file to `--chip-data-dir`: ```bash - go run ./ec_fw --chip-data-dir=/path/to/custom_chips --chip=custom_chip --csv=board.csv + ./firmweaver dtsi --chip-data-dir=/path/to/custom_chips --chip=custom_chip --csv=board.csv ``` ### 5. Running Tests -Run unit tests across the `ec_fw` tool: +Run unit tests across the `zephyr_dtsi` tool: ```bash -go test -v ./ec_fw/... +go test -v ./zephyr_dtsi/... ```
diff --git a/zephyr_dtsi/chips.go b/zephyr_dtsi/chips.go index 7007069..a9cf881 100644 --- a/zephyr_dtsi/chips.go +++ b/zephyr_dtsi/chips.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package zephyr_dtsi import ( "embed"
diff --git a/zephyr_dtsi/chips_test.go b/zephyr_dtsi/chips_test.go index 3fca81d..f2db0b2 100644 --- a/zephyr_dtsi/chips_test.go +++ b/zephyr_dtsi/chips_test.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package zephyr_dtsi import ( "os"
diff --git a/zephyr_dtsi/generator.go b/zephyr_dtsi/generator.go index f7dd536..030ef17 100644 --- a/zephyr_dtsi/generator.go +++ b/zephyr_dtsi/generator.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package zephyr_dtsi import ( "cmp"
diff --git a/zephyr_dtsi/generator_test.go b/zephyr_dtsi/generator_test.go index 629b109..aa304cc 100644 --- a/zephyr_dtsi/generator_test.go +++ b/zephyr_dtsi/generator_test.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package zephyr_dtsi import ( "bytes"
diff --git a/zephyr_dtsi/main.go b/zephyr_dtsi/main.go deleted file mode 100644 index 0846fa8..0000000 --- a/zephyr_dtsi/main.go +++ /dev/null
@@ -1,90 +0,0 @@ -// Copyright 2026 The ChromiumOS Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package main - -import ( - "flag" - "fmt" - "io" - "os" - "path/filepath" -) - -func main() { - if err := run(); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } -} - -func run() error { - chipFlag := flag.String("chip", "", "Supported EC chip name or alias (e.g. npcx99, npcx993, it81302, it81202, it82002, it82202)") - csvFlag := flag.String("csv", "", "Path to the EC pinout CSV file") - pinColFlag := flag.String("pin-col", "", "Optional: Column header containing pin numbers (e.g. 'NPCX99 (NPCX)' or 'IT82002 (ITE)')") - chipDataDirFlag := flag.String("chip-data-dir", "", "Optional: Path to directory containing custom chip JSON data files") - outputFlag := flag.String("output", "", "Path to the output DTSI file (defaults to stdout)") - flag.Parse() - - if *chipDataDirFlag != "" { - if err := LoadExternalChipDir(*chipDataDirFlag); err != nil { - return fmt.Errorf("error loading chip data dir %s: %w", *chipDataDirFlag, err) - } - } - - if *chipFlag == "" { - flag.Usage() - return fmt.Errorf("error: --chip is required. Available chips: %s", GetAvailableChips()) - } - - if *csvFlag == "" { - flag.Usage() - return fmt.Errorf("error: --csv is required") - } - - chip, ok := GetChip(*chipFlag) - if !ok { - return fmt.Errorf("error: Unsupported chip %q. Available: %s", *chipFlag, GetAvailableChips()) - } - - csvFile, err := os.Open(*csvFlag) - if err != nil { - return fmt.Errorf("error opening CSV file: %w", err) - } - defer csvFile.Close() - - configs, err := ParseCSV(csvFile, *pinColFlag) - if err != nil { - return fmt.Errorf("error parsing CSV: %w", err) - } - - var out io.Writer = os.Stdout - var tmpFile *os.File - if *outputFlag != "" { - dir := filepath.Dir(*outputFlag) - var err error - tmpFile, err = os.CreateTemp(dir, ".tmp_generated_*.dtsi") - if err != nil { - return fmt.Errorf("error creating temp file: %w", err) - } - defer func() { - tmpFile.Close() - os.Remove(tmpFile.Name()) - }() - out = tmpFile - } - - if err := GenerateDTSI(out, configs, chip); err != nil { - return fmt.Errorf("error generating DTSI: %w", err) - } - - if tmpFile != nil { - tmpFile.Close() - if err := os.Rename(tmpFile.Name(), *outputFlag); err != nil { - return fmt.Errorf("error saving output file: %w", err) - } - } - - return nil -}
diff --git a/zephyr_dtsi/main_test.go b/zephyr_dtsi/main_test.go index c598e4c..c0e7f29 100644 --- a/zephyr_dtsi/main_test.go +++ b/zephyr_dtsi/main_test.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package zephyr_dtsi import ( "slices"
diff --git a/zephyr_dtsi/parser.go b/zephyr_dtsi/parser.go index d91d6d2..6e9327e 100644 --- a/zephyr_dtsi/parser.go +++ b/zephyr_dtsi/parser.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package zephyr_dtsi import ( "encoding/csv"
diff --git a/zephyr_dtsi/parser_test.go b/zephyr_dtsi/parser_test.go index 3468617..ea0a7fd 100644 --- a/zephyr_dtsi/parser_test.go +++ b/zephyr_dtsi/parser_test.go
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package main +package zephyr_dtsi import ( "strings"
diff --git a/zephyr_dtsi/run.go b/zephyr_dtsi/run.go new file mode 100644 index 0000000..a731bf9 --- /dev/null +++ b/zephyr_dtsi/run.go
@@ -0,0 +1,104 @@ +// Copyright 2026 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package zephyr_dtsi + +import ( + "errors" + "flag" + "fmt" + "io" + "os" + "path/filepath" +) + +// Run executes the EC firmware pinmux DTSI generation logic. +func Run(args []string) error { + fs := flag.NewFlagSet("zephyr_dtsi", flag.ContinueOnError) + chipFlag := fs.String("chip", "", "Supported EC chip name or alias (e.g. npcx99, it82002)") + csvFlag := fs.String("csv", "", "Path to the local EC pinout CSV file") + pinColFlag := fs.String("pin-col", "", "Optional: Column header containing pin numbers") + chipDataDirFlag := fs.String("chip-data-dir", "", "Optional: Path to directory containing custom chip JSON data files") + outputFlag := fs.String("output", "", "Path to the output DTSI file (defaults to stdout)") + + if err := fs.Parse(args); err != nil { + if errors.Is(err, flag.ErrHelp) { + return nil + } + return err + } + if len(fs.Args()) > 0 { + return fmt.Errorf("unexpected arguments: %v", fs.Args()) + } + + if *chipDataDirFlag != "" { + if err := LoadExternalChipDir(*chipDataDirFlag); err != nil { + return fmt.Errorf("loading chip data dir %s: %w", *chipDataDirFlag, err) + } + } + + if *chipFlag == "" { + return fmt.Errorf("--chip is required (available: %s)", GetAvailableChips()) + } + if *csvFlag == "" { + return fmt.Errorf("--csv is required") + } + + chip, ok := GetChip(*chipFlag) + if !ok { + return fmt.Errorf("unsupported chip %q (available: %s)", *chipFlag, GetAvailableChips()) + } + + csvFile, err := os.Open(*csvFlag) + if err != nil { + return fmt.Errorf("opening CSV file: %w", err) + } + defer csvFile.Close() + + configs, err := ParseCSV(csvFile, *pinColFlag) + if err != nil { + return fmt.Errorf("parsing CSV: %w", err) + } + + var out io.Writer = os.Stdout + var tmpFile *os.File + var tmpPath string + if *outputFlag != "" { + dir := filepath.Dir(*outputFlag) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("creating output directory: %w", err) + } + tmp, err := os.CreateTemp(dir, ".tmp_dtsi_*") + if err != nil { + return fmt.Errorf("creating temporary file: %w", err) + } + tmpFile = tmp + tmpPath = tmp.Name() + out = tmp + } + defer func() { + if tmpFile != nil { + _ = tmpFile.Close() + if tmpPath != "" { + _ = os.Remove(tmpPath) + } + } + }() + + if err := GenerateDTSI(out, configs, chip); err != nil { + return fmt.Errorf("generating DTSI: %w", err) + } + + if tmpFile != nil { + if err := tmpFile.Close(); err != nil { + return fmt.Errorf("closing output file: %w", err) + } + if err := os.Rename(tmpPath, *outputFlag); err != nil { + return fmt.Errorf("finalizing output file: %w", err) + } + tmpFile = nil + } + + return nil +}
diff --git a/zephyr_dtsi/run_test.go b/zephyr_dtsi/run_test.go new file mode 100644 index 0000000..c363cba --- /dev/null +++ b/zephyr_dtsi/run_test.go
@@ -0,0 +1,52 @@ +// Copyright 2026 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package zephyr_dtsi + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestDtsiTool_RunFlagValidation(t *testing.T) { + if err := Run([]string{"-h"}); err != nil { + t.Errorf("Run('-h') unexpected error: %v", err) + } + if err := Run([]string{"--help"}); err != nil { + t.Errorf("Run(--help) expected nil, got %v", err) + } + if err := Run([]string{}); err == nil { + t.Errorf("expected error with no flags") + } + if err := Run([]string{"--chip=npcx99"}); err == nil { + t.Errorf("expected error without --csv") + } + if err := Run([]string{"--chip=invalid", "--csv=test/ExampleEcPinout.csv"}); err == nil { + t.Errorf("expected error with invalid chip") + } +} + +func TestDtsiTool_RunSuccess(t *testing.T) { + out := filepath.Join(t.TempDir(), "out.dtsi") + err := Run([]string{ + "--chip=npcx99", + "--pin-col=NPCX99 (NPCX)", + "--csv=test/ExampleEcPinout.csv", + "--output=" + out, + }) + if err != nil { + t.Fatalf("Run failed: %v", err) + } + + data, err := os.ReadFile(out) + if err != nil { + t.Fatalf("failed to read generated file: %v", err) + } + content := string(data) + if !strings.Contains(content, "named-gpios") { + t.Errorf("expected generated DTSI to contain named-gpios") + } +}
diff --git a/zephyr_dtsi/test/README.md b/zephyr_dtsi/test/README.md index f49146b..2cdcf3f 100644 --- a/zephyr_dtsi/test/README.md +++ b/zephyr_dtsi/test/README.md
@@ -1,6 +1,6 @@ -# `ec_fw` Test & Example Directory +# `zephyr_dtsi` Test & Example Directory -This directory contains reference example files for validating and testing the `ec_fw` EC Devicetree generator. +This directory contains reference example files for validating and testing the `zephyr_dtsi` EC Devicetree generator. --- @@ -18,10 +18,10 @@ Run the following command from the repository root: ```bash -go run ./ec_fw --chip=npcx99 \ - --csv=ec_fw/test/ExampleEcPinout.csv \ +./firmweaver dtsi --chip=npcx99 \ + --csv=zephyr_dtsi/test/ExampleEcPinout.csv \ --pin-col="NPCX99 (NPCX)" \ - --output=ec_fw/test/generated.dtsi + --output=zephyr_dtsi/test/generated.dtsi ``` ### 2. Generate Reference Devicetree (`IT82002` Variant - ITE IT82002) @@ -29,8 +29,8 @@ To generate for an ITE-based variant from the same spreadsheet: ```bash -go run ./ec_fw --chip=it82002 \ - --csv=ec_fw/test/ExampleEcPinout.csv \ +./firmweaver dtsi --chip=it82002 \ + --csv=zephyr_dtsi/test/ExampleEcPinout.csv \ --pin-col="IT82002 (ITE)" \ - --output=ec_fw/test/generated_ite.dtsi + --output=zephyr_dtsi/test/generated_ite.dtsi ```