33//! This crate requires an esp-hal timer to operate.
44//!
55//! ```rust, no_run
6- #![ doc = esp_hal:: before_snippet!( ) ]
76//! use esp_hal::timer::timg::TimerGroup;
8- //!
97//! let timg0 = TimerGroup::new(peripherals.TIMG0);
10- //! esp_preempt::start(timg0.timer0);
11- //!
8+ #![ doc = esp_hal:: before_snippet!( ) ]
9+ #![ cfg_attr(
10+ any( riscv, multi_core) ,
11+ doc = "
12+
13+ use esp_hal::interrupt::software::SoftwareInterruptControl;
14+ let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);"
15+ ) ]
16+ #![ cfg_attr(
17+ xtensa,
18+ doc = "
19+
20+ esp_preempt::start(timg0.timer0);"
21+ ) ]
22+ #![ cfg_attr(
23+ riscv,
24+ doc = "
25+
26+ esp_preempt::start(timg0.timer0, software_interrupt.software_interrupt0);"
27+ ) ]
28+ #![ cfg_attr(
29+ all( xtensa, multi_core) ,
30+ doc = "
31+ // Optionally, start the scheduler on the second core
32+ esp_preempt::start_second_core(
33+ software_interrupt.software_interrupt0,
34+ software_interrupt.software_interrupt1,
35+ || {}, // Second core's main function.
36+ );
37+ "
38+ ) ]
39+ #![ cfg_attr(
40+ all( riscv, multi_core) ,
41+ doc = "
42+ // Optionally, start the scheduler on the second core
43+ esp_preempt::start_second_core(
44+ software_interrupt.software_interrupt1,
45+ || {}, // Second core's main function.
46+ );
47+ "
48+ ) ]
49+ #![ doc = "" ]
1250//! // You can now start esp-radio:
1351//! // let esp_radio_controller = esp_radio::init().unwrap();
1452//! # }
@@ -35,10 +73,19 @@ mod wait_queue;
3573use core:: mem:: MaybeUninit ;
3674
3775pub ( crate ) use esp_alloc:: InternalMemory ;
76+ #[ cfg( any( multi_core, riscv) ) ]
77+ use esp_hal:: interrupt:: software:: SoftwareInterrupt ;
3878use esp_hal:: {
3979 Blocking ,
80+ system:: Cpu ,
4081 timer:: { AnyTimer , OneShotTimer } ,
4182} ;
83+ #[ cfg( multi_core) ]
84+ use esp_hal:: {
85+ peripherals:: CPU_CTRL ,
86+ system:: { CpuControl , Stack } ,
87+ time:: { Duration , Instant } ,
88+ } ;
4289pub ( crate ) use scheduler:: SCHEDULER ;
4390
4491use crate :: timer:: TimeDriver ;
@@ -105,6 +152,8 @@ where
105152
106153/// Starts the scheduler.
107154///
155+ /// The current context will be converted into the main task, and will be pinned to the first core.
156+ ///
108157/// # The `timer` argument
109158///
110159/// The `timer` argument is a timer source that is used by the scheduler to
@@ -119,7 +168,10 @@ where
119168 multi_core,
120169 doc = " \n Note that `esp_radio::init()` must be called on the same core as `esp_preempt::start()`."
121170) ]
122- pub fn start ( timer : impl TimerSource ) {
171+ pub fn start ( timer : impl TimerSource , #[ cfg( riscv) ] int0 : SoftwareInterrupt < ' static , 0 > ) {
172+ trace ! ( "Starting scheduler for the first core" ) ;
173+ assert_eq ! ( Cpu :: current( ) , Cpu :: ProCpu ) ;
174+
123175 SCHEDULER . with ( move |scheduler| {
124176 scheduler. setup ( TimeDriver :: new ( timer. timer ( ) ) ) ;
125177
@@ -138,11 +190,71 @@ pub fn start(timer: impl TimerSource) {
138190 ) ;
139191 task:: allocate_main_task ( scheduler, stack_slice) ;
140192
141- task:: setup_multitasking ( ) ;
193+ task:: setup_multitasking (
194+ #[ cfg( riscv) ]
195+ int0,
196+ ) ;
142197
143- unwrap ! ( scheduler. time_driver. as_mut( ) ) . start ( ) ;
144198 task:: yield_task ( ) ;
145199 } )
146200}
147201
202+ /// Starts the scheduler on the second CPU core.
203+ ///
204+ /// Note that the scheduler must be started first, before starting the second core.
205+ ///
206+ /// The supplied stack and function will be used as the main thread of the second core. The thread
207+ /// will be pinned to the second core.
208+ #[ cfg( multi_core) ]
209+ pub fn start_second_core < const STACK_SIZE : usize > (
210+ cpu_control : CPU_CTRL ,
211+ #[ cfg( xtensa) ] int0 : SoftwareInterrupt < ' static , 0 > ,
212+ int1 : SoftwareInterrupt < ' static , 1 > ,
213+ stack : & ' static mut Stack < STACK_SIZE > ,
214+ func : impl FnOnce ( ) + Send + ' static ,
215+ ) {
216+ trace ! ( "Starting scheduler for the second core" ) ;
217+
218+ #[ cfg( xtensa) ]
219+ task:: setup_smp ( int0) ;
220+
221+ struct SecondCoreStack {
222+ stack : * mut [ MaybeUninit < u32 > ] ,
223+ }
224+ unsafe impl Send for SecondCoreStack { }
225+ let stack_ptrs = SecondCoreStack {
226+ stack : core:: ptr:: slice_from_raw_parts_mut (
227+ stack. bottom ( ) . cast :: < MaybeUninit < u32 > > ( ) ,
228+ STACK_SIZE ,
229+ ) ,
230+ } ;
231+
232+ let mut cpu_control = CpuControl :: new ( cpu_control) ;
233+ let guard = cpu_control
234+ . start_app_core ( stack, move || {
235+ trace ! ( "Second core running" ) ;
236+ task:: setup_smp ( int1) ;
237+ SCHEDULER . with ( move |scheduler| {
238+ // Make sure the whole struct is captured, not just a !Send field.
239+ let ptrs = stack_ptrs;
240+ assert ! (
241+ scheduler. time_driver. is_some( ) ,
242+ "The scheduler must be started on the first core first."
243+ ) ;
244+ task:: allocate_main_task ( scheduler, ptrs. stack ) ;
245+ task:: yield_task ( ) ;
246+ trace ! ( "Second core scheduler initialized" ) ;
247+ } ) ;
248+
249+ func ( ) ;
250+
251+ loop {
252+ SCHEDULER . sleep_until ( Instant :: EPOCH + Duration :: MAX ) ;
253+ }
254+ } )
255+ . unwrap ( ) ;
256+
257+ core:: mem:: forget ( guard) ;
258+ }
259+
148260const TICK_RATE : u32 = esp_config:: esp_config_int!( u32 , "ESP_PREEMPT_CONFIG_TICK_RATE_HZ" ) ;
0 commit comments