1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/* thread.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Multithreading primitives.
//!
//! AVRoxide allows you to create multiple threads, which will be scheduled
//! cooperatively (through the [`yield_now()`] method, or by calling any
//! blocking I/O routine), or pre-emptively if a suitable interrupt source is
//! nominated for pre-emption via a crate feature flag.
//!
//! # Limitations
//! There is a limit to the number of threads you can create, and additionally
//! since each thread gets a stack allocated from the heap, the available
//! heap memory is a limit.
//!
//! The initial (main()) thread is allocated a relatively large default stack
//! size, in recognition that it may be the only thread in the program and that
//! it is also likely to allocate a lot of 'global' data on its own stack.
//! This stack size can be changed by passing the `stacksize` attribute to
//! the `avr_oxide::main` macro.
//!
//! Subsequent threads are given a smaller default stack size, although this
//! can be overridden using the [`Builder::stack_size()`] method to create
//! the thread.
//!
//! The following table summarises the default thread limits:
//!
//! | For processor | Max threads | default main() stack | default new thread stack |
//! | ------------- | ----------- | ------------------ | ------------------------------ |
//! | `atmega4809`  | 3           | 512 bytes          | 128 bytes |
//! | `atmega328p`  | 3           | 384 bytes          | 64 bytes  |
//!
//! > Note: The maximum number of threads in the table *includes* the `main()`
//! >       thread, but not the default `Idle` thread that is created by the
//! >       kernel and must always exist.
//!
//! # Thread completion and cleanup
//! When a thread completes, it will enter a *Zombie* state.  It will remain
//! in this state - and the thread context and stack will not be cleaned up,
//! releasing any associated memory - until another thread joins it using
//! the [`JoinHandle::join()`] method.
//!
//! # Pre-Emptive Multithreading
//! Thread pre-emption depends on one (or more) interrupt sources being
//! nominated to drive the scheduler.  This is done by enabling a
//! `pmt_<interrupt_name>` feature when including the AVRoxide crate in your
//! `cargo.toml`.
//!
//! Typically, you would nominate a timer interrupt; which interrupt will
//! depend on the device:
//!
//! | For processor | Typical Feature flag   | Effect |
//! | ------------- | -----------------------| ------ |
//! | `atmega4809`  | `pmt_tcb0_int`         | Threads will be rescheduled every time TimerControlBlock 0 generates an interrupt |
//!
//! # Example
//! ```rust,no_run
//! #![no_std]
//! #![no_main]
//!
//! use avr_oxide::devices::{ UsesPin, OxideLed, OxideMasterClock };
//! use avr_oxide::thread;
//! use avr_oxide::hardware;
//! use avr_oxide::boards::board;
//! use avr_oxide::StaticWrap;
//!
//! #[avr_oxide::main(chip="atmega4809",stacksize=1024)]
//! pub fn main() {
//!   let supervisor = avr_oxide::oxide::instance();
//!
//!   // Configure a 50Hz master clock device on TCB0
//!   let master_clock = StaticWrap::new(OxideMasterClock::with_timer::<50>(hardware::timer::tcb0::instance()));
//!   supervisor.listen(master_clock.borrow());
//!
//!   // If our code builds AVRoxide using the `pmt_tcb0_int` feature, threads
//!   // will now be pre-emptively multitasked, and this code will work without
//!   // locking up:
//!   let _jh = thread::Builder::new().stack_size(32).spawn(||{
//!     let white_led = OxideLed::with_pin(board::pin_d(10));
//!
//!     loop {
//!       white_led.toggle();
//!     }
//!   });
//!
//!   // Note that all the usual functionality of the MasterClock device
//!   // (delay timers, regular Oxide event handlers) remains available -
//!   // it will be used for thread preemption *in addition* to its usual
//!   // function, not instead.
//!
//!   supervisor.run();
//! }
//! ```

// Imports ===================================================================
use avr_oxide::alloc::boxed::Box;
use avr_oxide::concurrency::scheduler::{ThreadContext, ThreadState};
use avr_oxide::concurrency::stack::{ThreadStack, DynamicThreadStack};
use avr_oxide::concurrency::util::{ThreadId, ThreadSet};
use avr_oxide::concurrency::{interrupt, scheduler};
use avr_oxide::cpu;
use avr_oxide::deviceconsts::oxide::{DEFAULT_THREAD_STACK_SIZE};
use avr_oxide::hal::generic::cpu::ProcessorContext;
use avr_oxide::util::datatypes::{BitField, BitIndex};
use avr_oxide::hal::generic::cpu::Cpu;

// Declarations ==============================================================
/**
 * Handle that allows us to join a thread
 */
pub struct JoinHandle {
  thread: Thread
}

