Michael Niewöhner has submitted this change. ( https://review.coreboot.org/c/coreboot/+/80470?usp=email )
Change subject: util: Add hda-decoder ......................................................................
util: Add hda-decoder
This tool helps take off the burden of manually decoding default configuration registers. Using decoded values can make code more self-documenting compared to shrouding it with magic numbers.
This is also written as a module which allows easy integration with other tools written in Go (e.g. autoport).
Change-Id: Ib4fb652e178517b2b7aceaac8be005c5b2d3b03e Signed-off-by: Nicholas Sudsgaard devel+coreboot@nsudsgaard.com Reviewed-on: https://review.coreboot.org/c/coreboot/+/80470 Tested-by: build bot (Jenkins) no-reply@coreboot.org Reviewed-by: Felix Singer service+coreboot-gerrit@felixsinger.de Reviewed-by: Michael Niewöhner foss@mniewoehner.de --- A util/hda-decoder/.gitignore A util/hda-decoder/Makefile A util/hda-decoder/decoder/lib.go A util/hda-decoder/decoder/lib_test.go A util/hda-decoder/description.md A util/hda-decoder/go.mod A util/hda-decoder/main.go 7 files changed, 477 insertions(+), 0 deletions(-)
Approvals: build bot (Jenkins): Verified Felix Singer: Looks good to me, approved Michael Niewöhner: Looks good to me, approved
diff --git a/util/hda-decoder/.gitignore b/util/hda-decoder/.gitignore new file mode 100644 index 0000000..1390027 --- /dev/null +++ b/util/hda-decoder/.gitignore @@ -0,0 +1 @@ +hda-decoder diff --git a/util/hda-decoder/Makefile b/util/hda-decoder/Makefile new file mode 100644 index 0000000..c67aaf8 --- /dev/null +++ b/util/hda-decoder/Makefile @@ -0,0 +1,10 @@ +## SPDX-License-Identifier: GPL-2.0-only + +PROJECT_NAME = hda-decoder + +default: + go version + go build -v -o $(PROJECT_NAME) + +clean: + rm -Rf $(PROJECT_NAME) diff --git a/util/hda-decoder/decoder/lib.go b/util/hda-decoder/decoder/lib.go new file mode 100644 index 0000000..834e830 --- /dev/null +++ b/util/hda-decoder/decoder/lib.go @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +package decoder + +import ( + "fmt" + "math/bits" +) + +type Fields[T uint32 | string] struct { + PortConnectivity T + Location T + DefaultDevice T + ConnectionType T + Color T + Misc T + DefaultAssociation T + Sequence T +} + +func getField(config uint32, mask uint32) uint32 { + return (config & mask) >> bits.TrailingZeros32(mask) +} + +func Decode(config uint32) Fields[uint32] { + return Fields[uint32]{ + PortConnectivity: getField(config, 0xc0000000), + Location: getField(config, 0x3f000000), + DefaultDevice: getField(config, 0x00f00000), + ConnectionType: getField(config, 0x000f0000), + Color: getField(config, 0x0000f000), + Misc: getField(config, 0x00000f00), + DefaultAssociation: getField(config, 0x000000f0), + Sequence: getField(config, 0x0000000f), + } +} + +func PortIsConnected(config uint32) bool { + return Decode(config).PortConnectivity != 0x1 +} + +var portConnectivityDescriptions = map[uint32]string{ + 0x0: "AZALIA_JACK", + 0x1: "AZALIA_NC", + 0x2: "AZALIA_INTEGRATED", + 0x3: "AZALIA_JACK_AND_INTEGRATED", +} + +var grossLocationDescriptions = map[uint32]string{ + 0x00: "AZALIA_EXTERNAL_PRIMARY_CHASSIS", + 0x10: "AZALIA_INTERNAL", + 0x20: "AZALIA_SEPARATE_CHASSIS", + 0x30: "AZALIA_LOCATION_OTHER", +} + +var geometricLocationDescriptions = map[uint32]string{ + 0x00: "AZALIA_GEOLOCATION_NA", + 0x01: "AZALIA_REAR", + 0x02: "AZALIA_FRONT", + 0x03: "AZALIA_LEFT", + 0x04: "AZALIA_RIGHT", + 0x05: "AZALIA_TOP", + 0x06: "AZALIA_BOTTOM", + 0x07: "AZALIA_SPECIAL7", + 0x08: "AZALIA_SPECIAL8", + 0x09: "AZALIA_SPECIAL9", +} + +var specialLocationDescriptions = map[uint32]string{ + 0x00 | 0x07: "AZALIA_REAR_PANEL", + 0x00 | 0x08: "AZALIA_DRIVE_BAY", + 0x10 | 0x07: "AZALIA_RISER", + 0x10 | 0x08: "AZALIA_DIGITAL_DISPLAY", + 0x10 | 0x09: "AZALIA_ATAPI", + 0x30 | 0x07: "AZALIA_MOBILE_LID_INSIDE", + 0x30 | 0x08: "AZALIA_MOBILE_LID_OUTSIDE", +} + +var defaultDeviceDescriptions = map[uint32]string{ + 0x0: "AZALIA_LINE_OUT", + 0x1: "AZALIA_SPEAKER", + 0x2: "AZALIA_HP_OUT", + 0x3: "AZALIA_CD", + 0x4: "AZALIA_SPDIF_OUT", + 0x5: "AZALIA_DIGITAL_OTHER_OUT", + 0x6: "AZALIA_MODEM_LINE_SIDE", + 0x7: "AZALIA_MODEM_HANDSET_SIDE", + 0x8: "AZALIA_LINE_IN", + 0x9: "AZALIA_AUX", + 0xa: "AZALIA_MIC_IN", + 0xb: "AZALIA_TELEPHONY", + 0xc: "AZALIA_SPDIF_IN", + 0xd: "AZALIA_DIGITAL_OTHER_IN", + 0xf: "AZALIA_DEVICE_OTHER", +} + +var connectionTypeDescriptions = map[uint32]string{ + 0x0: "AZALIA_TYPE_UNKNOWN", + 0x1: "AZALIA_STEREO_MONO_1_8", + 0x2: "AZALIA_STEREO_MONO_1_4", + 0x3: "AZALIA_ATAPI_INTERNAL", + 0x4: "AZALIA_RCA", + 0x5: "AZALIA_OPTICAL", + 0x6: "AZALIA_OTHER_DIGITAL", + 0x7: "AZALIA_OTHER_ANALOG", + 0x8: "AZALIA_MULTICHANNEL_ANALOG", + 0x9: "AZALIA_XLR", + 0xa: "AZALIA_RJ_11", + 0xb: "AZALIA_COMBINATION", + 0xf: "AZALIA_TYPE_OTHER", +} + +var colorDescriptions = map[uint32]string{ + 0x0: "AZALIA_COLOR_UNKNOWN", + 0x1: "AZALIA_BLACK", + 0x2: "AZALIA_GREY", + 0x3: "AZALIA_BLUE", + 0x4: "AZALIA_GREEN", + 0x5: "AZALIA_RED", + 0x6: "AZALIA_ORANGE", + 0x7: "AZALIA_YELLOW", + 0x8: "AZALIA_PURPLE", + 0x9: "AZALIA_PINK", + 0xe: "AZALIA_WHITE", + 0xf: "AZALIA_COLOR_OTHER", +} + +var miscDescriptions = map[uint32]string{ + 0x0: "AZALIA_JACK_PRESENCE_DETECT", + 0x1: "AZALIA_NO_JACK_PRESENCE_DETECT", +} + +func getDescription(field uint32, descriptions map[uint32]string) string { + desc, exists := descriptions[field] + + if !exists { + return fmt.Sprintf("0x%x", field) + } + return desc +} + +func getLocationDescription(field uint32) string { + desc, isSpecialLocation := specialLocationDescriptions[field] + if isSpecialLocation { + return desc + } + + grossLocation := field & 0x30 + geometricLocation := field & 0x0f + + desc = grossLocationDescriptions[grossLocation] + if geometricLocation != 0x00 { + desc += " | " + getDescription(geometricLocation, geometricLocationDescriptions) + } + return desc +} + +func getMiscDescription(field uint32) string { + presenceBit := field & 0b0001 + reservedBits := field & 0b1110 + + desc := miscDescriptions[presenceBit] + if bits.OnesCount32(reservedBits) > 0 { + desc += fmt.Sprintf(" | 0x%x", reservedBits) + } + return desc +} + +func ToHumanReadable(fields Fields[uint32]) Fields[string] { + return Fields[string]{ + PortConnectivity: getDescription(fields.PortConnectivity, portConnectivityDescriptions), + Location: getLocationDescription(fields.Location), + DefaultDevice: getDescription(fields.DefaultDevice, defaultDeviceDescriptions), + ConnectionType: getDescription(fields.ConnectionType, connectionTypeDescriptions), + Color: getDescription(fields.Color, colorDescriptions), + Misc: getMiscDescription(fields.Misc), + DefaultAssociation: fmt.Sprintf("%d", fields.DefaultAssociation), + Sequence: fmt.Sprintf("%d", fields.Sequence), + } +} diff --git a/util/hda-decoder/decoder/lib_test.go b/util/hda-decoder/decoder/lib_test.go new file mode 100644 index 0000000..045c0fc --- /dev/null +++ b/util/hda-decoder/decoder/lib_test.go @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +package decoder + +import ( + "reflect" + "testing" +) + +type portIsConnectedTest struct { + arg1 uint32 + expected bool +} + +var portIsConnectedTests = []portIsConnectedTest{ + portIsConnectedTest{0x20000000, true}, + portIsConnectedTest{0xC0000000, true}, + portIsConnectedTest{0x40000000, false}, +} + +func TestPortIsConnected(t *testing.T) { + for _, test := range portIsConnectedTests { + output := PortIsConnected(test.arg1) + if output != test.expected { + t.Errorf("Expected %v, received %v", test.expected, output) + } + } +} + +type decodeTest struct { + arg1 uint32 + expected Fields[uint32] +} + +var decodeTests = []decodeTest{ + decodeTest{0xe23d1a0e, Fields[uint32]{0x3, 0x22, 0x3, 0xd, 0x1, 0xa, 0x0, 0xe}}, + decodeTest{0x66a8a2e4, Fields[uint32]{0x1, 0x26, 0xa, 0x8, 0xa, 0x2, 0xe, 0x4}}, + decodeTest{0x2e00a164, Fields[uint32]{0x0, 0x2e, 0x0, 0x0, 0xa, 0x1, 0x6, 0x4}}, + decodeTest{0x3b83dfe9, Fields[uint32]{0x0, 0x3b, 0x8, 0x3, 0xd, 0xf, 0xe, 0x9}}, + decodeTest{0x51708701, Fields[uint32]{0x1, 0x11, 0x7, 0x0, 0x8, 0x7, 0x0, 0x1}}, +} + +func TestDecode(t *testing.T) { + for _, test := range decodeTests { + output := Decode(test.arg1) + if !reflect.DeepEqual(output, test.expected) { + t.Errorf("Expected %v, received %v", test.expected, output) + } + } +} + +type toHumanReadableTest struct { + arg1 uint32 + expected Fields[string] +} + +var toHumanReadableTests = []toHumanReadableTest{ + toHumanReadableTest{0xe23d1a0e, Fields[string]{ + "AZALIA_JACK_AND_INTEGRATED", + "AZALIA_SEPARATE_CHASSIS | AZALIA_FRONT", + "AZALIA_CD", + "0xd", + "AZALIA_BLACK", + "AZALIA_JACK_PRESENCE_DETECT | 0xa", + "0", + "14", + }}, + + toHumanReadableTest{0x57708701, Fields[string]{ + "AZALIA_NC", + "AZALIA_RISER", + "AZALIA_MODEM_HANDSET_SIDE", + "AZALIA_TYPE_UNKNOWN", + "AZALIA_PURPLE", + "AZALIA_NO_JACK_PRESENCE_DETECT | 0x6", + "0", + "1", + }}, + + toHumanReadableTest{0x2e00a164, Fields[string]{ + "AZALIA_JACK", + "AZALIA_SEPARATE_CHASSIS | 0xe", + "AZALIA_LINE_OUT", + "AZALIA_TYPE_UNKNOWN", + "0xa", + "AZALIA_NO_JACK_PRESENCE_DETECT", + "6", + "4", + }}, + + toHumanReadableTest{0x80949653, Fields[string]{ + "AZALIA_INTEGRATED", + "AZALIA_EXTERNAL_PRIMARY_CHASSIS", + "AZALIA_AUX", + "AZALIA_RCA", + "AZALIA_PINK", + "AZALIA_JACK_PRESENCE_DETECT | 0x6", + "5", + "3", + }}, +} + +func TestToHumanReadable(t *testing.T) { + for _, test := range toHumanReadableTests { + output := ToHumanReadable(Decode(test.arg1)) + if output != test.expected { + t.Errorf("Expected %v, received %v", test.expected, output) + } + } +} diff --git a/util/hda-decoder/description.md b/util/hda-decoder/description.md new file mode 100644 index 0000000..0c9c97e --- /dev/null +++ b/util/hda-decoder/description.md @@ -0,0 +1 @@ +Dumps decoded HDA default configuration registers into a format which can be used in coreboot's verb table `Go` diff --git a/util/hda-decoder/go.mod b/util/hda-decoder/go.mod new file mode 100644 index 0000000..a3c611a --- /dev/null +++ b/util/hda-decoder/go.mod @@ -0,0 +1,3 @@ +module review.coreboot.org/coreboot.git/util/hda-decoder + +go 1.18 diff --git a/util/hda-decoder/main.go b/util/hda-decoder/main.go new file mode 100644 index 0000000..83ddcbb --- /dev/null +++ b/util/hda-decoder/main.go @@ -0,0 +1,174 @@ +// 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" +) + +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) { + 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()) + pin := stringToUint32(fields[0]) + config := stringToUint32(fields[1]) + + 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) + 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") + flag.Parse() + + if isFlagPassed("config") { + decodeConfig(uint32(*config)) + } else if isFlagPassed("file") { + decodeFile(*file, uint32(*codec)) + } 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") + } + } +}