summaryrefslogtreecommitdiff
path: root/util/hda-decoder/main.go
blob: c0a88adf2ebd25943719a3496a573a41e3e81eaf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// SPDX-License-Identifier: GPL-2.0-only
package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"review.coreboot.org/coreboot.git/util/hda-decoder/decoder"
	"strconv"
	"strings"
)

type decodeOperation int

const (
	decodeToHumanReadable decodeOperation = iota
	decodeToVerbs
)

var indentLevel int = 0

func indentedPrintf(format string, args ...interface{}) (n int, err error) {
	s := fmt.Sprintf("%s%s", strings.Repeat("\t", indentLevel), format)
	return fmt.Printf(s, args...)
}

func stringToUint32(s string) uint32 {
	s = strings.Replace(s, "0x", "", -1)
	v, err := strconv.ParseUint(s, 16, 32)
	if err != nil {
		log.Fatal(err)
	}
	return uint32(v)
}

func decodeConfig(config uint32) {
	out := decoder.ToHumanReadable(decoder.Decode(config))

	indentedPrintf("%s,\n", out.PortConnectivity)
	indentedPrintf("%s,\n", out.Location)
	indentedPrintf("%s,\n", out.DefaultDevice)
	indentedPrintf("%s,\n", out.ConnectionType)
	indentedPrintf("%s,\n", out.Color)
	indentedPrintf("%s,\n", out.Misc)
	indentedPrintf("%s, %s\n", out.DefaultAssociation, out.Sequence)
}

func printDisconnectedPort(config uint32) {
	// The value 0x411111f0 is not defined in the specification, but is a
	// common value vendors use to indicate "not connected".
	const nc uint32 = 0x411111f0

	// Setting some values (e.g. 0x40000000) as `AZALIA_PIN_CFG_NC(0)` is
	// probably harmless. However, we will stay on the safe side for now.
	if (config & 0xfffffff0) != nc {
		// Do not decode these values, as they would likely describe a
		// bogus device which could be slighly confusing.
		fmt.Printf("0x%08x), // does not describe a jack or internal device\n", config)
	} else {
		fmt.Printf("AZALIA_PIN_CFG_NC(%d)),\n", (config & 0x0000000f))
	}
}

func decodeFile(path string, codec uint32, operation decodeOperation) {
	file, err := os.Open(path)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
		fields := strings.Fields(scanner.Text())
		if len(fields) != 2 {
			fmt.Print("// Something went wrong\n")
			continue
		}

		pin := stringToUint32(fields[0])
		config := stringToUint32(fields[1])

		switch operation {
		case decodeToVerbs:
			fmt.Printf("address: %d, node ID: %#02x, configuration default: %#08x\n",
				codec, pin, config)

			verbs := decoder.ConfigToVerbs(codec, pin, config)
			fmt.Printf("  %#08x\n", verbs[0])
			fmt.Printf("  %#08x\n", verbs[1])
			fmt.Printf("  %#08x\n", verbs[2])
			fmt.Printf("  %#08x\n", verbs[3])
		case decodeToHumanReadable:
			indentedPrintf("AZALIA_PIN_CFG(%d, 0x%02x, ", codec, pin)
			if decoder.PortIsConnected(config) {
				fmt.Printf("AZALIA_PIN_DESC(\n")
				indentLevel += 1
				decodeConfig(config)
				indentLevel -= 1
				indentedPrintf(")),\n")
			} else {
				printDisconnectedPort(config)
			}
		}
	}
}

func getFileContents(path string) string {
	contents, err := os.ReadFile(path)
	if err != nil {
		log.Fatal(err)
	}
	return strings.TrimSpace(string(contents))
}

func getLineCount(path string) int {
	return len(strings.Split(getFileContents(path), "\n"))
}

func decodeDeviceCodec(path string, codec uint32, isLastCodec bool, generate bool) {
	if generate {
		vendorId := getFileContents(path + "/vendor_id")
		vendorName := getFileContents(path + "/vendor_name")
		chipName := getFileContents(path + "/chip_name")
		subsystemId := getFileContents(path + "/subsystem_id")
		lineCount := getLineCount(path + "/init_pin_configs")

		indentedPrintf("%s, // Vendor/Device ID: %s %s\n", vendorId, vendorName, chipName)
		indentedPrintf("%s, // Subsystem ID\n", subsystemId)
		indentedPrintf("%d,\n", lineCount+1)
		indentedPrintf("AZALIA_SUBVENDOR(%d, %s),\n\n", codec, subsystemId)
	}

	decodeFile(path+"/init_pin_configs", codec, decodeToHumanReadable)
	if !isLastCodec {
		fmt.Printf("\n")
	}
}

func decodeDeviceCodecs(generate bool) {
	matches, err := filepath.Glob("/sys/class/sound/hwC0D*")
	if err != nil {
		log.Fatal(err)
	}
	re := regexp.MustCompile(`D([0-9]+)$`)

	for i, match := range matches {
		codec := stringToUint32(re.FindStringSubmatch(match)[1])
		isLastCodec := (i + 1) == len(matches)

		decodeDeviceCodec(match, codec, isLastCodec, generate)
	}
}

func isFlagPassed(name string) bool {
	found := false

	flag.Visit(func(f *flag.Flag) {
		if f.Name == name {
			found = true
		}
	})
	return found
}

func main() {
	codec := flag.Uint64("codec", 0, "Set the codec number when decoding a file\n"+
		"This flag is only meaningful in combination with the 'file' flag")
	config := flag.Uint64("config", 0, "Decode a single configuration")
	file := flag.String("file", "", "Decode configurations in a file\n"+
		"The decoder assumes each line in the file has the format: <pin> <config>")
	generate := flag.Bool("generate", false, "Automatically generate hda_verb.c for the host device")
	toVerbs := flag.Bool("to-verbs", false, "Convert configuration defaults to their corresponding verbs\n"+
		"This flag is only meaningful in combination with the 'file' flag")
	flag.Parse()

	operation := decodeToHumanReadable
	if *toVerbs {
		operation = decodeToVerbs
	}

	if isFlagPassed("config") {
		decodeConfig(uint32(*config))
	} else if isFlagPassed("file") {
		decodeFile(*file, uint32(*codec), operation)
	} else {
		if *generate {
			fmt.Printf("/* SPDX-License-Identifier: GPL-2.0-only */\n\n")
			fmt.Printf("#include <device/azalia_device.h>\n\n")
			fmt.Printf("const u32 cim_verb_data[] = {\n")
			indentLevel += 1
		}
		decodeDeviceCodecs(*generate)
		if *generate {
			indentLevel -= 1
			fmt.Printf("};\n\n")
			fmt.Printf("const u32 pc_beep_verbs[] = {};\n")
			fmt.Printf("AZALIA_ARRAY_SIZES;\n")
		}
	}
}