mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
0757ccc657
* tools/gen-device-svd: orderPeripherals: prevent skipping base peripherals derived by name Recent SVDs from stm32-rs, like the one for stm32u595, define derivedFrom attributes that do not refer to a peripheral group name, but to a peripheral name. For instance, the peripheral I2C5 may be derived from "I2C1", not from "I2C". The previous algorithm records, in case the group name is non-empty, only the group name in the knownBasePeripherals map, not the name of the peripheral itself. So in case of the base peripheral I2C1 with group name I2C: although the peripheral gets added to the sorted list, it would be added to knownBasePeripherals with the group name "I2C" as key, not with "I2C1". A following peripheral, I2C5, derived from I2C1, with an empty group name, would be recorded as known with key "I2C5", but omitted from the sorted list, because "I2C1" is not recognized as known. The following peripheral SEC_I2C5, derived from I2C5, with empty group name, would be added to both the knownPeripherals map and the sorted list. So if, later, the sorted list is examined, it would find SEC_I2C5 earlier than its base peripheral I2C5, which would be missing from "peripheralDict", resulting in a nil pointer access. This patch makes sure that, to stay with the example, that "I2C1" is recorded as known too (not only the group name "I2C"), so that "I2C5" won't be skipped anymore, preventing the program from crashing. * tools/gen-device-svd: orderPeripherals: ensure ordered content of missingBasePeripherals After the first run, missingBasePeripherals may contain peripherals with dependencies that are not guaranteed to be in proper order. This change implements additional loop runs that try to reduce the size of the missingBasePeripherals as far as possible. [With recent SVDs from stm32-rs this change will not produce different results, though (since these source files contain already properly ordered peripherals).] * tools/gen-device-svd: Register: move dim array decoding to utility type dimArray This allows encoding of dim increment and array indices to be re-used by other elements supporting dim arrays. This change just restructures parts of register specific code, it does not change the output of the program. * tools/gen-device-svd: parseBitfields: support field dim arrays Patched SVD files from stm32-rs recently contain many fields with dim array parameters and names containing %s (like "CC%sIF"). This change adjusts parseBitfields so that these field elements get resolved. * tools/gen-device-svd: SVDField: allow multiple enumeratedValues In recent patched SVD files from stm32-rs there may be two enumeratedValues elements per SVDField, not just one. The SVD specification allows up to two entries (they may be used to define different enums for read and write access). This change extends SVDField and parseBitfields so that two enumeratedValues are processed like a single one. * tools/gen-device-svd: orderPeripherals: sort peripherals of same group with larger number of registers/bitfields first In group "TIM" there may be general purpose timers like TIM16 and advanced timers like TIM1. The advanced peripheral may contain a larger number of registers than the general purpose ones. TIM1 may contain CCR1..CCR4, SMCR and OR1, while TIM16 only knows about CCR1. Unfortunately in some SVDs, like the one for stm32g031, TIM16 is defined before TIM1. Since register and bitfield constants are generated taking only the first peripheral of a group into account, the resulting .go file may lack definitions for e.g. CCR2..CCR4, SMCR, and OR1. This change adjusts orderPeripherals so that, to stay with the example, a peripheral like TIM1 will be moved in front of TIM16, resulting in an output file containing the larger set of definitions. * tools/gen-device-svd: SVDEnumeration: support isDefault Recent SVD files created by stm32-rs use "isDefault" in enumeratedValue elements for purposes like the Div1 enum for clock prescaler registers without specifying a specific value. Previously, these enumeratedValues would be skipped because of the enumEl.Value == 0 condition, and the corresponding const definitions like "RCC_CFGR2_PPRE2_Div1 = 0x0" would be missing from the resulting .go files, so existing code relying on these constants would not compile anymore. This change adds a utility type enumDefaultResolver that helps finding an actual value that is unused by the enumeratedValues that are defined for the field. More examples for values marked as "isDefault", along with their resolved values: DAC_CR_WAVE1_Triangle => 2 IWDG_PR_PR_DivideBy256 => 6 DAC_CR_MAMP2_Amp4095 => 0xb * tools/gen-device-svd: support derivedFrom attribute at field level This ensures that some more constants are included in the .go files that would otherwise be skipped (like e.g. ADC_SMPR2_SMP1_Cycles* of some STM32 devices), which prevented compilation of some programs. To avoid extending a lot of func argument lists, and since there is no context.Context in use yet, this change introduces a global derivationContext. * tools/gen-device-svd: tweak: stm32: ensure USART_ISR_TXE/TXFNF are present * tools/gen-device-svd: stm32: ensure CCMR*_Output alternate registers are sorted first * tools/gen-device-svd: stm32: add IWDG peripheral alias if SVD defines IWDG1
1854 lines
53 KiB
Go
Executable File
1854 lines
53 KiB
Go
Executable File
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/xml"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"unicode"
|
|
)
|
|
|
|
var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
|
var validDimableName = regexp.MustCompile(`^((%s)|(%s)[_A-Za-z]{1}[_A-Za-z0-9]*)|([_A-Za-z]{1}[_A-Za-z0-9]*(\[%s\])?)|([_A-Za-z]{1}[_A-Za-z0-9]*(%s)?[_A-Za-z0-9]*)$`)
|
|
var enumBitSpecifier = regexp.MustCompile("^#x*[01]+[01x]*$")
|
|
|
|
type SVDFile struct {
|
|
XMLName xml.Name `xml:"device"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
LicenseText string `xml:"licenseText"`
|
|
CPU *struct {
|
|
Name string `xml:"name"`
|
|
FPUPresent bool `xml:"fpuPresent"`
|
|
NVICPrioBits int `xml:"nvicPrioBits"`
|
|
} `xml:"cpu"`
|
|
Peripherals []SVDPeripheral `xml:"peripherals>peripheral"`
|
|
}
|
|
|
|
type SVDPeripheral 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"`
|
|
}
|
|
|
|
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 {
|
|
DerivedFrom string `xml:"derivedFrom,attr"`
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
Dim *string `xml:"dim"`
|
|
DimIndex *string `xml:"dimIndex"`
|
|
DimIncrement string `xml:"dimIncrement"`
|
|
Lsb *uint32 `xml:"lsb"`
|
|
Msb *uint32 `xml:"msb"`
|
|
BitOffset *uint32 `xml:"bitOffset"`
|
|
BitWidth *uint32 `xml:"bitWidth"`
|
|
BitRange *string `xml:"bitRange"`
|
|
EnumeratedValues []SVDEnumeration `xml:"enumeratedValues"`
|
|
}
|
|
|
|
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 SVDEnumeration struct {
|
|
DerivedFrom string `xml:"derivedFrom,attr"`
|
|
Name string `xml:"name"`
|
|
EnumeratedValue []struct {
|
|
Name string `xml:"name"`
|
|
Description string `xml:"description"`
|
|
Value string `xml:"value"`
|
|
IsDefault bool `xml:"isDefault"`
|
|
} `xml:"enumeratedValue"`
|
|
}
|
|
|
|
type Device struct {
|
|
Metadata *Metadata
|
|
Interrupts []*Interrupt
|
|
Peripherals []*Peripheral
|
|
PeripheralDict map[string]*Peripheral
|
|
}
|
|
|
|
type Metadata struct {
|
|
File string
|
|
DescriptorSource string
|
|
Name string
|
|
NameLower string
|
|
Description string
|
|
LicenseBlock string
|
|
|
|
HasCPUInfo bool // set if the following fields are populated
|
|
CPUName string
|
|
FPUPresent bool
|
|
NVICPrioBits int
|
|
}
|
|
|
|
type Interrupt struct {
|
|
Name string
|
|
HandlerName string
|
|
PeripheralIndex int
|
|
Value int // interrupt number
|
|
Description string
|
|
}
|
|
|
|
type Peripheral struct {
|
|
Name string
|
|
Alias 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
|
|
Constants []Constant
|
|
ShortName string // name stripped of "spaced array" suffix
|
|
Bitfields []Bitfield // set of bit-fields provided by this
|
|
HasBitfields bool // set true when Bitfields was set for a first PeripheralField of "spaced array".
|
|
}
|
|
|
|
type Constant struct {
|
|
Name string
|
|
Description string
|
|
Value uint64
|
|
}
|
|
|
|
type Bitfield struct {
|
|
Name string
|
|
Offset uint32
|
|
Mask uint32
|
|
}
|
|
|
|
func formatText(text string) string {
|
|
text = regexp.MustCompile(`[ \t\n]+`).ReplaceAllString(text, " ") // Collapse whitespace (like in HTML)
|
|
text = strings.ReplaceAll(text, "\\n ", "\n")
|
|
text = strings.TrimSpace(text)
|
|
return text
|
|
}
|
|
|
|
func isMultiline(s string) bool {
|
|
return strings.Index(s, "\n") >= 0
|
|
}
|
|
|
|
func splitLine(s string) []string {
|
|
return strings.Split(s, "\n")
|
|
}
|
|
|
|
// 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 {
|
|
return cleanIdentifier(text, validName)
|
|
}
|
|
|
|
func cleanDimableName(text string) string {
|
|
return cleanIdentifier(text, validDimableName)
|
|
}
|
|
|
|
func cleanIdentifier(text string, valid *regexp.Regexp) string {
|
|
text = cleanString(text, valid)
|
|
if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') {
|
|
// Identifiers may not start with a number.
|
|
// Add an underscore instead.
|
|
text = "_" + text
|
|
}
|
|
return text
|
|
}
|
|
|
|
func cleanString(s string, valid *regexp.Regexp) string {
|
|
if !valid.MatchString(s) {
|
|
result := make([]rune, 0, len(s))
|
|
for _, c := range s {
|
|
if validName.MatchString(string(c)) {
|
|
result = append(result, c)
|
|
} else {
|
|
result = append(result, '_')
|
|
}
|
|
}
|
|
s = string(result)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func processSubCluster(p *Peripheral, cluster *SVDCluster, clusterOffset uint64, clusterName string, peripheralDict map[string]*Peripheral) []*Peripheral {
|
|
var peripheralsList []*Peripheral
|
|
clusterPrefix := clusterName + "_"
|
|
cpRegisters := []*PeripheralField{}
|
|
|
|
for _, regEl := range cluster.Registers {
|
|
cpRegisters = append(cpRegisters, parseRegister(p.GroupName, regEl, p.BaseAddress+clusterOffset, clusterPrefix)...)
|
|
}
|
|
// handle sub-clusters of registers
|
|
for _, subClusterEl := range cluster.Clusters {
|
|
subclusterName := strings.ReplaceAll(subClusterEl.Name, "[%s]", "")
|
|
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{}
|
|
for _, regEl := range subClusterEl.Registers {
|
|
subcpRegisters = append(subcpRegisters, parseRegister(p.GroupName, regEl, p.BaseAddress+clusterOffset+subclusterOffset, subclusterPrefix)...)
|
|
}
|
|
|
|
cpRegisters = append(cpRegisters, &PeripheralField{
|
|
Name: subclusterName,
|
|
Address: p.BaseAddress + clusterOffset + subclusterOffset,
|
|
Description: subClusterEl.Description,
|
|
Registers: subcpRegisters,
|
|
Array: subdim,
|
|
ElementSize: int(subdimIncrement),
|
|
ShortName: clusterPrefix + subclusterName,
|
|
})
|
|
} else {
|
|
for _, regEl := range subClusterEl.Registers {
|
|
cpRegisters = append(cpRegisters, parseRegister(regEl.Name, regEl, p.BaseAddress+clusterOffset+subclusterOffset, subclusterPrefix)...)
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.SliceStable(cpRegisters, func(i, j int) bool {
|
|
return cpRegisters[i].Address < cpRegisters[j].Address
|
|
})
|
|
clusterPeripheral := &Peripheral{
|
|
Name: p.Name + "_" + clusterName,
|
|
GroupName: p.GroupName + "_" + clusterName,
|
|
Description: p.Description + " - " + clusterName,
|
|
ClusterName: clusterName,
|
|
BaseAddress: p.BaseAddress + clusterOffset,
|
|
Registers: cpRegisters,
|
|
}
|
|
peripheralsList = append(peripheralsList, clusterPeripheral)
|
|
peripheralDict[clusterPeripheral.Name] = clusterPeripheral
|
|
p.Subtypes = append(p.Subtypes, clusterPeripheral)
|
|
|
|
return peripheralsList
|
|
}
|
|
|
|
func processCluster(p *Peripheral, clusters []*SVDCluster, peripheralDict map[string]*Peripheral) []*Peripheral {
|
|
var peripheralsList []*Peripheral
|
|
for _, cluster := range clusters {
|
|
clusterName := strings.ReplaceAll(cluster.Name, "[%s]", "")
|
|
if cluster.DimIndex != nil {
|
|
clusterName = strings.ReplaceAll(clusterName, "%s", "")
|
|
}
|
|
clusterPrefix := clusterName + "_"
|
|
clusterOffset, err := strconv.ParseUint(cluster.AddressOffset, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var dim, dimIncrement int
|
|
if cluster.Dim == nil {
|
|
// Nordic SVD have sub-clusters with another sub-clusters.
|
|
if clusterOffset == 0 || len(cluster.Clusters) > 0 {
|
|
peripheralsList = append(peripheralsList, processSubCluster(p, cluster, clusterOffset, clusterName, peripheralDict)...)
|
|
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 := p.GroupName
|
|
if regName == "" {
|
|
regName = p.Name
|
|
}
|
|
clusterRegisters = append(clusterRegisters, parseRegister(regName, regEl, p.BaseAddress+clusterOffset, clusterPrefix)...)
|
|
}
|
|
sort.SliceStable(clusterRegisters, func(i, j int) bool {
|
|
return clusterRegisters[i].Address < clusterRegisters[j].Address
|
|
})
|
|
if dimIncrement == -1 && len(clusterRegisters) > 0 {
|
|
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: p.BaseAddress + clusterOffset,
|
|
Description: cluster.Description,
|
|
Registers: clusterRegisters,
|
|
Array: dim,
|
|
ElementSize: dimIncrement,
|
|
ShortName: clusterName,
|
|
})
|
|
}
|
|
sort.SliceStable(p.Registers, func(i, j int) bool {
|
|
return p.Registers[i].Address < p.Registers[j].Address
|
|
})
|
|
return peripheralsList
|
|
}
|
|
|
|
// 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
|
|
|
|
// Some SVD files have peripheral elements derived from a peripheral that
|
|
// comes later in the file. To make sure this works, sort the peripherals if
|
|
// needed.
|
|
orderedPeripherals := orderPeripherals(device.Peripherals)
|
|
globalDerivationCtx.peripherals = orderedPeripherals
|
|
|
|
for _, periphEl := range orderedPeripherals {
|
|
description := formatText(periphEl.Description)
|
|
baseAddress, err := strconv.ParseUint(periphEl.BaseAddress, 0, 64)
|
|
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)
|
|
if groupName == "" {
|
|
groupName = cleanName(periphEl.Name)
|
|
}
|
|
|
|
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, "")...)
|
|
}
|
|
peripheralsList = append(peripheralsList, processCluster(p, periphEl.Clusters, peripheralDict)...)
|
|
}
|
|
|
|
// 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 description, with comments.
|
|
description := ""
|
|
if text := device.Description; text != "" {
|
|
description = "// " + strings.ReplaceAll(text, "\n", "\n// ")
|
|
description = regexp.MustCompile(`\s+\n`).ReplaceAllString(description, "\n")
|
|
}
|
|
|
|
// Properly format the license block, with comments.
|
|
licenseBlock := ""
|
|
if text := formatText(device.LicenseText); text != "" {
|
|
licenseBlock = "// " + strings.ReplaceAll(text, "\n", "\n// ")
|
|
licenseBlock = regexp.MustCompile(`\s+\n`).ReplaceAllString(licenseBlock, "\n")
|
|
}
|
|
|
|
// Remove "-" characters from the device name because such characters cannot
|
|
// be used in build tags. Necessary for the ESP32-C3 for example.
|
|
nameLower := strings.ReplaceAll(strings.ToLower(device.Name), "-", "")
|
|
metadata := &Metadata{
|
|
File: filepath.Base(path),
|
|
DescriptorSource: sourceURL,
|
|
Name: device.Name,
|
|
NameLower: nameLower,
|
|
Description: description,
|
|
LicenseBlock: licenseBlock,
|
|
}
|
|
if device.CPU != nil {
|
|
metadata.HasCPUInfo = true
|
|
metadata.CPUName = device.CPU.Name
|
|
metadata.FPUPresent = device.CPU.FPUPresent
|
|
metadata.NVICPrioBits = device.CPU.NVICPrioBits
|
|
}
|
|
return &Device{
|
|
Metadata: metadata,
|
|
Interrupts: interruptList,
|
|
Peripherals: peripheralsList,
|
|
PeripheralDict: peripheralDict,
|
|
}, nil
|
|
}
|
|
|
|
// orderPeripherals sorts the peripherals so that derived peripherals come after
|
|
// base peripherals. This is necessary for some SVD files.
|
|
func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral {
|
|
var sortedPeripherals []*SVDPeripheral
|
|
var missingBasePeripherals []*SVDPeripheral
|
|
knownBasePeripherals := map[string]struct{}{}
|
|
|
|
tryProcess := func(p *SVDPeripheral) {
|
|
groupName := p.GroupName
|
|
if groupName != "" {
|
|
knownBasePeripherals[groupName] = struct{}{}
|
|
}
|
|
knownBasePeripherals[p.Name] = struct{}{}
|
|
if p.DerivedFrom != "" {
|
|
if _, ok := knownBasePeripherals[p.DerivedFrom]; !ok {
|
|
missingBasePeripherals = append(missingBasePeripherals, p)
|
|
return
|
|
}
|
|
}
|
|
sortedPeripherals = append(sortedPeripherals, p)
|
|
}
|
|
for i := range input {
|
|
tryProcess(&input[i])
|
|
}
|
|
orderPeripheralsByNumBitfields(sortedPeripherals)
|
|
|
|
// missingBasePeripherals may still contain unordered entries;
|
|
// repeat the process until missingBasePeripheral does not change anymore.
|
|
prevNumPending := 0
|
|
for {
|
|
pending := missingBasePeripherals
|
|
if len(pending) == prevNumPending {
|
|
break
|
|
}
|
|
// reuse the same slice as input and for keeping track of
|
|
// missing base periphal
|
|
missingBasePeripherals = missingBasePeripherals[:0]
|
|
for _, p := range pending {
|
|
tryProcess(p)
|
|
}
|
|
prevNumPending = len(pending)
|
|
}
|
|
|
|
// Let's hope all base peripherals are now included.
|
|
sortedPeripherals = append(sortedPeripherals, missingBasePeripherals...)
|
|
|
|
return sortedPeripherals
|
|
}
|
|
|
|
func orderPeripheralsByNumBitfields(list []*SVDPeripheral) {
|
|
seenGroup := make(map[string]struct{})
|
|
for i, p := range list {
|
|
groupName := p.GroupName
|
|
if groupName == "" || p.DerivedFrom != "" {
|
|
continue
|
|
}
|
|
if _, ok := seenGroup[groupName]; ok {
|
|
continue
|
|
}
|
|
iMax, nMax := -1, p.bitfieldCount()
|
|
for j, p2 := range list[i+1:] {
|
|
if p2.GroupName != groupName || p2.DerivedFrom != "" {
|
|
continue
|
|
}
|
|
if n2 := p2.bitfieldCount(); n2 > nMax {
|
|
iMax = i + 1 + j
|
|
nMax = n2
|
|
}
|
|
}
|
|
if iMax != -1 {
|
|
pMax := list[iMax]
|
|
// swap peripherals
|
|
copy(list[i+1:iMax+1], list[i:iMax])
|
|
list[i] = pMax
|
|
seenGroup[groupName] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *SVDPeripheral) bitfieldCount() int {
|
|
n := 0
|
|
for _, r := range p.Registers {
|
|
for _, f := range r.Fields {
|
|
dim := decodeDim(f.Dim)
|
|
if dim > 0 {
|
|
n += dim
|
|
} else {
|
|
n++
|
|
}
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
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)\n",
|
|
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) ([]Constant, []Bitfield) {
|
|
var fields []Constant
|
|
var bitfields []Bitfield
|
|
var enumDefault enumDefaultResolver
|
|
enumSeen := map[string]int64{}
|
|
for _, fieldEl := range fieldEls {
|
|
|
|
if fieldEl.DerivedFrom != "" {
|
|
err := globalDerivationCtx.deriveField(fieldEl, fieldEls)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "unable to derive field %q from %q: %v\n", fieldEl.Name, fieldEl.DerivedFrom, err.Error())
|
|
}
|
|
}
|
|
|
|
// Some bitfields (like the STM32H7x7) contain invalid bitfield
|
|
// names like "CNT[31]". Replace invalid characters with "_" when
|
|
// needed.
|
|
fieldNameTpl := cleanDimableName(fieldEl.Name)
|
|
if !unicode.IsUpper(rune(fieldNameTpl[0])) && !unicode.IsDigit(rune(fieldNameTpl[0])) {
|
|
fieldNameTpl = strings.ReplaceAll(strings.ToUpper(fieldNameTpl), "%S", "%s")
|
|
}
|
|
|
|
// 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:", fieldNameTpl)
|
|
continue
|
|
}
|
|
|
|
da := decodeDimArray(fieldEl.Dim, fieldEl.DimIndex, fieldEl.DimIncrement, "field", fieldNameTpl)
|
|
da.rangeElems(func(ia int, _ uint32) bool {
|
|
if da != nil {
|
|
lsb += da.incr
|
|
msb += da.incr
|
|
}
|
|
fieldName := da.replace(fieldNameTpl, ia)
|
|
|
|
// The enumerated values can be the same as another field, so to avoid
|
|
// duplication SVD files can simply refer to another set of enumerated
|
|
// values in the same register.
|
|
// See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues
|
|
for i := range fieldEl.EnumeratedValues {
|
|
enumeratedValues := &fieldEl.EnumeratedValues[i]
|
|
if enumeratedValues.DerivedFrom != "" {
|
|
parts := strings.Split(enumeratedValues.DerivedFrom, ".")
|
|
if len(parts) == 1 {
|
|
found := false
|
|
for _, otherFieldEl := range fieldEls {
|
|
for i := range otherFieldEl.EnumeratedValues {
|
|
otherEnum := &otherFieldEl.EnumeratedValues[i]
|
|
if otherEnum.Name == parts[0] {
|
|
found = true
|
|
*enumeratedValues = *otherEnum
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName)
|
|
}
|
|
} else {
|
|
// The derivedFrom attribute may also point to enumerated values
|
|
// in other registers and even peripherals, but this feature
|
|
// isn't often used in SVD files.
|
|
fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom)
|
|
}
|
|
}
|
|
}
|
|
|
|
bitfields = append(bitfields, Bitfield{
|
|
Name: fieldName,
|
|
Offset: lsb,
|
|
Mask: (0xffffffff >> (31 - (msb - lsb))) << lsb,
|
|
})
|
|
fields = append(fields, Constant{
|
|
Name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName),
|
|
Description: fmt.Sprintf("Position of %s field.", fieldName),
|
|
Value: uint64(lsb),
|
|
})
|
|
fields = append(fields, Constant{
|
|
Name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName),
|
|
Description: fmt.Sprintf("Bit mask of %s field.", fieldName),
|
|
Value: (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb,
|
|
})
|
|
if lsb == msb { // single bit
|
|
fields = append(fields, Constant{
|
|
Name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName),
|
|
Description: fmt.Sprintf("Bit %s.", fieldName),
|
|
Value: 1 << lsb,
|
|
})
|
|
}
|
|
for i := range fieldEl.EnumeratedValues {
|
|
enumDefault.reset(1<<(msb+1-lsb) - 1)
|
|
fields0Pos := len(fields)
|
|
for _, enumEl := range fieldEl.EnumeratedValues[i].EnumeratedValue {
|
|
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)
|
|
}
|
|
enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName)
|
|
enumDescription := formatText(enumEl.Description)
|
|
|
|
if enumEl.IsDefault {
|
|
enumDefault.setDefaultAction(func(value uint64) {
|
|
if value == 0 {
|
|
// put zero value in front of other constants
|
|
fields = slices.Insert(fields, fields0Pos, Constant{})
|
|
appendConstant(fields[:fields0Pos], enumName, enumDescription, value, enumSeen)
|
|
} else {
|
|
fields = appendConstant(fields, enumName, enumDescription, value, enumSeen)
|
|
}
|
|
})
|
|
continue
|
|
}
|
|
|
|
// Renesas has enum without actual values that we have to skip
|
|
if enumEl.Value == "" {
|
|
continue
|
|
}
|
|
|
|
var enumValue uint64
|
|
var err error
|
|
if strings.HasPrefix(enumEl.Value, "0b") {
|
|
val := strings.TrimPrefix(enumEl.Value, "0b")
|
|
enumValue, err = strconv.ParseUint(val, 2, 64)
|
|
} else {
|
|
enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64)
|
|
}
|
|
if err != nil {
|
|
if enumBitSpecifier.MatchString(enumEl.Value) {
|
|
// NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values
|
|
enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
enumDefault.collectValue(enumValue)
|
|
fields = appendConstant(fields, enumName, enumDescription, enumValue, enumSeen)
|
|
}
|
|
enumDefault.resolve()
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
return fields, bitfields
|
|
}
|
|
|
|
var globalDerivationCtx derivationContext
|
|
|
|
type derivationContext struct {
|
|
peripherals []*SVDPeripheral
|
|
}
|
|
|
|
func (ctx *derivationContext) deriveField(fieldEl *SVDField, localFieldEls []*SVDField) error {
|
|
from := fieldEl.DerivedFrom
|
|
parts := strings.Split(from, ".")
|
|
srcName := parts[0]
|
|
var srcFieldEl *SVDField
|
|
switch len(parts) {
|
|
case 3, 4:
|
|
src, err := ctx.lookupGlobal(parts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srcFieldEl = src
|
|
case 1:
|
|
// resolve locally, in current register
|
|
for _, f := range localFieldEls {
|
|
if f == fieldEl {
|
|
continue
|
|
}
|
|
if f.Name == srcName {
|
|
srcFieldEl = f
|
|
break
|
|
}
|
|
}
|
|
if srcFieldEl == nil {
|
|
return fmt.Errorf("not found")
|
|
}
|
|
default:
|
|
return fmt.Errorf("cannot decode source path")
|
|
}
|
|
|
|
// copy enumeratedValues from source to current field
|
|
if fieldEl.DimIndex == nil && strings.Contains(fieldEl.Name, "%s") {
|
|
fieldEl.DimIndex = srcFieldEl.DimIndex
|
|
fieldEl.DimIncrement = srcFieldEl.DimIncrement
|
|
fieldEl.Dim = srcFieldEl.Dim
|
|
}
|
|
if fieldEl.Description == "" {
|
|
fieldEl.Description = srcFieldEl.Description
|
|
}
|
|
if fieldEl.BitWidth == nil {
|
|
fieldEl.BitWidth = srcFieldEl.BitWidth
|
|
}
|
|
if fieldEl.BitOffset == nil {
|
|
fieldEl.BitOffset = srcFieldEl.BitOffset
|
|
}
|
|
if fieldEl.BitRange == nil {
|
|
fieldEl.BitRange = srcFieldEl.BitRange
|
|
}
|
|
|
|
fieldEl.EnumeratedValues = srcFieldEl.EnumeratedValues
|
|
return nil
|
|
}
|
|
|
|
func (ctx *derivationContext) lookupGlobal(path []string) (*SVDField, error) {
|
|
curPath := path[:1]
|
|
for _, p := range ctx.peripherals {
|
|
if p.Name == path[0] {
|
|
if len(path) == 4 {
|
|
curPath = path[:2]
|
|
for _, c := range p.Clusters {
|
|
if c.Name == path[1] {
|
|
return ctx.lookupFieldInRegs(path[2:], c.Registers, curPath)
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("cluster not found: %q", path[2])
|
|
|
|
}
|
|
return ctx.lookupFieldInRegs(path[1:], p.Registers, curPath)
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("peripheral not found: %s", path[0])
|
|
}
|
|
|
|
func (ctx *derivationContext) lookupFieldInRegs(path []string, registers []*SVDRegister, curPath []string) (*SVDField, error) {
|
|
curPath = curPath[:len(curPath)+1]
|
|
for _, r := range registers {
|
|
if r.Name == path[0] {
|
|
curPath = curPath[:len(curPath)+1]
|
|
for _, f := range r.Fields {
|
|
if f.Name == path[1] {
|
|
return f, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("field not found: %q", strings.Join(curPath, "."))
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("register not found: %q", strings.Join(curPath, "."))
|
|
}
|
|
|
|
func appendConstant(fields []Constant, enumName, enumDescription string, enumValue uint64, enumSeen map[string]int64) []Constant {
|
|
// Avoid duplicate values. Duplicate names with the same value are
|
|
// allowed, but the same name with a different value is not. Instead
|
|
// of trying to work around those cases, remove the value entirely
|
|
// as there is probably not one correct answer in such a case.
|
|
// For example, SVD files from NXP have enums limited to 20
|
|
// characters, leading to lots of duplicates when these enum names
|
|
// are long. Nothing here can really fix those cases.
|
|
previousEnumValue, seenBefore := enumSeen[enumName]
|
|
if seenBefore {
|
|
if previousEnumValue < 0 {
|
|
// There was a mismatch before, ignore all equally named fields.
|
|
return fields
|
|
}
|
|
if int64(enumValue) != previousEnumValue {
|
|
// There is a mismatch. Mark it as such, and remove the
|
|
// existing enum bitfield value.
|
|
enumSeen[enumName] = -1
|
|
for i, field := range fields {
|
|
if field.Name == enumName {
|
|
fields = append(fields[:i], fields[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return fields
|
|
}
|
|
enumSeen[enumName] = int64(enumValue)
|
|
|
|
fields = append(fields, Constant{
|
|
Name: enumName,
|
|
Description: enumDescription,
|
|
Value: enumValue,
|
|
})
|
|
return fields
|
|
}
|
|
|
|
// enumDefaultResolver helps determine the actual numeric value for an
|
|
// enumeratedValue marked as the default (i.e., where "isDefault" is set).
|
|
//
|
|
// Some SVD files use "isDefault" to indicate a fallback value (e.g., Div1 in
|
|
// clock prescaler registers) without specifying the exact value when it's not
|
|
// critical. This type is used to collect all defined enumValues, and once
|
|
// collection is complete, derive a sensible default value that does not conflict
|
|
// with any explicitly defined ones.
|
|
//
|
|
// Typically, it prefers zero as a default if available; otherwise, it will
|
|
// choose a suitable unused value below the field's maximum.
|
|
type enumDefaultResolver struct {
|
|
values []uint64
|
|
maxValue uint64
|
|
handleDefault func(value uint64)
|
|
}
|
|
|
|
func (dr *enumDefaultResolver) reset(maxValue uint64) {
|
|
dr.values = dr.values[:0]
|
|
dr.maxValue = maxValue
|
|
dr.handleDefault = nil
|
|
}
|
|
|
|
func (dr *enumDefaultResolver) setDefaultAction(action func(v uint64)) {
|
|
dr.handleDefault = action
|
|
}
|
|
|
|
func (dr *enumDefaultResolver) collectValue(value uint64) {
|
|
dr.values = append(dr.values, value)
|
|
}
|
|
|
|
// resolve tries to find an actual value for the enumerated Value
|
|
// marked as default.
|
|
func (dr *enumDefaultResolver) resolve() {
|
|
if dr.handleDefault == nil {
|
|
return
|
|
}
|
|
list := dr.values
|
|
n := len(list)
|
|
if n == 0 {
|
|
return
|
|
}
|
|
slices.Sort(list)
|
|
|
|
var value uint64
|
|
// try to use zero as default value
|
|
if list[0] == 0 {
|
|
// not available, now try the highest value +1
|
|
largest := list[n-1]
|
|
if largest < dr.maxValue {
|
|
value = largest + 1
|
|
} else {
|
|
value = 1
|
|
// not available, now lookup the first free value
|
|
for _, enumValue := range list[1:] {
|
|
if value < enumValue {
|
|
break
|
|
}
|
|
value = enumValue + 1
|
|
if value == dr.maxValue {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dr.handleDefault(value)
|
|
}
|
|
|
|
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.ReplaceAll(r.element.Name, "[%s]", "")
|
|
}
|
|
|
|
func (r *Register) description() string {
|
|
return formatText(r.element.Description)
|
|
}
|
|
|
|
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 {
|
|
return decodeDim(r.element.Dim)
|
|
}
|
|
|
|
func decodeDim(s *string) int {
|
|
if s == nil {
|
|
return -1 // no dim elements
|
|
}
|
|
dim, err := strconv.ParseInt(*s, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return int(dim)
|
|
}
|
|
|
|
type dimArray struct {
|
|
dim int
|
|
idx []string
|
|
incr uint32
|
|
}
|
|
|
|
func decodeDimArray(dimSpec, dimIndex *string, dimIncr, elType, elName string) *dimArray {
|
|
dim := decodeDim(dimSpec)
|
|
if dim <= 0 {
|
|
return nil
|
|
}
|
|
a := new(dimArray)
|
|
a.dim = dim
|
|
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
fmt.Println(elType, elName)
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
incr, err := strconv.ParseUint(dimIncr, 0, 32)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
a.incr = uint32(incr)
|
|
|
|
if dimIndex == nil {
|
|
idx := make([]string, dim)
|
|
for i := range idx {
|
|
idx[i] = strconv.FormatInt(int64(i), 10)
|
|
}
|
|
a.idx = idx
|
|
return a
|
|
}
|
|
|
|
t := strings.Split(*dimIndex, "-")
|
|
if len(t) == 2 {
|
|
// renesas uses hex letters e.g. A-B
|
|
if strings.Contains("ABCDEFabcdef", t[0]) {
|
|
t[0] = "0x" + t[0]
|
|
}
|
|
if strings.Contains("ABCDEFabcdef", t[1]) {
|
|
t[1] = "0x" + t[1]
|
|
}
|
|
|
|
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)
|
|
}
|
|
a.idx = idx
|
|
return a
|
|
} else if len(t) > 2 {
|
|
panic("invalid dimIndex")
|
|
}
|
|
|
|
s := strings.Split(*dimIndex, ",")
|
|
if len(s) != dim {
|
|
panic("invalid dimIndex")
|
|
}
|
|
a.idx = s
|
|
return a
|
|
}
|
|
|
|
func (da *dimArray) replace(s string, i int) string {
|
|
if da == nil {
|
|
return s
|
|
}
|
|
if i >= len(da.idx) {
|
|
return s
|
|
}
|
|
return strings.ReplaceAll(s, "%s", da.idx[i])
|
|
}
|
|
|
|
func (da *dimArray) rangeElems(yield func(i int, incr uint32) bool) {
|
|
if da == nil {
|
|
yield(0, 0)
|
|
return
|
|
}
|
|
for i := range da.dim {
|
|
if !yield(i, uint32(i)*da.incr) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
name := reg.name()
|
|
da := decodeDimArray(regEl.Dim, regEl.DimIndex, regEl.DimIncrement, "register", name)
|
|
if da != nil && strings.Contains(name, "%s") {
|
|
// a "spaced array" of registers, special processing required
|
|
// we need to generate a separate register for each "element"
|
|
var results []*PeripheralField
|
|
shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(name, "_%s", ""), "%s", ""))
|
|
for i := range da.idx {
|
|
regAddress := reg.address() + (uint64(i) * uint64(da.incr))
|
|
results = append(results, &PeripheralField{
|
|
Name: strings.ToUpper(da.replace(name, i)),
|
|
Address: regAddress,
|
|
Description: reg.description(),
|
|
Array: -1,
|
|
ElementSize: reg.size(),
|
|
ShortName: shortName,
|
|
})
|
|
}
|
|
// set first result bitfield
|
|
results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix)
|
|
results[0].HasBitfields = len(results[0].Bitfields) > 0
|
|
for i := 1; i < len(results); i++ {
|
|
results[i].Bitfields = results[0].Bitfields
|
|
results[i].HasBitfields = results[0].HasBitfields
|
|
}
|
|
return results
|
|
}
|
|
regName := reg.name()
|
|
if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) {
|
|
regName = strings.ToUpper(regName)
|
|
}
|
|
regName = cleanName(regName)
|
|
|
|
constants, bitfields := parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix)
|
|
return []*PeripheralField{&PeripheralField{
|
|
Name: regName,
|
|
Address: reg.address(),
|
|
Description: reg.description(),
|
|
Constants: constants,
|
|
Array: reg.dim(),
|
|
ElementSize: reg.size(),
|
|
ShortName: regName,
|
|
Bitfields: bitfields,
|
|
HasBitfields: len(bitfields) > 0,
|
|
}}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
interruptHandlerMap := make(map[string]*Interrupt)
|
|
var interruptHandlers []*Interrupt
|
|
for _, intr := range device.Interrupts {
|
|
if _, ok := interruptHandlerMap[intr.HandlerName]; !ok {
|
|
interruptHandlerMap[intr.HandlerName] = intr
|
|
interruptHandlers = append(interruptHandlers, intr)
|
|
}
|
|
}
|
|
|
|
t := template.Must(template.New("go").Funcs(template.FuncMap{
|
|
"bytesNeeded": func(i, j uint64) uint64 { return j - i },
|
|
"isMultiline": isMultiline,
|
|
"splitLine": splitLine,
|
|
}).Parse(`// Automatically generated file. DO NOT EDIT.
|
|
// Generated by gen-device-svd.go from {{.device.Metadata.File}}, see {{.device.Metadata.DescriptorSource}}
|
|
|
|
//go:build {{.pkgName}} && {{.device.Metadata.NameLower}}
|
|
|
|
/*
|
|
{{.device.Metadata.Description}}
|
|
*/
|
|
{{.device.Metadata.LicenseBlock}}
|
|
package {{.pkgName}}
|
|
|
|
import (
|
|
"runtime/volatile"
|
|
"unsafe"
|
|
)
|
|
|
|
// Some information about this device.
|
|
const (
|
|
Device = "{{.device.Metadata.Name}}"
|
|
{{- if .device.Metadata.HasCPUInfo }}
|
|
CPU = "{{.device.Metadata.CPUName}}"
|
|
FPUPresent = {{.device.Metadata.FPUPresent}}
|
|
NVICPrioBits = {{.device.Metadata.NVICPrioBits}}
|
|
{{- end }}
|
|
)
|
|
|
|
// Interrupt numbers.
|
|
const (
|
|
{{- range .device.Interrupts}}
|
|
{{- if .Description}}
|
|
{{- range .Description|splitLine}}
|
|
// {{.}}
|
|
{{- end}}
|
|
{{- end}}
|
|
IRQ_{{.Name}} = {{.Value}}
|
|
{{- "\n"}}
|
|
{{- end}}
|
|
// Highest interrupt number on this device.
|
|
IRQ_max = {{.interruptMax}}
|
|
)
|
|
|
|
// Pseudo function call that is replaced by the compiler with the actual
|
|
// functions registered through interrupt.New.
|
|
//go:linkname callHandlers runtime/interrupt.callHandlers
|
|
func callHandlers(num int)
|
|
|
|
{{- if eq .interruptSystem "hardware"}}
|
|
{{- range .interruptHandlers}}
|
|
//export {{.HandlerName}}
|
|
func interrupt{{.Name}}() {
|
|
callHandlers(IRQ_{{.Name}})
|
|
}
|
|
{{- end}}
|
|
{{- end}}
|
|
|
|
{{- if eq .interruptSystem "software"}}
|
|
func HandleInterrupt(num int) {
|
|
switch num {
|
|
{{- range .interruptHandlers}}
|
|
case IRQ_{{.Name}}:
|
|
callHandlers(IRQ_{{.Name}})
|
|
{{- end}}
|
|
}
|
|
}
|
|
{{- end}}
|
|
|
|
// Peripherals.
|
|
var (
|
|
{{- range .device.Peripherals}}
|
|
{{- if .Description}}
|
|
{{- range .Description|splitLine}}
|
|
// {{.}}
|
|
{{- end}}
|
|
{{- end}}
|
|
{{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}})))
|
|
{{- if .Alias}}
|
|
{{.Alias}} = {{.Name}}
|
|
{{- end}}
|
|
{{- "\n"}}
|
|
{{- end}}
|
|
)
|
|
|
|
`))
|
|
pkgName := filepath.Base(strings.TrimRight(outdir, "/"))
|
|
tweakDevice(device, pkgName)
|
|
|
|
err = t.Execute(w, map[string]interface{}{
|
|
"device": device,
|
|
"pkgName": pkgName,
|
|
"interruptMax": maxInterruptValue,
|
|
"interruptSystem": interruptSystem,
|
|
"interruptHandlers": interruptHandlers,
|
|
})
|
|
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.Fprintln(w)
|
|
if peripheral.Description != "" {
|
|
for _, l := range splitLine(peripheral.Description) {
|
|
fmt.Fprintf(w, "// %s\n", l)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "type %s_Type struct {\n", peripheral.GroupName)
|
|
|
|
address := peripheral.BaseAddress
|
|
type clusterInfo struct {
|
|
name string
|
|
description string
|
|
address uint64
|
|
size uint64
|
|
registers []*PeripheralField
|
|
}
|
|
clusters := []clusterInfo{}
|
|
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)
|
|
// remove bit fields from such register
|
|
register.Bitfields = nil
|
|
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 = peripheral.GroupName + "_" + register.Name
|
|
clusters = append(clusters, clusterInfo{regType, register.Description, register.Address, uint64(register.ElementSize), register.Registers})
|
|
regType = regType + "_Type"
|
|
subaddress := register.Address
|
|
for _, subregister := range register.Registers {
|
|
|
|
if subaddress != subregister.Address {
|
|
bytesNeeded := subregister.Address - subaddress
|
|
subaddress += bytesNeeded
|
|
}
|
|
var subregSize uint64
|
|
if subregister.Array != -1 {
|
|
subregSize = uint64(subregister.Array * subregister.ElementSize)
|
|
} else {
|
|
subregSize = uint64(subregister.ElementSize)
|
|
}
|
|
subaddress += subregSize
|
|
}
|
|
if register.Array == -1 {
|
|
lastCluster = true
|
|
}
|
|
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")
|
|
|
|
for _, register := range peripheral.Registers {
|
|
regName := register.Name
|
|
writeGoRegisterBitfieldType(w, register, peripheral.GroupName, regName)
|
|
}
|
|
|
|
// Define clusters
|
|
for i := 0; i < len(clusters); i++ {
|
|
cluster := clusters[i]
|
|
if len(cluster.registers) == 0 {
|
|
continue
|
|
}
|
|
|
|
if _, ok := device.PeripheralDict[cluster.name]; ok {
|
|
continue
|
|
}
|
|
|
|
fmt.Fprintln(w)
|
|
if cluster.description != "" {
|
|
for _, l := range splitLine(cluster.description) {
|
|
fmt.Fprintf(w, "// %s\n", l)
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "type %s_Type struct {\n", cluster.name)
|
|
|
|
address := cluster.address
|
|
|
|
for _, register := range cluster.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 = peripheral.GroupName + "_" + register.Name
|
|
clusters = append(clusters, clusterInfo{regType, register.Description, register.Address, uint64(register.ElementSize), register.Registers})
|
|
regType = regType + "_Type"
|
|
|
|
subaddress := register.Address
|
|
for _, subregister := range register.Registers {
|
|
if subaddress != subregister.Address {
|
|
bytesNeeded := subregister.Address - subaddress
|
|
subaddress += bytesNeeded
|
|
}
|
|
var subregSize uint64
|
|
if subregister.Array != -1 {
|
|
subregSize = uint64(subregister.Array * subregister.ElementSize)
|
|
} else {
|
|
subregSize = uint64(subregister.ElementSize)
|
|
}
|
|
subaddress += subregSize
|
|
}
|
|
if register.Array == -1 {
|
|
lastCluster = true
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
// make sure the structure is full
|
|
if cluster.size > (address - cluster.registers[0].Address) {
|
|
bytesNeeded := cluster.size - (address - cluster.registers[0].Address)
|
|
if bytesNeeded == 1 {
|
|
w.WriteString("\t_ byte\n")
|
|
} else {
|
|
fmt.Fprintf(w, "\t_ [%d]byte\n", bytesNeeded)
|
|
}
|
|
} else if cluster.size != (address - cluster.registers[0].Address) {
|
|
println("peripheral:", peripheral.Name, "cluster:", cluster.name, "size:", cluster.size, "struct size:", (address - cluster.registers[0].Address))
|
|
}
|
|
w.WriteString("}\n")
|
|
|
|
for _, register := range cluster.registers {
|
|
regName := register.Name
|
|
if register.Array == -1 {
|
|
writeGoRegisterBitfieldType(w, register, cluster.name, regName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Define bitfields.
|
|
for _, peripheral := range device.Peripherals {
|
|
if peripheral.Registers == nil {
|
|
// This peripheral was derived from another peripheral. Constants are
|
|
// already defined.
|
|
continue
|
|
}
|
|
fmt.Fprintf(w, "\n// Constants for %s", peripheral.Name)
|
|
if isMultiline(peripheral.Description) {
|
|
for _, l := range splitLine(peripheral.Description) {
|
|
fmt.Fprintf(w, "\n// %s", l)
|
|
}
|
|
} else if peripheral.Description != "" {
|
|
fmt.Fprintf(w, ": %s", peripheral.Description)
|
|
}
|
|
|
|
fmt.Fprint(w, "\nconst(")
|
|
for _, register := range peripheral.Registers {
|
|
if len(register.Constants) != 0 {
|
|
writeGoRegisterConstants(w, register, register.Name)
|
|
}
|
|
if register.Registers == nil {
|
|
continue
|
|
}
|
|
for _, subregister := range register.Registers {
|
|
writeGoRegisterConstants(w, subregister, register.Name+"."+subregister.Name)
|
|
}
|
|
}
|
|
w.WriteString(")\n")
|
|
}
|
|
|
|
return w.Flush()
|
|
}
|
|
|
|
func writeGoRegisterConstants(w *bufio.Writer, register *PeripheralField, name string) {
|
|
w.WriteString("\n\t// " + name)
|
|
if register.Description != "" {
|
|
if isMultiline(register.Description) {
|
|
for _, l := range splitLine(register.Description) {
|
|
w.WriteString("\n\t// " + l)
|
|
}
|
|
} else {
|
|
w.WriteString(": " + register.Description)
|
|
}
|
|
}
|
|
w.WriteByte('\n')
|
|
for _, bitfield := range register.Constants {
|
|
if bitfield.Description != "" {
|
|
for _, l := range splitLine(bitfield.Description) {
|
|
w.WriteString("\t// " + l + "\n")
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "\t%s = 0x%x\n", bitfield.Name, bitfield.Value)
|
|
}
|
|
}
|
|
|
|
func writeGoRegisterBitfieldType(w *bufio.Writer, register *PeripheralField, peripheralName, registerName string) {
|
|
if len(register.Bitfields) == 0 {
|
|
return
|
|
}
|
|
w.WriteString("\n// " + peripheralName + "." + registerName)
|
|
if register.Description != "" {
|
|
if isMultiline(register.Description) {
|
|
for _, l := range splitLine(register.Description) {
|
|
w.WriteString("\n\t// " + l)
|
|
}
|
|
} else {
|
|
w.WriteString(": " + register.Description)
|
|
}
|
|
}
|
|
w.WriteByte('\n')
|
|
var bitSize int
|
|
var maxMask uint32
|
|
switch register.ElementSize {
|
|
case 8:
|
|
bitSize = 64
|
|
maxMask = 0xffffffff
|
|
// maxMask = 0xffffffffffffffff // TODO how to handle 64-bit fields
|
|
case 4:
|
|
bitSize = 32
|
|
maxMask = 0xffffffff
|
|
case 2:
|
|
bitSize = 16
|
|
maxMask = 0xffff
|
|
case 1:
|
|
bitSize = 8
|
|
maxMask = 0xff
|
|
default:
|
|
bitSize = 32
|
|
maxMask = 0xffffffff
|
|
}
|
|
|
|
typeName := fmt.Sprintf("%s_Type", peripheralName)
|
|
|
|
for _, bitfield := range register.Bitfields {
|
|
idxArg := ""
|
|
regAccess := "&o." + registerName + ".Reg"
|
|
if register.Array != -1 {
|
|
idxArg = "idx int, "
|
|
regAccess = "&o." + registerName + "[idx].Reg"
|
|
}
|
|
var funcSuffix string
|
|
if maxMask == bitfield.Mask || registerName == bitfield.Name {
|
|
funcSuffix = registerName
|
|
} else {
|
|
funcSuffix = registerName + "_" + bitfield.Name
|
|
}
|
|
fmt.Fprintf(w, "func (o *%s) Set%s(%s value uint%d) {\n", typeName, funcSuffix, idxArg, bitSize)
|
|
if maxMask == bitfield.Mask {
|
|
fmt.Fprintf(w, "\tvolatile.StoreUint%d(%s, value)\n", bitSize, regAccess)
|
|
} else if bitfield.Offset > 0 {
|
|
fmt.Fprintf(w, "\tvolatile.StoreUint%d(%s, volatile.LoadUint%d(%s)&^(0x%x)|value<<%d)\n", bitSize, regAccess, bitSize, regAccess, bitfield.Mask, bitfield.Offset)
|
|
} else {
|
|
fmt.Fprintf(w, "\tvolatile.StoreUint%d(%s, volatile.LoadUint%d(%s)&^(0x%x)|value)\n", bitSize, regAccess, bitSize, regAccess, bitfield.Mask)
|
|
}
|
|
w.WriteString("}\n")
|
|
fmt.Fprintf(w, "func (o *%s) Get%s(%s) uint%d {\n", typeName, funcSuffix, idxArg, bitSize)
|
|
if maxMask == bitfield.Mask {
|
|
fmt.Fprintf(w, "\treturn volatile.LoadUint%d(%s)\n", bitSize, regAccess)
|
|
} else if bitfield.Offset > 0 {
|
|
fmt.Fprintf(w, "\treturn (volatile.LoadUint%d(%s)&0x%x) >> %d\n", bitSize, regAccess, bitfield.Mask, bitfield.Offset)
|
|
} else {
|
|
fmt.Fprintf(w, "\treturn volatile.LoadUint%d(%s)&0x%x\n", bitSize, regAccess, bitfield.Mask)
|
|
}
|
|
w.WriteString("}\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
|
|
__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)
|
|
}
|
|
w.WriteString(`
|
|
.size __isr_vector, .-__isr_vector
|
|
`)
|
|
return w.Flush()
|
|
}
|
|
|
|
func generate(indir, outdir, sourceURL, interruptSystem string) error {
|
|
if _, err := os.Stat(indir); errors.Is(err, fs.ErrNotExist) {
|
|
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)
|
|
}
|
|
}
|