Browse Source

runtime/fe310: add support for PLIC interrupts

This commit adds support for software vectoring in the PLIC interrupt.
The interrupt table is created by the compiler, which leads to very
compact code while retaining the flexibility that the interrupt API
provides.
pull/861/head
Ayke van Laethem 5 years ago
committed by Ron Evans
parent
commit
415c60551e
  1. 2
      Makefile
  2. 5
      src/runtime/interrupt/interrupt.go
  3. 8
      src/runtime/interrupt/interrupt_hwvector.go
  4. 18
      src/runtime/interrupt/interrupt_sifive.go
  5. 15
      src/runtime/runtime_fe310.go
  6. 37
      tools/gen-device-svd/gen-device-svd.go
  7. 79
      transform/interrupt.go
  8. 9
      transform/llvm.go

2
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

5
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

8
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

18
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))
}

15
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)

37
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)

79
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.

9
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

Loading…
Cancel
Save