coreboot-kgpe-d16/util/autoport/main.go

897 lines
19 KiB
Go

/* 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"
"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
}
type IOAPICIRQ struct {
APICID int
IRQNO [4]int
}
var IOAPICIRQs map[PCIAddr]IOAPICIRQ = map[PCIAddr]IOAPICIRQ{}
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
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, dev DevTreeNode) {
Offset(dt, offset)
switch dev.Chip {
case "cpu_cluster", "lapic", "domain", "ioapic":
fmt.Fprintf(dt, "device %s 0x%x ", dev.Chip, dev.Dev)
writeOn(dt, dev)
case "pci", "pnp":
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)
}
ioapic, ok := IOAPICIRQs[PCIAddr{Bus: dev.Bus, Dev: dev.Dev, Func: dev.Func}]
if dev.Chip == "pci" && ok {
for pin, irq := range ioapic.IRQNO {
if irq != 0 {
Offset(dt, offset+1)
fmt.Fprintf(dt, "ioapic_irq %d INT%c 0x%x\n", ioapic.APICID, 'A'+pin, irq)
}
}
}
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 {
WriteDev(dt, offset+1, 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)
KconfigString["VGA_BIOS_FILE"] = fmt.Sprintf("pci%04x,%04x.rom",
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.inc")
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()
mainboard.WriteString("#include <device/device.h>\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()
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 <acpi/acpi.h>
DefinitionBlock(
"dsdt.aml",
"DSDT",
ACPI_DSDT_REV_2,
OEM_ID,
ACPI_TABLE_CREATOR,
0x20141018 /* OEM revision */
)
{
#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;
`)
}
}