mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-12 06:53:40 +00:00
WIP: interrupt API
This commit is contained in:
@@ -217,7 +217,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
path = path[len(tinygoPath+"/src/"):]
|
path = path[len(tinygoPath+"/src/"):]
|
||||||
}
|
}
|
||||||
switch path {
|
switch path {
|
||||||
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing", "internal/reflectlite":
|
case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite":
|
||||||
return path
|
return path
|
||||||
default:
|
default:
|
||||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||||
@@ -828,9 +828,6 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
||||||
frame.fn.LLVMFn.SetUnnamedAddr(true)
|
frame.fn.LLVMFn.SetUnnamedAddr(true)
|
||||||
}
|
}
|
||||||
if frame.fn.IsInterrupt() && strings.HasPrefix(c.Triple(), "avr") {
|
|
||||||
frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some functions have a pragma controlling the inlining level.
|
// Some functions have a pragma controlling the inlining level.
|
||||||
switch frame.fn.Inline() {
|
switch frame.fn.Inline() {
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
|
|||||||
transform.OptimizeStringToBytes(c.mod)
|
transform.OptimizeStringToBytes(c.mod)
|
||||||
transform.OptimizeAllocs(c.mod)
|
transform.OptimizeAllocs(c.mod)
|
||||||
transform.LowerInterfaces(c.mod)
|
transform.LowerInterfaces(c.mod)
|
||||||
|
|
||||||
|
errs := transform.LowerInterruptRegistrations(c.mod)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
if c.funcImplementation() == funcValueSwitch {
|
if c.funcImplementation() == funcValueSwitch {
|
||||||
transform.LowerFuncValues(c.mod)
|
transform.LowerFuncValues(c.mod)
|
||||||
}
|
}
|
||||||
@@ -100,6 +106,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
|
errs := transform.LowerInterruptRegistrations(c.mod)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if c.VerifyIR() {
|
if c.VerifyIR() {
|
||||||
if errs := c.checkModule(); errs != nil {
|
if errs := c.checkModule(); errs != nil {
|
||||||
|
|||||||
@@ -27,14 +27,13 @@ type Program struct {
|
|||||||
// Function or method.
|
// Function or method.
|
||||||
type Function struct {
|
type Function struct {
|
||||||
*ssa.Function
|
*ssa.Function
|
||||||
LLVMFn llvm.Value
|
LLVMFn llvm.Value
|
||||||
module string // go:wasm-module
|
module string // go:wasm-module
|
||||||
linkName string // go:linkname, go:export, go:interrupt
|
linkName string // go:linkname, go:export
|
||||||
exported bool // go:export
|
exported bool // go:export
|
||||||
nobounds bool // go:nobounds
|
nobounds bool // go:nobounds
|
||||||
flag bool // used by dead code elimination
|
flag bool // used by dead code elimination
|
||||||
interrupt bool // go:interrupt
|
inline InlineType // go:inline
|
||||||
inline InlineType // go:inline
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface type that is at some point used in a type assert (to check whether
|
// Interface type that is at some point used in a type assert (to check whether
|
||||||
@@ -243,18 +242,6 @@ func (f *Function) parsePragmas() {
|
|||||||
f.inline = InlineHint
|
f.inline = InlineHint
|
||||||
case "//go:noinline":
|
case "//go:noinline":
|
||||||
f.inline = InlineNone
|
f.inline = InlineNone
|
||||||
case "//go:interrupt":
|
|
||||||
if len(parts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := parts[1]
|
|
||||||
if strings.HasSuffix(name, "_vect") {
|
|
||||||
// AVR vector naming
|
|
||||||
name = "__vector_" + name[:len(name)-5]
|
|
||||||
}
|
|
||||||
f.linkName = name
|
|
||||||
f.exported = true
|
|
||||||
f.interrupt = true
|
|
||||||
case "//go:linkname":
|
case "//go:linkname":
|
||||||
if len(parts) != 3 || parts[1] != f.Name() {
|
if len(parts) != 3 || parts[1] != f.Name() {
|
||||||
continue
|
continue
|
||||||
@@ -288,14 +275,6 @@ func (f *Function) IsExported() bool {
|
|||||||
return f.exported || f.CName() != ""
|
return f.exported || f.CName() != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true for functions annotated with //go:interrupt. The function name is
|
|
||||||
// already customized in LinkName() to hook up in the interrupt vector.
|
|
||||||
//
|
|
||||||
// On some platforms (like AVR), interrupts need a special compiler flag.
|
|
||||||
func (f *Function) IsInterrupt() bool {
|
|
||||||
return f.interrupt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the inline directive of this function.
|
// Return the inline directive of this function.
|
||||||
func (f *Function) Inline() InlineType {
|
func (f *Function) Inline() InlineType {
|
||||||
return f.inline
|
return f.inline
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package machine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"device/avr"
|
"device/avr"
|
||||||
|
"runtime/interrupt"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -232,6 +233,18 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
config.BaudRate = 9600
|
config.BaudRate = 9600
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register the UART interrupt.
|
||||||
|
interrupt.New(avr.IRQ_USART_RX, func(intr interrupt.Interrupt) {
|
||||||
|
// Read register to clear it.
|
||||||
|
data := avr.UDR0.Get()
|
||||||
|
|
||||||
|
// Ensure no error.
|
||||||
|
if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) {
|
||||||
|
// Put data from UDR register into buffer.
|
||||||
|
UART0.Receive(byte(data))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Set baud rate based on prescale formula from
|
// Set baud rate based on prescale formula from
|
||||||
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
// https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html
|
||||||
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
// ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
|
||||||
@@ -254,15 +267,3 @@ func (uart UART) WriteByte(c byte) error {
|
|||||||
avr.UDR0.Set(c) // send char
|
avr.UDR0.Set(c) // send char
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:interrupt USART_RX_vect
|
|
||||||
func handleUSART_RX() {
|
|
||||||
// Read register to clear it.
|
|
||||||
data := avr.UDR0.Get()
|
|
||||||
|
|
||||||
// Ensure no error.
|
|
||||||
if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) {
|
|
||||||
// Put data from UDR register into buffer.
|
|
||||||
UART0.Receive(byte(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"device/arm"
|
"device/arm"
|
||||||
"device/sam"
|
"device/sam"
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime/interrupt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1368,7 +1369,8 @@ func (usbcdc USBCDC) Configure(config UARTConfig) {
|
|||||||
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
|
sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE)
|
||||||
|
|
||||||
// enable IRQ
|
// enable IRQ
|
||||||
arm.EnableIRQ(sam.IRQ_USB)
|
intr := interrupt.New(sam.IRQ_USB, handleUSB)
|
||||||
|
intr.Enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePadCalibration() {
|
func handlePadCalibration() {
|
||||||
@@ -1414,8 +1416,7 @@ func handlePadCalibration() {
|
|||||||
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
|
sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:export USB_IRQHandler
|
func handleUSB(intr interrupt.Interrupt) {
|
||||||
func handleUSB() {
|
|
||||||
// reset all interrupt flags
|
// reset all interrupt flags
|
||||||
flags := sam.USB_DEVICE.INTFLAG.Get()
|
flags := sam.USB_DEVICE.INTFLAG.Get()
|
||||||
sam.USB_DEVICE.INTFLAG.Set(flags)
|
sam.USB_DEVICE.INTFLAG.Set(flags)
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
package machine
|
package machine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device/arm"
|
|
||||||
"device/nrf"
|
"device/nrf"
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime/interrupt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -88,8 +88,11 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk)
|
nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk)
|
||||||
|
|
||||||
// Enable RX IRQ.
|
// Enable RX IRQ.
|
||||||
arm.SetPriority(nrf.IRQ_UART0, 0xc0) // low priority
|
intr := interrupt.New(nrf.IRQ_UART0, func(intr interrupt.Interrupt) {
|
||||||
arm.EnableIRQ(nrf.IRQ_UART0)
|
UART0.handleInterrupt()
|
||||||
|
})
|
||||||
|
intr.SetPriority(0xc0) // low priority
|
||||||
|
intr.Enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBaudRate sets the communication speed for the UART.
|
// SetBaudRate sets the communication speed for the UART.
|
||||||
|
|||||||
@@ -20,11 +20,6 @@ func (uart UART) setPins(tx, rx Pin) {
|
|||||||
nrf.UART0.PSELRXD.Set(uint32(rx))
|
nrf.UART0.PSELRXD.Set(uint32(rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:export UART0_IRQHandler
|
|
||||||
func handleUART0() {
|
|
||||||
UART0.handleInterrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i2c I2C) setPins(scl, sda Pin) {
|
func (i2c I2C) setPins(scl, sda Pin) {
|
||||||
i2c.Bus.PSELSCL.Set(uint32(scl))
|
i2c.Bus.PSELSCL.Set(uint32(scl))
|
||||||
i2c.Bus.PSELSDA.Set(uint32(sda))
|
i2c.Bus.PSELSDA.Set(uint32(sda))
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ func (uart UART) setPins(tx, rx Pin) {
|
|||||||
nrf.UART0.PSELRXD.Set(uint32(rx))
|
nrf.UART0.PSELRXD.Set(uint32(rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:export UARTE0_UART0_IRQHandler
|
|
||||||
func handleUART0() {
|
|
||||||
UART0.handleInterrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i2c I2C) setPins(scl, sda Pin) {
|
func (i2c I2C) setPins(scl, sda Pin) {
|
||||||
i2c.Bus.PSELSCL.Set(uint32(scl))
|
i2c.Bus.PSELSCL.Set(uint32(scl))
|
||||||
i2c.Bus.PSELSDA.Set(uint32(sda))
|
i2c.Bus.PSELSDA.Set(uint32(sda))
|
||||||
|
|||||||
@@ -25,11 +25,6 @@ func (uart UART) setPins(tx, rx Pin) {
|
|||||||
nrf.UART0.PSEL.RXD.Set(uint32(rx))
|
nrf.UART0.PSEL.RXD.Set(uint32(rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:export UARTE0_UART0_IRQHandler
|
|
||||||
func handleUART0() {
|
|
||||||
UART0.handleInterrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i2c I2C) setPins(scl, sda Pin) {
|
func (i2c I2C) setPins(scl, sda Pin) {
|
||||||
i2c.Bus.PSEL.SCL.Set(uint32(scl))
|
i2c.Bus.PSEL.SCL.Set(uint32(scl))
|
||||||
i2c.Bus.PSEL.SDA.Set(uint32(sda))
|
i2c.Bus.PSEL.SDA.Set(uint32(sda))
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Package interrupt provides access to hardware interrupts. It provides a way
|
||||||
|
// to define interrupts and to enable/disable them.
|
||||||
|
package interrupt
|
||||||
|
|
||||||
|
// Interrupt provides direct access to hardware interrupts. You can configure
|
||||||
|
// this interrupt through this interface.
|
||||||
|
//
|
||||||
|
// Do not use the zero value of an Interrupt object. Instead, call New to obtain
|
||||||
|
// an interrupt handle.
|
||||||
|
type Interrupt struct {
|
||||||
|
// Make this number unexported so it cannot be set directly. This provides
|
||||||
|
// some encapsulation.
|
||||||
|
num int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New is a compiler intrinsic that creates a new Interrupt object. You may call
|
||||||
|
// it only once, and must pass constant parameters to it. That means that the
|
||||||
|
// interrupt ID must be a Go constant and that the handler must be a simple
|
||||||
|
// function: closures are not supported.
|
||||||
|
func New(id int, handler func(Interrupt)) Interrupt
|
||||||
|
|
||||||
|
// Register is used to declare an interrupt. You should not normally call this
|
||||||
|
// function: it is only for telling the compiler about the mapping between an
|
||||||
|
// interrupt number and the interrupt handler name.
|
||||||
|
func Register(id int, handlerName string) int
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// +build cortexm
|
||||||
|
|
||||||
|
package interrupt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device/arm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enable enables this interrupt. Right after calling this function, the
|
||||||
|
// interrupt may be invoked if it was already pending.
|
||||||
|
func (irq Interrupt) Enable() {
|
||||||
|
arm.EnableIRQ(uint32(irq.num))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPriority sets the interrupt priority for this interrupt. A lower number
|
||||||
|
// means a higher priority. Additionally, most hardware doesn't implement all
|
||||||
|
// priority bits (only the uppoer bits).
|
||||||
|
//
|
||||||
|
// Examples: 0xff (lowest priority), 0xc0 (low priority), 0x00 (highest possible
|
||||||
|
// priority).
|
||||||
|
func (irq Interrupt) SetPriority(priority uint8) {
|
||||||
|
arm.SetPriority(uint32(irq.num), uint32(priority))
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"device/arm"
|
"device/arm"
|
||||||
"device/sam"
|
"device/sam"
|
||||||
"machine"
|
"machine"
|
||||||
|
"runtime/interrupt"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@@ -214,8 +215,14 @@ func initRTC() {
|
|||||||
sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE)
|
sam.RTC_MODE0.CTRL.SetBits(sam.RTC_MODE0_CTRL_ENABLE)
|
||||||
waitForSync()
|
waitForSync()
|
||||||
|
|
||||||
arm.SetPriority(sam.IRQ_RTC, 0xc0)
|
intr := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) {
|
||||||
arm.EnableIRQ(sam.IRQ_RTC)
|
// disable IRQ for CMP0 compare
|
||||||
|
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
|
||||||
|
|
||||||
|
timerWakeup.Set(1)
|
||||||
|
})
|
||||||
|
intr.SetPriority(0xc0)
|
||||||
|
intr.Enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForSync() {
|
func waitForSync() {
|
||||||
@@ -286,14 +293,6 @@ func timerSleep(ticks uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:export RTC_IRQHandler
|
|
||||||
func handleRTC() {
|
|
||||||
// disable IRQ for CMP0 compare
|
|
||||||
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
|
|
||||||
|
|
||||||
timerWakeup.Set(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initUSBClock() {
|
func initUSBClock() {
|
||||||
// Turn on clock for USB
|
// Turn on clock for USB
|
||||||
sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_)
|
sam.PM.APBBMASK.SetBits(sam.PM_APBBMASK_USB_)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"device/arm"
|
"device/arm"
|
||||||
"device/nrf"
|
"device/nrf"
|
||||||
"machine"
|
"machine"
|
||||||
|
"runtime/interrupt"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,8 +44,13 @@ func initLFCLK() {
|
|||||||
|
|
||||||
func initRTC() {
|
func initRTC() {
|
||||||
nrf.RTC1.TASKS_START.Set(1)
|
nrf.RTC1.TASKS_START.Set(1)
|
||||||
arm.SetPriority(nrf.IRQ_RTC1, 0xc0) // low priority
|
intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
|
||||||
arm.EnableIRQ(nrf.IRQ_RTC1)
|
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
|
||||||
|
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
|
||||||
|
rtc_wakeup.Set(1)
|
||||||
|
})
|
||||||
|
intr.SetPriority(0xc0) // low priority
|
||||||
|
intr.Enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func putchar(c byte) {
|
func putchar(c byte) {
|
||||||
@@ -96,10 +102,3 @@ func rtc_sleep(ticks uint32) {
|
|||||||
arm.Asm("wfi")
|
arm.Asm("wfi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:export RTC1_IRQHandler
|
|
||||||
func handleRTC1() {
|
|
||||||
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
|
|
||||||
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
|
|
||||||
rtc_wakeup.Set(1)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ func writeGo(outdir string, device *Device) error {
|
|||||||
package {{.pkgName}}
|
package {{.pkgName}}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime/interrupt"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@@ -277,6 +278,12 @@ const ({{range .interrupts}}
|
|||||||
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
|
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Map interrupt numbers to function names.
|
||||||
|
// These aren't real calls, they're removed by the compiler.
|
||||||
|
var ({{range .interrupts}}
|
||||||
|
_ = interrupt.Register(IRQ_{{.Name}}, "__vector_{{.Name}}"){{end}}
|
||||||
|
)
|
||||||
|
|
||||||
// Peripherals.
|
// Peripherals.
|
||||||
var ({{range .peripherals}}
|
var ({{range .peripherals}}
|
||||||
// {{.Caption}}
|
// {{.Caption}}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ type Device struct {
|
|||||||
|
|
||||||
type interrupt struct {
|
type interrupt struct {
|
||||||
Name string
|
Name string
|
||||||
|
HandlerName string
|
||||||
peripheralIndex int
|
peripheralIndex int
|
||||||
Value int // interrupt number
|
Value int // interrupt number
|
||||||
Description string
|
Description string
|
||||||
@@ -171,12 +172,12 @@ func readSVD(path, sourceURL string) (*Device, error) {
|
|||||||
groupName := cleanName(periphEl.GroupName)
|
groupName := cleanName(periphEl.GroupName)
|
||||||
|
|
||||||
for _, interrupt := range periphEl.Interrupts {
|
for _, interrupt := range periphEl.Interrupts {
|
||||||
addInterrupt(interrupts, interrupt.Name, interrupt.Index, description)
|
addInterrupt(interrupts, interrupt.Name, interrupt.Name, interrupt.Index, description)
|
||||||
// As a convenience, also use the peripheral name as the interrupt
|
// 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
|
// name. Only do that for the nrf for now, as the stm32 .svd files
|
||||||
// don't always put interrupts in the correct peripheral...
|
// don't always put interrupts in the correct peripheral...
|
||||||
if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") {
|
if len(periphEl.Interrupts) == 1 && strings.HasPrefix(device.Name, "nrf") {
|
||||||
addInterrupt(interrupts, periphEl.Name, interrupt.Index, description)
|
addInterrupt(interrupts, periphEl.Name, interrupt.Name, interrupt.Index, description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +389,7 @@ func readSVD(path, sourceURL string) (*Device, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInterrupt(interrupts map[string]*interrupt, name string, index int, description string) {
|
func addInterrupt(interrupts map[string]*interrupt, name, interruptName string, index int, description string) {
|
||||||
if _, ok := interrupts[name]; ok {
|
if _, ok := interrupts[name]; ok {
|
||||||
if interrupts[name].Value != index {
|
if interrupts[name].Value != index {
|
||||||
// Note: some SVD files like the one for STM32H7x7 contain mistakes.
|
// Note: some SVD files like the one for STM32H7x7 contain mistakes.
|
||||||
@@ -409,6 +410,7 @@ func addInterrupt(interrupts map[string]*interrupt, name string, index int, desc
|
|||||||
} else {
|
} else {
|
||||||
interrupts[name] = &interrupt{
|
interrupts[name] = &interrupt{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
HandlerName: interruptName + "_IRQHandler",
|
||||||
peripheralIndex: len(interrupts),
|
peripheralIndex: len(interrupts),
|
||||||
Value: index,
|
Value: index,
|
||||||
Description: description,
|
Description: description,
|
||||||
@@ -619,6 +621,7 @@ func writeGo(outdir string, device *Device) error {
|
|||||||
package {{.pkgName}}
|
package {{.pkgName}}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime/interrupt"
|
||||||
"runtime/volatile"
|
"runtime/volatile"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@@ -628,12 +631,18 @@ const (
|
|||||||
DEVICE = "{{.metadata.name}}"
|
DEVICE = "{{.metadata.name}}"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interrupt numbers
|
// Interrupt numbers.
|
||||||
const ({{range .interrupts}}
|
const ({{range .interrupts}}
|
||||||
IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}}
|
IRQ_{{.Name}} = {{.Value}} // {{.Description}}{{end}}
|
||||||
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
|
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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}}
|
||||||
|
)
|
||||||
|
|
||||||
// Peripherals.
|
// Peripherals.
|
||||||
var (
|
var (
|
||||||
{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}}
|
{{range .peripherals}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) // {{.Description}}
|
||||||
@@ -879,7 +888,7 @@ Default_Handler:
|
|||||||
num++
|
num++
|
||||||
}
|
}
|
||||||
num++
|
num++
|
||||||
fmt.Fprintf(w, " .long %s_IRQHandler\n", intr.Name)
|
fmt.Fprintf(w, " .long %s\n", intr.HandlerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteString(`
|
w.WriteString(`
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errorAt returns an error value at the location of the value.
|
||||||
|
// The location information may not be complete as it depends on debug
|
||||||
|
// information in the IR.
|
||||||
|
func errorAt(val llvm.Value, msg string) scanner.Error {
|
||||||
|
return scanner.Error{
|
||||||
|
Pos: getPosition(val),
|
||||||
|
Msg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPosition returns the position information for the given value, as far as
|
||||||
|
// it is available.
|
||||||
|
func getPosition(val llvm.Value) token.Position {
|
||||||
|
if !val.IsAInstruction().IsNil() {
|
||||||
|
loc := val.InstructionDebugLoc()
|
||||||
|
if loc.IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
file := loc.LocationScope().ScopeFile()
|
||||||
|
return token.Position{
|
||||||
|
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||||
|
Line: int(loc.LocationLine()),
|
||||||
|
Column: int(loc.LocationColumn()),
|
||||||
|
}
|
||||||
|
} else if !val.IsAFunction().IsNil() {
|
||||||
|
loc := val.Subprogram()
|
||||||
|
if loc.IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
file := loc.ScopeFile()
|
||||||
|
return token.Position{
|
||||||
|
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||||
|
Line: int(loc.SubprogramLine()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LowerInterruptRegistrations(mod llvm.Module) []error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Discover interrupts. The runtime/interrupt.Register call is a compiler
|
||||||
|
// intrinsic that maps interrupt numbers to handler names.
|
||||||
|
handlerNames := map[int64]string{}
|
||||||
|
for _, call := range getUses(mod.NamedFunction("runtime/interrupt.Register")) {
|
||||||
|
if call.IsACallInst().IsNil() {
|
||||||
|
errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.Register?"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
num := call.Operand(0)
|
||||||
|
if num.IsAConstant().IsNil() {
|
||||||
|
errs = append(errs, errorAt(call, "non-constant interrupt number?"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the interrupt name
|
||||||
|
nameStrGEP := call.Operand(1)
|
||||||
|
if nameStrGEP.IsAConstantExpr().IsNil() || nameStrGEP.Opcode() != llvm.GetElementPtr {
|
||||||
|
errs = append(errs, errorAt(call, "expected a string operand?"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nameStrPtr := nameStrGEP.Operand(0) // note: assuming it's a GEP to the first byte
|
||||||
|
nameStrLen := call.Operand(2)
|
||||||
|
if nameStrPtr.IsAGlobalValue().IsNil() || !nameStrPtr.IsGlobalConstant() || nameStrLen.IsAConstant().IsNil() {
|
||||||
|
errs = append(errs, errorAt(call, "non-constant interrupt name?"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep track of this name
|
||||||
|
name := string(getGlobalBytes(nameStrPtr)[:nameStrLen.SExtValue()])
|
||||||
|
handlerNames[num.SExtValue()] = name
|
||||||
|
|
||||||
|
// remove this pseudo-call
|
||||||
|
call.ReplaceAllUsesWith(llvm.ConstNull(call.Type()))
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
create := mod.NamedFunction("runtime/interrupt.New")
|
||||||
|
if create.IsNil() {
|
||||||
|
// No interrupt handlers to create.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := mod.Context()
|
||||||
|
nullptr := llvm.ConstNull(llvm.PointerType(ctx.Int8Type(), 0))
|
||||||
|
builder := ctx.NewBuilder()
|
||||||
|
defer builder.Dispose()
|
||||||
|
|
||||||
|
var dibuilder *llvm.DIBuilder
|
||||||
|
|
||||||
|
// Create a function type with the signature of an interrupt handler.
|
||||||
|
fnType := llvm.FunctionType(ctx.VoidType(), nil, false)
|
||||||
|
|
||||||
|
for _, call := range getUses(create) {
|
||||||
|
if call.IsACallInst().IsNil() {
|
||||||
|
errs = append(errs, errorAt(call, "expected a call to runtime/interrupt.New"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if call.OperandsCount() != 6 {
|
||||||
|
// 6 params:
|
||||||
|
// * 1 for the IRQ number
|
||||||
|
// * 2 for the function
|
||||||
|
// * 2 extra: context and parentHandle
|
||||||
|
// * one more for the called value?
|
||||||
|
errs = append(errs, errorAt(call, fmt.Sprintf("unexpected interrupt.New signature, expected 6 operands, got %d", call.OperandsCount())))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
num := call.Operand(0)
|
||||||
|
if num.IsAConstant().IsNil() {
|
||||||
|
errs = append(errs, errorAt(call, "non-constant interrupt number"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := handlerNames[num.SExtValue()]
|
||||||
|
if name == "" {
|
||||||
|
errs = append(errs, errorAt(call, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue())))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the func value.
|
||||||
|
handlerContext := call.Operand(1)
|
||||||
|
handlerFuncPtr := call.Operand(2)
|
||||||
|
if isFunctionLocal(handlerContext) || isFunctionLocal(handlerFuncPtr) {
|
||||||
|
errs = append(errs, errorAt(call, "func value must be constant"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !handlerFuncPtr.IsAConstantExpr().IsNil() && handlerFuncPtr.Opcode() == llvm.PtrToInt {
|
||||||
|
// This is a ptrtoint: the IR was created for func lowering using a
|
||||||
|
// switch statement.
|
||||||
|
global := handlerFuncPtr.Operand(0)
|
||||||
|
if global.IsAGlobalValue().IsNil() {
|
||||||
|
errs = append(errs, errorAt(call, "internal error: expected a global for func lowering"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
initializer := global.Initializer()
|
||||||
|
if initializer.Type() != mod.GetTypeByName("runtime.funcValueWithSignature") {
|
||||||
|
errs = append(errs, errorAt(call, "internal error: func lowering global has unexpected type"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ptrtoint := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||||
|
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||||
|
errs = append(errs, errorAt(call, "internal error: func lowering global has unexpected func ptr type"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handlerFuncPtr = ptrtoint.Operand(0)
|
||||||
|
}
|
||||||
|
if handlerFuncPtr.Type().TypeKind() != llvm.PointerTypeKind || handlerFuncPtr.Type().ElementType().TypeKind() != llvm.FunctionTypeKind {
|
||||||
|
errs = append(errs, errorAt(call, "internal error: unexpected LLVM types in func value"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for an existing handler, and report it as an error if there is
|
||||||
|
// one.
|
||||||
|
fn := mod.NamedFunction(name)
|
||||||
|
if fn.IsNil() {
|
||||||
|
fn = llvm.AddFunction(mod, name, fnType)
|
||||||
|
} else if fn.Type().ElementType() != fnType {
|
||||||
|
// Don't bother with a precise error message (listing the
|
||||||
|
// previsous location) because this should not normally happen
|
||||||
|
// anyway.
|
||||||
|
errs = append(errs, errorAt(call, name+" redeclared with a different signature"))
|
||||||
|
continue
|
||||||
|
} else if fn.IsDeclaration() {
|
||||||
|
// Interrupt handler was already defined. Check the first
|
||||||
|
// instruction (which should be a call) whether this handler would
|
||||||
|
// be identical anyway.
|
||||||
|
firstInst := fn.FirstBasicBlock().FirstInstruction()
|
||||||
|
if !firstInst.IsACallInst().IsNil() && firstInst.OperandsCount() == 4 && firstInst.Operand(0) == num && firstInst.Operand(1) != handlerContext {
|
||||||
|
// Already defined and apparently identical, so assume this is
|
||||||
|
// fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
errValue := name + " redeclared in this program"
|
||||||
|
fnPos := getPosition(fn)
|
||||||
|
if fnPos.IsValid() {
|
||||||
|
errValue += "\n\tprevious declaration at " + fnPos.String()
|
||||||
|
}
|
||||||
|
errs = append(errs, errorAt(call, errValue))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the wrapper function.
|
||||||
|
fn.SetUnnamedAddr(true)
|
||||||
|
entryBlock := ctx.AddBasicBlock(fn, "entry")
|
||||||
|
builder.SetInsertPointAtEnd(entryBlock)
|
||||||
|
|
||||||
|
// Set the 'interrupt' flag if needed on this platform.
|
||||||
|
if strings.HasPrefix(mod.Target(), "avr") {
|
||||||
|
fn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach a proper debug location to this function.
|
||||||
|
// Use the location of the call instruction, because as far as the user
|
||||||
|
// is concerned that is where the interrupt is defined. This is
|
||||||
|
// especially relevant for multiple definition errors, where you'd
|
||||||
|
// really want the previous interrupt.New call location to be used for
|
||||||
|
// easy debugging.
|
||||||
|
loc := call.InstructionDebugLoc()
|
||||||
|
if !loc.IsNil() {
|
||||||
|
if dibuilder == nil {
|
||||||
|
dibuilder = llvm.NewDIBuilder(mod)
|
||||||
|
defer func() {
|
||||||
|
dibuilder.Finalize()
|
||||||
|
dibuilder.Destroy()
|
||||||
|
}()
|
||||||
|
// Must create a new compile unit for some reason.
|
||||||
|
dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||||
|
File: "<interrupt-lowering>",
|
||||||
|
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
|
||||||
|
Producer: "TinyGo",
|
||||||
|
Optimized: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach debug info to the function.
|
||||||
|
file := loc.LocationScope().ScopeFile()
|
||||||
|
diFnType := dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||||
|
File: file,
|
||||||
|
})
|
||||||
|
difunc := dibuilder.CreateFunction(file, llvm.DIFunction{
|
||||||
|
Name: name,
|
||||||
|
LinkageName: name,
|
||||||
|
File: file,
|
||||||
|
Line: int(loc.LocationLine()),
|
||||||
|
Type: diFnType,
|
||||||
|
LocalToUnit: false,
|
||||||
|
IsDefinition: true,
|
||||||
|
ScopeLine: 0,
|
||||||
|
Flags: llvm.FlagPrototyped,
|
||||||
|
Optimized: true,
|
||||||
|
})
|
||||||
|
fn.SetSubprogram(difunc)
|
||||||
|
|
||||||
|
// Use the same location inside the thunk.
|
||||||
|
builder.SetCurrentDebugLocation(loc.LocationLine(), loc.LocationColumn(), difunc, llvm.Metadata{})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the function declaration with the forwarding call.
|
||||||
|
builder.CreateCall(handlerFuncPtr, []llvm.Value{num, handlerContext, nullptr}, "")
|
||||||
|
builder.CreateRetVoid()
|
||||||
|
|
||||||
|
// Replace the function call.
|
||||||
|
interruptValue := llvm.ConstNamedStruct(mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
|
||||||
|
call.ReplaceAllUsesWith(interruptValue)
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
@@ -89,3 +89,19 @@ func typeHasPointers(t llvm.Type) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isFunctionLocal returns true if (and only if) this value is local to a
|
||||||
|
// function. That is, it returns true for instructions and parameters, and false
|
||||||
|
// for constants and globals.
|
||||||
|
func isFunctionLocal(val llvm.Value) bool {
|
||||||
|
if !val.IsAConstant().IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !val.IsAInstruction().IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !val.IsAGlobalValue().IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
panic("unknown value kind")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user