2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Implements an event loop with cooperative multitasking and async I/O. Tasks in
|
|
|
|
the form of python coroutines (either plain generators or `async` functions) are
|
|
|
|
stepped through until completion, and can get asynchronously blocked by
|
|
|
|
`yield`ing or `await`ing a syscall.
|
2016-04-28 05:43:37 +00:00
|
|
|
|
2017-08-16 13:02:03 +00:00
|
|
|
See `schedule_task`, `run`, and syscalls `Sleep`, `Select`, `Signal`
|
2017-01-02 14:45:56 +00:00
|
|
|
and `Wait`.
|
|
|
|
'''
|
2016-04-29 19:48:59 +00:00
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
import utime
|
|
|
|
import utimeq
|
|
|
|
from micropython import const
|
|
|
|
from trezor import log
|
2017-08-14 09:08:47 +00:00
|
|
|
from trezor import io
|
2016-05-12 14:18:40 +00:00
|
|
|
|
2017-08-14 09:08:47 +00:00
|
|
|
TOUCH = io.TOUCH
|
|
|
|
TOUCH_START = io.TOUCH_START
|
|
|
|
TOUCH_MOVE = io.TOUCH_MOVE
|
|
|
|
TOUCH_END = io.TOUCH_END
|
2017-06-26 14:03:20 +00:00
|
|
|
|
2017-08-14 09:08:47 +00:00
|
|
|
READ = io.POLL_READ
|
|
|
|
WRITE = io.POLL_WRITE
|
2016-05-02 14:22:22 +00:00
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
after_step_hook = None # function, called after each task step
|
|
|
|
|
|
|
|
_MAX_SELECT_DELAY = const(1000000) # usec delay if queue is empty
|
|
|
|
_MAX_QUEUE_SIZE = const(64) # maximum number of scheduled tasks
|
|
|
|
|
|
|
|
_paused_tasks = {} # {message interface: [task]}
|
|
|
|
_scheduled_tasks = utimeq.utimeq(_MAX_QUEUE_SIZE)
|
|
|
|
|
|
|
|
if __debug__:
|
|
|
|
# for performance stats
|
|
|
|
import array
|
|
|
|
log_delay_pos = 0
|
|
|
|
log_delay_rb_len = const(10)
|
|
|
|
log_delay_rb = array.array('i', [0] * log_delay_rb_len)
|
|
|
|
|
2016-08-05 10:32:37 +00:00
|
|
|
|
2016-11-12 11:22:05 +00:00
|
|
|
def schedule_task(task, value=None, deadline=None):
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Schedule task to be executed with `value` on given `deadline` (in
|
2017-08-16 13:02:03 +00:00
|
|
|
microseconds). Does not start the event loop itself, see `run`.
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
2016-11-12 11:22:05 +00:00
|
|
|
if deadline is None:
|
|
|
|
deadline = utime.ticks_us()
|
2017-01-02 14:45:56 +00:00
|
|
|
_scheduled_tasks.push(deadline, task, value)
|
2016-08-05 10:32:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
def unschedule_task(task):
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Remove task from the time queue. Cancels previous `schedule_task`.
|
|
|
|
'''
|
|
|
|
global _scheduled_tasks
|
|
|
|
task_entry = [0, 0, 0] # deadline, task, value
|
|
|
|
queue_copy = utimeq.utimeq(_MAX_QUEUE_SIZE)
|
|
|
|
while _scheduled_tasks:
|
|
|
|
_scheduled_tasks.pop(task_entry)
|
|
|
|
if task_entry[1] is not task:
|
|
|
|
queue_copy.push(task_entry[0], task_entry[1], task_entry[2])
|
|
|
|
_scheduled_tasks = queue_copy
|
|
|
|
|
|
|
|
|
|
|
|
def _pause_task(task, iface):
|
|
|
|
tasks = _paused_tasks.get(iface, None)
|
|
|
|
if tasks is None:
|
|
|
|
tasks = _paused_tasks[iface] = []
|
|
|
|
tasks.append(task)
|
2016-08-05 10:32:37 +00:00
|
|
|
|
2016-05-16 15:10:12 +00:00
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
def _unpause_task(task):
|
|
|
|
for iface in _paused_tasks:
|
|
|
|
if task in _paused_tasks[iface]:
|
|
|
|
_paused_tasks[iface].remove(task)
|
2016-05-16 15:10:12 +00:00
|
|
|
|
|
|
|
|
2017-08-16 13:02:03 +00:00
|
|
|
def run():
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Loop forever, stepping through scheduled tasks and awaiting I/O events
|
|
|
|
inbetween. Use `schedule_task` first to add a coroutine to the task queue.
|
|
|
|
Tasks yield back to the scheduler on any I/O, usually by calling `await` on
|
|
|
|
a `Syscall`.
|
|
|
|
'''
|
2016-05-16 15:10:12 +00:00
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
if __debug__:
|
|
|
|
global log_delay_pos
|
2016-08-05 10:32:37 +00:00
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
task_entry = [0, 0, 0] # deadline, task, value
|
2017-06-26 14:03:20 +00:00
|
|
|
msg_entry = [0, 0] # iface | flags, value
|
2017-01-02 14:45:56 +00:00
|
|
|
while True:
|
|
|
|
# compute the maximum amount of time we can wait for a message
|
|
|
|
if _scheduled_tasks:
|
|
|
|
delay = utime.ticks_diff(
|
2017-03-30 21:18:13 +00:00
|
|
|
_scheduled_tasks.peektime(), utime.ticks_us())
|
2017-01-02 14:45:56 +00:00
|
|
|
else:
|
|
|
|
delay = _MAX_SELECT_DELAY
|
|
|
|
|
|
|
|
if __debug__:
|
|
|
|
# add current delay to ring buffer for performance stats
|
|
|
|
log_delay_rb[log_delay_pos] = delay
|
|
|
|
log_delay_pos = (log_delay_pos + 1) % log_delay_rb_len
|
|
|
|
|
2017-07-04 16:09:08 +00:00
|
|
|
if io.poll(_paused_tasks, msg_entry, delay):
|
2017-01-02 14:45:56 +00:00
|
|
|
# message received, run tasks paused on the interface
|
2017-06-26 14:03:20 +00:00
|
|
|
msg_tasks = _paused_tasks.pop(msg_entry[0], ())
|
2017-01-02 14:45:56 +00:00
|
|
|
for task in msg_tasks:
|
2017-09-06 13:43:50 +00:00
|
|
|
_step_task(task, msg_entry[1])
|
2017-01-02 14:45:56 +00:00
|
|
|
else:
|
|
|
|
# timeout occurred, run the first scheduled task
|
|
|
|
if _scheduled_tasks:
|
|
|
|
_scheduled_tasks.pop(task_entry)
|
|
|
|
_step_task(task_entry[1], task_entry[2])
|
|
|
|
|
|
|
|
|
|
|
|
def _step_task(task, value):
|
2016-08-05 10:32:37 +00:00
|
|
|
try:
|
|
|
|
if isinstance(value, Exception):
|
|
|
|
result = task.throw(value)
|
|
|
|
else:
|
|
|
|
result = task.send(value)
|
|
|
|
except StopIteration as e:
|
2017-08-21 11:22:35 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug(__name__, 'finish: %s', task)
|
2016-08-05 10:32:37 +00:00
|
|
|
except Exception as e:
|
2017-08-21 11:22:35 +00:00
|
|
|
if __debug__:
|
|
|
|
log.exception(__name__, e)
|
2016-06-17 18:52:36 +00:00
|
|
|
else:
|
2016-08-05 10:32:37 +00:00
|
|
|
if isinstance(result, Syscall):
|
|
|
|
result.handle(task)
|
|
|
|
elif result is None:
|
|
|
|
schedule_task(task)
|
|
|
|
else:
|
2017-08-21 11:22:35 +00:00
|
|
|
if __debug__:
|
|
|
|
log.error(__name__, 'unknown syscall: %s', result)
|
2017-01-02 14:45:56 +00:00
|
|
|
if after_step_hook:
|
|
|
|
after_step_hook()
|
2016-05-12 14:18:40 +00:00
|
|
|
|
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
class Syscall:
|
|
|
|
'''
|
|
|
|
When tasks want to perform any I/O, or do any sort of communication with the
|
|
|
|
scheduler, they do so through instances of a class derived from `Syscall`.
|
|
|
|
'''
|
2016-08-05 10:32:37 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
2017-01-02 14:45:56 +00:00
|
|
|
# support `yield from` or `await` on syscalls
|
2016-08-05 10:32:37 +00:00
|
|
|
return (yield self)
|
2016-05-30 14:15:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Sleep(Syscall):
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Pause current task and resume it after given delay. Although the delay is
|
|
|
|
given in microseconds, sub-millisecond precision is not guaranteed. Result
|
|
|
|
value is the calculated deadline.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
planned = await loop.Sleep(1000 * 1000) # sleep for 1ms
|
|
|
|
print('missed by %d us', utime.ticks_diff(utime.ticks_us(), planned))
|
|
|
|
'''
|
2016-05-12 14:18:40 +00:00
|
|
|
|
2016-08-05 10:32:37 +00:00
|
|
|
def __init__(self, delay_us):
|
2017-02-09 12:49:10 +00:00
|
|
|
self.delay_us = delay_us
|
2016-05-12 14:18:40 +00:00
|
|
|
|
2016-08-05 10:32:37 +00:00
|
|
|
def handle(self, task):
|
2017-02-09 12:49:10 +00:00
|
|
|
deadline = utime.ticks_add(utime.ticks_us(), self.delay_us)
|
|
|
|
schedule_task(task, deadline, deadline)
|
2016-05-30 14:15:34 +00:00
|
|
|
|
2016-05-12 14:18:40 +00:00
|
|
|
|
2016-05-30 14:15:34 +00:00
|
|
|
class Select(Syscall):
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Pause current task, and resume only after a message on `msg_iface` is
|
|
|
|
received. Messages are received either from an USB interface, or the
|
|
|
|
touch display. Result value a tuple of message values.
|
2016-05-12 14:18:40 +00:00
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
Example:
|
|
|
|
hid_report, = await loop.Select(0xABCD) # await USB HID report
|
|
|
|
event, x, y = await loop.Select(loop.TOUCH) # await touch event
|
|
|
|
'''
|
|
|
|
|
|
|
|
def __init__(self, msg_iface):
|
|
|
|
self.msg_iface = msg_iface
|
2016-04-29 23:20:57 +00:00
|
|
|
|
2016-08-05 10:32:37 +00:00
|
|
|
def handle(self, task):
|
2017-01-02 14:45:56 +00:00
|
|
|
_pause_task(task, self.msg_iface)
|
2016-08-05 10:32:37 +00:00
|
|
|
|
|
|
|
|
2017-01-02 14:45:56 +00:00
|
|
|
_NO_VALUE = ()
|
2016-08-05 10:32:37 +00:00
|
|
|
|
|
|
|
|
2016-11-25 14:42:33 +00:00
|
|
|
class Signal(Syscall):
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Pause current task, and let other running task to resume it later with a
|
|
|
|
result value or an exception.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
# in task #1:
|
|
|
|
signal = loop.Signal()
|
|
|
|
result = await signal
|
|
|
|
print('awaited result:', result)
|
|
|
|
# in task #2:
|
|
|
|
signal.send('hello from task #2')
|
|
|
|
# prints in the next iteration of the event loop
|
|
|
|
'''
|
2016-08-05 10:32:37 +00:00
|
|
|
|
|
|
|
def __init__(self):
|
2017-01-02 14:45:56 +00:00
|
|
|
self.value = _NO_VALUE
|
2016-08-05 10:32:37 +00:00
|
|
|
self.task = None
|
|
|
|
|
|
|
|
def handle(self, task):
|
|
|
|
self.task = task
|
2016-11-15 12:47:36 +00:00
|
|
|
self._deliver()
|
2016-08-05 10:32:37 +00:00
|
|
|
|
2016-11-25 14:42:33 +00:00
|
|
|
def send(self, value):
|
2016-11-15 12:47:36 +00:00
|
|
|
self.value = value
|
|
|
|
self._deliver()
|
2016-08-05 10:32:37 +00:00
|
|
|
|
|
|
|
def _deliver(self):
|
2017-01-02 14:45:56 +00:00
|
|
|
if self.task is not None and self.value is not _NO_VALUE:
|
2016-11-15 12:47:36 +00:00
|
|
|
schedule_task(self.task, self.value)
|
|
|
|
self.task = None
|
2017-01-02 14:45:56 +00:00
|
|
|
self.value = _NO_VALUE
|
2016-05-16 15:10:12 +00:00
|
|
|
|
2016-05-02 14:22:22 +00:00
|
|
|
|
2016-05-30 14:15:34 +00:00
|
|
|
class Wait(Syscall):
|
2017-01-02 14:45:56 +00:00
|
|
|
'''
|
|
|
|
Execute one or more children tasks and wait until one or more of them exit.
|
|
|
|
Return value of `Wait` is the return value of task that triggered the
|
|
|
|
completion. By default, `Wait` returns after the first child completes, and
|
|
|
|
other running children are killed (by cancelling any pending schedules and
|
|
|
|
calling `close()`).
|
|
|
|
|
|
|
|
Example:
|
|
|
|
# async def wait_for_touch(): ...
|
|
|
|
# async def animate_logo(): ...
|
|
|
|
touch_task = wait_for_touch()
|
|
|
|
animation_task = animate_logo()
|
|
|
|
waiter = loop.Wait((touch_task, animation_task))
|
|
|
|
result = await waiter
|
|
|
|
if animation_task in waiter.finished:
|
|
|
|
print('animation task returned', result)
|
|
|
|
else:
|
|
|
|
print('touch task returned', result)
|
|
|
|
|
|
|
|
Note: You should not directly `yield` a `Wait` instance, see logic in
|
|
|
|
`Wait.__iter__` for explanation. Always use `await`.
|
|
|
|
'''
|
2016-05-11 12:39:57 +00:00
|
|
|
|
2016-08-05 10:32:37 +00:00
|
|
|
def __init__(self, children, wait_for=1, exit_others=True):
|
|
|
|
self.children = children
|
2016-05-02 14:06:08 +00:00
|
|
|
self.wait_for = wait_for
|
|
|
|
self.exit_others = exit_others
|
2017-02-09 12:49:10 +00:00
|
|
|
self.scheduled = None
|
|
|
|
self.finished = None
|
2016-05-02 14:06:08 +00:00
|
|
|
self.callback = None
|
|
|
|
|
2016-08-05 10:32:37 +00:00
|
|
|
def handle(self, task):
|
|
|
|
self.callback = task
|
2017-02-09 12:49:10 +00:00
|
|
|
self.finished = []
|
2016-08-05 10:32:37 +00:00
|
|
|
self.scheduled = [self._wait(c) for c in self.children]
|
|
|
|
for ct in self.scheduled:
|
|
|
|
schedule_task(ct)
|
2016-05-02 14:06:08 +00:00
|
|
|
|
2016-05-25 12:27:22 +00:00
|
|
|
def exit(self):
|
2016-08-05 10:32:37 +00:00
|
|
|
for task in self.scheduled:
|
|
|
|
if task not in self.finished:
|
2017-01-02 14:45:56 +00:00
|
|
|
_unpause_task(task)
|
2016-08-05 10:32:37 +00:00
|
|
|
unschedule_task(task)
|
|
|
|
task.close()
|
2016-05-25 12:27:22 +00:00
|
|
|
|
2016-11-15 12:47:36 +00:00
|
|
|
async def _wait(self, child):
|
2016-05-25 12:27:22 +00:00
|
|
|
try:
|
2016-11-15 12:47:36 +00:00
|
|
|
result = await child
|
2016-08-05 10:32:37 +00:00
|
|
|
except Exception as e:
|
|
|
|
self._finish(child, e)
|
2016-05-17 13:16:59 +00:00
|
|
|
else:
|
2016-08-05 10:32:37 +00:00
|
|
|
self._finish(child, result)
|
2016-04-29 23:20:57 +00:00
|
|
|
|
2016-08-05 10:32:37 +00:00
|
|
|
def _finish(self, child, result):
|
|
|
|
self.finished.append(child)
|
2016-05-25 12:27:22 +00:00
|
|
|
if self.wait_for == len(self.finished) or isinstance(result, Exception):
|
2016-05-02 14:06:08 +00:00
|
|
|
if self.exit_others:
|
2016-05-25 12:27:22 +00:00
|
|
|
self.exit()
|
2016-08-05 10:32:37 +00:00
|
|
|
schedule_task(self.callback, result)
|
2016-09-25 13:55:08 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
try:
|
|
|
|
return (yield self)
|
|
|
|
except:
|
2016-10-06 12:41:50 +00:00
|
|
|
# exception was raised on the waiting task externally with
|
2017-01-02 14:45:56 +00:00
|
|
|
# close() or throw(), kill the children tasks and re-raise
|
2016-09-25 13:55:08 +00:00
|
|
|
self.exit()
|
|
|
|
raise
|
2017-05-23 10:42:22 +00:00
|
|
|
|
2017-06-12 16:16:06 +00:00
|
|
|
|
2017-07-04 16:09:08 +00:00
|
|
|
class Put(Syscall):
|
|
|
|
|
|
|
|
def __init__(self, chan, value=None):
|
|
|
|
self.chan = chan
|
|
|
|
self.value = value
|
|
|
|
|
|
|
|
def __call__(self, value):
|
|
|
|
self.value = value
|
|
|
|
return self
|
|
|
|
|
|
|
|
def handle(self, task):
|
|
|
|
self.chan.schedule_put(schedule_task, task, self.value)
|
|
|
|
|
|
|
|
|
|
|
|
class Take(Syscall):
|
|
|
|
|
|
|
|
def __init__(self, chan):
|
|
|
|
self.chan = chan
|
|
|
|
|
|
|
|
def __call__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def handle(self, task):
|
|
|
|
if self.chan.schedule_take(schedule_task, task) and self.chan.id is not None:
|
|
|
|
_pause_task(self.chan, self.chan.id)
|
|
|
|
|
|
|
|
|
|
|
|
class Chan:
|
|
|
|
|
|
|
|
def __init__(self, id=None):
|
|
|
|
self.id = id
|
|
|
|
self.putters = []
|
|
|
|
self.takers = []
|
|
|
|
self.put = Put(self)
|
|
|
|
self.take = Take(self)
|
|
|
|
|
|
|
|
def schedule_publish(self, schedule, value):
|
|
|
|
if self.takers:
|
|
|
|
for taker in self.takers:
|
|
|
|
schedule(taker, value)
|
|
|
|
self.takers.clear()
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def schedule_put(self, schedule, putter, value):
|
|
|
|
if self.takers:
|
|
|
|
taker = self.takers.pop(0)
|
|
|
|
schedule(taker, value)
|
|
|
|
schedule(putter, value)
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
self.putters.append((putter, value))
|
|
|
|
return False
|
|
|
|
|
|
|
|
def schedule_take(self, schedule, taker):
|
|
|
|
if self.putters:
|
|
|
|
putter, value = self.putters.pop(0)
|
|
|
|
schedule(taker, value)
|
|
|
|
schedule(putter, value)
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
self.takers.append(taker)
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2017-05-23 10:42:22 +00:00
|
|
|
select = Select
|
|
|
|
sleep = Sleep
|
|
|
|
wait = Wait
|
|
|
|
signal = Signal
|