Skip to content

Commit f3907af

Browse files
bugadanig1ibby
andauthored
Possibly awful SMP implementation (#4152)
* Test and fix time slicing * Possibly awful SMP implementation Co-authored-by: g1ibby <[email protected]> * Remove __esp_radio_builtin_scheduler --------- Co-authored-by: g1ibby <[email protected]>
1 parent 129fa96 commit f3907af

File tree

41 files changed

+1394
-369
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1394
-369
lines changed

esp-hal/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,6 @@ default = ["rt", "exception-handler"]
136136
__bluetooth = []
137137
# Reserves FROM_CPU_INTR3 for multi-core MCUs.
138138
__esp_hal_embassy = []
139-
# Reserves FROM_CPU_INTR2 for RISC-V MCUs.
140-
__esp_radio_builtin_scheduler = []
141139
__usb_otg = [
142140
"dep:embassy-usb-driver",
143141
"dep:embassy-usb-synopsys-otg",

esp-hal/src/exception_handler/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ unsafe extern "C" fn __user_exception(
77
cause: xtensa_lx_rt::exception::ExceptionCause,
88
context: &TrapFrame,
99
) {
10-
panic!("\n\nException occurred '{:?}'\n{:?}", cause, context);
10+
panic!(
11+
"\n\nException occurred on {:?} '{:?}'\n{:?}",
12+
crate::system::Cpu::current(),
13+
cause,
14+
context
15+
);
1116
}
1217

1318
#[cfg(riscv)]

esp-hal/src/interrupt/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub mod software;
8787
#[cfg(feature = "rt")]
8888
#[unsafe(no_mangle)]
8989
extern "C" fn EspDefaultHandler() {
90-
panic!("Unhandled interrupt");
90+
panic!("Unhandled interrupt on {:?}", crate::system::Cpu::current());
9191
}
9292

9393
/// Default (unhandled) interrupt handler

esp-hal/src/interrupt/software.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,7 @@ pub struct SoftwareInterruptControl<'d> {
170170
pub software_interrupt0: SoftwareInterrupt<'d, 0>,
171171
/// Software interrupt 1.
172172
pub software_interrupt1: SoftwareInterrupt<'d, 1>,
173-
/// Software interrupt 2. Not available when using esp-radio's builtin
174-
/// scheduler on RISC-V architectures.
175-
#[cfg(not(all(feature = "__esp_radio_builtin_scheduler", riscv)))]
173+
/// Software interrupt 2.
176174
pub software_interrupt2: SoftwareInterrupt<'d, 2>,
177175
#[cfg(not(all(feature = "__esp_hal_embassy", multi_core)))]
178176
/// Software interrupt 3. Not available when using `esp-hal-embassy`,
@@ -190,7 +188,6 @@ impl<'d> SoftwareInterruptControl<'d> {
190188
software_interrupt1: SoftwareInterrupt {
191189
_lifetime: PhantomData,
192190
},
193-
#[cfg(not(all(feature = "__esp_radio_builtin_scheduler", riscv)))]
194191
software_interrupt2: SoftwareInterrupt {
195192
_lifetime: PhantomData,
196193
},

esp-preempt/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ bench = false
3030
[dependencies]
3131
esp-hal = { version = "1.0.0-rc.0", path = "../esp-hal", features = [
3232
"requires-unstable",
33-
"__esp_radio_builtin_scheduler",
3433
] }
3534

3635
cfg-if = "1"

esp-preempt/src/lib.rs

Lines changed: 119 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,50 @@
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;
3573
use core::mem::MaybeUninit;
3674

3775
pub(crate) use esp_alloc::InternalMemory;
76+
#[cfg(any(multi_core, riscv))]
77+
use esp_hal::interrupt::software::SoftwareInterrupt;
3878
use 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+
};
4289
pub(crate) use scheduler::SCHEDULER;
4390

4491
use 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 = " \nNote 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+
148260
const TICK_RATE: u32 = esp_config::esp_config_int!(u32, "ESP_PREEMPT_CONFIG_TICK_RATE_HZ");

esp-preempt/src/run_queue.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::task::{TaskExt, TaskPtr, TaskQueue, TaskReadyQueueElement, TaskState};
22

3+
#[derive(Clone, Copy)]
34
pub(crate) struct MaxPriority {
45
max: usize,
56
mask: usize,
@@ -71,7 +72,39 @@ impl RunQueue {
7172
let current_prio = self.ready_priority.ready();
7273
debug!("pop - from level: {}", current_prio);
7374

74-
let popped = self.ready_tasks[current_prio].pop();
75+
cfg_if::cfg_if! {
76+
if #[cfg(multi_core)] {
77+
use esp_hal::system::Cpu;
78+
79+
let other = match Cpu::current() {
80+
Cpu::AppCpu => Cpu::ProCpu,
81+
Cpu::ProCpu => Cpu::AppCpu,
82+
};
83+
84+
// Look iteratively through priority levels - this will ensure we can find a task even if all high priority tasks are pinned to the other CPU.
85+
let mut priority_level = self.ready_priority;
86+
87+
let mut current_prio = current_prio;
88+
let mut popped;
89+
loop {
90+
popped = self.ready_tasks[current_prio].pop_if(|t| t.pinned_to != Some(other));
91+
92+
if popped.is_none() {
93+
priority_level.unmark(current_prio);
94+
if current_prio == 0 {
95+
break;
96+
}
97+
current_prio = priority_level.ready();
98+
continue;
99+
}
100+
101+
break;
102+
}
103+
104+
} else {
105+
let popped = self.ready_tasks[current_prio].pop();
106+
}
107+
}
75108

76109
if self.ready_tasks[current_prio].is_empty() {
77110
self.ready_priority.unmark(current_prio);

0 commit comments

Comments
 (0)