-
Notifications
You must be signed in to change notification settings - Fork 174
/
arm_m.rs
1640 lines (1495 loc) · 58.2 KB
/
arm_m.rs
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Architecture support for ARMv{6,7,8}-M.
//!
//! Mostly ARMv7-M at the moment.
//!
//! # ARM-M timer
//!
//! We use the system tick timer as the kernel timer, but it's only suitable for
//! producing periodic interrupts -- its counter is small and only counts down.
//! So, at each interrupt, we increment the `TICKS` global that contains the
//! real kernel timestamp. This has the downside that we take regular interrupts
//! to maintain `TICKS`, but has the upside that we don't need special SoC
//! support for timing.
//!
//! # Notes on ARM-M interrupts
//!
//! For performance and (believe it or not) simplicity, this implementation uses
//! several different interrupt service routines:
//!
//! - `SVCall` implements the `SVC` instruction used to make syscalls.
//! - `SysTick` handles interrupts from the System Tick Timer, used to maintain
//! the kernel timestamp.
//! - `PendSV` handles deferred context switches from interrupts.
//!
//! The first two are expected; the last one's a bit odd and deserves an
//! explanation.
//!
//! It has to do with interrupt latency.
//!
//! On any interrupt, the processor stacks a small subset of machine state and
//! then calls our ISR. Our ISR is a normal Rust function, and follows the
//! normal (C) calling convention: there are some registers that it can use
//! without saving, and there are others it must save first. When the ISR
//! returns, it restores any registers it saved.
//!
//! This is great, as long as the code you're returning to is the *same code
//! that called you* -- but in the case of a context switch, it isn't.
//!
//! There's another problem, which is that we'd like to be able to read the
//! values of some of the user registers for syscall arguments and the like...
//! but if we rely on the automatic saving to put them somewhere on the stack,
//! that "somewhere" is opaque and we can't manipulate it.
//!
//! And so, if you want to be able to inspect callee registers (beyond `r0`
//! through `r3`) or switch tasks, you need to do something more elaborate than
//! the basic hardware interrupt behavior: you need to carefully deposit all
//! user state into the `Task`, and then read it back on the way out (possibly
//! from a different `Task` if the context has switched).
//!
//! This is relatively costly, so it's only appropriate to do this in an ISR
//! that you believe will result in a context switch. `SVCall` usually does --
//! our most-used system calls are blocking. `SysTick` usually *does not* -- it
//! will cause a context switch only when it causes a higher-priority timer to
//! fire, which is a sometimes thing. And most hardware interrupt handlers are
//! also not guaranteed to cause a context switch immediately.
//!
//! So, we do the full save/restore sequence around `SVCall` (see the assembly
//! code in that function), but *not* around `SysTick`, and not around other
//! hardware IRQs. Instead, if one of those routines discovers that a context
//! switch is required, it pokes a register that sets the `PendSV` interrupt
//! pending.
//!
//! `PendSV` is intended for this exact use. It will kick in when our ISR exits
//! (i.e. it won't preempt our ISR, but follow it) and perform the full
//! save/restore sequence around invoking the scheduler.
//!
//! We didn't invent this idea -- it's covered in most books on the Cortex-M.
//! We might later decide that most ISRs (including ticks) tend to trigger
//! context switches, and just always do full save/restore, eliminating PendSV.
//! We'll see.
use core::ptr::NonNull;
use zerocopy::FromBytes;
use crate::app;
use crate::task;
use crate::time::Timestamp;
use crate::umem::USlice;
use abi::FaultInfo;
#[cfg(any(armv7m, armv8m))]
use abi::FaultSource;
use unwrap_lite::UnwrapLite;
/// Log things from kernel context. This macro is made visible to the rest of
/// the kernel by a chain of `#[macro_use]` attributes, but its implementation
/// is very architecture-specific at the moment.
///
/// At the moment, there are two (architecture-specific) ways to log: via
/// semihosting (configured via the "klog-semihosting" feature) or via the
/// ARM's Instrumentation Trace Macrocell (configured via the "klog-itm"
/// feature). If neither of these features is enabled, klog! will be stubbed
/// out.
///
/// In the future, we will likely want to add at least one more mechanism for
/// logging (one that can be presumably be made neutral with respect to
/// architecure), whereby kernel logs can be produced somewhere (e.g., a ring
/// buffer) from which they can be consumed by some entity for shipping
/// elsewhere.
///
#[cfg(not(any(feature = "klog-semihosting", feature = "klog-itm")))]
macro_rules! klog {
($s:expr) => {};
($s:expr, $($tt:tt)*) => {};
}
#[cfg(feature = "klog-itm")]
macro_rules! klog {
($s:expr) => {
#[allow(unused_unsafe)]
unsafe {
let stim = &mut (*cortex_m::peripheral::ITM::ptr()).stim[0];
cortex_m::iprintln!(stim, $s);
}
};
($s:expr, $($tt:tt)*) => {
#[allow(unused_unsafe)]
unsafe {
let stim = &mut (*cortex_m::peripheral::ITM::ptr()).stim[0];
cortex_m::iprintln!(stim, $s, $($tt)*);
}
};
}
#[cfg(feature = "klog-semihosting")]
macro_rules! klog {
($s:expr) => { let _ = cortex_m_semihosting::hprintln!($s); };
($s:expr, $($tt:tt)*) => { let _ = cortex_m_semihosting::hprintln!($s, $($tt)*); };
}
macro_rules! uassert {
($cond : expr) => {
if !$cond {
panic!("Assertion failed!");
}
};
}
macro_rules! uassert_eq {
($cond1 : expr, $cond2 : expr) => {
if !($cond1 == $cond2) {
panic!("Assertion failed!");
}
};
}
/// On ARMvx-M we use a global to record the task table position and extent.
#[no_mangle]
static mut TASK_TABLE_BASE: Option<NonNull<task::Task>> = None;
#[no_mangle]
static mut TASK_TABLE_SIZE: usize = 0;
/// On ARMvx-M we have to use a global to record the current task pointer, since
/// we don't have a scratch register.
#[no_mangle]
static mut CURRENT_TASK_PTR: Option<NonNull<task::Task>> = None;
/// To allow our clock frequency to be easily determined from a debugger, we
/// store it in memory.
#[no_mangle]
static mut CLOCK_FREQ_KHZ: u32 = 0;
/// ARMvx-M volatile registers that must be saved across context switches.
#[repr(C)]
#[derive(Debug, Default)]
pub struct SavedState {
// NOTE: the following fields must be kept contiguous!
r4: u32,
r5: u32,
r6: u32,
r7: u32,
r8: u32,
r9: u32,
r10: u32,
r11: u32,
psp: u32,
exc_return: u32,
// gosh it would sure be nice if cfg_if were legal here
#[cfg(any(armv7m, armv8m))]
s16: u32,
#[cfg(any(armv7m, armv8m))]
s17: u32,
#[cfg(any(armv7m, armv8m))]
s18: u32,
#[cfg(any(armv7m, armv8m))]
s19: u32,
#[cfg(any(armv7m, armv8m))]
s20: u32,
#[cfg(any(armv7m, armv8m))]
s21: u32,
#[cfg(any(armv7m, armv8m))]
s22: u32,
#[cfg(any(armv7m, armv8m))]
s23: u32,
#[cfg(any(armv7m, armv8m))]
s24: u32,
#[cfg(any(armv7m, armv8m))]
s25: u32,
#[cfg(any(armv7m, armv8m))]
s26: u32,
#[cfg(any(armv7m, armv8m))]
s27: u32,
#[cfg(any(armv7m, armv8m))]
s28: u32,
#[cfg(any(armv7m, armv8m))]
s29: u32,
#[cfg(any(armv7m, armv8m))]
s30: u32,
#[cfg(any(armv7m, armv8m))]
s31: u32,
// NOTE: the above fields must be kept contiguous!
}
/// Map the volatile registers to (architecture-independent) syscall argument
/// and return slots.
impl task::ArchState for SavedState {
fn stack_pointer(&self) -> u32 {
self.psp
}
/// Reads syscall argument register 0.
fn arg0(&self) -> u32 {
self.r4
}
fn arg1(&self) -> u32 {
self.r5
}
fn arg2(&self) -> u32 {
self.r6
}
fn arg3(&self) -> u32 {
self.r7
}
fn arg4(&self) -> u32 {
self.r8
}
fn arg5(&self) -> u32 {
self.r9
}
fn arg6(&self) -> u32 {
self.r10
}
fn syscall_descriptor(&self) -> u32 {
self.r11
}
/// Writes syscall return argument 0.
fn ret0(&mut self, x: u32) {
self.r4 = x
}
fn ret1(&mut self, x: u32) {
self.r5 = x
}
fn ret2(&mut self, x: u32) {
self.r6 = x
}
fn ret3(&mut self, x: u32) {
self.r7 = x
}
fn ret4(&mut self, x: u32) {
self.r8 = x
}
fn ret5(&mut self, x: u32) {
self.r9 = x
}
}
/// Stuff placed on the stack at exception entry whether or not an FPU is
/// present.
#[derive(Debug, FromBytes, Default)]
#[repr(C)]
pub struct BaseExceptionFrame {
r0: u32,
r1: u32,
r2: u32,
r3: u32,
r12: u32,
lr: u32,
pc: u32,
xpsr: u32,
}
cfg_if::cfg_if! {
if #[cfg(any(armv7m, armv8m))] {
/// Extended version for FPU.
#[derive(Debug, FromBytes, Default)]
#[repr(C)]
pub struct ExtendedExceptionFrame {
base: BaseExceptionFrame,
fpu_regs: [u32; 16],
fpscr: u32,
reserved: u32,
}
} else if #[cfg(armv6m)] {
/// Wee version for non-FPU.
#[derive(Debug, FromBytes, Default)]
#[repr(C)]
pub struct ExtendedExceptionFrame {
base: BaseExceptionFrame,
}
} else {
compiler_error!("unknown M-profile");
}
}
/// Initially we just set the Thumb Mode bit, the minimum required.
const INITIAL_PSR: u32 = 1 << 24;
/// We don't really care about the initial FPU mode; 0 is reasonable.
#[cfg(any(armv7m, armv8m))]
const INITIAL_FPSCR: u32 = 0;
/// Records `tasks` as the system-wide task table.
///
/// If a task table has already been set, panics.
///
/// # Safety
///
/// This stashes a copy of `tasks` without revoking your right to access it,
/// which is a potential aliasing violation if you call `with_task_table`. So
/// don't do that. The normal kernel entry sequences avoid this issue.
pub unsafe fn set_task_table(tasks: &mut [task::Task]) {
let prev_task_table = core::mem::replace(
&mut TASK_TABLE_BASE,
Some(NonNull::from(&mut tasks[0])),
);
// Catch double-uses of this function.
uassert_eq!(prev_task_table, None);
// Record length as well.
TASK_TABLE_SIZE = tasks.len();
}
// Because debuggers need to know the clock frequency to set the SWO clock
// scaler that enables ITM, and because ITM is particularly useful when
// debugging boot failures, this should be set as early in boot as it can
// be.
pub unsafe fn set_clock_freq(tick_divisor: u32) {
CLOCK_FREQ_KHZ = tick_divisor;
}
pub fn reinitialize(task: &mut task::Task) {
*task.save_mut() = SavedState::default();
let initial_stack = task.descriptor().initial_stack;
// Modern ARMvX-M machines require 8-byte stack alignment. Make sure that's
// still true. Note that this carries the risk of panic on task re-init if
// the task table is corrupted -- this is deliberate.
uassert!(initial_stack & 0x7 == 0);
// The remaining state is stored on the stack.
// TODO: this assumes availability of an FPU.
// Use checked operations to get a reference to the exception frame.
let frame_size = core::mem::size_of::<ExtendedExceptionFrame>();
// The subtract below can overflow if the task table is corrupt -- let's
// make that failure a little easier to read:
uassert!(initial_stack as usize >= frame_size);
// Ok. Generate a uslice for the task's starting stack frame.
let mut frame_uslice: USlice<ExtendedExceptionFrame> =
USlice::from_raw(initial_stack as usize - frame_size, 1).unwrap_lite();
// Before we set our frame, find the region that contains our initial stack
// pointer, and zap the region from the base to the stack pointer with a
// distinct (and storied) pattern.
for region in task.region_table().iter() {
if initial_stack < region.base {
continue;
}
if initial_stack > region.base + region.size {
continue;
}
let mut uslice: USlice<u32> = USlice::from_raw(
region.base as usize,
(initial_stack as usize - frame_size - region.base as usize) >> 2,
)
.unwrap_lite();
let zap = task.try_write(&mut uslice).unwrap_lite();
for word in zap.iter_mut() {
*word = 0xbaddcafe;
}
}
let descriptor = task.descriptor();
let frame = &mut task.try_write(&mut frame_uslice).unwrap_lite()[0];
// Conservatively/defensively zero the entire frame.
*frame = ExtendedExceptionFrame::default();
// Now fill in the bits we actually care about.
frame.base.pc = descriptor.entry_point | 1; // for thumb
frame.base.xpsr = INITIAL_PSR;
frame.base.lr = 0xFFFF_FFFF; // trap on return from main
#[cfg(any(armv7m, armv8m))]
{
frame.fpscr = INITIAL_FPSCR;
}
// Set the initial stack pointer, *not* to the stack top, but to the base of
// this frame.
task.save_mut().psp = frame as *const _ as u32;
// Finally, record the EXC_RETURN we'll use to enter the task.
// TODO: this assumes floating point is in use.
task.save_mut().exc_return = EXC_RETURN_CONST;
}
#[cfg(any(armv6m, armv7m))]
pub fn apply_memory_protection(task: &task::Task) {
// We are manufacturing authority to interact with the MPU here, because we
// can't thread a cortex-specific peripheral through an
// architecture-independent API. This approach might bear revisiting later.
let mpu = unsafe {
// At least by not taking a &mut we're confident we're not violating
// aliasing....
&*cortex_m::peripheral::MPU::ptr()
};
for (i, region) in task.region_table().iter().enumerate() {
let rbar = (i as u32) // region number
| (1 << 4) // honor the region number
| region.base;
let ratts = region.attributes;
let xn = !ratts.contains(app::RegionAttributes::EXECUTE);
// These AP encodings are chosen such that we never deny *privileged*
// code (i.e. us) access to the memory.
let ap = if ratts.contains(app::RegionAttributes::WRITE) {
0b011
} else if ratts.contains(app::RegionAttributes::READ) {
0b010
} else {
0b001
};
// Set the TEX/SCB bits to configure memory type, caching policy, and
// shareability (with other cores or masters). See table B3-13 in the
// ARMv7-M ARM. (Settings are identical on v6-M but the sharability and
// TEX bits tend to be ignored.)
let (tex, scb) = if ratts.contains(app::RegionAttributes::DEVICE) {
// Device memory.
(0b000, 0b001)
} else if ratts.contains(app::RegionAttributes::DMA) {
// Conservative settings for normal memory assuming that DMA might
// be a problem:
// - Outer and inner non-cacheable.
// - Shared.
(0b001, 0b100)
} else {
// Aggressive settings for normal memory assume that it is used only
// by this processor:
// - Outer and inner write-back
// - Read and write allocate.
// - Not shared.
(0b001, 0b011)
};
// On v6/7-M the MPU expresses size of a region in log2 form _minus
// one._ So, the minimum allowed size of 32 bytes is represented as 4,
// because `2**(4 + 1) == 32`.
//
// We store sizes in the region table in an architecture-independent
// form (number of bytes) because it simplifies basically everything
// else but this routine. Here we must convert between the two -- and
// quickly, because this is called on every context switch.
//
// The image-generation tools check at build time that region sizes are
// powers of two. So, we can assume that the size has a single 1 bit. We
// can cheaply compute log2 of this by counting trailing zeroes, but
// ARMv7-M doesn't have a native instruction for that -- only leading
// zeroes. The equivalent using leading zeroes is
//
// log2(N) = bits_in_word - 1 - clz(N)
//
// Because we want log2 _minus one_ we compute it as...
//
// log2_m1(N) = bits_in_word - 2 - clz(N)
//
// If the size is zero or one, this subtraction will underflow. This
// should not occur in a valid image, but could occur due to runtime
// flash corruption. Any region size under 32 bytes is illegal on
// ARMv7-M anyway, so panicking is better than triggering possibly
// undefined hardware behavior.
//
// On ARMv6-M, there is no CLZ instruction either. This winds up
// generating decent intrinsic code for `leading_zeros` so we'll live
// with it.
let l2size = 30 - region.size.leading_zeros();
let rasr = (xn as u32) << 28
| ap << 24
| tex << 19
| scb << 16
| l2size << 1
| (1 << 0); // enable
unsafe {
mpu.rbar.write(rbar);
mpu.rasr.write(rasr);
}
}
}
#[cfg(armv8m)]
pub fn apply_memory_protection(task: &task::Task) {
// Sigh cortex-m crate doesn't have armv8-m support
// Let's poke it manually to make sure we're doing this right..
let mpu = unsafe {
// At least by not taking a &mut we're confident we're not violating
// aliasing....
&*cortex_m::peripheral::MPU::ptr()
};
unsafe {
const DISABLE: u32 = 0b000;
const PRIVDEFENA: u32 = 0b100;
// From the ARMv8m MPU manual
//
// Any outstanding memory transactions must be forced to complete by
// executing a DMB instruction and the MPU disabled before it can be
// configured
cortex_m::asm::dmb();
mpu.ctrl.write(DISABLE | PRIVDEFENA);
}
for (i, region) in task.region_table().iter().enumerate() {
// This MPU requires that all regions are 32-byte aligned...in part
// because it stuffs extra stuff into the bottom five bits.
debug_assert_eq!(region.base & 0x1F, 0);
let rnr = i as u32;
let ratts = region.attributes;
let xn = !ratts.contains(app::RegionAttributes::EXECUTE);
// ARMv8m has less granularity than ARMv7m for privilege
// vs non-privilege so there's no way to say that privilege
// can be read write but non-privilge can only be read only
// This _should_ be okay?
let ap = if ratts.contains(app::RegionAttributes::WRITE) {
0b01 // RW by any privilege level
} else if ratts.contains(app::RegionAttributes::READ) {
0b11 // Read only by any privilege level
} else {
0b00 // RW by privilege code only
};
let (mair, sh) = if ratts.contains(app::RegionAttributes::DEVICE) {
// Most restrictive: device memory, outer shared.
(0b00000000, 0b10)
} else if ratts.contains(app::RegionAttributes::DMA) {
// Outer/inner non-cacheable, outer shared.
(0b01000100, 0b10)
} else {
let rw = u32::from(ratts.contains(app::RegionAttributes::READ))
<< 1
| u32::from(ratts.contains(app::RegionAttributes::WRITE));
// write-back transient, not shared
(0b0100_0100 | rw | rw << 4, 0b00)
};
// RLAR = our upper bound
let rlar = region.base + region.size
| (i as u32) << 1 // AttrIndx
| (1 << 0); // enable
// RBAR = the base
let rbar = (xn as u32)
| ap << 1
| (sh as u32) << 3 // sharability
| region.base;
unsafe {
// RNR
core::ptr::write_volatile(0xe000_ed98 as *mut u32, rnr);
// MAIR
if rnr < 4 {
let mut mair0 = (0xe000_edc0 as *const u32).read_volatile();
mair0 = mair0 | (mair as u32) << (rnr * 8);
core::ptr::write_volatile(0xe000_edc0 as *mut u32, mair0);
} else {
let mut mair1 = (0xe000_edc4 as *const u32).read_volatile();
mair1 = mair1 | (mair as u32) << ((rnr - 4) * 8);
core::ptr::write_volatile(0xe000_edc4 as *mut u32, mair1);
}
// RBAR
core::ptr::write_volatile(0xe000_ed9c as *mut u32, rbar);
// RLAR
core::ptr::write_volatile(0xe000_eda0 as *mut u32, rlar);
}
}
unsafe {
const ENABLE: u32 = 0b001;
const PRIVDEFENA: u32 = 0b100;
mpu.ctrl.write(ENABLE | PRIVDEFENA);
// From the ARMv8m MPU manual
//
// The final step is to enable the MPU by writing to MPU_CTRL. Code
// should then execute a memory barrier to ensure that the register
// updates are seen by any subsequent memory accesses. An Instruction
// Synchronization Barrier (ISB) ensures the updated configuration
// [is] used by any subsequent instructions.
cortex_m::asm::dmb();
cortex_m::asm::isb();
}
}
pub fn start_first_task(tick_divisor: u32, task: &task::Task) -> ! {
// Enable faults and set fault/exception priorities to reasonable settings.
// Our goal here is to keep the kernel non-preemptive, which means the
// kernel entry points (SVCall, PendSV, SysTick, interrupt handlers) must be
// at one priority level. Fault handlers need to be higher priority,
// however, so that we can detect faults in the kernel.
//
// Safety: this is actually fairly safe. We're purely lowering priorities
// from their defaults, so it can't cause any surprise preemption or
// anything. But these operations are `unsafe` in the `cortex_m` crate.
unsafe {
let scb = &*cortex_m::peripheral::SCB::ptr();
// Faults on, on the processors that distinguish faults. This
// distinguishes the following faults from HardFault:
//
// - ARMv7+: MEMFAULT, BUSFAULT, USGFAULT
// - ARMv8: SECUREFAULT
cfg_if::cfg_if! {
if #[cfg(armv7m)] {
scb.shcsr.modify(|x| x | 0b111 << 16);
} else if #[cfg(armv8m)] {
scb.shcsr.modify(|x| x | 0b1111 << 16);
} else if #[cfg(armv6m)] {
// This facility is missing.
} else {
compile_error!("missing fault setup for ARM profile");
}
}
// Set fault and standard exception priorities.
cfg_if::cfg_if! {
if #[cfg(armv6m)] {
// ARMv6 only has 4 priority levels and no configurable fault
// priorities. Set priorities of SVCall, SysTick and PendSV to 3
// (the lowest configurable).
scb.shpr[0].modify(|x| x | 0b11 << 30);
scb.shpr[1].modify(|x| x | 0b11 << 22 | 0b11 << 30);
} else if #[cfg(any(armv7m, armv8m))] {
// Set priority of Usage, Bus, MemManage to 0 (highest
// configurable).
scb.shpr[0].write(0x00);
scb.shpr[1].write(0x00);
scb.shpr[2].write(0x00);
// Set priority of SVCall to 0xFF (lowest configurable).
scb.shpr[7].write(0xFF);
// SysTick and PendSV also to 0xFF
scb.shpr[10].write(0xFF);
scb.shpr[11].write(0xFF);
} else {
compile_error!("missing fault priorities for ARM profile");
}
}
#[cfg(any(armv7m, armv8m))]
{
// ARM's default disposition is that division by zero doesn't
// actually fail, but rather returns 0. (!) It's unclear how
// placating this kind of programmatic sloppiness doesn't ultimately
// end in tears; we explicitly configure ourselves to trap on any
// divide by zero.
const DIV_0_TRP: u32 = 1 << 4;
scb.ccr.modify(|x| x | DIV_0_TRP);
}
// Configure the priority of all external interrupts so that they can't
// preempt the kernel.
let nvic = &*cortex_m::peripheral::NVIC::ptr();
cfg_if::cfg_if! {
if #[cfg(armv6m)] {
// On ARMv6 there are 8 IPR registers, each containing 4
// interrupt priorities. Only 2 bits, stored at bits[7:6], are
// used for the priority level, giving a range of 0-192 in steps
// of 64. Writes to the other bits are ignored, so we just set
// everything high, i.e. the lowest priority. For more
// information see:
//
// ARMv6-M Architecture Reference Manual section B3.4.7
//
// Do not believe what the docs for the `cortex_m` crate suggest
// -- the IPR registers on ARMv6M are 32-bits wide.
for i in 0..8 {
nvic.ipr[i].write(0xFFFF_FFFF);
}
} else if #[cfg(any(armv7m, armv8m))] {
// How many IRQs have we got on ARMv7+? This information is
// stored in a separate area of the address space, away from the
// NVIC, and is (presumably due to an oversight) not present in
// the cortex_m API, so let's fake it.
let ictr = (0xe000_e004 as *const u32).read_volatile();
// This gives interrupt count in blocks of 32, minus 1, so there
// are always at least 32 interrupts.
let irq_block_count = (ictr as usize & 0xF) + 1;
let irq_count = irq_block_count * 32;
// Blindly poke all the interrupts to 0xFF. IPR registers on
// ARMv7/8 are modeled as `u8` by `cortex_m`, unlike on ARMv6.
// We're explicit with the `u8` suffix below to ensure that we
// notice if this changes.
for i in 0..irq_count {
nvic.ipr[i].write(0xFFu8);
}
} else {
compile_error!("missing IRQ priorities for ARM profile");
}
}
}
// Safety: this, too, is safe in practice but unsafe in API.
unsafe {
// Configure the timer.
let syst = &*cortex_m::peripheral::SYST::ptr();
// Program reload value.
syst.rvr.write(tick_divisor - 1);
// Clear current value.
syst.cvr.write(0);
// Enable counter and interrupt.
syst.csr.modify(|v| v | 0b111);
}
// We are manufacturing authority to interact with the MPU here, because we
// can't thread a cortex-specific peripheral through an
// architecture-independent API. This approach might bear revisiting later.
let mpu = unsafe {
// At least by not taking a &mut we're confident we're not violating
// aliasing....
&*cortex_m::peripheral::MPU::ptr()
};
const ENABLE: u32 = 0b001;
const PRIVDEFENA: u32 = 0b100;
// Safety: this has no memory safety implications. The worst it can do is
// cause us to fault, which is safe. The register API doesn't know this.
unsafe {
mpu.ctrl.write(ENABLE | PRIVDEFENA);
}
unsafe {
CURRENT_TASK_PTR = Some(NonNull::from(task));
}
extern "C" {
// Exposed by the linker script.
static _stack_base: u32;
}
// Safety: this is setting the Main stack pointer (i.e. kernel/interrupt
// stack pointer) limit register. There are two potential outcomes from
// this:
// 1. We proceed without issue because we have not yet overflowed our stack.
// 2. We take an immediate fault.
//
// Both these outcomes are safe, even if the second one is annoying.
#[cfg(armv8m)]
unsafe {
cortex_m::register::msplim::write(
core::ptr::addr_of!(_stack_base) as u32
);
}
// Safety: this is setting the Process (task) stack pointer, which has no
// effect _assuming_ this code is running on the Main (kernel) stack.
unsafe {
cortex_m::register::psp::write(task.save().psp);
}
// Run the final pre-kernel assembly sequence to set up the kernel
// environment!
//
// Our basic goal here is to flip into Handler mode (i.e. interrupt state)
// so that we can switch Thread mode (not-interrupt state) to unprivileged
// and running off the Process Stack Pointer. The easiest way to do this on
// ARM-M is by entering Handler mode by a trap. We use SVC, which we also
// use for system calls; the SVC entry sequence (also in this file) has code
// to detect this condition and do kernel startup rather than processing it
// as a syscall.
cfg_if::cfg_if! {
if #[cfg(armv6m)] {
unsafe {
asm!("
@ restore the callee-save registers
ldm r0!, {{r4-r7}}
ldm r0, {{r0-r3}}
mov r11, r3
mov r10, r2
mov r9, r1
mov r8, r0
@ Trap into the kernel.
svc #0xFF
@ noreturn generates a UDF here in case that should return.
",
in("r0") &task.save().r4,
options(noreturn),
)
}
} else if #[cfg(any(armv7m, armv8m))] {
unsafe {
asm!("
@ Restore callee-save registers.
ldm {task}, {{r4-r11}}
@ Trap into the kernel.
svc #0xFF
@ noreturn generates a UDF here in case that should return.
",
task = in(reg) &task.save().r4,
options(noreturn),
)
}
} else {
compile_error!("missing kernel bootstrap sequence for ARM profile");
}
}
}
/// Handler that gets linked into the vector table for the Supervisor Call (SVC)
/// instruction. (Name is dictated by the `cortex_m` crate.)
#[allow(non_snake_case)]
#[naked]
#[no_mangle]
pub unsafe extern "C" fn SVCall() {
// TODO: could shave several cycles off SVC entry with more careful ordering
// of instructions below, though the precise details depend on how complex
// of an M-series processor you're targeting -- so I've punted on this for
// the time being.
// All the syscall handlers use the same strategy, but the implementation
// differs on different profile variants.
//
// First, we inspect LR, which on exception entry contains bits describing
// the _previous_ (interrupted) processor state. We can use this to detect
// if the SVC came from the Main (interrupt) stack. This only happens once,
// during startup, so we vector to a different routine in this case.
//
// We then store the calling task's context into the TCB.
//
// Then, we call into `syscall_entry`.
//
// After that, we repeat the same steps in the opposite order to restore
// task context (possibly for a different task!).
cfg_if::cfg_if! {
if #[cfg(armv6m)] {
asm!("
@ Inspect LR to figure out the caller's mode.
mov r0, lr
ldr r1, =0xFFFFFFF3
bics r0, r0, r1
@ Is the call coming from thread mode + main stack, i.e.
@ from the kernel startup routine?
cmp r0, #0x8
@ If so, this is startup; jump ahead. The common case falls
@ through because branch-not-taken tends to be faster on small
@ cores.
beq 1f
@ store volatile state.
@ first, get a pointer to the current task.
ldr r0, =CURRENT_TASK_PTR
ldr r1, [r0]
@ now, store volatile registers, plus the PSP, plus LR.
stm r1!, {{r4-r7}}
mov r4, r8
mov r5, r9
mov r6, r10
mov r7, r11
stm r1!, {{r4-r7}}
mrs r4, PSP
mov r5, lr
stm r1!, {{r4, r5}}
@ syscall number is passed in r11. Move it into r0 to pass
@ it as an argument to the handler, then call the handler.
mov r0, r11
bl syscall_entry
@ we're returning back to *some* task, maybe not the same one.
ldr r0, =CURRENT_TASK_PTR
ldr r0, [r0]
@ restore volatile registers, plus PSP. We will do this in
@ slightly reversed order for efficiency. First, do the high
@ ones.
movs r1, r0
adds r1, r1, #(4 * 4)
ldm r1!, {{r4-r7}}
mov r11, r7
mov r10, r6
mov r9, r5
mov r8, r4
ldm r1!, {{r4, r5}}
msr PSP, r4
mov lr, r5
@ Now that we no longer need r4-r7 as temporary registers,
@ restore them too.
ldm r0!, {{r4-r7}}
@ resume
bx lr
1: @ starting up the first task.
@ Drop privilege in Thread mode.
movs r0, #1
msr CONTROL, r0
@ note: no barrier here because exc return serves as barrier
@ Manufacture a new EXC_RETURN to change the processor mode
@ when we return.
ldr r0, ={exc_return}
mov lr, r0
bx lr @ branch into user mode
",
exc_return = const EXC_RETURN_CONST as u32,
options(noreturn),
)
} else if #[cfg(any(armv7m, armv8m))] {
asm!("
@ Inspect LR to figure out the caller's mode.
mov r0, lr
mov r1, #0xFFFFFFF3
bic r0, r1
@ Is the call coming from thread mode + main stack, i.e.
@ from the kernel startup routine?
cmp r0, #0x8
@ If so, this is startup; jump ahead. The common case falls
@ through because branch-not-taken tends to be faster on small
@ cores.
beq 1f
@ store volatile state.
@ first, get a pointer to the current task.
movw r0, #:lower16:CURRENT_TASK_PTR
movt r0, #:upper16:CURRENT_TASK_PTR
ldr r1, [r0]
@ fetch the process-mode stack pointer.
@ fetching into r12 means the order in the stm below is right.
mrs r12, PSP
@ now, store volatile registers, plus the PSP in r12, plus LR.
stm r1!, {{r4-r12, lr}}
vstm r1, {{s16-s31}}
@ syscall number is passed in r11. Move it into r0 to pass it as
@ an argument to the handler, then call the handler.
movs r0, r11
bl syscall_entry
@ we're returning back to *some* task, maybe not the same one.
movw r0, #:lower16:CURRENT_TASK_PTR
movt r0, #:upper16:CURRENT_TASK_PTR
ldr r0, [r0]
@ restore volatile registers, plus load PSP into r12
ldm r0!, {{r4-r12, lr}}
vldm r0, {{s16-s31}}
msr PSP, r12
@ resume
bx lr
1: @ starting up the first task.
movs r0, #1 @ get bitmask to...
msr CONTROL, r0 @ ...shed privs from thread mode.
@ note: now barrier here because exc return
@ serves as barrier
mov lr, {exc_return} @ materialize EXC_RETURN value to
@ return into thread mode, PSP, FP on
bx lr @ branch into user mode
",
exc_return = const EXC_RETURN_CONST as u32,
options(noreturn),
)
} else {
compile_error!("missing SVCall impl for ARM profile.");
}
}
}
/// Manufacture a mutable/exclusive reference to the task table from thin air
/// and hand it to `body`. This bypasses borrow checking and should only be used
/// at kernel entry points, then passed around.
///
/// Because the lifetime of the reference passed into `body` is anonymous, the
/// reference can't easily be stored, which is deliberate.
///
/// # Safety
///
/// You can use this safely at kernel entry points, exactly once, to create a
/// reference to the task table.
pub unsafe fn with_task_table<R>(
body: impl FnOnce(&mut [task::Task]) -> R,
) -> R {
let tasks = core::slice::from_raw_parts_mut(
TASK_TABLE_BASE.expect("kernel not started").as_mut(),
TASK_TABLE_SIZE,
);
body(tasks)
}