
For now, this is just an extra flag that can be used to print stack frame information, but this is intended to provide a way to determine stack sizes for goroutines at compile time in many cases. Stack sizes are often somewhere around 350 bytes so are in fact not all that big usually. Once this can be determined at compile time in many cases, it is possible to use this information when available and as a result increase the fallback stack size if the size cannot be determined at compile time. This should reduce stack overflows while at the same time reducing RAM consumption in many cases. Interesting output for testdata/channel.go: function stack usage (in bytes) Reset_Handler 332 .Lcommand-line-arguments.fastreceiver 220 .Lcommand-line-arguments.fastsender 192 .Lcommand-line-arguments.iterator 192 .Lcommand-line-arguments.main$1 184 .Lcommand-line-arguments.main$2 200 .Lcommand-line-arguments.main$3 200 .Lcommand-line-arguments.main$4 328 .Lcommand-line-arguments.receive 176 .Lcommand-line-arguments.selectDeadlock 72 .Lcommand-line-arguments.selectNoOp 72 .Lcommand-line-arguments.send 184 .Lcommand-line-arguments.sendComplex 192 .Lcommand-line-arguments.sender 192 .Lruntime.run$1 548 This shows that the stack size (if these numbers are correct) can in fact be determined automatically in many cases, especially for small goroutines. One of the great things about Go is lightweight goroutines, and reducing stack sizes is very important to make goroutines lightweight on microcontrollers.
1047 строки
30 КиБ
Go
Исполняемый файл
1047 строки
30 КиБ
Go
Исполняемый файл
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/xml"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"unicode"
|
|
)
|
|
|
|
var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
|
var enumBitSpecifier = regexp.MustCompile("^#[x01]+$")
|
|
|
|
type SVDFile struct {
|
|
XMLName xml.Name `xml:"device"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
LicenseText string `xml:"licenseText"`
|
|
Peripherals []struct {
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
BaseAddress string `xml:"baseAddress"`
|
|
GroupName string `xml:"groupName"`
|
|
DerivedFrom string `xml:"derivedFrom,attr"`
|
|
Interrupts []struct {
|
|
Name string `xml:"name"`
|
|
Index int `xml:"value"`
|
|
} `xml:"interrupt"`
|
|
Registers []*SVDRegister `xml:"registers>register"`
|
|
Clusters []*SVDCluster `xml:"registers>cluster"`
|
|
} `xml:"peripherals>peripheral"`
|
|
}
|
|
|
|
type SVDRegister struct {
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
Dim *string `xml:"dim"`
|
|
DimIndex *string `xml:"dimIndex"`
|
|
DimIncrement string `xml:"dimIncrement"`
|
|
Size *string `xml:"size"`
|
|
Fields []*SVDField `xml:"fields>field"`
|
|
Offset *string `xml:"offset"`
|
|
AddressOffset *string `xml:"addressOffset"`
|
|
}
|
|
|
|
type SVDField struct {
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
Lsb *uint32 `xml:"lsb"`
|
|
Msb *uint32 `xml:"msb"`
|
|
BitOffset *uint32 `xml:"bitOffset"`
|
|
BitWidth *uint32 `xml:"bitWidth"`
|
|
BitRange *string `xml:"bitRange"`
|
|
EnumeratedValues []struct {
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
Value string `xml:"value"`
|
|
} `xml:"enumeratedValues>enumeratedValue"`
|
|
}
|
|
|
|
type SVDCluster struct {
|
|
Dim *int `xml:"dim"`
|
|
DimIncrement string `xml:"dimIncrement"`
|
|
DimIndex *string `xml:"dimIndex"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
Registers []*SVDRegister `xml:"register"`
|
|
Clusters []*SVDCluster `xml:"cluster"`
|
|
AddressOffset string `xml:"addressOffset"`
|
|
}
|
|
|
|
type Device struct {
|
|
metadata map[string]string
|
|
interrupts []*interrupt
|
|
peripherals []*peripheral
|
|
}
|
|
|
|
type interrupt struct {
|
|
Name string
|
|
HandlerName string
|
|
peripheralIndex int
|
|
Value int // interrupt number
|
|
Description string
|
|
}
|
|
|
|
type peripheral struct {
|
|
Name string
|
|
GroupName string
|
|
BaseAddress uint64
|
|
Description string
|
|
ClusterName string
|
|
registers []*PeripheralField
|
|
subtypes []*peripheral
|
|
}
|
|
|
|
// A PeripheralField is a single field in a peripheral type. It may be a full
|
|
// peripheral or a cluster within a peripheral.
|
|
type PeripheralField struct {
|
|
name string
|
|
address uint64
|
|
description string
|
|
registers []*PeripheralField // contains fields if this is a cluster
|
|
array int
|
|
elementSize int
|
|
bitfields []Bitfield
|
|
}
|
|
|
|
type Bitfield struct {
|
|
name string
|
|
description string
|
|
value uint32
|
|
}
|
|
|
|
func formatText(text string) string {
|
|
text = regexp.MustCompile(`[ \t\n]+`).ReplaceAllString(text, " ") // Collapse whitespace (like in HTML)
|
|
text = strings.Replace(text, "\\n ", "\n", -1)
|
|
text = strings.TrimSpace(text)
|
|
return text
|
|
}
|
|
|
|
// Replace characters that are not allowed in a symbol name with a '_'. This is
|
|
// useful to be able to process SVD files with errors.
|
|
func cleanName(text string) string {
|
|
if !validName.MatchString(text) {
|
|
result := make([]rune, 0, len(text))
|
|
for _, c := range text {
|
|
if validName.MatchString(string(c)) {
|
|
result = append(result, c)
|
|
} else {
|
|
result = append(result, '_')
|
|
}
|
|
}
|
|
text = string(result)
|
|
}
|
|
return text
|
|
}
|
|
|
|
// Read ARM SVD files.
|
|
func readSVD(path, sourceURL string) (*Device, error) {
|
|
// Open the XML file.
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
decoder := xml.NewDecoder(f)
|
|
device := &SVDFile{}
|
|
err = decoder.Decode(device)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
peripheralDict := map[string]*peripheral{}
|
|
groups := map[string]*peripheral{}
|
|
|
|
interrupts := make(map[string]*interrupt)
|
|
var peripheralsList []*peripheral
|
|
|
|
for _, periphEl := range device.Peripherals {
|
|
description := formatText(periphEl.Description)
|
|
baseAddress, err := strconv.ParseUint(periphEl.BaseAddress, 0, 32)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid base address: %w", err)
|
|
}
|
|
// Some group names (for example the STM32H7A3x) have an invalid
|
|
// group name. Replace invalid characters with "_".
|
|
groupName := cleanName(periphEl.GroupName)
|
|
|
|
for _, interrupt := range periphEl.Interrupts {
|
|
addInterrupt(interrupts, interrupt.Name, interrupt.Name, interrupt.Index, description)
|
|
// As a convenience, also use the peripheral name as the interrupt
|
|
// name. Only do that for the nrf for now, as the stm32 .svd files
|
|
// don't always put interrupts in the correct peripheral...
|
|
if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") {
|
|
addInterrupt(interrupts, periphEl.Name, interrupt.Name, interrupt.Index, description)
|
|
}
|
|
}
|
|
|
|
if _, ok := groups[groupName]; ok || periphEl.DerivedFrom != "" {
|
|
var derivedFrom *peripheral
|
|
if periphEl.DerivedFrom != "" {
|
|
derivedFrom = peripheralDict[periphEl.DerivedFrom]
|
|
} else {
|
|
derivedFrom = groups[groupName]
|
|
}
|
|
p := &peripheral{
|
|
Name: periphEl.Name,
|
|
GroupName: derivedFrom.GroupName,
|
|
Description: description,
|
|
BaseAddress: baseAddress,
|
|
}
|
|
if p.Description == "" {
|
|
p.Description = derivedFrom.Description
|
|
}
|
|
peripheralsList = append(peripheralsList, p)
|
|
peripheralDict[p.Name] = p
|
|
for _, subtype := range derivedFrom.subtypes {
|
|
peripheralsList = append(peripheralsList, &peripheral{
|
|
Name: periphEl.Name + "_" + subtype.ClusterName,
|
|
GroupName: subtype.GroupName,
|
|
Description: subtype.Description,
|
|
BaseAddress: baseAddress,
|
|
})
|
|
}
|
|
continue
|
|
}
|
|
|
|
p := &peripheral{
|
|
Name: periphEl.Name,
|
|
GroupName: groupName,
|
|
Description: description,
|
|
BaseAddress: baseAddress,
|
|
registers: []*PeripheralField{},
|
|
}
|
|
if p.GroupName == "" {
|
|
p.GroupName = periphEl.Name
|
|
}
|
|
peripheralsList = append(peripheralsList, p)
|
|
peripheralDict[periphEl.Name] = p
|
|
|
|
if _, ok := groups[groupName]; !ok && groupName != "" {
|
|
groups[groupName] = p
|
|
}
|
|
|
|
for _, register := range periphEl.Registers {
|
|
regName := groupName // preferably use the group name
|
|
if regName == "" {
|
|
regName = periphEl.Name // fall back to peripheral name
|
|
}
|
|
p.registers = append(p.registers, parseRegister(regName, register, baseAddress, "")...)
|
|
}
|
|
for _, cluster := range periphEl.Clusters {
|
|
clusterName := strings.Replace(cluster.Name, "[%s]", "", -1)
|
|
if cluster.DimIndex != nil {
|
|
clusterName = strings.Replace(clusterName, "%s", "", -1)
|
|
}
|
|
clusterPrefix := clusterName + "_"
|
|
clusterOffset, err := strconv.ParseUint(cluster.AddressOffset, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var dim, dimIncrement int
|
|
if cluster.Dim == nil {
|
|
if clusterOffset == 0 {
|
|
// make this a separate peripheral
|
|
cpRegisters := []*PeripheralField{}
|
|
for _, regEl := range cluster.Registers {
|
|
cpRegisters = append(cpRegisters, parseRegister(groupName, regEl, baseAddress, clusterName+"_")...)
|
|
}
|
|
// handle sub-clusters of registers
|
|
for _, subClusterEl := range cluster.Clusters {
|
|
subclusterName := strings.Replace(subClusterEl.Name, "[%s]", "", -1)
|
|
subclusterPrefix := subclusterName + "_"
|
|
subclusterOffset, err := strconv.ParseUint(subClusterEl.AddressOffset, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subdim := *subClusterEl.Dim
|
|
subdimIncrement, err := strconv.ParseInt(subClusterEl.DimIncrement, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if subdim > 1 {
|
|
subcpRegisters := []*PeripheralField{}
|
|
subregSize := 0
|
|
for _, regEl := range subClusterEl.Registers {
|
|
size, err := strconv.ParseInt(*regEl.Size, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
subregSize += int(size)
|
|
subcpRegisters = append(subcpRegisters, parseRegister(groupName, regEl, baseAddress+subclusterOffset, subclusterPrefix)...)
|
|
}
|
|
cpRegisters = append(cpRegisters, &PeripheralField{
|
|
name: subclusterName,
|
|
address: baseAddress + subclusterOffset,
|
|
description: subClusterEl.Description,
|
|
registers: subcpRegisters,
|
|
array: subdim,
|
|
elementSize: int(subdimIncrement),
|
|
})
|
|
} else {
|
|
for _, regEl := range subClusterEl.Registers {
|
|
cpRegisters = append(cpRegisters, parseRegister(regEl.Name, regEl, baseAddress+subclusterOffset, subclusterPrefix)...)
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.SliceStable(cpRegisters, func(i, j int) bool {
|
|
return cpRegisters[i].address < cpRegisters[j].address
|
|
})
|
|
clusterPeripheral := &peripheral{
|
|
Name: periphEl.Name + "_" + clusterName,
|
|
GroupName: groupName + "_" + clusterName,
|
|
Description: description + " - " + clusterName,
|
|
ClusterName: clusterName,
|
|
BaseAddress: baseAddress,
|
|
registers: cpRegisters,
|
|
}
|
|
peripheralsList = append(peripheralsList, clusterPeripheral)
|
|
peripheralDict[clusterPeripheral.Name] = clusterPeripheral
|
|
p.subtypes = append(p.subtypes, clusterPeripheral)
|
|
continue
|
|
}
|
|
dim = -1
|
|
dimIncrement = -1
|
|
} else {
|
|
dim = *cluster.Dim
|
|
if dim == 1 {
|
|
dimIncrement = -1
|
|
} else {
|
|
inc, err := strconv.ParseUint(cluster.DimIncrement, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dimIncrement = int(inc)
|
|
}
|
|
}
|
|
clusterRegisters := []*PeripheralField{}
|
|
for _, regEl := range cluster.Registers {
|
|
regName := groupName
|
|
if regName == "" {
|
|
regName = periphEl.Name
|
|
}
|
|
clusterRegisters = append(clusterRegisters, parseRegister(regName, regEl, baseAddress+clusterOffset, clusterPrefix)...)
|
|
}
|
|
sort.SliceStable(clusterRegisters, func(i, j int) bool {
|
|
return clusterRegisters[i].address < clusterRegisters[j].address
|
|
})
|
|
if dimIncrement == -1 {
|
|
lastReg := clusterRegisters[len(clusterRegisters)-1]
|
|
lastAddress := lastReg.address
|
|
if lastReg.array != -1 {
|
|
lastAddress = lastReg.address + uint64(lastReg.array*lastReg.elementSize)
|
|
}
|
|
firstAddress := clusterRegisters[0].address
|
|
dimIncrement = int(lastAddress - firstAddress)
|
|
}
|
|
|
|
if !unicode.IsUpper(rune(clusterName[0])) && !unicode.IsDigit(rune(clusterName[0])) {
|
|
clusterName = strings.ToUpper(clusterName)
|
|
}
|
|
|
|
p.registers = append(p.registers, &PeripheralField{
|
|
name: clusterName,
|
|
address: baseAddress + clusterOffset,
|
|
description: cluster.Description,
|
|
registers: clusterRegisters,
|
|
array: dim,
|
|
elementSize: dimIncrement,
|
|
})
|
|
}
|
|
sort.SliceStable(p.registers, func(i, j int) bool {
|
|
return p.registers[i].address < p.registers[j].address
|
|
})
|
|
}
|
|
|
|
// Make a sorted list of interrupts.
|
|
interruptList := make([]*interrupt, 0, len(interrupts))
|
|
for _, intr := range interrupts {
|
|
interruptList = append(interruptList, intr)
|
|
}
|
|
sort.SliceStable(interruptList, func(i, j int) bool {
|
|
if interruptList[i].Value != interruptList[j].Value {
|
|
return interruptList[i].Value < interruptList[j].Value
|
|
}
|
|
return interruptList[i].peripheralIndex < interruptList[j].peripheralIndex
|
|
})
|
|
|
|
// Properly format the license block, with comments.
|
|
licenseBlock := ""
|
|
if text := formatText(device.LicenseText); text != "" {
|
|
licenseBlock = "// " + strings.Replace(text, "\n", "\n// ", -1)
|
|
licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n")
|
|
}
|
|
|
|
return &Device{
|
|
metadata: map[string]string{
|
|
"file": filepath.Base(path),
|
|
"descriptorSource": sourceURL,
|
|
"name": device.Name,
|
|
"nameLower": strings.ToLower(device.Name),
|
|
"description": strings.TrimSpace(device.Description),
|
|
"licenseBlock": licenseBlock,
|
|
},
|
|
interrupts: interruptList,
|
|
peripherals: peripheralsList,
|
|
}, nil
|
|
}
|
|
|
|
func addInterrupt(interrupts map[string]*interrupt, name, interruptName string, index int, description string) {
|
|
if _, ok := interrupts[name]; ok {
|
|
if interrupts[name].Value != index {
|
|
// Note: some SVD files like the one for STM32H7x7 contain mistakes.
|
|
// Instead of throwing an error, simply log it.
|
|
fmt.Fprintf(os.Stderr, "interrupt with the same name has different indexes: %s (%d vs %d)",
|
|
name, interrupts[name].Value, index)
|
|
}
|
|
parts := strings.Split(interrupts[name].Description, " // ")
|
|
hasDescription := false
|
|
for _, part := range parts {
|
|
if part == description {
|
|
hasDescription = true
|
|
}
|
|
}
|
|
if !hasDescription {
|
|
interrupts[name].Description += " // " + description
|
|
}
|
|
} else {
|
|
interrupts[name] = &interrupt{
|
|
Name: name,
|
|
HandlerName: interruptName + "_IRQHandler",
|
|
peripheralIndex: len(interrupts),
|
|
Value: index,
|
|
Description: description,
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPrefix string) []Bitfield {
|
|
var fields []Bitfield
|
|
for _, fieldEl := range fieldEls {
|
|
// Some bitfields (like the STM32H7x7) contain invalid bitfield
|
|
// names like "CNT[31]". Replace invalid characters with "_" when
|
|
// needed.
|
|
fieldName := cleanName(fieldEl.Name)
|
|
if !unicode.IsUpper(rune(fieldName[0])) && !unicode.IsDigit(rune(fieldName[0])) {
|
|
fieldName = strings.ToUpper(fieldName)
|
|
}
|
|
|
|
// Find the lsb/msb that is encoded in various ways.
|
|
// Standards are great, that's why there are so many to choose from!
|
|
var lsb, msb uint32
|
|
if fieldEl.Lsb != nil && fieldEl.Msb != nil {
|
|
// try to use lsb/msb tags
|
|
lsb = *fieldEl.Lsb
|
|
msb = *fieldEl.Msb
|
|
} else if fieldEl.BitOffset != nil && fieldEl.BitWidth != nil {
|
|
// try to use bitOffset/bitWidth tags
|
|
lsb = *fieldEl.BitOffset
|
|
msb = *fieldEl.BitWidth + lsb - 1
|
|
} else if fieldEl.BitRange != nil {
|
|
// try use bitRange
|
|
// example string: "[20:16]"
|
|
parts := strings.Split(strings.Trim(*fieldEl.BitRange, "[]"), ":")
|
|
l, err := strconv.ParseUint(parts[1], 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
lsb = uint32(l)
|
|
m, err := strconv.ParseUint(parts[0], 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
msb = uint32(m)
|
|
} else {
|
|
// this is an error. what to do?
|
|
fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldName)
|
|
continue
|
|
}
|
|
|
|
fields = append(fields, Bitfield{
|
|
name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName),
|
|
description: fmt.Sprintf("Position of %s field.", fieldName),
|
|
value: lsb,
|
|
})
|
|
fields = append(fields, Bitfield{
|
|
name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName),
|
|
description: fmt.Sprintf("Bit mask of %s field.", fieldName),
|
|
value: (0xffffffff >> (31 - (msb - lsb))) << lsb,
|
|
})
|
|
if lsb == msb { // single bit
|
|
fields = append(fields, Bitfield{
|
|
name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName),
|
|
description: fmt.Sprintf("Bit %s.", fieldName),
|
|
value: 1 << lsb,
|
|
})
|
|
}
|
|
for _, enumEl := range fieldEl.EnumeratedValues {
|
|
enumName := enumEl.Name
|
|
if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) {
|
|
continue
|
|
}
|
|
if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) {
|
|
enumName = strings.ToUpper(enumName)
|
|
}
|
|
enumDescription := strings.Replace(enumEl.Description, "\n", " ", -1)
|
|
enumValue, err := strconv.ParseUint(enumEl.Value, 0, 32)
|
|
if err != nil {
|
|
if enumBitSpecifier.MatchString(enumEl.Value) {
|
|
// NXP SVDs use the form #xx1x, #x0xx, etc for values
|
|
enumValue, err = strconv.ParseUint(strings.Replace(enumEl.Value[1:], "x", "0", -1), 2, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
fields = append(fields, Bitfield{
|
|
name: fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName),
|
|
description: enumDescription,
|
|
value: uint32(enumValue),
|
|
})
|
|
}
|
|
}
|
|
return fields
|
|
}
|
|
|
|
type Register struct {
|
|
element *SVDRegister
|
|
baseAddress uint64
|
|
}
|
|
|
|
func NewRegister(element *SVDRegister, baseAddress uint64) *Register {
|
|
return &Register{
|
|
element: element,
|
|
baseAddress: baseAddress,
|
|
}
|
|
}
|
|
|
|
func (r *Register) name() string {
|
|
return strings.Replace(r.element.Name, "[%s]", "", -1)
|
|
}
|
|
|
|
func (r *Register) description() string {
|
|
return strings.Replace(r.element.Description, "\n", " ", -1)
|
|
}
|
|
|
|
func (r *Register) address() uint64 {
|
|
offsetString := r.element.Offset
|
|
if offsetString == nil {
|
|
offsetString = r.element.AddressOffset
|
|
}
|
|
addr, err := strconv.ParseUint(*offsetString, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r.baseAddress + addr
|
|
}
|
|
|
|
func (r *Register) dim() int {
|
|
if r.element.Dim == nil {
|
|
return -1 // no dim elements
|
|
}
|
|
dim, err := strconv.ParseInt(*r.element.Dim, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return int(dim)
|
|
}
|
|
|
|
func (r *Register) dimIndex() []string {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
fmt.Println("register", r.name())
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
dim := r.dim()
|
|
if r.element.DimIndex == nil {
|
|
if dim <= 0 {
|
|
return nil
|
|
}
|
|
|
|
idx := make([]string, dim)
|
|
for i := range idx {
|
|
idx[i] = strconv.FormatInt(int64(i), 10)
|
|
}
|
|
return idx
|
|
}
|
|
|
|
t := strings.Split(*r.element.DimIndex, "-")
|
|
if len(t) == 2 {
|
|
x, err := strconv.ParseInt(t[0], 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
y, err := strconv.ParseInt(t[1], 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if x < 0 || y < x || y-x != int64(dim-1) {
|
|
panic("invalid dimIndex")
|
|
}
|
|
|
|
idx := make([]string, dim)
|
|
for i := x; i <= y; i++ {
|
|
idx[i-x] = strconv.FormatInt(i, 10)
|
|
}
|
|
return idx
|
|
} else if len(t) > 2 {
|
|
panic("invalid dimIndex")
|
|
}
|
|
|
|
s := strings.Split(*r.element.DimIndex, ",")
|
|
if len(s) != dim {
|
|
panic("invalid dimIndex")
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (r *Register) size() int {
|
|
if r.element.Size != nil {
|
|
size, err := strconv.ParseInt(*r.element.Size, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return int(size) / 8
|
|
}
|
|
return 4
|
|
}
|
|
|
|
func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bitfieldPrefix string) []*PeripheralField {
|
|
reg := NewRegister(regEl, baseAddress)
|
|
|
|
if reg.dim() != -1 {
|
|
dimIncrement, err := strconv.ParseUint(regEl.DimIncrement, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if strings.Contains(reg.name(), "%s") {
|
|
// a "spaced array" of registers, special processing required
|
|
// we need to generate a separate register for each "element"
|
|
var results []*PeripheralField
|
|
for i, j := range reg.dimIndex() {
|
|
regAddress := reg.address() + (uint64(i) * dimIncrement)
|
|
results = append(results, &PeripheralField{
|
|
name: strings.ToUpper(strings.Replace(reg.name(), "%s", j, -1)),
|
|
address: regAddress,
|
|
description: reg.description(),
|
|
array: -1,
|
|
elementSize: reg.size(),
|
|
})
|
|
}
|
|
// set first result bitfield
|
|
shortName := strings.ToUpper(strings.Replace(strings.Replace(reg.name(), "_%s", "", -1), "%s", "", -1))
|
|
results[0].bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix)
|
|
return results
|
|
}
|
|
}
|
|
regName := reg.name()
|
|
if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) {
|
|
regName = strings.ToUpper(regName)
|
|
}
|
|
|
|
bitfields := parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix)
|
|
return []*PeripheralField{&PeripheralField{
|
|
name: regName,
|
|
address: reg.address(),
|
|
description: reg.description(),
|
|
bitfields: bitfields,
|
|
array: reg.dim(),
|
|
elementSize: reg.size(),
|
|
}}
|
|
}
|
|
|
|
// The Go module for this device.
|
|
func writeGo(outdir string, device *Device, interruptSystem string) error {
|
|
outf, err := os.Create(filepath.Join(outdir, device.metadata["nameLower"]+".go"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer outf.Close()
|
|
w := bufio.NewWriter(outf)
|
|
|
|
maxInterruptValue := 0
|
|
for _, intr := range device.interrupts {
|
|
if intr.Value > maxInterruptValue {
|
|
maxInterruptValue = intr.Value
|
|
}
|
|
}
|
|
|
|
t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT.
|
|
// Generated by gen-device-svd.go from {{.metadata.file}}, see {{.metadata.descriptorSource}}
|
|
|
|
// +build {{.pkgName}},{{.metadata.nameLower}}
|
|
|
|
// {{.metadata.description}}
|
|
//
|
|
{{.metadata.licenseBlock}}
|
|
package {{.pkgName}}
|
|
|
|
import (
|
|
{{if eq .interruptSystem "hardware"}}"runtime/interrupt"{{end}}
|
|
"runtime/volatile"
|
|
"unsafe"
|
|
)
|
|
|
|
// Some information about this device.
|
|
const (
|
|
DEVICE = "{{.metadata.name}}"
|
|
)
|
|
|
|
// Interrupt numbers.
|
|
const ({{range .interrupts}}
|
|
IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}}
|
|
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
|
|
)
|
|
|
|
{{if eq .interruptSystem "hardware"}}
|
|
// Map interrupt numbers to function names.
|
|
// These aren't real calls, they're removed by the compiler.
|
|
var ({{range .interrupts}}
|
|
_ = interrupt.Register(IRQ_{{.Name}}, "{{.HandlerName}}"){{end}}
|
|
)
|
|
{{end}}
|
|
|
|
// Peripherals.
|
|
var (
|
|
{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}}
|
|
{{end}})
|
|
`))
|
|
err = t.Execute(w, map[string]interface{}{
|
|
"metadata": device.metadata,
|
|
"interrupts": device.interrupts,
|
|
"peripherals": device.peripherals,
|
|
"pkgName": filepath.Base(strings.TrimRight(outdir, "/")),
|
|
"interruptMax": maxInterruptValue,
|
|
"interruptSystem": interruptSystem,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Define peripheral struct types.
|
|
for _, peripheral := range device.peripherals {
|
|
if peripheral.registers == nil {
|
|
// This peripheral was derived from another peripheral. No new type
|
|
// needs to be defined for it.
|
|
continue
|
|
}
|
|
fmt.Fprintf(w, "\n// %s\ntype %s_Type struct {\n", peripheral.Description, peripheral.GroupName)
|
|
address := peripheral.BaseAddress
|
|
for _, register := range peripheral.registers {
|
|
if register.registers == nil && address > register.address {
|
|
// In Nordic SVD files, these registers are deprecated or
|
|
// duplicates, so can be ignored.
|
|
//fmt.Fprintf(os.Stderr, "skip: %s.%s 0x%x - 0x%x %d\n", peripheral.Name, register.name, address, register.address, register.elementSize)
|
|
continue
|
|
}
|
|
|
|
var regType string
|
|
switch register.elementSize {
|
|
case 8:
|
|
regType = "volatile.Register64"
|
|
case 4:
|
|
regType = "volatile.Register32"
|
|
case 2:
|
|
regType = "volatile.Register16"
|
|
case 1:
|
|
regType = "volatile.Register8"
|
|
default:
|
|
regType = "volatile.Register32"
|
|
}
|
|
|
|
// insert padding, if needed
|
|
if address < register.address {
|
|
bytesNeeded := register.address - address
|
|
if bytesNeeded == 1 {
|
|
w.WriteString("\t_ byte\n")
|
|
} else {
|
|
fmt.Fprintf(w, "\t_ [%d]byte\n", bytesNeeded)
|
|
}
|
|
address = register.address
|
|
}
|
|
|
|
lastCluster := false
|
|
if register.registers != nil {
|
|
// This is a cluster, not a register. Create the cluster type.
|
|
regType = "struct {\n"
|
|
subaddress := register.address
|
|
for _, subregister := range register.registers {
|
|
var subregType string
|
|
switch subregister.elementSize {
|
|
case 8:
|
|
subregType = "volatile.Register64"
|
|
case 4:
|
|
subregType = "volatile.Register32"
|
|
case 2:
|
|
subregType = "volatile.Register16"
|
|
case 1:
|
|
subregType = "volatile.Register8"
|
|
}
|
|
if subregType == "" {
|
|
panic("unknown element size")
|
|
}
|
|
|
|
if subregister.array != -1 {
|
|
subregType = fmt.Sprintf("[%d]%s", subregister.array, subregType)
|
|
}
|
|
if subaddress != subregister.address {
|
|
bytesNeeded := subregister.address - subaddress
|
|
if bytesNeeded == 1 {
|
|
regType += "\t\t_ byte\n"
|
|
} else {
|
|
regType += fmt.Sprintf("\t\t_ [%d]byte\n", bytesNeeded)
|
|
}
|
|
subaddress += bytesNeeded
|
|
}
|
|
var subregSize uint64
|
|
if subregister.array != -1 {
|
|
subregSize = uint64(subregister.array * subregister.elementSize)
|
|
} else {
|
|
subregSize = uint64(subregister.elementSize)
|
|
}
|
|
subaddress += subregSize
|
|
regType += fmt.Sprintf("\t\t%s %s\n", subregister.name, subregType)
|
|
}
|
|
if register.array != -1 {
|
|
if subaddress != register.address+uint64(register.elementSize) {
|
|
bytesNeeded := (register.address + uint64(register.elementSize)) - subaddress
|
|
if bytesNeeded == 1 {
|
|
regType += "\t_ byte\n"
|
|
} else {
|
|
regType += fmt.Sprintf("\t_ [%d]byte\n", bytesNeeded)
|
|
}
|
|
}
|
|
} else {
|
|
lastCluster = true
|
|
}
|
|
regType += "\t}"
|
|
address = subaddress
|
|
}
|
|
|
|
if register.array != -1 {
|
|
regType = fmt.Sprintf("[%d]%s", register.array, regType)
|
|
}
|
|
fmt.Fprintf(w, "\t%s %s // 0x%X\n", register.name, regType, register.address-peripheral.BaseAddress)
|
|
|
|
// next address
|
|
if lastCluster {
|
|
lastCluster = false
|
|
} else if register.array != -1 {
|
|
address = register.address + uint64(register.elementSize*register.array)
|
|
} else {
|
|
address = register.address + uint64(register.elementSize)
|
|
}
|
|
}
|
|
w.WriteString("}\n")
|
|
}
|
|
|
|
// Define bitfields.
|
|
for _, peripheral := range device.peripherals {
|
|
if peripheral.registers == nil {
|
|
// This peripheral was derived from another peripheral. Bitfields are
|
|
// already defined.
|
|
continue
|
|
}
|
|
fmt.Fprintf(w, "\n// Bitfields for %s: %s\nconst(", peripheral.Name, peripheral.Description)
|
|
for _, register := range peripheral.registers {
|
|
if len(register.bitfields) != 0 {
|
|
writeGoRegisterBitfields(w, register, register.name)
|
|
}
|
|
if register.registers == nil {
|
|
continue
|
|
}
|
|
for _, subregister := range register.registers {
|
|
writeGoRegisterBitfields(w, subregister, register.name+"."+subregister.name)
|
|
}
|
|
}
|
|
w.WriteString(")\n")
|
|
}
|
|
|
|
return w.Flush()
|
|
}
|
|
|
|
func writeGoRegisterBitfields(w *bufio.Writer, register *PeripheralField, name string) {
|
|
w.WriteString("\n\t// " + name)
|
|
if register.description != "" {
|
|
w.WriteString(": " + register.description)
|
|
}
|
|
w.WriteByte('\n')
|
|
for _, bitfield := range register.bitfields {
|
|
fmt.Fprintf(w, "\t%s = 0x%x", bitfield.name, bitfield.value)
|
|
if bitfield.description != "" {
|
|
w.WriteString(" // " + bitfield.description)
|
|
}
|
|
w.WriteByte('\n')
|
|
}
|
|
}
|
|
|
|
// The interrupt vector, which is hard to write directly in Go.
|
|
func writeAsm(outdir string, device *Device) error {
|
|
outf, err := os.Create(filepath.Join(outdir, device.metadata["nameLower"]+".s"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer outf.Close()
|
|
w := bufio.NewWriter(outf)
|
|
|
|
t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT.
|
|
// Generated by gen-device-svd.go from {{.file}}, see {{.descriptorSource}}
|
|
|
|
// {{.description}}
|
|
//
|
|
{{.licenseBlock}}
|
|
|
|
.syntax unified
|
|
|
|
// This is the default handler for interrupts, if triggered but not defined.
|
|
.section .text.Default_Handler
|
|
.global Default_Handler
|
|
.type Default_Handler, %function
|
|
Default_Handler:
|
|
wfe
|
|
b Default_Handler
|
|
.size Default_Handler, .-Default_Handler
|
|
|
|
// Avoid the need for repeated .weak and .set instructions.
|
|
.macro IRQ handler
|
|
.weak \handler
|
|
.set \handler, Default_Handler
|
|
.endm
|
|
|
|
// Must set the "a" flag on the section:
|
|
// https://svnweb.freebsd.org/base/stable/11/sys/arm/arm/locore-v4.S?r1=321049&r2=321048&pathrev=321049
|
|
// https://sourceware.org/binutils/docs/as/Section.html#ELF-Version
|
|
.section .isr_vector, "a", %progbits
|
|
.global __isr_vector
|
|
// Interrupt vector as defined by Cortex-M, starting with the stack top.
|
|
// On reset, SP is initialized with *0x0 and PC is loaded with *0x4, loading
|
|
// _stack_top and Reset_Handler.
|
|
.long _stack_top
|
|
.long Reset_Handler
|
|
.long NMI_Handler
|
|
.long HardFault_Handler
|
|
.long MemoryManagement_Handler
|
|
.long BusFault_Handler
|
|
.long UsageFault_Handler
|
|
.long 0
|
|
.long 0
|
|
.long 0
|
|
.long 0
|
|
.long SVC_Handler
|
|
.long DebugMon_Handler
|
|
.long 0
|
|
.long PendSV_Handler
|
|
.long SysTick_Handler
|
|
|
|
// Extra interrupts for peripherals defined by the hardware vendor.
|
|
`))
|
|
err = t.Execute(w, device.metadata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
num := 0
|
|
for _, intr := range device.interrupts {
|
|
if intr.Value == num-1 {
|
|
continue
|
|
}
|
|
if intr.Value < num {
|
|
panic("interrupt numbers are not sorted")
|
|
}
|
|
for intr.Value > num {
|
|
w.WriteString(" .long 0\n")
|
|
num++
|
|
}
|
|
num++
|
|
fmt.Fprintf(w, " .long %s\n", intr.HandlerName)
|
|
}
|
|
|
|
w.WriteString(`
|
|
// Define default implementations for interrupts, redirecting to
|
|
// Default_Handler when not implemented.
|
|
IRQ NMI_Handler
|
|
IRQ HardFault_Handler
|
|
IRQ MemoryManagement_Handler
|
|
IRQ BusFault_Handler
|
|
IRQ UsageFault_Handler
|
|
IRQ SVC_Handler
|
|
IRQ DebugMon_Handler
|
|
IRQ PendSV_Handler
|
|
IRQ SysTick_Handler
|
|
`)
|
|
for _, intr := range device.interrupts {
|
|
fmt.Fprintf(w, " IRQ %s_IRQHandler\n", intr.Name)
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
func generate(indir, outdir, sourceURL, interruptSystem string) error {
|
|
if _, err := os.Stat(indir); os.IsNotExist(err) {
|
|
fmt.Fprintln(os.Stderr, "cannot find input directory:", indir)
|
|
os.Exit(1)
|
|
}
|
|
os.MkdirAll(outdir, 0777)
|
|
|
|
infiles, err := filepath.Glob(filepath.Join(indir, "*.svd"))
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "could not read .svd files:", err)
|
|
os.Exit(1)
|
|
}
|
|
sort.Strings(infiles)
|
|
for _, infile := range infiles {
|
|
fmt.Println(infile)
|
|
device, err := readSVD(infile, sourceURL)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read: %w", err)
|
|
}
|
|
err = writeGo(outdir, device, interruptSystem)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write Go file: %w", err)
|
|
}
|
|
switch interruptSystem {
|
|
case "software":
|
|
// Nothing to do.
|
|
case "hardware":
|
|
err = writeAsm(outdir, device)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write assembly file: %w", err)
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown interrupt system: %s", interruptSystem)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
sourceURL := flag.String("source", "<unknown>", "source SVD file")
|
|
interruptSystem := flag.String("interrupts", "hardware", "interrupt system in use (software, hardware)")
|
|
flag.Parse()
|
|
if flag.NArg() != 2 {
|
|
fmt.Fprintln(os.Stderr, "provide exactly two arguments: input directory (with .svd files) and output directory for generated files")
|
|
flag.PrintDefaults()
|
|
return
|
|
}
|
|
indir := flag.Arg(0)
|
|
outdir := flag.Arg(1)
|
|
err := generate(indir, outdir, *sourceURL, *interruptSystem)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|