aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--src/runtime/interrupt/interrupt.go5
-rw-r--r--src/runtime/interrupt/interrupt_hwvector.go8
-rw-r--r--src/runtime/interrupt/interrupt_sifive.go18
-rw-r--r--src/runtime/runtime_fe310.go15
-rwxr-xr-xtools/gen-device-svd/gen-device-svd.go37
-rw-r--r--transform/interrupt.go79
-rw-r--r--transform/llvm.go9
8 files changed, 152 insertions, 21 deletions
diff --git a/Makefile b/Makefile
index c7bc22038..81bbfacfb 100644
--- a/Makefile
+++ b/Makefile
@@ -121,7 +121,7 @@ gen-device-sam: build/gen-device-svd
GO111MODULE=off $(GO) fmt ./src/device/sam
gen-device-sifive: build/gen-device-svd
- ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
+ ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
GO111MODULE=off $(GO) fmt ./src/device/sifive
gen-device-stm32: build/gen-device-svd
diff --git a/src/runtime/interrupt/interrupt.go b/src/runtime/interrupt/interrupt.go
index c37b2c11e..498389b6a 100644
--- a/src/runtime/interrupt/interrupt.go
+++ b/src/runtime/interrupt/interrupt.go
@@ -19,11 +19,6 @@ type Interrupt struct {
// 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
-
// handle is used internally, between IR generation and interrupt lowering. The
// frontend will create runtime/interrupt.handle objects, cast them to an int,
// and use that in an Interrupt object. That way the compiler will be able to
diff --git a/src/runtime/interrupt/interrupt_hwvector.go b/src/runtime/interrupt/interrupt_hwvector.go
new file mode 100644
index 000000000..f210da589
--- /dev/null
+++ b/src/runtime/interrupt/interrupt_hwvector.go
@@ -0,0 +1,8 @@
+// +build avr cortexm
+
+package 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
diff --git a/src/runtime/interrupt/interrupt_sifive.go b/src/runtime/interrupt/interrupt_sifive.go
new file mode 100644
index 000000000..1af18ec2a
--- /dev/null
+++ b/src/runtime/interrupt/interrupt_sifive.go
@@ -0,0 +1,18 @@
+// +build sifive
+
+package interrupt
+
+import "device/sifive"
+
+// Enable enables this interrupt. Right after calling this function, the
+// interrupt may be invoked if it was already pending.
+func (irq Interrupt) Enable() {
+ sifive.PLIC.ENABLE[irq.num/32].SetBits(1 << (uint(irq.num) % 32))
+}
+
+// SetPriority sets the interrupt priority for this interrupt. A higher priority
+// number means a higher priority (unlike Cortex-M). Priority 0 effectively
+// disables the interrupt.
+func (irq Interrupt) SetPriority(priority uint8) {
+ sifive.PLIC.PRIORITY[irq.num].Set(uint32(priority))
+}
diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go
index d96f4e469..a6f63ea7e 100644
--- a/src/runtime/runtime_fe310.go
+++ b/src/runtime/runtime_fe310.go
@@ -42,6 +42,10 @@ func main() {
// of MTVEC won't be zero.
riscv.MTVEC.Set(uintptr(unsafe.Pointer(&handleInterruptASM)))
+ // Reset the MIE register and enable external interrupts.
+ // It must be reset here because it not zeroed at startup.
+ riscv.MIE.Set(1 << 11) // bit 11 is for machine external interrupts
+
// Enable global interrupts now that they've been set up.
riscv.MSTATUS.SetBits(1 << 3) // MIE
@@ -68,6 +72,13 @@ func handleInterrupt() {
// Disable the timer, to avoid triggering the interrupt right after
// this interrupt returns.
riscv.MIE.ClearBits(1 << 7) // MTIE bit
+ case 11: // Machine external interrupt
+ // Claim this interrupt.
+ id := sifive.PLIC.CLAIM.Get()
+ // Call the interrupt handler, if any is registered for this ID.
+ callInterruptHandler(int(id))
+ // Complete this interrupt.
+ sifive.PLIC.CLAIM.Set(id)
}
} else {
// Topmost bit is clear, so it is an exception of some sort.
@@ -167,3 +178,7 @@ func handleException(code uint) {
println()
abort()
}
+
+// callInterruptHandler is a compiler-generated function that calls the
+// appropriate interrupt handler for the given interrupt ID.
+func callInterruptHandler(id int)
diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go
index f5aebcfe8..2e64651e5 100755
--- a/tools/gen-device-svd/gen-device-svd.go
+++ b/tools/gen-device-svd/gen-device-svd.go
@@ -595,7 +595,7 @@ func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bit
}
// The Go module for this device.
-func writeGo(outdir string, device *Device) error {
+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
@@ -621,7 +621,7 @@ func writeGo(outdir string, device *Device) error {
package {{.pkgName}}
import (
- "runtime/interrupt"
+{{if eq .interruptSystem "hardware"}}"runtime/interrupt"{{end}}
"runtime/volatile"
"unsafe"
)
@@ -637,11 +637,13 @@ const ({{range .interrupts}}
IRQ_max = {{.interruptMax}} // Highest interrupt number on this device.
)
+{{if eq .interruptSystem "hardware"}}
// Map interrupt numbers to function names.
// These aren't real calls, they're removed by the compiler.
var ({{range .interrupts}}
_ = interrupt.Register(IRQ_{{.Name}}, "{{.HandlerName}}"){{end}}
)
+{{end}}
// Peripherals.
var (
@@ -649,11 +651,12 @@ var (
{{end}})
`))
err = t.Execute(w, map[string]interface{}{
- "metadata": device.metadata,
- "interrupts": device.interrupts,
- "peripherals": device.peripherals,
- "pkgName": filepath.Base(strings.TrimRight(outdir, "/")),
- "interruptMax": maxInterruptValue,
+ "metadata": device.metadata,
+ "interrupts": device.interrupts,
+ "peripherals": device.peripherals,
+ "pkgName": filepath.Base(strings.TrimRight(outdir, "/")),
+ "interruptMax": maxInterruptValue,
+ "interruptSystem": interruptSystem,
})
if err != nil {
return err
@@ -910,7 +913,7 @@ Default_Handler:
return w.Flush()
}
-func generate(indir, outdir, sourceURL string) error {
+func generate(indir, outdir, sourceURL, interruptSystem string) error {
if _, err := os.Stat(indir); os.IsNotExist(err) {
fmt.Fprintln(os.Stderr, "cannot find input directory:", indir)
os.Exit(1)
@@ -929,13 +932,20 @@ func generate(indir, outdir, sourceURL string) error {
if err != nil {
return fmt.Errorf("failed to read: %w", err)
}
- err = writeGo(outdir, device)
+ err = writeGo(outdir, device, interruptSystem)
if err != nil {
return fmt.Errorf("failed to write Go file: %w", err)
}
- err = writeAsm(outdir, device)
- if err != nil {
- return fmt.Errorf("failed to write assembly 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
@@ -943,6 +953,7 @@ func generate(indir, outdir, sourceURL string) error {
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")
@@ -951,7 +962,7 @@ func main() {
}
indir := flag.Arg(0)
outdir := flag.Arg(1)
- err := generate(indir, outdir, *sourceURL)
+ err := generate(indir, outdir, *sourceURL, *interruptSystem)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
diff --git a/transform/interrupt.go b/transform/interrupt.go
index 2a0dbe02e..1c3247d5e 100644
--- a/transform/interrupt.go
+++ b/transform/interrupt.go
@@ -2,6 +2,8 @@ package transform
import (
"fmt"
+ "sort"
+ "strconv"
"strings"
"tinygo.org/x/go-llvm"
@@ -60,6 +62,9 @@ func LowerInterrupts(mod llvm.Module) []error {
call.EraseFromParentAsInstruction()
}
+ hasSoftwareVectoring := hasUses(mod.NamedFunction("runtime.callInterruptHandler"))
+ softwareVector := make(map[int64]llvm.Value)
+
ctx := mod.Context()
nullptr := llvm.ConstNull(llvm.PointerType(ctx.Int8Type(), 0))
builder := ctx.NewBuilder()
@@ -90,9 +95,25 @@ func LowerInterrupts(mod llvm.Module) []error {
num := llvm.ConstExtractValue(initializer, []uint32{1, 0})
name := handlerNames[num.SExtValue()]
+ isSoftwareVectored := false
if name == "" {
- errs = append(errs, errorAt(global, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue())))
- continue
+ // No function name was defined for this interrupt number, which
+ // probably means one of two things:
+ // * runtime/interrupt.Register wasn't called to give the interrupt
+ // number a function name (such as on Cortex-M).
+ // * We're using software vectoring instead of hardware vectoring,
+ // which means the name of the handler doesn't matter (it will
+ // probably be inlined anyway).
+ if hasSoftwareVectoring {
+ isSoftwareVectored = true
+ if name == "" {
+ // Name doesn't matter, so pick something unique.
+ name = "runtime/interrupt.interruptHandler" + strconv.FormatInt(num.SExtValue(), 10)
+ }
+ } else {
+ errs = append(errs, errorAt(global, fmt.Sprintf("cannot find interrupt name for number %d", num.SExtValue())))
+ continue
+ }
}
// Extract the func value.
@@ -162,6 +183,10 @@ func LowerInterrupts(mod llvm.Module) []error {
// that is inserted in the interrupt vector.
fn.SetUnnamedAddr(true)
fn.SetSection(".text." + name)
+ if isSoftwareVectored {
+ fn.SetLinkage(llvm.InternalLinkage)
+ softwareVector[num.SExtValue()] = fn
+ }
entryBlock := ctx.AddBasicBlock(fn, "entry")
builder.SetInsertPointAtEnd(entryBlock)
@@ -200,6 +225,56 @@ func LowerInterrupts(mod llvm.Module) []error {
global.EraseFromParentAsGlobal()
}
+ // Create a dispatcher function that calls the appropriate interrupt handler
+ // for each interrupt ID. This is used in the case of software vectoring.
+ // The function looks like this:
+ // func callInterruptHandler(id int) {
+ // switch id {
+ // case IRQ_UART:
+ // interrupt.interruptHandler3()
+ // case IRQ_FOO:
+ // interrupt.interruptHandler7()
+ // default:
+ // // do nothing
+ // }
+ if hasSoftwareVectoring {
+ // Create a sorted list of interrupt vector IDs.
+ ids := make([]int64, 0, len(softwareVector))
+ for id := range softwareVector {
+ ids = append(ids, id)
+ }
+ sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
+
+ // Start creating the function body with the big switch.
+ dispatcher := mod.NamedFunction("runtime.callInterruptHandler")
+ entryBlock := ctx.AddBasicBlock(dispatcher, "entry")
+ defaultBlock := ctx.AddBasicBlock(dispatcher, "default")
+ builder.SetInsertPointAtEnd(entryBlock)
+ interruptID := dispatcher.Param(0)
+ sw := builder.CreateSwitch(interruptID, defaultBlock, len(ids))
+
+ // Create a switch case for each interrupt ID that calls the appropriate
+ // handler.
+ for _, id := range ids {
+ block := ctx.AddBasicBlock(dispatcher, "interrupt"+strconv.FormatInt(id, 10))
+ builder.SetInsertPointAtEnd(block)
+ builder.CreateCall(softwareVector[id], nil, "")
+ builder.CreateRetVoid()
+ sw.AddCase(llvm.ConstInt(interruptID.Type(), uint64(id), true), block)
+ }
+
+ // Create a default case that just returns.
+ // Perhaps it is better to call some default interrupt handler here that
+ // logs an error?
+ builder.SetInsertPointAtEnd(defaultBlock)
+ builder.CreateRetVoid()
+
+ // Make sure the dispatcher is optimized.
+ // Without this, it will probably not get inlined.
+ dispatcher.SetLinkage(llvm.InternalLinkage)
+ dispatcher.SetUnnamedAddr(true)
+ }
+
// Remove now-useless runtime/interrupt.use calls. These are used for some
// platforms like AVR that do not need to enable interrupts to use them, so
// need another way to keep them alive.
diff --git a/transform/llvm.go b/transform/llvm.go
index 86dd897e9..90b7a7c75 100644
--- a/transform/llvm.go
+++ b/transform/llvm.go
@@ -21,6 +21,15 @@ func getUses(value llvm.Value) []llvm.Value {
return uses
}
+// hasUses returns whether the given value has any uses. It is equivalent to
+// getUses(value) != nil but faster.
+func hasUses(value llvm.Value) bool {
+ if value.IsNil() {
+ return false
+ }
+ return !value.FirstUse().IsNil()
+}
+
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set