diff --git a/src/internal/task/interruptqueue.go b/src/internal/task/interruptqueue.go new file mode 100644 index 0000000000..4eb48a8bc2 --- /dev/null +++ b/src/internal/task/interruptqueue.go @@ -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) + } +} diff --git a/src/internal/task/nonest_arm.go b/src/internal/task/nonest_arm.go new file mode 100644 index 0000000000..4040e430f6 --- /dev/null +++ b/src/internal/task/nonest_arm.go @@ -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) +} diff --git a/src/internal/task/nonest_none.go b/src/internal/task/nonest_none.go new file mode 100644 index 0000000000..1589b92942 --- /dev/null +++ b/src/internal/task/nonest_none.go @@ -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() {} diff --git a/src/internal/task/queue.go b/src/internal/task/queue.go index c86bc596cb..10cfde6935 100644 --- a/src/internal/task/queue.go +++ b/src/internal/task/queue.go @@ -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 { @@ -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.