/**
 * The 'userland facing' representation of a Thread
 */
#[repr(C)]
#[derive(Clone,Copy)]
pub struct Thread {
  thread_id: ThreadId
}

/**
 * A thread factory that allows us to set the stack size for the thread that
 * we create.
 */
pub struct Builder {
  stack_size: usize
}

// Code ======================================================================
/**
 * Spawn a new thread, returning a JoinHandle for it.  Panics if the thread
 * cannot be spawned.
 */
pub fn spawn<F>(f: F) -> JoinHandle
where
  F: FnOnce() -> u8,
  F: Send + 'static
{
  Builder::new().spawn(f)
}

/**
 * Cooperatively have the current thread yield to another.
 */
pub fn yield_now() {
  unsafe {
    if cpu!().interrupts_enabled() && !cpu!().in_isr() {
      scheduler::userland_schedule_and_switch();
    } else {
      avr_oxide::oserror::halt(avr_oxide::oserror::OsError::CannotYield)
    }
  }
}

/**
 * Spawn a new thread, returning a JoinHandle for it.  Panics if the thread
 * cannot be spawned.
 */
pub(crate) fn spawn_with_stack(_isotoken: avr_oxide::concurrency::interrupt::token::Isolated, code: Box<dyn FnOnce() -> u8>, stack: Box<dyn ThreadStack>) -> JoinHandle {
  unsafe {
    let scheduler = scheduler::instance();

    for i in ThreadId::MIN..ThreadId::MAX {
      match scheduler.threads[i] {
        None => {
          let stack_top = stack.get_stack_top();

          let thread = ThreadContext {
            state: ThreadState::Schedulable,
            entrypoint: Some(code),
            returncode: 0,
            waiting_threads: ThreadSet::new(),
            stack: Some(stack),
            cpu_context: ProcessorContext {
              sreg: BitField::with_bits_set(&[BitIndex::bit_c(7)]), // We enable interrupts in userland
              gpregs: [0x00; 32],
              pc: scheduler::thread_entrypoint as u16,
              sp: stack_top as u16,
              tid: i,
              #[cfg(feature="extended_addressing")]
              rampx: 0,
              #[cfg(feature="extended_addressing")]
              rampy: 0,
              #[cfg(feature="extended_addressing")]
              rampz: 0,
              #[cfg(feature="extended_addressing")]
              eind: 0
            },
            guard: 0xf0.into()
          };
          scheduler.threads[i] = Some(thread);

          return JoinHandle {
            thread: Thread {
              thread_id: i,
            }
          };
        },
        Some(_) => {}
      }
    }
    // If we got here, no free threads
    avr_oxide::oserror::halt(avr_oxide::oserror::OsError::OutOfThreads);
  }
}

impl JoinHandle {
  /**
   * Wait for the associated thread to complete execution.
   */
  pub fn join(self) -> u8 {
    unsafe {
      loop {
        let return_code = interrupt::isolated(|isotoken|{
          let target_thread = scheduler::get_thread_by_id(self.thread.thread_id);

          if target_thread.state == ThreadState::Zombie {
            // Aha!  The thread is ready to die...
            scheduler::set_thread_state(isotoken, self.thread.thread_id, ThreadState::Dead);
            Some(target_thread.returncode)
          } else {
            // OK, it's not dead yet.  We need to wait for it
            target_thread.waiting_threads.add_current_thread(isotoken);
            scheduler::set_current_thread_state(isotoken, ThreadState::BlockedOnThread);
            None
          }
        });

        match return_code {
          Some(value) => {
            return value
          },
          None => {
            yield_now();
          }
        }
      }
    }
  }

  /**
   * Return a reference to the underlying thread object
   */
  pub fn thread(&self) -> &Thread {
    &self.thread
  }
}

impl Thread {
  /**
   * Get the thread's unique identifier.  Note that thread IDs are only
   * unique as long as the thread is running, and may be recycled.
   */
  pub fn id(&self) -> ThreadId {
    self.thread_id
  }
}

impl Builder {
  pub fn new() -> Builder {
    Builder {
      stack_size: DEFAULT_THREAD_STACK_SIZE
    }
  }

  pub fn stack_size(mut self, size: usize) -> Builder {
    self.stack_size = size;
    self
  }

  pub fn spawn<F>(self, f: F) -> JoinHandle
  where
    F: FnOnce() -> u8,
    F: Send + 'static
  {
    interrupt::isolated(|isotoken|{
      // First, let's clean up any dead threads
      scheduler::reap_dead_threads(isotoken);

      let stack = Box::new(DynamicThreadStack::new(self.stack_size));
      let code = Box::new(f);

      spawn_with_stack(isotoken, code, stack)
    })
  }
}

// Tests =====================================================================