-
Notifications
You must be signed in to change notification settings - Fork 996
WIP - Interrupt-Driven Scheduling #606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| // This is a echo console running on the device UART. | ||
| // Connect using default baudrate for this hardware, 8-N-1 with your terminal program. | ||
| package main | ||
|
|
||
| import ( | ||
| "machine" | ||
| "time" | ||
| ) | ||
|
|
||
| // change these to test a different UART or pins if available | ||
| var ( | ||
| uart = machine.UART0 | ||
| tx = machine.UART_TX_PIN | ||
| rx = machine.UART_RX_PIN | ||
| ) | ||
|
|
||
| func keepAlive() { | ||
| for { | ||
| time.Sleep(10 * time.Millisecond) | ||
| } | ||
| } | ||
|
|
||
| func main() { | ||
| machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) | ||
| time.Sleep(time.Second) | ||
| time.Sleep(time.Second) | ||
| time.Sleep(time.Second) | ||
| time.Sleep(time.Second) | ||
| time.Sleep(time.Second) | ||
| time.Sleep(time.Second) | ||
| time.Sleep(time.Second) | ||
| time.Sleep(time.Second) | ||
|
|
||
| //uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) | ||
| uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n")) | ||
|
|
||
| go keepAlive() | ||
|
|
||
| input := make([]byte, 64) | ||
| i := 0 | ||
| for { | ||
| uart.Cond.Wait() | ||
| uart.Cond.Clear() | ||
| for uart.Buffered() > 0 { | ||
| data, _ := uart.ReadByte() | ||
|
|
||
| switch data { | ||
| case 13: | ||
| // return key | ||
| uart.Write([]byte("\r\n")) | ||
| uart.Write([]byte("You typed: ")) | ||
| uart.Write(input[:i]) | ||
| uart.Write([]byte("\r\n")) | ||
| i = 0 | ||
| default: | ||
| // just echo the character | ||
| uart.WriteByte(data) | ||
| input[i] = data | ||
| i++ | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| package sync | ||
|
|
||
| import "unsafe" | ||
|
|
||
| //go:linkname pushInt runtime.pushInt | ||
| func pushInt(t unsafe.Pointer) | ||
|
|
||
| //go:linkname blockTask runtime.blockTask | ||
| func blockTask(t unsafe.Pointer, chain *unsafe.Pointer) | ||
|
|
||
| //go:linkname getCoroutine runtime.getCoroutine | ||
| func getCoroutine() unsafe.Pointer | ||
|
|
||
| //go:linkname yield runtime.yield | ||
| func yield() | ||
|
|
||
| //go:linkname unblock runtime.unblock | ||
| func unblock(t unsafe.Pointer) unsafe.Pointer | ||
|
|
||
| // Cond is a condition variable that can be used by interrupts to notify goroutines. | ||
| type Cond struct { | ||
| state condState | ||
| t unsafe.Pointer | ||
| chain unsafe.Pointer | ||
| } | ||
|
|
||
| type condState uint8 | ||
|
|
||
| const ( | ||
| condStateReady condState = iota | ||
| condStateFired | ||
| condStateFiredAck | ||
| ) | ||
|
|
||
| // Notify marks the Cond as completed, and unblocks all blockers. | ||
| // An interruptor of this call must not try to notify. | ||
| // Returns true if and only if the condition had not previously been notified. | ||
| func (c *Cond) Notify() bool { | ||
| switch c.state { | ||
| case condStateReady: | ||
| // condition variable has not been previously notified | ||
| if c.t != nil { | ||
| // there is a task blocked on this Cond, push it into the wakeup queue and acknowledge that we noticed it | ||
| pushInt(c.t) | ||
| c.state = condStateFiredAck | ||
| } else { | ||
| // the Cond has not yet been blocked on, mark it as notified | ||
| c.state = condStateFired | ||
| } | ||
|
|
||
| // we think we did something | ||
| return true | ||
| default: | ||
| // already fired, we did nothing | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| // Wait blocks until the Cond is notified. | ||
| // If the Cond is notified before this call, this will unblock immediately. | ||
| // This does not clear the notification, so subsequent calls to Wait will not block. | ||
| func (c *Cond) Wait() { | ||
| if c.t != nil { | ||
| // task already blocked on Cond | ||
| // enqueue ourselves after it | ||
| blockTask(getCoroutine(), &c.chain) | ||
|
|
||
| // wait for the first blocker to wake us up | ||
| yield() | ||
| return | ||
| } | ||
|
|
||
| // place ourselves as a blocker in the Cond | ||
| // we need to do this before checking c.fired, in case an interrupt fires here | ||
| c.t = getCoroutine() | ||
|
|
||
| switch c.state { | ||
| case condStateFiredAck: | ||
| // an interrupt called notify and saw our coroutine | ||
| // we are on the wakeup queue | ||
| // we need to yield, and will be immediately awoken | ||
| yield() | ||
| case condStateFired: | ||
| // an interrupt called notify but did not see our coroutine | ||
| // we are not on the wakeup queue | ||
| // we do not need to wait | ||
| c.t = nil | ||
| default: | ||
| // nothing has been notified yet | ||
| // wait for notification | ||
| yield() | ||
| } | ||
|
|
||
| // detatch ourself | ||
| c.t = nil | ||
|
|
||
| // unblock all other things that are blocked on the cond | ||
| for t := c.chain; t != nil; t = unblock(t) {} | ||
| c.chain = nil | ||
|
|
||
| // finalize state | ||
| c.state = condStateFired | ||
| } | ||
|
|
||
| // Clear resets the condition variable. | ||
| // Subsequent calls to Wait will block until Notify is called again. | ||
| func (c *Cond) Clear() { | ||
| if c.t != nil { | ||
| panic("cannot clear a blocked condition variable") | ||
| } | ||
| c.state = condStateReady | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package runtime | ||
|
|
||
| // This file implements functionality that acts as a bridge between interrupts and the scheduler. | ||
|
|
||
| // a double-buffered linked queue used to transfer awoken tasks from the interrupt handler to the scheduler | ||
| var wakeupQueue [2]*task | ||
| var wakeupQueueSelect bool | ||
|
|
||
| // previous interrupt; used to handle nesting of interrupts | ||
| var prevInt *task | ||
|
|
||
| // pushInt pushes a task onto the scheduler's interrupt wakeup queue. | ||
| // This is meant to be called from an interrupt to wake up a task. | ||
| // In order for this to work properly, a reference to the task must be held elsewhere. | ||
| func pushInt(t *task) { | ||
| // interrupts before reading prevInt do not matter | ||
|
|
||
| prev := prevInt // this load has to be atomic for this to work correctly | ||
|
|
||
| // interrupt point A: if something interrupts here then prev.state().next will be non-nil | ||
|
|
||
| prevInt = t // this store has to be atomic for this to work correctly | ||
|
|
||
| if prev != nil { | ||
| // if we were interrupted at point A, then prev.next will have a chain - so find the tail of this chain | ||
| // nothing past prev will be modified if this is interrupted | ||
| tail := prev | ||
| for tail.state().next != nil { | ||
| tail = tail.state().next | ||
| } | ||
| tail.state().next = t | ||
| } else { | ||
| // we are at the lowest level - store the base of this chain into the wakeup queue | ||
| // we can safely be interrupted during this - any interuptors will simply place their tasks underneath t | ||
|
|
||
| // select the wakeup queue not currently being accessed by the scheduler | ||
| var wq **task | ||
| if wakeupQueueSelect { | ||
| wq = &wakeupQueue[1] | ||
| } else { | ||
| wq = &wakeupQueue[0] | ||
| } | ||
|
|
||
| // store interrupt chain into queue | ||
| if *wq == nil { | ||
| *wq = t | ||
| } else { | ||
| (*wq).state().next, *wq = *wq, t | ||
| } | ||
| } | ||
|
|
||
| prevInt = prev | ||
| } | ||
|
|
||
| // popInt pops a chain of tasks off of the interrupt wakeup queue. | ||
| // This must be called twice before letting the CPU go to sleep. | ||
| func popInt() *task { | ||
| // toggle buffers, so any interrupts that fire now write into the opposite buffer | ||
| wakeupQueueSelect = !wakeupQueueSelect | ||
|
|
||
| // get task chain | ||
| var wq **task | ||
| if wakeupQueueSelect { | ||
| wq = &wakeupQueue[0] | ||
| } else { | ||
| wq = &wakeupQueue[1] | ||
| } | ||
| chain := *wq | ||
| *wq = nil | ||
|
|
||
| return chain | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not quite understand why this prevented it from working. Can someone explain what is going on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably because
UART0is already configured as part of the runtime setup on most platforms. Otherwise we cannot do anyprintln()statements on panic etc.