Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
- run: cargo doc --workspace --no-deps --target thumbv7em-none-eabihf

host:
name: host crates (rugus-proto + rugus-cli)
name: host crates (rugus-proto + rugus-cli + rugus-host-tests)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -86,12 +86,14 @@ jobs:
workspaces: |
crates/rugus-proto
crates/rugus-cli
crates/rugus-host-tests
- name: Install BLE/serial system deps
run: sudo apt-get update && sudo apt-get install -y libudev-dev libdbus-1-dev pkg-config
- name: rustfmt (host crates)
run: |
cargo fmt --manifest-path crates/rugus-proto/Cargo.toml --all -- --check
cargo fmt --manifest-path crates/rugus-cli/Cargo.toml --all -- --check
cargo fmt --manifest-path crates/rugus-host-tests/Cargo.toml --all -- --check
- name: clippy + test rugus-proto
run: |
cargo clippy --manifest-path crates/rugus-proto/Cargo.toml --all-targets -- -D warnings
Expand All @@ -100,3 +102,7 @@ jobs:
run: |
cargo clippy --manifest-path crates/rugus-cli/Cargo.toml --all-targets -- -D warnings
cargo build --manifest-path crates/rugus-cli/Cargo.toml
- name: clippy + test rugus-host-tests (scheduler + MPU sandbox + ABI)
run: |
cargo clippy --manifest-path crates/rugus-host-tests/Cargo.toml --all-targets -- -D warnings
cargo test --manifest-path crates/rugus-host-tests/Cargo.toml
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ members = [
exclude = [
"crates/rugus-proto",
"crates/rugus-cli",
"crates/rugus-host-tests",
]

[workspace.package]
Expand Down
2 changes: 1 addition & 1 deletion crates/rugus-arch-cortex-m/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub struct CortexM;
/// Los campos `mpu_*` se escriben en la región [`mpu::region::APP_STACK`] dentro
/// del propio context switch (PendSV/bootstrap), de forma atómica con la
/// conmutación de registros: garantizan que la región MPU del stack corresponde
/// SIEMPRE a la tarea que se restaura (ver [`mpu::app_region_for`]). El orden de
/// SIEMPRE a la tarea que se restaura (ver `mpu::app_region_for`). El orden de
/// los campos es ABI con el ASM de `switch.rs` (offsets 0/4/8/12).
#[repr(C)]
#[derive(Default)]
Expand Down
44 changes: 22 additions & 22 deletions crates/rugus-arch-cortex-m/src/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,21 @@ global_asm!(
// Región MPU APP_STACK de la tarea entrante, atómica con el switch: r1 sigue
// apuntando al Context. Offsets 8/12 = mpu_rbar/mpu_rasr. RASR=0 deshabilita
// (tareas privilegiadas). Inmune al desincronizado del modelo diferido.
" ldr r3, =0xE000ED98", // MPU_RNR
" movs r0, #4", // región APP_STACK
" ldr r3, =0xE000ED98", // MPU_RNR
" movs r0, #4", // región APP_STACK
" str r0, [r3]",
" ldr r0, [r1, #8]", // Context.mpu_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #12]", // Context.mpu_rasr
" str r0, [r3, #8]", // RASR
" ldr r0, [r1, #8]", // Context.mpu_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #12]", // Context.mpu_rasr
" str r0, [r3, #8]", // RASR
// Región MPU STACK_GUARD (7) de la tarea entrante: 32 B sin acceso en la base
// del stack. Offsets 16/20 = mpu_guard_rbar/mpu_guard_rasr.
" movs r0, #7", // región STACK_GUARD
" movs r0, #7", // región STACK_GUARD
" str r0, [r3]",
" ldr r0, [r1, #16]", // Context.mpu_guard_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #20]", // Context.mpu_guard_rasr
" str r0, [r3, #8]", // RASR
" ldr r0, [r1, #16]", // Context.mpu_guard_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #20]", // Context.mpu_guard_rasr
" str r0, [r3, #8]", // RASR
" dsb",
" isb",
"3:",
Expand Down Expand Up @@ -328,20 +328,20 @@ global_asm!(
" msr control, r3",
" isb",
// Región MPU APP_STACK de la tarea entrante (ver variante eabihf).
" ldr r3, =0xE000ED98", // MPU_RNR
" movs r0, #4", // región APP_STACK
" ldr r3, =0xE000ED98", // MPU_RNR
" movs r0, #4", // región APP_STACK
" str r0, [r3]",
" ldr r0, [r1, #8]", // Context.mpu_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #12]", // Context.mpu_rasr
" str r0, [r3, #8]", // RASR
" ldr r0, [r1, #8]", // Context.mpu_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #12]", // Context.mpu_rasr
" str r0, [r3, #8]", // RASR
// Región MPU STACK_GUARD (7) de la tarea entrante (ver variante eabihf).
" movs r0, #7", // región STACK_GUARD
" movs r0, #7", // región STACK_GUARD
" str r0, [r3]",
" ldr r0, [r1, #16]", // Context.mpu_guard_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #20]", // Context.mpu_guard_rasr
" str r0, [r3, #8]", // RASR
" ldr r0, [r1, #16]", // Context.mpu_guard_rbar
" str r0, [r3, #4]", // RBAR
" ldr r0, [r1, #20]", // Context.mpu_guard_rasr
" str r0, [r3, #8]", // RASR
" dsb",
" isb",
"3:",
Expand Down
2 changes: 1 addition & 1 deletion crates/rugus-arch-cortex-m/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! El `Arch` trait se mantiene mínimo y agnóstico de periféricos; el tiempo
//! es una capacidad propia del backend Cortex-M, igual que la MPU. Un tick de
//! SysTick a 1 kHz incrementa un contador de milisegundos en
//! [`now_ms`](crate::time::now_ms). La plataforma decide la frecuencia del core
//! [`now_ms`]. La plataforma decide la frecuencia del core
//! ([`init`]) y construye sus primitivas de espera cooperativa sobre `now_ms`.
//!
//! `u32` de milisegundos desborda a ~49,7 días de uptime continuo; suficiente
Expand Down
5 changes: 5 additions & 0 deletions crates/rugus-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ log = ["dep:defmt"]
# Fuera del TCB mínimo: las personalidades sin heap (p. ej. lite/F103) NO lo
# activan. Solo las placas con región de heap (full/F4/F7) lo habilitan.
alloc = ["dep:linked_list_allocator"]
# Expone semillas de prueba (`*_for_test`) para tests host del scheduler sin un
# `Arch` real que salte a tareas. OFF por defecto: los builds embebidos NUNCA la
# activan, así que no añade superficie ni coste al binario del kernel. La usa el
# crate host `rugus-host-tests` (excluido del workspace embebido).
test-util = []
35 changes: 33 additions & 2 deletions crates/rugus-core/src/sched.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub struct Scheduler<A: Arch> {
/// turno y `from` sea siempre la misma.
last_served: [usize; PRIORITY_BANDS],
/// Ticks de SysTick acumulados en la rodaja actual. [`Self::preempt_tick`]
/// lo incrementa; al llegar a [`SLICE_TICKS`] fuerza un cambio de contexto
/// lo incrementa; al llegar a `SLICE_TICKS` fuerza un cambio de contexto
/// preemptivo y lo reinicia.
slice_ticks: u32,
}
Expand Down Expand Up @@ -227,7 +227,7 @@ impl<A: Arch> Scheduler<A> {

/// Preempción por tick de SysTick: invocada desde la ISR de SysTick (1 ms).
///
/// Acumula ticks; al vencer la rodaja ([`SLICE_TICKS`]) elige round-robin la
/// Acumula ticks; al vencer la rodaja (`SLICE_TICKS`) elige round-robin la
/// siguiente tarea de la banda de mayor prioridad lista y pende un cambio de
/// contexto. El PendSV tiene la misma prioridad que SysTick y un núm. de
/// excepción menor, así que hace *tail-chain* al salir de esta ISR.
Expand Down Expand Up @@ -539,6 +539,37 @@ impl<A: Arch> Scheduler<A> {
// SAFETY: idx < count y solo se escribe en spawn.
unsafe { self.tasks[idx].assume_init_ref() }
}

/// Marca el scheduler como arrancado y elige la primera tarea SIN saltar a
/// ella (no invoca [`Arch::start_first`], que nunca retorna). Habilita
/// pruebas host de [`Self::yield_now`]/[`Self::preempt_tick`]/[`Self::sleep_ms`]
/// con un `Arch` simulado cuyo `switch_context` es un no-op.
///
/// Solo con la feature `test-util`; ausente en los builds embebidos.
#[cfg(feature = "test-util")]
pub fn force_start_for_test(&mut self) {
if self.count == 0 {
return;
}
self.started = true;
self.current = self.pick_next(usize::MAX);
}

/// Marca la tarea `idx` como muerta (`Killed`) sin pasar por el camino de
/// fault (que termina en [`Arch::resume_after_fault`], que nunca retorna).
/// Permite a las pruebas host ejercitar [`Self::respawn`] y los contadores de
/// salud sin un `Arch` real.
///
/// Solo con la feature `test-util`; ausente en los builds embebidos.
#[cfg(feature = "test-util")]
pub fn mark_killed_for_test(&mut self, idx: usize) {
if idx < self.count {
// SAFETY: idx < count; slot inicializado en spawn.
unsafe {
self.tasks[idx].assume_init_mut().state = TaskState::Killed;
}
}
}
}

impl<A: Arch> Default for Scheduler<A> {
Expand Down
6 changes: 6 additions & 0 deletions crates/rugus-core/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ pub fn dispatch(id: Id, args: [u32; 4]) -> i32 {
}

/// Trampolines userland — ejecutan `SVC #imm8` (ARMv7-M ABI).
///
/// Solo se compilan en targets ARM: usan ensamblador en línea con registros
/// `r0..r3`, inexistentes en el triple host donde corren los tests
/// (`rugus-host-tests`). El resto del ABI (`Id`, `dispatch`,
/// `validate_user_range`) es agnóstico y sí se prueba en host.
#[cfg(target_arch = "arm")]
pub mod user {
use super::Id;

Expand Down
2 changes: 1 addition & 1 deletion crates/rugus-hal-stm32f1/src/postmortem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl ResetCause {
/// Registro de fault recuperado del dominio de respaldo.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FaultRecord {
/// Código de [`rugus_core::fault::FaultKind`] (1=MemManage…4=HardFault).
/// Código de `rugus_core::fault::FaultKind` (1=MemManage…4=HardFault).
pub kind: u8,
/// Id de la tarea que faultó.
pub task: u8,
Expand Down
5 changes: 4 additions & 1 deletion crates/rugus-hal-stm32f4/src/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ impl Adc {
write_volatile(RCC_APB2ENR as *mut u32, v | ADC1EN);
let _ = read_volatile(RCC_APB2ENR as *const u32);

write_volatile(ADC_CCR as *mut u32, read_volatile(ADC_CCR as *const u32) | CCR_TSVREFE);
write_volatile(
ADC_CCR as *mut u32,
read_volatile(ADC_CCR as *const u32) | CCR_TSVREFE,
);
// Tiempo de muestreo máx (0b111 = 480 ciclos) para el canal 17 (SMP17
// en SMPR1 bits 23:21). VREFINT necesita muestreo largo.
write_reg(SMPR1, 0b111 << 21);
Expand Down
6 changes: 5 additions & 1 deletion crates/rugus-hal-stm32f4/src/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ impl GpioPin for Pin {
// ventana RMW sobre ODR). Cooperativo, single-thread.
unsafe {
let on = read_reg(self.port, ODR) & (1 << self.pin) != 0;
let bit = if on { 1 << (self.pin + 16) } else { 1 << self.pin };
let bit = if on {
1 << (self.pin + 16)
} else {
1 << self.pin
};
write_reg(self.port, BSRR, bit);
}
Ok(())
Expand Down
9 changes: 2 additions & 7 deletions crates/rugus-hal-stm32f4/src/usart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,7 @@ impl Usart2 {
},
);
configure(pclk1, baud, true);
Self {
_tx: tx,
_rx: None,
}
Self { _tx: tx, _rx: None }
}

/// Escribe un byte (polling TXE).
Expand Down Expand Up @@ -160,9 +157,7 @@ impl SerialPort for Usart2 {

fn flush(&mut self) -> Result<(), Self::Error> {
// SAFETY: espera a transmisión completa (TC).
unsafe {
while read_reg(SR) & SR_TC == 0 {}
}
unsafe { while read_reg(SR) & SR_TC == 0 {} }
Ok(())
}
}
Expand Down
5 changes: 4 additions & 1 deletion crates/rugus-hal-stm32f7/src/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ impl Adc {
write_volatile(RCC_APB2ENR as *mut u32, v | ADC1EN);
let _ = read_volatile(RCC_APB2ENR as *const u32);

write_volatile(ADC_CCR as *mut u32, read_volatile(ADC_CCR as *const u32) | CCR_TSVREFE);
write_volatile(
ADC_CCR as *mut u32,
read_volatile(ADC_CCR as *const u32) | CCR_TSVREFE,
);
// Tiempo de muestreo máx (0b111 = 480 ciclos) para el canal 17 (SMP17
// en SMPR1 bits 23:21). VREFINT necesita muestreo largo.
write_reg(SMPR1, 0b111 << 21);
Expand Down
6 changes: 5 additions & 1 deletion crates/rugus-hal-stm32f7/src/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,11 @@ impl GpioPin for Pin {
// ventana RMW sobre ODR). Cooperativo, single-thread.
unsafe {
let on = read_reg(self.port, ODR) & (1 << self.pin) != 0;
let bit = if on { 1 << (self.pin + 16) } else { 1 << self.pin };
let bit = if on {
1 << (self.pin + 16)
} else {
1 << self.pin
};
write_reg(self.port, BSRR, bit);
}
Ok(())
Expand Down
11 changes: 3 additions & 8 deletions crates/rugus-hal-stm32f7/src/usart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,7 @@ impl Usart2 {
},
);
configure(pclk1, baud, true);
Self {
_tx: tx,
_rx: None,
}
Self { _tx: tx, _rx: None }
}

/// Escribe un byte (polling TXE).
Expand Down Expand Up @@ -159,9 +156,7 @@ impl SerialPort for Usart2 {

fn flush(&mut self) -> Result<(), Self::Error> {
// SAFETY: espera a transmisión completa (TC).
unsafe {
while read_reg(ISR) & ISR_TC == 0 {}
}
unsafe { while read_reg(ISR) & ISR_TC == 0 {} }
Ok(())
}
}
Expand All @@ -184,7 +179,7 @@ fn configure(pclk1: u32, baud: u32, loopback: bool) {
let _ = read_volatile(RCC_APB1ENR as *const u32);

write_reg(CR1, 0); // UE=0 mientras configuramos.
// F7: con OVER8=0 el BRR es directamente el divisor (sin partir mantisa).
// F7: con OVER8=0 el BRR es directamente el divisor (sin partir mantisa).
write_reg(BRR, (pclk1 + baud / 2) / baud);
write_reg(CR3, if loopback { CR3_HDSEL } else { 0 });
write_reg(CR1, CR1_UE | CR1_TE | CR1_RE);
Expand Down
14 changes: 14 additions & 0 deletions crates/rugus-host-tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions crates/rugus-host-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "rugus-host-tests"
description = "Tests host (std) de la lógica arch-agnóstica de rugus-core: scheduler, sandbox MPU y ABI de syscalls. Excluido del workspace embebido; corre en el triple nativo."
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
license = "MIT OR Apache-2.0"
authors = ["Luis Guillermo Hernandez <luiguihez93@gmail.com>"]
repository = "https://github.com/guillo93/Rugus"

# Crate de pruebas: no produce artefacto reutilizable, solo aloja un `Arch`
# simulado y los tests. No se publica.
publish = false

[lib]
name = "rugus_host_tests"

[dependencies]
# Activa la feature `test-util` para las semillas de prueba del scheduler. Como
# este crate está EXCLUIDO del workspace embebido, esa feature no se filtra a los
# binarios del kernel (la unificación de features no cruza la frontera).
rugus-core = { path = "../rugus-core", features = ["test-util"] }
Loading