xref: /aosp_15_r20/external/coreboot/util/spd_tools/src/part_id_gen/part_id_gen.go (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2
3package main
4
5import (
6	"encoding/csv"
7	"fmt"
8	"io"
9	"io/ioutil"
10	"log"
11	"os"
12	"path/filepath"
13	"strconv"
14	"strings"
15)
16
17/*
18 * This program allocates DRAM strap IDs for different parts that are being used by the variant.
19 *
20 * It expects the following inputs:
21 *  Name of the SoC platform, e.g. TGL.
22 *  Memory technology used by the variant, e.g. lp4x.
23 *  Path to Makefile directory. Makefile.mk generated by this program is placed in this
24 *  location.
25 *  Text file containing a list of memory part names used by the board. Each line in the file
26 *  is expected to have one memory part name.
27 */
28const (
29	SPDManifestFileName       = "parts_spd_manifest.generated.txt"
30	PlatformsManifestFileName = "platforms_manifest.generated.txt"
31	SPDEmptyFileName          = "spd-empty.hex"
32	MakefileName              = "Makefile.mk"
33	DRAMIdFileName            = "dram_id.generated.txt"
34	MaxMemoryId               = 15
35)
36
37var supportedPlatforms = [...]string{
38	"TGL",
39	"ADL",
40	"JSL",
41	"PCO",
42	"CZN",
43	"MDN",
44	"MTL",
45	"PHX",
46}
47
48var supportedMemTechs = [...]string{
49	"lp4x",
50	"ddr4",
51	"lp5",
52}
53
54func usage() {
55	fmt.Printf("\nUsage: %s <platform> <mem_technology> <makefile_dir> <mem_parts_used_file>\n\n", os.Args[0])
56	fmt.Printf("   where,\n")
57	fmt.Printf("   platform = SoC platform which the board is based on\n")
58	fmt.Printf("              supported platforms: %v\n", supportedPlatforms)
59	fmt.Printf("   mem_technology = Memory technology used by the board\n")
60	fmt.Printf("                    supported technologies: %v\n", supportedMemTechs)
61	fmt.Printf("   makefile_dir = Directory path where generated Makefile.mk should be placed\n")
62	fmt.Printf("   mem_parts_used_file = CSV file containing list of memory parts used by the board and optional fixed ids\n\n\n")
63}
64
65func checkArgs(platform string, memTech string, makefileDir string, memPartsUsedFile string) error {
66	supported := false
67	for _, p := range supportedPlatforms {
68		if strings.ToUpper(platform) == p {
69			supported = true
70			break
71		}
72	}
73	if !supported {
74		return fmt.Errorf("Platform %s is not supported", platform)
75	}
76
77	supported = false
78	for _, m := range supportedMemTechs {
79		if strings.ToLower(memTech) == m {
80			supported = true
81			break
82		}
83	}
84	if !supported {
85		return fmt.Errorf("Memory technology %s is not supported", memTech)
86	}
87
88	if _, err := os.Stat(makefileDir); err != nil {
89		return fmt.Errorf("Invalid makefile_dir %s: %v", makefileDir, err)
90	}
91
92	if _, err := os.Stat(memPartsUsedFile); err != nil {
93		return fmt.Errorf("Invalid mem_parts_used_file %s: %v", memPartsUsedFile, err)
94	}
95
96	return nil
97}
98
99type mappingType int
100
101const (
102	Auto mappingType = iota
103	Fixed
104	Exclusive
105)
106
107type usedPart struct {
108	partName    string
109	index       int
110	mapping     mappingType
111	SPDOverride string
112}
113
114func readPlatformsManifest(memTech string) (map[string]string, error) {
115	manifestFilePath := filepath.Join("spd", strings.ToLower(memTech), PlatformsManifestFileName)
116	f, err := os.Open(manifestFilePath)
117	if err != nil {
118		return nil, err
119	}
120	defer f.Close()
121	r := csv.NewReader(f)
122	r.Comment = '#'
123
124	platformToSetMap := make(map[string]string)
125
126	for {
127		fields, err := r.Read()
128
129		if err == io.EOF {
130			break
131		}
132
133		if err != nil {
134			return nil, err
135		}
136
137		if len(fields) != 2 {
138			return nil, fmt.Errorf("Platforms manifest file is incorrectly formatted: %s", manifestFilePath)
139		}
140
141		platformToSetMap[fields[0]] = fields[1]
142	}
143
144	return platformToSetMap, nil
145}
146
147func getSPDDir(platform string, memTech string) (string, error) {
148	platformToSetMap, err := readPlatformsManifest(memTech)
149	if err != nil {
150		return "", err
151	}
152
153	setName, ok := platformToSetMap[strings.ToUpper(platform)]
154	if !ok {
155		return "", fmt.Errorf("Platform %s does not support memory technology %s", platform, memTech)
156	}
157
158	return filepath.Join("spd", strings.ToLower(memTech), setName), nil
159}
160
161/*
162 * Read input file CSV that contains list of memory part names used by the variant
163 * and an optional assigned id.
164 */
165func readParts(memPartsUsedFileName string) ([]usedPart, error) {
166	f, err := os.Open(memPartsUsedFileName)
167	if err != nil {
168		return nil, err
169	}
170	defer f.Close()
171	r := csv.NewReader(f)
172	r.FieldsPerRecord = -1 // Allow variable length records
173	r.TrimLeadingSpace = true
174	r.Comment = '#'
175
176	parts := []usedPart{}
177
178	for {
179		fields, err := r.Read()
180
181		if err == io.EOF {
182			break
183		}
184
185		if err != nil {
186			return nil, err
187		}
188
189		if len(fields) == 1 {
190			parts = append(parts, usedPart{fields[0], -1, Auto, ""})
191		} else {
192			var mapping = Auto
193			var assignedId = -1
194			var err error = nil
195			var spdOverride string = ""
196
197			// Second column, ID override
198			if len(fields) >= 2 {
199				if len(fields[1]) >= 2 && fields[1][0] == '*' {
200					// Exclusive mapping
201					mapping = Exclusive
202					assignedId, err = strconv.Atoi(fields[1][1:])
203				} else if fields[1] != "" {
204					// Fixed mapping
205					mapping = Fixed
206					assignedId, err = strconv.Atoi(fields[1])
207				}
208			}
209
210			// Third column, SPD file override
211			if len(fields) >= 3 {
212				if len(fields[2]) == 0 {
213					err = fmt.Errorf("mem_parts_used_file file is incorrectly formatted, SPD file column is empty")
214				} else {
215					spdOverride = fields[2]
216				}
217			}
218
219			if err != nil {
220				return nil, err
221			}
222
223			if assignedId > MaxMemoryId {
224				return nil, fmt.Errorf("Out of bounds assigned id %d for part %s", assignedId, fields[0])
225			}
226
227			parts = append(parts, usedPart{fields[0], assignedId, mapping, spdOverride})
228		}
229	}
230
231	return parts, nil
232}
233
234/*
235 * Read SPD manifest file(CSV) generated by gen_spd program and generate two maps:
236 * 1. Part to SPD Map : This maps global memory part name to generated SPD file name
237 * 2. SPD to Index Map: This generates a map of deduplicated SPD file names to index assigned to
238 *                      that SPD. This function sets the index for all SPDs to -1. This index gets
239 *                      updated as part of genPartIdInfo() depending upon the SPDs actually used
240 *                      by the variant.
241 */
242func readSPDManifest(SPDDirName string) (map[string]string, map[string]int, error) {
243	f, err := os.Open(filepath.Join(SPDDirName, SPDManifestFileName))
244	if err != nil {
245		return nil, nil, err
246	}
247	defer f.Close()
248	r := csv.NewReader(f)
249	r.Comment = '#'
250
251	partToSPDMap := make(map[string]string)
252	SPDToIndexMap := make(map[string]int)
253
254	for {
255		fields, err := r.Read()
256
257		if err == io.EOF {
258			break
259		}
260
261		if err != nil {
262			return nil, nil, err
263		}
264
265		if len(fields) != 2 {
266			return nil, nil, fmt.Errorf("CSV file is incorrectly formatted")
267		}
268
269		partToSPDMap[fields[0]] = fields[1]
270		SPDToIndexMap[fields[1]] = -1
271	}
272
273	return partToSPDMap, SPDToIndexMap, nil
274}
275
276/* Print information about memory part used by variant and ID assigned to it. */
277func appendPartIdInfo(s *string, partName string, index int) {
278	*s += fmt.Sprintf("%-30s %d (%04b)\n", partName, index, int64(index))
279}
280
281type partIds struct {
282	SPDFileName string
283	memParts    string
284}
285
286func getFileHeader() string {
287	return `# SPDX-License-Identifier: GPL-2.0-or-later
288# This is an auto-generated file. Do not edit!!
289# Generated by:
290` + fmt.Sprintf("# %s\n\n", strings.Join(os.Args[0:], " "))
291}
292
293/*
294 * For each part used by the variant, check if the SPD (as per the manifest) already has an ID
295 * assigned to it. If yes, then add the part name to the list of memory parts supported by the
296 * SPD entry. If not, then assign the next ID to the SPD file and add the part name to the
297 * list of memory parts supported by the SPD entry.
298 *
299 * Returns list of partIds that contains spdFileName and supported memory parts for each
300 * assigned ID.
301 */
302func genPartIdInfo(parts []usedPart, partToSPDMap map[string]string, SPDToIndexMap map[string]int, makefileDirName string) ([]partIds, error) {
303	partIdList := []partIds{}
304	assignedMapping := []mappingType{}
305	var s string
306
307	// Assign parts with fixed ids first
308	for _, p := range parts {
309		if p.index == -1 {
310			continue
311		}
312
313		if p.partName == "" {
314			return nil, fmt.Errorf("Invalid part entry")
315		}
316
317		SPDFileName, ok := partToSPDMap[p.partName]
318		if !ok {
319			return nil, fmt.Errorf("Failed to find part %s in SPD Manifest. Please add the part to global part list and regenerate SPD Manifest", p.partName)
320		}
321
322		// Extend partIdList and assignedMapping with empty entries if needed
323		for i := len(partIdList) - 1; i < p.index; i++ {
324			partIdList = append(partIdList, partIds{})
325			assignedMapping = append(assignedMapping, Auto)
326		}
327
328		// Only allow parts with the same index if they share the same SPD
329		assignedSPD := partIdList[p.index].SPDFileName
330		if assignedSPD != "" && assignedSPD != partToSPDMap[p.partName] {
331			return nil, fmt.Errorf("ID %d is already assigned to %s, conflicting with %s(%s)", p.index, assignedSPD, p.partName, SPDFileName)
332		}
333
334		mapping := assignedMapping[p.index]
335		if (mapping == Fixed && p.mapping == Exclusive) || (mapping == Exclusive && p.mapping == Fixed) {
336			return nil, fmt.Errorf("Exclusive/non-exclusive conflict in assigning %s to ID %d", p.partName, p.index)
337		} else {
338			assignedMapping[p.index] = p.mapping
339		}
340
341		if partIdList[p.index].memParts == "" {
342			partIdList[p.index] = partIds{SPDFileName: SPDFileName, memParts: p.partName}
343		} else {
344			partIdList[p.index].memParts += ", " + p.partName
345		}
346
347		// SPDToIndexMap should point to first assigned index in the used part list
348		// Exclusive entries don't update the map because they're not valid for auto assigning
349		if SPDToIndexMap[SPDFileName] < 0 && p.mapping != Exclusive {
350			SPDToIndexMap[SPDFileName] = p.index
351		}
352	}
353
354	s += fmt.Sprintf("%-30s %s\n", "DRAM Part Name", "ID to assign")
355
356	// Assign parts with no fixed id
357	for _, p := range parts {
358		if p.partName == "" {
359			return nil, fmt.Errorf("Invalid part entry")
360		}
361
362		// Add assigned parts to dram id file in the order they appear
363		if p.index != -1 {
364			appendPartIdInfo(&s, p.partName, p.index)
365			continue
366		}
367
368		SPDFileName, ok := partToSPDMap[p.partName]
369		if !ok {
370			return nil, fmt.Errorf("Failed to find part ", p.partName, " in SPD Manifest. Please add the part to global part list and regenerate SPD Manifest")
371		}
372
373		index := SPDToIndexMap[SPDFileName]
374		// Only Exclusive mappings don't allow automatic assigning of parts
375		if index != -1 && assignedMapping[index] != Exclusive {
376			partIdList[index].memParts += ", " + p.partName
377			appendPartIdInfo(&s, p.partName, index)
378			continue
379		}
380
381		// Find first empty index
382		for i, partId := range partIdList {
383			if partId.SPDFileName == "" {
384				index = i
385				break
386			}
387		}
388
389		// Append new entry
390		if index == -1 {
391			index = len(partIdList)
392			if index > MaxMemoryId {
393				return nil, fmt.Errorf("Maximum part ID %d exceeded.", MaxMemoryId)
394			}
395			partIdList = append(partIdList, partIds{})
396			assignedMapping = append(assignedMapping, Auto)
397		}
398
399		SPDToIndexMap[SPDFileName] = index
400		appendPartIdInfo(&s, p.partName, index)
401		partIdList[index] = partIds{SPDFileName: SPDFileName, memParts: p.partName}
402	}
403
404	fmt.Printf("%s", s)
405
406	s = getFileHeader() + s
407	err := ioutil.WriteFile(filepath.Join(makefileDirName, DRAMIdFileName), []byte(s), 0644)
408
409	return partIdList, err
410}
411
412/*
413 * This function generates Makefile.mk under the variant directory path and adds assigned SPDs
414 * to SPD_SOURCES.
415 */
416func genMakefile(partIdList []partIds, makefileDirName string, SPDDir string, partsDir string) error {
417	s := getFileHeader()
418	s += fmt.Sprintf("SPD_SOURCES =\n")
419
420	for i := 0; i < len(partIdList); i++ {
421		if partIdList[i].SPDFileName == "" {
422			s += fmt.Sprintf("SPD_SOURCES += %v ", filepath.Join(SPDDir, SPDEmptyFileName))
423			s += fmt.Sprintf("     # ID = %d(0b%04b)\n", i, int64(i))
424		} else {
425			SPDFileName := partIdList[i].SPDFileName
426			path := filepath.Join(partsDir, SPDFileName)
427
428			// Check if the file exists in the directory of the parts file
429			if _, err := os.Stat(path); err != nil {
430				// File doesn't exist, check spd directory
431				path = filepath.Join(SPDDir, SPDFileName)
432				if _, err = os.Stat(path); err != nil {
433					return fmt.Errorf("Failed to write Makefile, SPD file '%s' doesn't exist", SPDFileName)
434				}
435			}
436			s += fmt.Sprintf("SPD_SOURCES += %v ", path)
437			s += fmt.Sprintf("     # ID = %d(0b%04b) ", i, int64(i))
438			s += fmt.Sprintf(" Parts = %04s\n", partIdList[i].memParts)
439		}
440	}
441
442	return ioutil.WriteFile(filepath.Join(makefileDirName, MakefileName), []byte(s), 0644)
443}
444
445func main() {
446	if len(os.Args) != 5 {
447		usage()
448		log.Fatal("Incorrect number of arguments")
449	}
450
451	platform, memTech, makefileDir, memPartsUsedFile := os.Args[1], os.Args[2], os.Args[3], os.Args[4]
452
453	err := checkArgs(platform, memTech, makefileDir, memPartsUsedFile)
454	if err != nil {
455		log.Fatal(err)
456	}
457
458	SPDDir, err := getSPDDir(platform, memTech)
459	if err != nil {
460		log.Fatal(err)
461	}
462
463	partToSPDMap, SPDToIndexMap, err := readSPDManifest(SPDDir)
464	if err != nil {
465		log.Fatal(err)
466	}
467
468	parts, err := readParts(memPartsUsedFile)
469	if err != nil {
470		log.Fatal(err)
471	}
472
473	// Update our SPD maps with part specific overrides
474	for _, p := range parts {
475		if p.SPDOverride != "" {
476			partToSPDMap[p.partName] = p.SPDOverride
477			SPDToIndexMap[p.SPDOverride] = -1
478		}
479	}
480
481	partIdList, err := genPartIdInfo(parts, partToSPDMap, SPDToIndexMap, makefileDir)
482	if err != nil {
483		log.Fatal(err)
484	}
485
486	if err := genMakefile(partIdList, makefileDir, SPDDir, filepath.Dir(memPartsUsedFile)); err != nil {
487		log.Fatal(err)
488	}
489}
490