diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | src/runtime/interrupt/interrupt.go | 5 | ||||
-rw-r--r-- | src/runtime/interrupt/interrupt_hwvector.go | 8 | ||||
-rw-r--r-- | src/runtime/interrupt/interrupt_sifive.go | 18 | ||||
-rw-r--r-- | src/runtime/runtime_fe310.go | 15 | ||||
-rwxr-xr-x | tools/gen-device-svd/gen-device-svd.go | 37 | ||||
-rw-r--r-- | transform/interrupt.go | 79 | ||||
-rw-r--r-- | transform/llvm.go | 9 |
8 files changed, 152 insertions, 21 deletions
@@ -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 |