Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
path = path[len(tinygoPath+"/src/"):]
}
switch path {
case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/task":
case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "runtime/sync", "sync", "testing", "internal/reflectlite", "internal/task":
return path
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
Expand Down Expand Up @@ -252,6 +252,13 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con

c.loadASTComments(lprogram)

// Forcibly preload special types.
runtimePkg := c.ir.Program.ImportedPackage("runtime")
c.getLLVMType(runtimePkg.Type("_interface").Type())
c.getLLVMType(runtimePkg.Type("_string").Type())
c.getLLVMType(runtimePkg.Type("hashmap").Type())
c.getLLVMType(runtimePkg.Type("channel").Type())

// Declare runtime types.
// TODO: lazily create runtime types in getLLVMRuntimeType when they are
// needed. Eventually this will be required anyway, when packages are
Expand Down
11 changes: 11 additions & 0 deletions src/examples/intsync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# TinyGo ARM SysTick example w/ condition variable

This example uses the ARM System Timer to awake a goroutine.
That goroutine sends to a channel, and another goroutine toggles an LED on every notification.

Many ARM-based chips have this timer feature. If you run the example and the
LED blinks, then you have one.

The System Timer runs from a cycle counter. The more cycles, the slower the
LED will blink. This counter is 24 bits wide, which places an upper bound on
the number of cycles, and the slowness of the blinking.
44 changes: 44 additions & 0 deletions src/examples/intsync/intsync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"device/arm"
"machine"
"runtime/sync"
)

func main() {
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})

timerch := make(chan struct{})

// Toggle the LED on every receive from the timer channel.
go func() {
for {
machine.LED.High()
<-timerch
machine.LED.Low()
<-timerch
}
}()

// Send to the timer channel on every timerCond notification.
go func() {
for {
timerCond.Wait()
timerch <- struct{}{}
}
}()

// timer fires 10 times per second
arm.SetupSystemTimer(machine.CPUFrequency() / 10)

select {}
}

// timerCond is a condition variable used to handle the systick interrupts.
var timerCond sync.IntCond

//go:export SysTick_Handler
func timer_isr() {
timerCond.Notify()
}
66 changes: 66 additions & 0 deletions src/internal/task/interruptqueue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package task

import "runtime/volatile"

// InterruptQueue is a specialized version of Queue, designed for working with interrupts.
// It can be safely pushed to from an interrupt (assuming that a memory reference to the task remains elsewhere), and popped outside of an interrupt.
// It cannot be pushed to outside of an interrupt or popped inside an interrupt.
type InterruptQueue struct {
// This implementation uses a double-buffer of queues.
// bufSelect contains the index of the queue currently available for pop operations.
// The opposite queue is available for push operations.
bufSelect volatile.Register8
queues [2]Queue
}

// Push a task onto the queue.
// This can only be safely called from inside an interrupt.
func (q *InterruptQueue) Push(t *Task) {
// Avoid nesting interrupts inside here.
var nest nonest
nest.Lock()
defer nest.Unlock()

// Push to inactive queue.
q.queues[1-q.bufSelect.Get()].Push(t)
}

// Check if the queue is empty.
// This will return false if any tasks were pushed strictly before this call.
// If any pushes occur during the call, the queue may or may not be marked as empty.
// This cannot be safely called inside an interrupt.
func (q *InterruptQueue) Empty() bool {
// Check currently active queue.
active := q.bufSelect.Get() & 1
if !q.queues[active].Empty() {
return false
}

// Swap to other queue.
active ^= 1
q.bufSelect.Set(active)

// Check other queue.
return q.queues[active].Empty()
}

// Pop removes a single task from the queue.
// This will return nil if the queue is empty (with the same semantics as Empty).
// This cannot be safely called inside an interrupt.
func (q *InterruptQueue) Pop() *Task {
// Select non-empty queue if one exists.
if q.Empty() {
return nil
}

// Pop from active queue.
return q.queues[q.bufSelect.Get()&1].Pop()
}

// AppendTo pops all tasks from this queue and pushes them to another queue.
// This operation has the same semantics as repeated calls to pop.
func (q *InterruptQueue) AppendTo(other *Queue) {
for !q.Empty() {
q.queues[q.bufSelect.Get()&1].AppendTo(other)
}
}
20 changes: 20 additions & 0 deletions src/internal/task/nonest_arm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// +build arm,baremetal,!avr

package task

import "device/arm"

// nonest is a sync.Locker that blocks nested interrupts while held.
type nonest struct {
state uintptr
}

//go:inline
func (n *nonest) Lock() {
n.state = arm.DisableInterrupts()
}

