util/spd_tools: Improve spd_gen CLI for Make

Add options that simplify integrating spd_gen into build systems:
- Allow choosing an output directory via -out/--out.
- Allow writing a single SPD hex file from a single-part JSON input.
- Accept either {name, attribs/attributes} or a single-part
  memory_parts.json.
- Strip // and /* */ comments from JSON inputs.
- Allow selecting the SPD set number for single-file output via
  -set/--set (default: 0).

The set number selects which set-N attributes are used when generating a
single output .hex file.

Change-Id: Iebbdcdaea7dc69e37b048ad2113007fae5471bad
Signed-off-by: Sean Rhodes <sean@starlabs.systems>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/89799
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
This commit is contained in:
Sean Rhodes 2025-10-28 20:20:23 +00:00 committed by Matt DeVillier
commit 9a8d22dcaa
2 changed files with 148 additions and 10 deletions

View file

@ -563,7 +563,7 @@ make -C util/spd_tools
Usage:
```
util/spd_tools/bin/spd_gen <mem_parts_list_json> <mem_technology>
util/spd_tools/bin/spd_gen [ -o <output_dir> ] <mem_parts_list_json> <mem_technology> [<output_dir>|<output_hex>]
```
Usage Examples:
@ -572,6 +572,8 @@ Usage Examples:
util/spd_tools/bin/spd_gen spd/ddr4/memory_parts.json ddr4
util/spd_tools/bin/spd_gen spd/lp4x/memory_parts.json lp4x
util/spd_tools/bin/spd_gen spd/lp5/memory_parts.json lp5
util/spd_tools/bin/spd_gen -o out/lp5 spd/lp5/memory_parts.json lp5
util/spd_tools/bin/spd_gen --set 0 part.spd.json lp5 out/spd.hex
```
### `part_id_gen`

View file

@ -3,7 +3,10 @@ package main
import (
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
@ -163,18 +166,29 @@ func findIndex(dedupedAttribs []interface{}, newSPDAttribs interface{}) int {
return -1
}
// readJSONNoComments reads a JSON file and strips supported comment styles:
// - line comments: "// ..." (on their own line, with optional leading spaces)
// - block comments: "/* ... */"
func readJSONNoComments(path string) ([]byte, error) {
dataBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
re := regexp.MustCompile(`(?m)^\s*//.*`)
dataBytes = re.ReplaceAll(dataBytes, []byte(""))
reBlock := regexp.MustCompile(`(?s)/\*.*?\*/`)
dataBytes = reBlock.ReplaceAll(dataBytes, []byte(""))
return dataBytes, nil
}
func readMemParts(memPartsFilePath string) (memParts, error) {
var memParts memParts
dataBytes, err := ioutil.ReadFile(memPartsFilePath)
dataBytes, err := readJSONNoComments(memPartsFilePath)
if err != nil {
return memParts, err
}
// Strip comments from json file
re := regexp.MustCompile(`(?m)^\s*//.*`)
dataBytes = re.ReplaceAll(dataBytes, []byte(""))
if err := json.Unmarshal(dataBytes, &memParts); err != nil {
return memParts, err
}
@ -182,6 +196,73 @@ func readMemParts(memPartsFilePath string) (memParts, error) {
return memParts, nil
}
func readSingleAttributes(path string) (string, interface{}, error) {
dataBytes, err := readJSONNoComments(path)
if err != nil {
return "", nil, err
}
var root interface{}
if err := json.Unmarshal(dataBytes, &root); err != nil {
return "", nil, err
}
m, ok := root.(map[string]interface{})
if !ok {
return "", nil, errors.New("expected JSON object at top-level")
}
baseName := ""
{
base := filepath.Base(path)
if i := strings.LastIndex(base, "."); i >= 0 {
base = base[:i]
}
baseName = base
}
// Accept either:
// 1) {"name": "...", "attributes": {...}} (or "attribs")
// 2) {"parts":[{"name":"...","attribs":{...}}]} (single-part memory_parts.json)
if partsRaw, ok := m["parts"]; ok {
parts, ok := partsRaw.([]interface{})
if !ok || len(parts) == 0 {
return "", nil, errors.New("expected non-empty 'parts' array")
}
if len(parts) != 1 {
return "", nil, errors.New("expected exactly one part in 'parts' array")
}
p, ok := parts[0].(map[string]interface{})
if !ok {
return "", nil, errors.New("expected part entry to be a JSON object")
}
name, _ := p["name"].(string)
if name == "" {
name = baseName
}
if a, ok := p["attributes"]; ok {
return name, a, nil
}
if a, ok := p["attribs"]; ok {
return name, a, nil
}
return "", nil, errors.New("missing 'attribs'/'attributes' in part entry")
}
name, _ := m["name"].(string)
if name == "" {
name = baseName
}
if a, ok := m["attributes"]; ok {
return name, a, nil
}
if a, ok := m["attribs"]; ok {
return name, a, nil
}
return "", nil, errors.New("missing 'attribs'/'attributes' in JSON")
}
func createSPD(memAttribs interface{}, t memTech) string {
var s string
@ -249,33 +330,88 @@ func writeSetMap(setMap map[int][]int, SPDDirName string) {
}
func usage() {
fmt.Printf("\nUsage: %s <mem_parts_list_json> <mem_technology>\n\n", os.Args[0])
fmt.Printf("\nUsage: %s [options] <mem_parts_list_json> <mem_technology> [<output_dir>|<output_hex>]\n\n", os.Args[0])
fmt.Printf(" where,\n")
fmt.Printf(" mem_parts_list_json = JSON File containing list of memory parts and attributes\n")
fmt.Printf(" mem_technology = Memory technology for which to generate SPDs\n")
fmt.Printf(" supported technologies: %v\n\n\n",
reflect.ValueOf(memTechMap).MapKeys())
fmt.Printf("Options:\n")
fmt.Printf(" -out <output_dir> Output directory (default: directory of JSON input)\n")
fmt.Printf(" -set <n> Set number when writing a single output_hex (default: 0)\n\n")
}
func main() {
if len(os.Args) != 3 {
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fs.SetOutput(io.Discard)
fs.Usage = usage
outOpt := fs.String("out", "", "Output directory (default: directory of JSON input)")
setOpt := fs.Int("set", 0, "Set number when writing a single output_hex (default: 0)")
if err := fs.Parse(os.Args[1:]); err != nil {
usage()
os.Exit(2)
}
if *setOpt < 0 {
log.Fatal("Invalid -set value: ", *setOpt)
}
varPos := fs.Args()
if len(varPos) != 2 && len(varPos) != 3 {
usage()
log.Fatal("Incorrect number of arguments")
}
var t memTech
memPartsFilePath, memTechnology := os.Args[1], os.Args[2]
memPartsFilePath, memTechnology := varPos[0], varPos[1]
t, ok := memTechMap[strings.ToLower(memTechnology)]
if !ok {
log.Fatal("Unsupported memory technology ", memTechnology)
}
SPDDir, err := filepath.Abs(filepath.Dir(memPartsFilePath))
var SPDDir string
fileOutput := false
SPDOutFile := ""
var err error
if len(varPos) == 3 && strings.HasSuffix(strings.ToLower(varPos[2]), ".hex") {
fileOutput = true
SPDOutFile, err = filepath.Abs(varPos[2])
} else if *outOpt != "" {
SPDDir, err = filepath.Abs(*outOpt)
} else if len(varPos) == 3 {
SPDDir, err = filepath.Abs(varPos[2])
} else {
SPDDir, err = filepath.Abs(filepath.Dir(memPartsFilePath))
}
if err != nil {
log.Fatal(err)
}
if fileOutput {
name, attrs, err := readSingleAttributes(memPartsFilePath)
if err != nil {
log.Fatal(err)
}
if err := t.addNewPart(name, attrs); err != nil {
log.Fatal(err)
}
spdAttribs, err := t.getSPDAttribs(name, *setOpt)
if err != nil {
log.Fatal(err)
}
s := createSPD(spdAttribs, t)
if err := os.MkdirAll(filepath.Dir(SPDOutFile), os.ModePerm); err != nil {
log.Fatal(err)
}
if err := ioutil.WriteFile(SPDOutFile, []byte(s), 0644); err != nil {
log.Fatal(err)
}
return
}
memParts, err := readMemParts(memPartsFilePath)
if err != nil {
log.Fatal(err)