/* This is just an experiment. Full automatic porting is probably not possible but a lot can be automated. */ package main import ( "bytes" "flag" "fmt" "log" "os" "path/filepath" "sort" "strings" ) type PCIAddr struct { Bus int Dev int Func int } type PCIDevData struct { PCIAddr PCIVenID uint16 PCIDevID uint16 ConfigDump []uint8 } type PCIDevice interface { Scan(ctx Context, addr PCIDevData) } type InteltoolData struct { GPIO map[uint16]uint32 RCBA map[uint16]uint32 IGD map[uint32]uint32 } type DMIData struct { Vendor string Model string Version string IsLaptop bool } type AzaliaCodec struct { Name string VendorID uint32 SubsystemID uint32 CodecNo int PinConfig map[int]uint32 } type DevReader interface { GetPCIList() []PCIDevData GetDMI() DMIData GetInteltool() InteltoolData GetAzaliaCodecs() []AzaliaCodec GetACPI() map[string][]byte GetCPUModel() []uint32 GetEC() []byte GetIOPorts() []IOPorts HasPS2() bool } type IOPorts struct { Start uint16 End uint16 Usage string } type SouthBridger interface { GetGPIOHeader() string EncodeGPE(int) int DecodeGPE(int) int EnableGPE(int) NeedRouteGPIOManually() } var SouthBridge SouthBridger var BootBlockFiles map[string]string = map[string]string{} var ROMStageFiles map[string]string = map[string]string{} var RAMStageFiles map[string]string = map[string]string{} var SMMFiles map[string]string = map[string]string{} var MainboardInit string var MainboardEnable string var MainboardIncludes []string type Context struct { MoboID string KconfigName string Vendor string Model string BaseDirectory string InfoSource DevReader SaneVendor string } var KconfigBool map[string]bool = map[string]bool{} var KconfigComment map[string]string = map[string]string{} var KconfigString map[string]string = map[string]string{} var KconfigHex map[string]uint32 = map[string]uint32{} var KconfigInt map[string]int = map[string]int{} var ROMSizeKB = 0 var ROMProtocol = "" var FlashROMSupport = "" func GetLE16(inp []byte) uint16 { return uint16(inp[0]) | (uint16(inp[1]) << 8) } func FormatHexLE16(inp []byte) string { return fmt.Sprintf("0x%04x", GetLE16(inp)) } func FormatHex32(u uint32) string { return fmt.Sprintf("0x%08x", u) } func FormatHex8(u uint8) string { return fmt.Sprintf("0x%02x", u) } func FormatInt32(u uint32) string { return fmt.Sprintf("%d", u) } func FormatHexLE32(d []uint8) string { u := uint32(d[0]) | (uint32(d[1]) << 8) | (uint32(d[2]) << 16) | (uint32(d[3]) << 24) return FormatHex32(u) } func FormatBool(inp bool) string { if inp { return "1" } else { return "0" } } func sanitize(inp string) string { result := strings.ToLower(inp) result = strings.Replace(result, " ", "_", -1) result = strings.Replace(result, ",", "_", -1) result = strings.Replace(result, "-", "_", -1) for strings.HasSuffix(result, ".") { result = result[0 : len(result)-1] } return result } func AddBootBlockFile(Name string, Condition string) { BootBlockFiles[Name] = Condition } func AddROMStageFile(Name string, Condition string) { ROMStageFiles[Name] = Condition } func AddRAMStageFile(Name string, Condition string) { RAMStageFiles[Name] = Condition } func AddSMMFile(Name string, Condition string) { SMMFiles[Name] = Condition } func IsIOPortUsedBy(ctx Context, port uint16, name string) bool { for _, io := range ctx.InfoSource.GetIOPorts() { if io.Start <= port && port <= io.End && io.Usage == name { return true } } return false } var FlagOutDir = flag.String("coreboot_dir", ".", "Resulting coreboot directory") func writeMF(mf *os.File, files map[string]string, category string) { keys := []string{} for file, _ := range files { keys = append(keys, file) } sort.Strings(keys) for _, file := range keys { condition := files[file] if condition == "" { fmt.Fprintf(mf, "%s-y += %s\n", category, file) } else { fmt.Fprintf(mf, "%s-$(%s) += %s\n", category, condition, file) } } } func Create(ctx Context, name string) *os.File { li := strings.LastIndex(name, "/") if li > 0 { os.MkdirAll(ctx.BaseDirectory+"/"+name[0:li], 0700) } mf, err := os.Create(ctx.BaseDirectory + "/" + name) if err != nil { log.Fatal(err) } return mf } func Add_gpl(f *os.File) { fmt.Fprintln(f, "/* SPDX-License-Identifier: GPL-2.0-only */") fmt.Fprintln(f) } func RestorePCI16Simple(f *os.File, pcidev PCIDevData, addr uint16) { fmt.Fprintf(f, " pci_write_config16(PCI_DEV(%d, 0x%02x, %d), 0x%02x, 0x%02x%02x);\n", pcidev.Bus, pcidev.Dev, pcidev.Func, addr, pcidev.ConfigDump[addr+1], pcidev.ConfigDump[addr]) } func RestorePCI32Simple(f *os.File, pcidev PCIDevData, addr uint16) { fmt.Fprintf(f, " pci_write_config32(PCI_DEV(%d, 0x%02x, %d), 0x%02x, 0x%02x%02x%02x%02x);\n", pcidev.Bus, pcidev.Dev, pcidev.Func, addr, pcidev.ConfigDump[addr+3], pcidev.ConfigDump[addr+2], pcidev.ConfigDump[addr+1], pcidev.ConfigDump[addr]) } func RestoreRCBA32(f *os.File, inteltool InteltoolData, addr uint16) { fmt.Fprintf(f, "\tRCBA32(0x%04x) = 0x%08x;\n", addr, inteltool.RCBA[addr]) } type PCISlot struct { PCIAddr alias string additionalComment string writeEmpty bool } type DevTreeNode struct { Bus int Dev int Func int Disabled bool Registers map[string]string IOs map[uint16]uint16 Children []DevTreeNode PCISlots []PCISlot PCIController bool ChildPCIBus int MissingParent string SubVendor uint16 SubSystem uint16 Chip string Comment string } var DevTree DevTreeNode var MissingChildren map[string][]DevTreeNode = map[string][]DevTreeNode{} var unmatchedPCIChips map[PCIAddr]DevTreeNode = map[PCIAddr]DevTreeNode{} var unmatchedPCIDevices map[PCIAddr]DevTreeNode = map[PCIAddr]DevTreeNode{} func Offset(dt *os.File, offset int) { for i := 0; i < offset; i++ { fmt.Fprintf(dt, "\t") } } func MatchDev(dev *DevTreeNode) { for idx := range dev.Children { MatchDev(&dev.Children[idx]) } for _, slot := range dev.PCISlots { slotChip, ok := unmatchedPCIChips[slot.PCIAddr] if !ok { continue } if slot.additionalComment != "" && slotChip.Comment != "" { slotChip.Comment = slot.additionalComment + " " + slotChip.Comment } else { slotChip.Comment = slot.additionalComment + slotChip.Comment } delete(unmatchedPCIChips, slot.PCIAddr) MatchDev(&slotChip) dev.Children = append(dev.Children, slotChip) } if dev.PCIController { for slot, slotDev := range unmatchedPCIChips { if slot.Bus == dev.ChildPCIBus { delete(unmatchedPCIChips, slot) MatchDev(&slotDev) dev.Children = append(dev.Children, slotDev) } } } for _, slot := range dev.PCISlots { slotDev, ok := unmatchedPCIDevices[slot.PCIAddr] if !ok { if slot.writeEmpty { dev.Children = append(dev.Children, DevTreeNode{ Registers: map[string]string{}, Chip: "pci", Bus: slot.Bus, Dev: slot.Dev, Func: slot.Func, Comment: slot.additionalComment, Disabled: true, }, ) } continue } if slot.additionalComment != "" && slotDev.Comment != "" { slotDev.Comment = slot.additionalComment + " " + slotDev.Comment } else { slotDev.Comment = slot.additionalComment + slotDev.Comment } MatchDev(&slotDev) dev.Children = append(dev.Children, slotDev) delete(unmatchedPCIDevices, slot.PCIAddr) } if dev.MissingParent != "" { for _, child := range MissingChildren[dev.MissingParent] { MatchDev(&child) dev.Children = append(dev.Children, child) } delete(MissingChildren, dev.MissingParent) } if dev.PCIController { for slot, slotDev := range unmatchedPCIDevices { if slot.Bus == dev.ChildPCIBus { MatchDev(&slotDev) dev.Children = append(dev.Children, slotDev) delete(unmatchedPCIDevices, slot) } } } } func writeOn(dt *os.File, dev DevTreeNode) { if dev.Disabled { fmt.Fprintf(dt, "off") } else { fmt.Fprintf(dt, "on") } } func WriteDev(dt *os.File, offset int, alias string, dev DevTreeNode) { Offset(dt, offset) switch dev.Chip { case "cpu_cluster", "domain": fmt.Fprintf(dt, "device %s 0x%x ", dev.Chip, dev.Dev) writeOn(dt, dev) case "pci", "pnp": if alias != "" { fmt.Fprintf(dt, "device ref %s ", alias) } else { fmt.Fprintf(dt, "device %s %02x.%x ", dev.Chip, dev.Dev, dev.Func) } writeOn(dt, dev) case "i2c": fmt.Fprintf(dt, "device %s %02x ", dev.Chip, dev.Dev) writeOn(dt, dev) default: fmt.Fprintf(dt, "chip %s", dev.Chip) } if dev.Comment != "" { fmt.Fprintf(dt, " # %s", dev.Comment) } fmt.Fprintf(dt, "\n") if dev.Chip == "pci" && dev.SubSystem != 0 && dev.SubVendor != 0 { Offset(dt, offset+1) fmt.Fprintf(dt, "subsystemid 0x%04x 0x%04x\n", dev.SubVendor, dev.SubSystem) } keys := []string{} for reg, _ := range dev.Registers { keys = append(keys, reg) } sort.Strings(keys) for _, reg := range keys { val := dev.Registers[reg] Offset(dt, offset+1) fmt.Fprintf(dt, "register \"%s\" = \"%s\"\n", reg, val) } ios := []int{} for reg, _ := range dev.IOs { ios = append(ios, int(reg)) } sort.Ints(ios) for _, reg := range ios { val := dev.IOs[uint16(reg)] Offset(dt, offset+1) fmt.Fprintf(dt, "io 0x%x = 0x%x\n", reg, val) } for _, child := range dev.Children { alias = "" for _, slot := range dev.PCISlots { if slot.PCIAddr.Bus == child.Bus && slot.PCIAddr.Dev == child.Dev && slot.PCIAddr.Func == child.Func { alias = slot.alias } } WriteDev(dt, offset+1, alias, child) } Offset(dt, offset) fmt.Fprintf(dt, "end\n") } func PutChip(domain string, cur DevTreeNode) { MissingChildren[domain] = append(MissingChildren[domain], cur) } func PutPCIChip(addr PCIDevData, cur DevTreeNode) { unmatchedPCIChips[addr.PCIAddr] = cur } func PutPCIDevParent(addr PCIDevData, comment string, parent string) { cur := DevTreeNode{ Registers: map[string]string{}, Chip: "pci", Bus: addr.Bus, Dev: addr.Dev, Func: addr.Func, MissingParent: parent, Comment: comment, } if addr.ConfigDump[0xa] == 0x04 && addr.ConfigDump[0xb] == 0x06 { cur.PCIController = true cur.ChildPCIBus = int(addr.ConfigDump[0x19]) loopCtr := 0 for capPtr := addr.ConfigDump[0x34]; capPtr != 0; capPtr = addr.ConfigDump[capPtr+1] { /* Avoid hangs. There are only 0x100 different possible values for capPtr. If we iterate longer than that, we're in endless loop. */ loopCtr++ if loopCtr > 0x100 { break } if addr.ConfigDump[capPtr] == 0x0d { cur.SubVendor = GetLE16(addr.ConfigDump[capPtr+4 : capPtr+6]) cur.SubSystem = GetLE16(addr.ConfigDump[capPtr+6 : capPtr+8]) } } } else { cur.SubVendor = GetLE16(addr.ConfigDump[0x2c:0x2e]) cur.SubSystem = GetLE16(addr.ConfigDump[0x2e:0x30]) } unmatchedPCIDevices[addr.PCIAddr] = cur } func PutPCIDev(addr PCIDevData, comment string) { PutPCIDevParent(addr, comment, "") } type GenericPCI struct { Comment string Bus0Subdiv string MissingParent string } type GenericVGA struct { GenericPCI } type DSDTInclude struct { Comment string File string } type DSDTDefine struct { Key string Comment string Value string } var DSDTIncludes []DSDTInclude var DSDTPCI0Includes []DSDTInclude var DSDTDefines []DSDTDefine func (g GenericPCI) Scan(ctx Context, addr PCIDevData) { PutPCIDevParent(addr, g.Comment, g.MissingParent) } var IGDEnabled bool = false func (g GenericVGA) Scan(ctx Context, addr PCIDevData) { KconfigString["VGA_BIOS_ID"] = fmt.Sprintf("%04x,%04x", addr.PCIVenID, addr.PCIDevID) PutPCIDevParent(addr, g.Comment, g.MissingParent) IGDEnabled = true } func makeKconfigName(ctx Context) { kn := Create(ctx, "Kconfig.name") defer kn.Close() fmt.Fprintf(kn, "config %s\n\tbool \"%s\"\n", ctx.KconfigName, ctx.Model) } func makeComment(name string) string { cmt, ok := KconfigComment[name] if !ok { return "" } return " # " + cmt } func makeKconfig(ctx Context) { kc := Create(ctx, "Kconfig") defer kc.Close() fmt.Fprintf(kc, "if %s\n\n", ctx.KconfigName) fmt.Fprintf(kc, "config BOARD_SPECIFIC_OPTIONS\n\tdef_bool y\n") keys := []string{} for name, val := range KconfigBool { if val { keys = append(keys, name) } } sort.Strings(keys) for _, name := range keys { fmt.Fprintf(kc, "\tselect %s%s\n", name, makeComment(name)) } keys = nil for name, val := range KconfigBool { if !val { keys = append(keys, name) } } sort.Strings(keys) for _, name := range keys { fmt.Fprintf(kc, ` config %s%s bool default n `, name, makeComment(name)) } keys = nil for name, _ := range KconfigString { keys = append(keys, name) } sort.Strings(keys) for _, name := range keys { fmt.Fprintf(kc, ` config %s%s string default "%s" `, name, makeComment(name), KconfigString[name]) } keys = nil for name, _ := range KconfigHex { keys = append(keys, name) } sort.Strings(keys) for _, name := range keys { fmt.Fprintf(kc, ` config %s%s hex default 0x%x `, name, makeComment(name), KconfigHex[name]) } keys = nil for name, _ := range KconfigInt { keys = append(keys, name) } sort.Strings(keys) for _, name := range keys { fmt.Fprintf(kc, ` config %s%s int default %d `, name, makeComment(name), KconfigInt[name]) } fmt.Fprintf(kc, "endif\n") } const MoboDir = "/src/mainboard/" func makeVendor(ctx Context) { vendor := ctx.Vendor vendorSane := ctx.SaneVendor vendorDir := *FlagOutDir + MoboDir + vendorSane vendorUpper := strings.ToUpper(vendorSane) kconfig := vendorDir + "/Kconfig" if _, err := os.Stat(kconfig); os.IsNotExist(err) { f, err := os.Create(kconfig) if err != nil { log.Fatal(err) } defer f.Close() f.WriteString(`if VENDOR_` + vendorUpper + ` choice prompt "Mainboard model" source "src/mainboard/` + vendorSane + `/*/Kconfig.name" endchoice source "src/mainboard/` + vendorSane + `/*/Kconfig" config MAINBOARD_VENDOR string default "` + vendor + `" endif # VENDOR_` + vendorUpper + "\n") } kconfigName := vendorDir + "/Kconfig.name" if _, err := os.Stat(kconfigName); os.IsNotExist(err) { f, err := os.Create(kconfigName) if err != nil { log.Fatal(err) } defer f.Close() f.WriteString(`config VENDOR_` + vendorUpper + ` bool "` + vendor + `" `) } } func GuessECGPE(ctx Context) int { /* FIXME:XX Use iasl -d and/or better parsing */ dsdt := ctx.InfoSource.GetACPI()["DSDT"] idx := bytes.Index(dsdt, []byte{0x08, '_', 'G', 'P', 'E', 0x0a}) /* Name (_GPE, byte). */ if idx > 0 { return int(dsdt[idx+6]) } return -1 } func GuessSPDMap(ctx Context) []uint8 { dmi := ctx.InfoSource.GetDMI() if dmi.Vendor == "LENOVO" { return []uint8{0x50, 0x52, 0x51, 0x53} } return []uint8{0x50, 0x51, 0x52, 0x53} } func main() { flag.Parse() ctx := Context{} ctx.InfoSource = MakeLogReader() dmi := ctx.InfoSource.GetDMI() ctx.Vendor = dmi.Vendor if dmi.Vendor == "LENOVO" { ctx.Model = dmi.Version } else { ctx.Model = dmi.Model } if dmi.IsLaptop { KconfigBool["SYSTEM_TYPE_LAPTOP"] = true } ctx.SaneVendor = sanitize(ctx.Vendor) for { last := ctx.SaneVendor for _, suf := range []string{"_inc", "_co", "_corp"} { ctx.SaneVendor = strings.TrimSuffix(ctx.SaneVendor, suf) } if last == ctx.SaneVendor { break } } ctx.MoboID = ctx.SaneVendor + "/" + sanitize(ctx.Model) ctx.KconfigName = "BOARD_" + strings.ToUpper(ctx.SaneVendor+"_"+sanitize(ctx.Model)) ctx.BaseDirectory = *FlagOutDir + MoboDir + ctx.MoboID KconfigString["MAINBOARD_DIR"] = ctx.MoboID KconfigString["MAINBOARD_PART_NUMBER"] = ctx.Model os.MkdirAll(ctx.BaseDirectory, 0700) makeVendor(ctx) ScanRoot(ctx) if IGDEnabled { KconfigBool["MAINBOARD_HAS_LIBGFXINIT"] = true KconfigComment["MAINBOARD_HAS_LIBGFXINIT"] = "FIXME: check this" AddRAMStageFile("gma-mainboard.ads", "CONFIG_MAINBOARD_USE_LIBGFXINIT") } if len(BootBlockFiles) > 0 || len(ROMStageFiles) > 0 || len(RAMStageFiles) > 0 || len(SMMFiles) > 0 { mf := Create(ctx, "Makefile.mk") defer mf.Close() writeMF(mf, BootBlockFiles, "bootblock") writeMF(mf, ROMStageFiles, "romstage") writeMF(mf, RAMStageFiles, "ramstage") writeMF(mf, SMMFiles, "smm") } devtree := Create(ctx, "devicetree.cb") defer devtree.Close() MatchDev(&DevTree) WriteDev(devtree, 0, "", DevTree) if MainboardInit != "" || MainboardEnable != "" || MainboardIncludes != nil { mainboard := Create(ctx, "mainboard.c") defer mainboard.Close() Add_gpl(mainboard) mainboard.WriteString("#include \n") for _, include := range MainboardIncludes { mainboard.WriteString("#include <" + include + ">\n") } mainboard.WriteString("\n") if MainboardInit != "" { mainboard.WriteString(`static void mainboard_init(struct device *dev) { ` + MainboardInit + "}\n\n") } if MainboardInit != "" || MainboardEnable != "" { mainboard.WriteString("static void mainboard_enable(struct device *dev)\n{\n") if MainboardInit != "" { mainboard.WriteString("\tdev->ops->init = mainboard_init;\n\n") } mainboard.WriteString(MainboardEnable) mainboard.WriteString("}\n\n") mainboard.WriteString(`struct chip_operations mainboard_ops = { .enable_dev = mainboard_enable, }; `) } } bi := Create(ctx, "board_info.txt") defer bi.Close() fixme := "" if dmi.IsLaptop { bi.WriteString("Category: laptop\n") } else { bi.WriteString("Category: desktop\n") fixme += "check category, " } missing := "ROM package, ROM socketed" if ROMProtocol != "" { fmt.Fprintf(bi, "ROM protocol: %s\n", ROMProtocol) } else { missing += ", ROM protocol" } if FlashROMSupport != "" { fmt.Fprintf(bi, "Flashrom support: %s\n", FlashROMSupport) } else { missing += ", Flashrom support" } missing += ", Release year" if fixme != "" { fmt.Fprintf(bi, "FIXME: %s, put %s\n", fixme, missing) } else { fmt.Fprintf(bi, "FIXME: put %s\n", missing) } if ROMSizeKB == 0 { KconfigBool["BOARD_ROMSIZE_KB_2048"] = true KconfigComment["BOARD_ROMSIZE_KB_2048"] = "FIXME: correct this" } else { KconfigBool[fmt.Sprintf("BOARD_ROMSIZE_KB_%d", ROMSizeKB)] = true } makeKconfig(ctx) makeKconfigName(ctx) dsdt := Create(ctx, "dsdt.asl") defer dsdt.Close() Add_gpl(dsdt) for _, define := range DSDTDefines { if define.Comment != "" { fmt.Fprintf(dsdt, "\t/* %s. */\n", define.Comment) } dsdt.WriteString("#define " + define.Key + " " + define.Value + "\n") } dsdt.WriteString( `#include DefinitionBlock( "dsdt.aml", "DSDT", ACPI_DSDT_REV_2, OEM_ID, ACPI_TABLE_CREATOR, 0x20141018 ) { #include #include "acpi/platform.asl" `) for _, x := range DSDTIncludes { if x.Comment != "" { fmt.Fprintf(dsdt, "\t/* %s. */\n", x.Comment) } fmt.Fprintf(dsdt, "\t#include <%s>\n", x.File) } dsdt.WriteString(` Device (\_SB.PCI0) { `) for _, x := range DSDTPCI0Includes { if x.Comment != "" { fmt.Fprintf(dsdt, "\t/* %s. */\n", x.Comment) } fmt.Fprintf(dsdt, "\t\t#include <%s>\n", x.File) } dsdt.WriteString( ` } } `) if IGDEnabled { gma := Create(ctx, "gma-mainboard.ads") defer gma.Close() gma.WriteString(`-- SPDX-License-Identifier: GPL-2.0-or-later with HW.GFX.GMA; with HW.GFX.GMA.Display_Probing; use HW.GFX.GMA; use HW.GFX.GMA.Display_Probing; private package GMA.Mainboard is -- FIXME: check this ports : constant Port_List := (DP1, DP2, DP3, HDMI1, HDMI2, HDMI3, Analog, LVDS, eDP); end GMA.Mainboard; `) } outputPath, _ := filepath.Abs(ctx.BaseDirectory) fmt.Printf("Done! Generated sources are in %s\n", outputPath) }