//go:inline
func (n *nonest) Unlock() {
arm.EnableInterrupts(n.state)
}
10 changes: 10 additions & 0 deletions src/internal/task/nonest_none.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build !arm !baremetal avr

package task

// nonest is a sync.Locker that blocks nested interrupts while held.
// On non-ARM platforms, this is a no-op.
type nonest struct{}

func (n nonest) Lock() {}
func (n nonest) Unlock() {}
10 changes: 10 additions & 0 deletions src/internal/task/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func (q *Queue) Pop() *Task {
return t
}

// Empty checks if there are any tasks in the queue.
func (q *Queue) Empty() bool {
return q.head == nil
}

// Append pops the contents of another queue and pushes them onto the end of this queue.
func (q *Queue) Append(other *Queue) {
if q.head == nil {
Expand All @@ -48,6 +53,11 @@ func (q *Queue) Append(other *Queue) {
other.head, other.tail = nil, nil
}

// AppendTo pops the contents of this queue and pushes them onto another queue.
func (q *Queue) AppendTo(other *Queue) {
other.Append(q)
}

// Stack is a LIFO container of tasks.
// The zero value is an empty stack.
// This is slightly cheaper than a queue, so it can be preferable when strict ordering is not necessary.
Expand Down
83 changes: 45 additions & 38 deletions src/runtime/runtime_atsamd21.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
package runtime

import (
"device/arm"
"device/sam"
"internal/task"
"machine"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)

Expand Down Expand Up @@ -217,10 +216,7 @@ func initRTC() {
waitForSync()

intr := interrupt.New(sam.IRQ_RTC, func(intr interrupt.Interrupt) {
// disable IRQ for CMP0 compare
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)

timerWakeup.Set(1)
rtc.handleInterrupt()
})
intr.SetPriority(0xc0)
intr.Enable()
Expand All @@ -239,42 +235,22 @@ var (
timerLastCounter uint64
)

var timerWakeup volatile.Register8

const asyncScheduler = false
type rtcTimer struct{}

// sleepTicks should sleep for d number of microseconds.
func sleepTicks(d timeUnit) {
for d != 0 {
ticks() // update timestamp
ticks := uint32(d)
timerSleep(ticks)
d -= timeUnit(ticks)
func (t rtcTimer) setTimer(wakeup timeUnit) {
now := ticks()
if now >= wakeup {
wakeup = now
}
}

// ticks returns number of microseconds since start.
func ticks() timeUnit {
// request read of count
sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ)
waitForSync()

rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us
offset := (rtcCounter - timerLastCounter) // change since last measurement
timerLastCounter = rtcCounter
timestamp += timeUnit(offset) // TODO: not precise
return timestamp
}
delay := wakeup - now

// ticks are in microseconds
func timerSleep(ticks uint32) {
timerWakeup.Set(0)
if ticks < 214 {
if delay < 214 {
// due to around 183us delay waiting for the register value to sync, the minimum sleep value
// for the SAMD21 is 214us.
// For related info, see:
// https://community.atmel.com/comment/2507091#comment-2507091
ticks = 214
delay = 214
}

// request read of count
Expand All @@ -283,15 +259,46 @@ func timerSleep(ticks uint32) {

// set compare value
cnt := sam.RTC_MODE0.COUNT.Get()
sam.RTC_MODE0.COMP0.Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us
sam.RTC_MODE0.COMP0.Set(uint32(cnt) + (uint32(delay) * 10 / 305)) // each counter tick == 30.5us
waitForSync()

// enable IRQ for CMP0 compare
sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0)
}

for timerWakeup.Get() == 0 {
arm.Asm("wfi")
}
func (t rtcTimer) disableTimer() {
// disable IRQ for CMP0 compare
sam.RTC_MODE0.INTFLAG.Set(sam.RTC_MODE0_INTENSET_CMP0)
}

var rtc = timerController{
t: rtcTimer{},
}

// Add this task to the sleep queue, assuming its state is set to sleeping.
func addSleepTask(t *task.Task, duration int64) {
rtc.enqueue(t, ticks()+timeUnit(duration/tickMicros))
}

// devicePoll polls for device-specific events.
// For atsamd21, the only device-specific events are RTC timers.
func devicePoll() bool {
return rtc.poll()
}

const asyncScheduler = false

// ticks returns number of microseconds since start.
func ticks() timeUnit {
// request read of count
sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ)
waitForSync()

rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us
offset := (rtcCounter - timerLastCounter) // change since last measurement
timerLastCounter = rtcCounter
timestamp += timeUnit(offset) // TODO: not precise
return timestamp
}

func initUSBClock() {
Expand Down
Loading