From 9da38a69da30ae16982f1dcf55890d159cf38cf4 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 30 Mar 2026 13:09:59 +0200 Subject: rv: Unify DA event handling functions across monitor types The DA event handling functions are mostly duplicated because the per-task monitors need to propagate the task struct while others do not. Unify the functions, handle the difference by always passing an identifier which is the task's pid for per-task monitors but is ignored for the other types. Only keep the actual tracepoint calling separated. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260330111010.153663-2-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/rv/da_monitor.h | 303 +++++++++++++++++++++--------------------------- 1 file changed, 132 insertions(+), 171 deletions(-) (limited to 'include') diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h index 7511f5464c48..89a0b81d4b3e 100644 --- a/include/rv/da_monitor.h +++ b/include/rv/da_monitor.h @@ -28,6 +28,13 @@ static struct rv_monitor rv_this; +/* + * Type for the target id, default to int but can be overridden. + */ +#ifndef da_id_type +#define da_id_type int +#endif + static void react(enum states curr_state, enum events event) { rv_react(&rv_this, @@ -97,90 +104,6 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon) return 1; } -#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU -/* - * Event handler for implicit monitors. Implicit monitor is the one which the - * handler does not need to specify which da_monitor to manipulate. Examples - * of implicit monitor are the per_cpu or the global ones. - * - * Retry in case there is a race between getting and setting the next state, - * warn and reset the monitor if it runs out of retries. The monitor should be - * able to handle various orders. - */ - -static inline bool da_event(struct da_monitor *da_mon, enum events event) -{ - enum states curr_state, next_state; - - curr_state = READ_ONCE(da_mon->curr_state); - for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) { - next_state = model_get_next_state(curr_state, event); - if (next_state == INVALID_STATE) { - react(curr_state, event); - CONCATENATE(trace_error_, MONITOR_NAME)( - model_get_state_name(curr_state), - model_get_event_name(event)); - return false; - } - if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) { - CONCATENATE(trace_event_, MONITOR_NAME)( - model_get_state_name(curr_state), - model_get_event_name(event), - model_get_state_name(next_state), - model_is_final_state(next_state)); - return true; - } - } - - trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event)); - pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS) - " retries reached for event %s, resetting monitor %s", - model_get_event_name(event), __stringify(MONITOR_NAME)); - return false; -} - -#elif RV_MON_TYPE == RV_MON_PER_TASK -/* - * Event handler for per_task monitors. - * - * Retry in case there is a race between getting and setting the next state, - * warn and reset the monitor if it runs out of retries. The monitor should be - * able to handle various orders. - */ - -static inline bool da_event(struct da_monitor *da_mon, struct task_struct *tsk, - enum events event) -{ - enum states curr_state, next_state; - - curr_state = READ_ONCE(da_mon->curr_state); - for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) { - next_state = model_get_next_state(curr_state, event); - if (next_state == INVALID_STATE) { - react(curr_state, event); - CONCATENATE(trace_error_, MONITOR_NAME)(tsk->pid, - model_get_state_name(curr_state), - model_get_event_name(event)); - return false; - } - if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) { - CONCATENATE(trace_event_, MONITOR_NAME)(tsk->pid, - model_get_state_name(curr_state), - model_get_event_name(event), - model_get_state_name(next_state), - model_is_final_state(next_state)); - return true; - } - } - - trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event)); - pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS) - " retries reached for event %s, resetting monitor %s", - model_get_event_name(event), __stringify(MONITOR_NAME)); - return false; -} -#endif /* RV_MON_TYPE */ - #if RV_MON_TYPE == RV_MON_GLOBAL /* * Functions to define, init and get a global monitor. @@ -335,115 +258,179 @@ static inline void da_monitor_destroy(void) #if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU /* - * Handle event for implicit monitor: da_get_monitor() will figure out - * the monitor. + * Trace events for implicit monitors. Implicit monitor is the one which the + * handler does not need to specify which da_monitor to manipulate. Examples + * of implicit monitor are the per_cpu or the global ones. */ -static inline void __da_handle_event(struct da_monitor *da_mon, - enum events event) +static inline void da_trace_event(struct da_monitor *da_mon, + char *curr_state, char *event, + char *next_state, bool is_final, + da_id_type id) { - bool retval; + CONCATENATE(trace_event_, MONITOR_NAME)(curr_state, event, next_state, + is_final); +} - retval = da_event(da_mon, event); - if (!retval) - da_monitor_reset(da_mon); +static inline void da_trace_error(struct da_monitor *da_mon, + char *curr_state, char *event, + da_id_type id) +{ + CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event); } +#elif RV_MON_TYPE == RV_MON_PER_TASK /* - * da_handle_event - handle an event + * Trace events for per_task monitors, report the PID of the task. */ -static inline void da_handle_event(enum events event) -{ - struct da_monitor *da_mon = da_get_monitor(); - bool retval; - retval = da_monitor_handling_event(da_mon); - if (!retval) - return; +static inline void da_trace_event(struct da_monitor *da_mon, + char *curr_state, char *event, + char *next_state, bool is_final, + da_id_type id) +{ + CONCATENATE(trace_event_, MONITOR_NAME)(id, curr_state, event, + next_state, is_final); +} - __da_handle_event(da_mon, event); +static inline void da_trace_error(struct da_monitor *da_mon, + char *curr_state, char *event, + da_id_type id) +{ + CONCATENATE(trace_error_, MONITOR_NAME)(id, curr_state, event); } +#endif /* RV_MON_TYPE */ /* - * da_handle_start_event - start monitoring or handle event - * - * This function is used to notify the monitor that the system is returning - * to the initial state, so the monitor can start monitoring in the next event. - * Thus: + * da_event - handle an event for the da_mon * - * If the monitor already started, handle the event. - * If the monitor did not start yet, start the monitor but skip the event. + * This function is valid for both implicit and id monitors. + * Retry in case there is a race between getting and setting the next state, + * warn and reset the monitor if it runs out of retries. The monitor should be + * able to handle various orders. */ -static inline bool da_handle_start_event(enum events event) +static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_type id) { - struct da_monitor *da_mon; + enum states curr_state, next_state; - if (!da_monitor_enabled()) - return 0; + curr_state = READ_ONCE(da_mon->curr_state); + for (int i = 0; i < MAX_DA_RETRY_RACING_EVENTS; i++) { + next_state = model_get_next_state(curr_state, event); + if (next_state == INVALID_STATE) { + react(curr_state, event); + da_trace_error(da_mon, model_get_state_name(curr_state), + model_get_event_name(event), id); + return false; + } + if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) { + da_trace_event(da_mon, model_get_state_name(curr_state), + model_get_event_name(event), + model_get_state_name(next_state), + model_is_final_state(next_state), id); + return true; + } + } + + trace_rv_retries_error(__stringify(MONITOR_NAME), model_get_event_name(event)); + pr_warn("rv: " __stringify(MAX_DA_RETRY_RACING_EVENTS) + " retries reached for event %s, resetting monitor %s", + model_get_event_name(event), __stringify(MONITOR_NAME)); + return false; +} - da_mon = da_get_monitor(); +static inline void __da_handle_event_common(struct da_monitor *da_mon, + enum events event, da_id_type id) +{ + if (!da_event(da_mon, event, id)) + da_monitor_reset(da_mon); +} +static inline void __da_handle_event(struct da_monitor *da_mon, + enum events event, da_id_type id) +{ + if (da_monitor_handling_event(da_mon)) + __da_handle_event_common(da_mon, event, id); +} + +static inline bool __da_handle_start_event(struct da_monitor *da_mon, + enum events event, da_id_type id) +{ + if (!da_monitor_enabled()) + return 0; if (unlikely(!da_monitoring(da_mon))) { da_monitor_start(da_mon); return 0; } - __da_handle_event(da_mon, event); + __da_handle_event_common(da_mon, event, id); return 1; } -/* - * da_handle_start_run_event - start monitoring and handle event - * - * This function is used to notify the monitor that the system is in the - * initial state, so the monitor can start monitoring and handling event. - */ -static inline bool da_handle_start_run_event(enum events event) +static inline bool __da_handle_start_run_event(struct da_monitor *da_mon, + enum events event, da_id_type id) { - struct da_monitor *da_mon; - if (!da_monitor_enabled()) return 0; - - da_mon = da_get_monitor(); - if (unlikely(!da_monitoring(da_mon))) da_monitor_start(da_mon); - __da_handle_event(da_mon, event); + __da_handle_event_common(da_mon, event, id); return 1; } -#elif RV_MON_TYPE == RV_MON_PER_TASK +#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU /* - * Handle event for per task. + * Handle event for implicit monitor: da_get_monitor() will figure out + * the monitor. */ -static inline void __da_handle_event(struct da_monitor *da_mon, - struct task_struct *tsk, enum events event) +/* + * da_handle_event - handle an event + */ +static inline void da_handle_event(enum events event) { - bool retval; + __da_handle_event(da_get_monitor(), event, 0); +} - retval = da_event(da_mon, tsk, event); - if (!retval) - da_monitor_reset(da_mon); +/* + * da_handle_start_event - start monitoring or handle event + * + * This function is used to notify the monitor that the system is returning + * to the initial state, so the monitor can start monitoring in the next event. + * Thus: + * + * If the monitor already started, handle the event. + * If the monitor did not start yet, start the monitor but skip the event. + */ +static inline bool da_handle_start_event(enum events event) +{ + return __da_handle_start_event(da_get_monitor(), event, 0); } /* - * da_handle_event - handle an event + * da_handle_start_run_event - start monitoring and handle event + * + * This function is used to notify the monitor that the system is in the + * initial state, so the monitor can start monitoring and handling event. */ -static inline void da_handle_event(struct task_struct *tsk, enum events event) +static inline bool da_handle_start_run_event(enum events event) { - struct da_monitor *da_mon = da_get_monitor(tsk); - bool retval; + return __da_handle_start_run_event(da_get_monitor(), event, 0); +} - retval = da_monitor_handling_event(da_mon); - if (!retval) - return; +#elif RV_MON_TYPE == RV_MON_PER_TASK +/* + * Handle event for per task. + */ - __da_handle_event(da_mon, tsk, event); +/* + * da_handle_event - handle an event + */ +static inline void da_handle_event(struct task_struct *tsk, enum events event) +{ + __da_handle_event(da_get_monitor(tsk), event, tsk->pid); } /* @@ -459,21 +446,7 @@ static inline void da_handle_event(struct task_struct *tsk, enum events event) static inline bool da_handle_start_event(struct task_struct *tsk, enum events event) { - struct da_monitor *da_mon; - - if (!da_monitor_enabled()) - return 0; - - da_mon = da_get_monitor(tsk); - - if (unlikely(!da_monitoring(da_mon))) { - da_monitor_start(da_mon); - return 0; - } - - __da_handle_event(da_mon, tsk, event); - - return 1; + return __da_handle_start_event(da_get_monitor(tsk), event, tsk->pid); } /* @@ -485,19 +458,7 @@ static inline bool da_handle_start_event(struct task_struct *tsk, static inline bool da_handle_start_run_event(struct task_struct *tsk, enum events event) { - struct da_monitor *da_mon; - - if (!da_monitor_enabled()) - return 0; - - da_mon = da_get_monitor(tsk); - - if (unlikely(!da_monitoring(da_mon))) - da_monitor_start(da_mon); - - __da_handle_event(da_mon, tsk, event); - - return 1; + return __da_handle_start_run_event(da_get_monitor(tsk), event, tsk->pid); } #endif /* RV_MON_TYPE */ -- cgit v1.2.3 From f5587d1b6ec938afb2f74fe399a68020d66923e4 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 30 Mar 2026 13:10:00 +0200 Subject: rv: Add Hybrid Automata monitor type Deterministic automata define which events are allowed in every state, but cannot define more sophisticated constraint taking into account the system's environment (e.g. time or other states not producing events). Add the Hybrid Automata monitor type as an extension of Deterministic automata where each state transition is validating a constraint on a finite number of environment variables. Hybrid automata can be used to implement timed automata, where the environment variables are clocks. Also implement the necessary functionality to handle clock constraints (ns or jiffy granularity) on state and events. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260330111010.153663-3-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/linux/rv.h | 38 ++++ include/rv/da_monitor.h | 73 ++++++- include/rv/ha_monitor.h | 475 +++++++++++++++++++++++++++++++++++++++++++++ kernel/trace/rv/Kconfig | 13 ++ kernel/trace/rv/rv_trace.h | 63 ++++++ 5 files changed, 658 insertions(+), 4 deletions(-) create mode 100644 include/rv/ha_monitor.h (limited to 'include') diff --git a/include/linux/rv.h b/include/linux/rv.h index 58774eb3aecf..0aef9e3c785c 100644 --- a/include/linux/rv.h +++ b/include/linux/rv.h @@ -81,11 +81,49 @@ struct ltl_monitor {}; #endif /* CONFIG_RV_LTL_MONITOR */ +#ifdef CONFIG_RV_HA_MONITOR +/* + * In the future, hybrid automata may rely on multiple + * environment variables, e.g. different clocks started at + * different times or running at different speed. + * For now we support only 1 variable. + */ +#define MAX_HA_ENV_LEN 1 + +/* + * Monitors can pick the preferred timer implementation: + * No timer: if monitors don't have state invariants. + * Timer wheel: lightweight invariants check but far less precise. + * Hrtimer: accurate invariants check with higher overhead. + */ +#define HA_TIMER_NONE 0 +#define HA_TIMER_WHEEL 1 +#define HA_TIMER_HRTIMER 2 + +/* + * Hybrid automaton per-object variables. + */ +struct ha_monitor { + struct da_monitor da_mon; + u64 env_store[MAX_HA_ENV_LEN]; + union { + struct hrtimer hrtimer; + struct timer_list timer; + }; +}; + +#else + +struct ha_monitor { }; + +#endif /* CONFIG_RV_HA_MONITOR */ + #define RV_PER_TASK_MONITOR_INIT (CONFIG_RV_PER_TASK_MONITORS) union rv_task_monitor { struct da_monitor da_mon; struct ltl_monitor ltl_mon; + struct ha_monitor ha_mon; }; #ifdef CONFIG_RV_REACTORS diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h index 89a0b81d4b3e..ab5fe0896a46 100644 --- a/include/rv/da_monitor.h +++ b/include/rv/da_monitor.h @@ -3,9 +3,9 @@ * Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira * * Deterministic automata (DA) monitor functions, to be used together - * with automata models in C generated by the dot2k tool. + * with automata models in C generated by the rvgen tool. * - * The dot2k tool is available at tools/verification/dot2k/ + * The rvgen tool is available at tools/verification/rvgen/ * * For further information, see: * Documentation/trace/rv/monitor_synthesis.rst @@ -28,6 +28,33 @@ static struct rv_monitor rv_this; +/* + * Hook to allow the implementation of hybrid automata: define it with a + * function that takes curr_state, event and next_state and returns true if the + * environment constraints (e.g. timing) are satisfied, false otherwise. + */ +#ifndef da_monitor_event_hook +#define da_monitor_event_hook(...) true +#endif + +/* + * Hook to allow the implementation of hybrid automata: define it with a + * function that takes the da_monitor and performs further initialisation + * (e.g. reset set up timers). + */ +#ifndef da_monitor_init_hook +#define da_monitor_init_hook(da_mon) +#endif + +/* + * Hook to allow the implementation of hybrid automata: define it with a + * function that takes the da_monitor and performs further reset (e.g. reset + * all clocks). + */ +#ifndef da_monitor_reset_hook +#define da_monitor_reset_hook(da_mon) +#endif + /* * Type for the target id, default to int but can be overridden. */ @@ -49,6 +76,7 @@ static void react(enum states curr_state, enum events event) */ static inline void da_monitor_reset(struct da_monitor *da_mon) { + da_monitor_reset_hook(da_mon); da_mon->monitoring = 0; da_mon->curr_state = model_get_initial_state(); } @@ -63,6 +91,7 @@ static inline void da_monitor_start(struct da_monitor *da_mon) { da_mon->curr_state = model_get_initial_state(); da_mon->monitoring = 1; + da_monitor_init_hook(da_mon); } /* @@ -142,7 +171,10 @@ static inline int da_monitor_init(void) /* * da_monitor_destroy - destroy the monitor */ -static inline void da_monitor_destroy(void) { } +static inline void da_monitor_destroy(void) +{ + da_monitor_reset_all(); +} #elif RV_MON_TYPE == RV_MON_PER_CPU /* @@ -188,7 +220,10 @@ static inline int da_monitor_init(void) /* * da_monitor_destroy - destroy the monitor */ -static inline void da_monitor_destroy(void) { } +static inline void da_monitor_destroy(void) +{ + da_monitor_reset_all(); +} #elif RV_MON_TYPE == RV_MON_PER_TASK /* @@ -209,6 +244,24 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk) return &tsk->rv[task_mon_slot].da_mon; } +/* + * da_get_task - return the task associated to the monitor + */ +static inline struct task_struct *da_get_task(struct da_monitor *da_mon) +{ + return container_of(da_mon, struct task_struct, rv[task_mon_slot].da_mon); +} + +/* + * da_get_id - return the id associated to the monitor + * + * For per-task monitors, the id is the task's PID. + */ +static inline da_id_type da_get_id(struct da_monitor *da_mon) +{ + return da_get_task(da_mon)->pid; +} + static void da_monitor_reset_all(void) { struct task_struct *g, *p; @@ -253,6 +306,8 @@ static inline void da_monitor_destroy(void) } rv_put_task_monitor_slot(task_mon_slot); task_mon_slot = RV_PER_TASK_MONITOR_INIT; + + da_monitor_reset_all(); } #endif /* RV_MON_TYPE */ @@ -279,6 +334,14 @@ static inline void da_trace_error(struct da_monitor *da_mon, CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event); } +/* + * da_get_id - unused for implicit monitors + */ +static inline da_id_type da_get_id(struct da_monitor *da_mon) +{ + return 0; +} + #elif RV_MON_TYPE == RV_MON_PER_TASK /* * Trace events for per_task monitors, report the PID of the task. @@ -323,6 +386,8 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_ return false; } if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) { + if (!da_monitor_event_hook(da_mon, curr_state, event, next_state, id)) + return false; da_trace_event(da_mon, model_get_state_name(curr_state), model_get_event_name(event), model_get_state_name(next_state), diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h new file mode 100644 index 000000000000..b6cf3b2ba989 --- /dev/null +++ b/include/rv/ha_monitor.h @@ -0,0 +1,475 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025-2028 Red Hat, Inc. Gabriele Monaco + * + * Hybrid automata (HA) monitor functions, to be used together + * with automata models in C generated by the rvgen tool. + * + * This type of monitors extends the Deterministic automata (DA) class by + * adding a set of environment variables (e.g. clocks) that can be used to + * constraint the valid transitions. + * + * The rvgen tool is available at tools/verification/rvgen/ + * + * For further information, see: + * Documentation/trace/rv/monitor_synthesis.rst + */ + +#ifndef _RV_HA_MONITOR_H +#define _RV_HA_MONITOR_H + +#include + +#ifndef da_id_type +#define da_id_type int +#endif + +static inline void ha_monitor_init_env(struct da_monitor *da_mon); +static inline void ha_monitor_reset_env(struct da_monitor *da_mon); +static inline void ha_setup_timer(struct ha_monitor *ha_mon); +static inline bool ha_cancel_timer(struct ha_monitor *ha_mon); +static bool ha_monitor_handle_constraint(struct da_monitor *da_mon, + enum states curr_state, + enum events event, + enum states next_state, + da_id_type id); +#define da_monitor_event_hook ha_monitor_handle_constraint +#define da_monitor_init_hook ha_monitor_init_env +#define da_monitor_reset_hook ha_monitor_reset_env + +#include +#include + +/* This simplifies things since da_mon and ha_mon coexist in the same union */ +_Static_assert(offsetof(struct ha_monitor, da_mon) == 0, + "da_mon must be the first element in an ha_mon!"); +#define to_ha_monitor(da) container_of(da, struct ha_monitor, da_mon) + +#define ENV_MAX CONCATENATE(env_max_, MONITOR_NAME) +#define ENV_MAX_STORED CONCATENATE(env_max_stored_, MONITOR_NAME) +#define envs CONCATENATE(envs_, MONITOR_NAME) + +/* Environment storage before being reset */ +#define ENV_INVALID_VALUE U64_MAX +/* Error with no event occurs only on timeouts */ +#define EVENT_NONE EVENT_MAX +#define EVENT_NONE_LBL "none" +#define ENV_BUFFER_SIZE 64 + +#ifdef CONFIG_RV_REACTORS + +/* + * ha_react - trigger the reaction after a failed environment constraint + * + * The transition from curr_state with event is otherwise valid, but the + * environment constraint is false. This function can be called also with no + * event from a timer (state constraints only). + */ +static void ha_react(enum states curr_state, enum events event, char *env) +{ + rv_react(&rv_this, + "rv: monitor %s does not allow event %s on state %s with env %s\n", + __stringify(MONITOR_NAME), + event == EVENT_NONE ? EVENT_NONE_LBL : model_get_event_name(event), + model_get_state_name(curr_state), env); +} + +#else /* CONFIG_RV_REACTOR */ + +static void ha_react(enum states curr_state, enum events event, char *env) { } +#endif + +/* + * model_get_state_name - return the (string) name of the given state + */ +static char *model_get_env_name(enum envs env) +{ + if ((env < 0) || (env >= ENV_MAX)) + return "INVALID"; + + return RV_AUTOMATON_NAME.env_names[env]; +} + +/* + * Monitors requiring a timer implementation need to request it explicitly. + */ +#ifndef HA_TIMER_TYPE +#define HA_TIMER_TYPE HA_TIMER_NONE +#endif + +#if HA_TIMER_TYPE == HA_TIMER_WHEEL +static void ha_monitor_timer_callback(struct timer_list *timer); +#elif HA_TIMER_TYPE == HA_TIMER_HRTIMER +static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *hrtimer); +#endif + +/* + * ktime_get_ns is expensive, since we usually don't require precise accounting + * of changes within the same event, cache the current time at the beginning of + * the constraint handler and use the cache for subsequent calls. + * Monitors without ns clocks automatically skip this. + */ +#ifdef HA_CLK_NS +#define ha_get_ns() ktime_get_ns() +#else +#define ha_get_ns() 0 +#endif /* HA_CLK_NS */ + +/* Should be supplied by the monitor */ +static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns); +static bool ha_verify_constraint(struct ha_monitor *ha_mon, + enum states curr_state, + enum events event, + enum states next_state, + u64 time_ns); + +/* + * ha_monitor_reset_all_stored - reset all environment variables in the monitor + */ +static inline void ha_monitor_reset_all_stored(struct ha_monitor *ha_mon) +{ + for (int i = 0; i < ENV_MAX_STORED; i++) + WRITE_ONCE(ha_mon->env_store[i], ENV_INVALID_VALUE); +} + +/* + * ha_monitor_init_env - setup timer and reset all environment + * + * Called from a hook in the DA start functions, it supplies the da_mon + * corresponding to the current ha_mon. + * Not all hybrid automata require the timer, still set it for simplicity. + */ +static inline void ha_monitor_init_env(struct da_monitor *da_mon) +{ + struct ha_monitor *ha_mon = to_ha_monitor(da_mon); + + ha_monitor_reset_all_stored(ha_mon); + ha_setup_timer(ha_mon); +} + +/* + * ha_monitor_reset_env - stop timer and reset all environment + * + * Called from a hook in the DA reset functions, it supplies the da_mon + * corresponding to the current ha_mon. + * Not all hybrid automata require the timer, still clear it for simplicity. + */ +static inline void ha_monitor_reset_env(struct da_monitor *da_mon) +{ + struct ha_monitor *ha_mon = to_ha_monitor(da_mon); + + /* Initialisation resets the monitor before initialising the timer */ + if (likely(da_monitoring(da_mon))) + ha_cancel_timer(ha_mon); +} + +/* + * ha_monitor_env_invalid - return true if env has not been initialised + */ +static inline bool ha_monitor_env_invalid(struct ha_monitor *ha_mon, enum envs env) +{ + return READ_ONCE(ha_mon->env_store[env]) == ENV_INVALID_VALUE; +} + +static inline void ha_get_env_string(struct seq_buf *s, + struct ha_monitor *ha_mon, u64 time_ns) +{ + const char *format_str = "%s=%llu"; + + for (int i = 0; i < ENV_MAX; i++) { + seq_buf_printf(s, format_str, model_get_env_name(i), + ha_get_env(ha_mon, i, time_ns)); + format_str = ",%s=%llu"; + } +} + +#if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU +static inline void ha_trace_error_env(struct ha_monitor *ha_mon, + char *curr_state, char *event, char *env, + da_id_type id) +{ + CONCATENATE(trace_error_env_, MONITOR_NAME)(curr_state, event, env); +} +#elif RV_MON_TYPE == RV_MON_PER_TASK +static inline void ha_trace_error_env(struct ha_monitor *ha_mon, + char *curr_state, char *event, char *env, + da_id_type id) +{ + CONCATENATE(trace_error_env_, MONITOR_NAME)(id, curr_state, event, env); +} +#endif /* RV_MON_TYPE */ + +/* + * ha_get_monitor - return the current monitor + */ +#define ha_get_monitor(...) to_ha_monitor(da_get_monitor(__VA_ARGS__)) + +/* + * ha_monitor_handle_constraint - handle the constraint on the current transition + * + * If the monitor implementation defines a constraint in the transition from + * curr_state to event, react and trace appropriately as well as return false. + * This function is called from the hook in the DA event handle function and + * triggers a failure in the monitor. + */ +static bool ha_monitor_handle_constraint(struct da_monitor *da_mon, + enum states curr_state, + enum events event, + enum states next_state, + da_id_type id) +{ + struct ha_monitor *ha_mon = to_ha_monitor(da_mon); + u64 time_ns = ha_get_ns(); + DECLARE_SEQ_BUF(env_string, ENV_BUFFER_SIZE); + + if (ha_verify_constraint(ha_mon, curr_state, event, next_state, time_ns)) + return true; + + ha_get_env_string(&env_string, ha_mon, time_ns); + ha_react(curr_state, event, env_string.buffer); + ha_trace_error_env(ha_mon, + model_get_state_name(curr_state), + model_get_event_name(event), + env_string.buffer, id); + return false; +} + +static inline void __ha_monitor_timer_callback(struct ha_monitor *ha_mon) +{ + enum states curr_state = READ_ONCE(ha_mon->da_mon.curr_state); + DECLARE_SEQ_BUF(env_string, ENV_BUFFER_SIZE); + u64 time_ns = ha_get_ns(); + + ha_get_env_string(&env_string, ha_mon, time_ns); + ha_react(curr_state, EVENT_NONE, env_string.buffer); + ha_trace_error_env(ha_mon, model_get_state_name(curr_state), + EVENT_NONE_LBL, env_string.buffer, + da_get_id(&ha_mon->da_mon)); + + da_monitor_reset(&ha_mon->da_mon); +} + +/* + * The clock variables have 2 different representations in the env_store: + * - The guard representation is the timestamp of the last reset + * - The invariant representation is the timestamp when the invariant expires + * As the representations are incompatible, care must be taken when switching + * between them: the invariant representation can only be used when starting a + * timer when the previous representation was guard (e.g. no other invariant + * started since the last reset operation). + * Likewise, switching from invariant to guard representation without a reset + * can be done only by subtracting the exact value used to start the invariant. + * + * Reading the environment variable (ha_get_clk) also reflects this difference + * any reads in states that have an invariant return the (possibly negative) + * time since expiration, other reads return the time since last reset. + */ + +/* + * Helper functions for env variables describing clocks with ns granularity + */ +static inline u64 ha_get_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns) +{ + return time_ns - READ_ONCE(ha_mon->env_store[env]); +} +static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 time_ns) +{ + WRITE_ONCE(ha_mon->env_store[env], time_ns); +} +static inline void ha_set_invariant_ns(struct ha_monitor *ha_mon, enum envs env, + u64 value, u64 time_ns) +{ + WRITE_ONCE(ha_mon->env_store[env], time_ns + value); +} +static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon, + enum envs env, u64 time_ns) +{ + return READ_ONCE(ha_mon->env_store[env]) >= time_ns; +} +/* + * ha_invariant_passed_ns - prepare the invariant and return the time since reset + */ +static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs env, + u64 expire, u64 time_ns) +{ + u64 passed = 0; + + if (env < 0 || env >= ENV_MAX_STORED) + return 0; + if (ha_monitor_env_invalid(ha_mon, env)) + return 0; + passed = ha_get_env(ha_mon, env, time_ns); + ha_set_invariant_ns(ha_mon, env, expire - passed, time_ns); + return passed; +} + +/* + * Helper functions for env variables describing clocks with jiffy granularity + */ +static inline u64 ha_get_clk_jiffy(struct ha_monitor *ha_mon, enum envs env) +{ + return get_jiffies_64() - READ_ONCE(ha_mon->env_store[env]); +} +static inline void ha_reset_clk_jiffy(struct ha_monitor *ha_mon, enum envs env) +{ + WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64()); +} +static inline void ha_set_invariant_jiffy(struct ha_monitor *ha_mon, + enum envs env, u64 value) +{ + WRITE_ONCE(ha_mon->env_store[env], get_jiffies_64() + value); +} +static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon, + enum envs env, u64 time_ns) +{ + return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64()); + +} +/* + * ha_invariant_passed_jiffy - prepare the invariant and return the time since reset + */ +static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs env, + u64 expire, u64 time_ns) +{ + u64 passed = 0; + + if (env < 0 || env >= ENV_MAX_STORED) + return 0; + if (ha_monitor_env_invalid(ha_mon, env)) + return 0; + passed = ha_get_env(ha_mon, env, time_ns); + ha_set_invariant_jiffy(ha_mon, env, expire - passed); + return passed; +} + +/* + * Retrieve the last reset time (guard representation) from the invariant + * representation (expiration). + * It the caller's responsibility to make sure the storage was actually in the + * invariant representation (e.g. the current state has an invariant). + * The provided value must be the same used when starting the invariant. + * + * This function's access to the storage is NOT atomic, due to the rarity when + * this is used. If a monitor allows writes concurrent to this, likely + * other things are broken and need rethinking the model or additional locking. + */ +static inline void ha_inv_to_guard(struct ha_monitor *ha_mon, enum envs env, + u64 value, u64 time_ns) +{ + WRITE_ONCE(ha_mon->env_store[env], READ_ONCE(ha_mon->env_store[env]) - value); +} + +#if HA_TIMER_TYPE == HA_TIMER_WHEEL +/* + * Helper functions to handle the monitor timer. + * Not all monitors require a timer, in such case the timer will be set up but + * never armed. + * Timers start since the last reset of the supplied env or from now if env is + * not an environment variable. If env was not initialised no timer starts. + * Timers can expire on any CPU unless the monitor is per-cpu, + * where we assume every event occurs on the local CPU. + */ +static void ha_monitor_timer_callback(struct timer_list *timer) +{ + struct ha_monitor *ha_mon = container_of(timer, struct ha_monitor, timer); + + __ha_monitor_timer_callback(ha_mon); +} +static inline void ha_setup_timer(struct ha_monitor *ha_mon) +{ + int mode = 0; + + if (RV_MON_TYPE == RV_MON_PER_CPU) + mode |= TIMER_PINNED; + timer_setup(&ha_mon->timer, ha_monitor_timer_callback, mode); +} +static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env, + u64 expire, u64 time_ns) +{ + u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns); + + mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed); +} +static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env, + u64 expire, u64 time_ns) +{ + u64 passed = ha_invariant_passed_ns(ha_mon, env, expire, time_ns); + + ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED, + nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns); +} +/* + * ha_cancel_timer - Cancel the timer + * + * Returns: + * * 1 when the timer was active + * * 0 when the timer was not active or running a callback + */ +static inline bool ha_cancel_timer(struct ha_monitor *ha_mon) +{ + return timer_delete(&ha_mon->timer); +} +#elif HA_TIMER_TYPE == HA_TIMER_HRTIMER +/* + * Helper functions to handle the monitor timer. + * Not all monitors require a timer, in such case the timer will be set up but + * never armed. + * Timers start since the last reset of the supplied env or from now if env is + * not an environment variable. If env was not initialised no timer starts. + * Timers can expire on any CPU unless the monitor is per-cpu, + * where we assume every event occurs on the local CPU. + */ +static enum hrtimer_restart ha_monitor_timer_callback(struct hrtimer *hrtimer) +{ + struct ha_monitor *ha_mon = container_of(hrtimer, struct ha_monitor, hrtimer); + + __ha_monitor_timer_callback(ha_mon); + return HRTIMER_NORESTART; +} +static inline void ha_setup_timer(struct ha_monitor *ha_mon) +{ + hrtimer_setup(&ha_mon->hrtimer, ha_monitor_timer_callback, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD); +} +static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env, + u64 expire, u64 time_ns) +{ + int mode = HRTIMER_MODE_REL_HARD; + u64 passed = ha_invariant_passed_ns(ha_mon, env, expire, time_ns); + + if (RV_MON_TYPE == RV_MON_PER_CPU) + mode |= HRTIMER_MODE_PINNED; + hrtimer_start(&ha_mon->hrtimer, ns_to_ktime(expire - passed), mode); +} +static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env, + u64 expire, u64 time_ns) +{ + u64 passed = ha_invariant_passed_jiffy(ha_mon, env, expire, time_ns); + + ha_start_timer_ns(ha_mon, ENV_MAX_STORED, + jiffies_to_nsecs(expire - passed), time_ns); +} +/* + * ha_cancel_timer - Cancel the timer + * + * Returns: + * * 1 when the timer was active + * * 0 when the timer was not active or running a callback + */ +static inline bool ha_cancel_timer(struct ha_monitor *ha_mon) +{ + return hrtimer_try_to_cancel(&ha_mon->hrtimer) == 1; +} +#else /* HA_TIMER_NONE */ +/* + * Start function is intentionally not defined, monitors using timers must + * set HA_TIMER_TYPE to either HA_TIMER_WHEEL or HA_TIMER_HRTIMER. + */ +static inline void ha_setup_timer(struct ha_monitor *ha_mon) { } +static inline bool ha_cancel_timer(struct ha_monitor *ha_mon) +{ + return false; +} +#endif + +#endif diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig index 5b4be87ba59d..4ad392dfc57f 100644 --- a/kernel/trace/rv/Kconfig +++ b/kernel/trace/rv/Kconfig @@ -23,6 +23,19 @@ config LTL_MON_EVENTS_ID config RV_LTL_MONITOR bool +config RV_HA_MONITOR + bool + +config HA_MON_EVENTS_IMPLICIT + select DA_MON_EVENTS_IMPLICIT + select RV_HA_MONITOR + bool + +config HA_MON_EVENTS_ID + select DA_MON_EVENTS_ID + select RV_HA_MONITOR + bool + menuconfig RV bool "Runtime Verification" select TRACING diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h index 4a6faddac614..7c598967bc0e 100644 --- a/kernel/trace/rv/rv_trace.h +++ b/kernel/trace/rv/rv_trace.h @@ -65,6 +65,36 @@ DECLARE_EVENT_CLASS(error_da_monitor, #include // Add new monitors based on CONFIG_DA_MON_EVENTS_IMPLICIT here +#ifdef CONFIG_HA_MON_EVENTS_IMPLICIT +/* For simplicity this class is marked as DA although relevant only for HA */ +DECLARE_EVENT_CLASS(error_env_da_monitor, + + TP_PROTO(char *state, char *event, char *env), + + TP_ARGS(state, event, env), + + TP_STRUCT__entry( + __string( state, state ) + __string( event, event ) + __string( env, env ) + ), + + TP_fast_assign( + __assign_str(state); + __assign_str(event); + __assign_str(env); + ), + + TP_printk("event %s not expected in the state %s with env %s", + __get_str(event), + __get_str(state), + __get_str(env)) +); + +// Add new monitors based on CONFIG_HA_MON_EVENTS_IMPLICIT here + +#endif + #endif /* CONFIG_DA_MON_EVENTS_IMPLICIT */ #ifdef CONFIG_DA_MON_EVENTS_ID @@ -128,6 +158,39 @@ DECLARE_EVENT_CLASS(error_da_monitor_id, #include // Add new monitors based on CONFIG_DA_MON_EVENTS_ID here +#ifdef CONFIG_HA_MON_EVENTS_ID +/* For simplicity this class is marked as DA although relevant only for HA */ +DECLARE_EVENT_CLASS(error_env_da_monitor_id, + + TP_PROTO(int id, char *state, char *event, char *env), + + TP_ARGS(id, state, event, env), + + TP_STRUCT__entry( + __field( int, id ) + __string( state, state ) + __string( event, event ) + __string( env, env ) + ), + + TP_fast_assign( + __assign_str(state); + __assign_str(event); + __assign_str(env); + __entry->id = id; + ), + + TP_printk("%d: event %s not expected in the state %s with env %s", + __entry->id, + __get_str(event), + __get_str(state), + __get_str(env)) +); + +// Add new monitors based on CONFIG_HA_MON_EVENTS_ID here + +#endif + #endif /* CONFIG_DA_MON_EVENTS_ID */ #ifdef CONFIG_LTL_MON_EVENTS_ID DECLARE_EVENT_CLASS(event_ltl_monitor_id, -- cgit v1.2.3 From 4a24127bd6cbf03fb17de8b43f2d8db3f55ca333 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 30 Mar 2026 13:10:06 +0200 Subject: rv: Add support for per-object monitors in DA/HA RV deterministic and hybrid automata currently only support global, per-cpu and per-task monitors. It isn't possible to write a model that would follow some different type of object, like a deadline entity or a lock. Define the generic per-object monitor implementation which shares part of the implementation with the per-task monitors. The user needs to provide an id for the object (e.g. pid for tasks) and define the data type for the monitor_target (e.g. struct task_struct * for tasks). Both are supplied to the event handlers, as the id may not be easily available in the target. The monitor storage (e.g. the rv monitor, pointer to the target, etc.) is stored in a hash table indexed by id. Monitor storage objects are automatically allocated unless specified otherwise (e.g. if the creation context is unsafe for allocation). Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260330111010.153663-9-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/linux/rv.h | 1 + include/rv/da_monitor.h | 300 +++++++++++++++++++++++++++++++++++++++++++++++- include/rv/ha_monitor.h | 5 +- 3 files changed, 300 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/linux/rv.h b/include/linux/rv.h index 0aef9e3c785c..541ba404926a 100644 --- a/include/linux/rv.h +++ b/include/linux/rv.h @@ -13,6 +13,7 @@ #define RV_MON_GLOBAL 0 #define RV_MON_PER_CPU 1 #define RV_MON_PER_TASK 2 +#define RV_MON_PER_OBJ 3 #ifdef CONFIG_RV #include diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h index ab5fe0896a46..39765ff6f098 100644 --- a/include/rv/da_monitor.h +++ b/include/rv/da_monitor.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include /* * Per-cpu variables require a unique name although static in some @@ -57,6 +59,9 @@ static struct rv_monitor rv_this; /* * Type for the target id, default to int but can be overridden. + * A long type can work as hash table key (PER_OBJ) but will be downgraded to + * int in the event tracepoint. + * Unused for implicit monitors. */ #ifndef da_id_type #define da_id_type int @@ -245,9 +250,9 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk) } /* - * da_get_task - return the task associated to the monitor + * da_get_target - return the task associated to the monitor */ -static inline struct task_struct *da_get_task(struct da_monitor *da_mon) +static inline struct task_struct *da_get_target(struct da_monitor *da_mon) { return container_of(da_mon, struct task_struct, rv[task_mon_slot].da_mon); } @@ -259,7 +264,7 @@ static inline struct task_struct *da_get_task(struct da_monitor *da_mon) */ static inline da_id_type da_get_id(struct da_monitor *da_mon) { - return da_get_task(da_mon)->pid; + return da_get_target(da_mon)->pid; } static void da_monitor_reset_all(void) @@ -309,6 +314,221 @@ static inline void da_monitor_destroy(void) da_monitor_reset_all(); } + +#elif RV_MON_TYPE == RV_MON_PER_OBJ +/* + * Functions to define, init and get a per-object monitor. + */ + +struct da_monitor_storage { + da_id_type id; + monitor_target target; + union rv_task_monitor rv; + struct hlist_node node; + struct rcu_head rcu; +}; + +#ifndef DA_MONITOR_HT_BITS +#define DA_MONITOR_HT_BITS 10 +#endif +static DEFINE_HASHTABLE(da_monitor_ht, DA_MONITOR_HT_BITS); + +/* + * da_create_empty_storage - pre-allocate an empty storage + */ +static inline struct da_monitor_storage *da_create_empty_storage(da_id_type id) +{ + struct da_monitor_storage *mon_storage; + + mon_storage = kmalloc_nolock(sizeof(struct da_monitor_storage), + __GFP_ZERO, NUMA_NO_NODE); + if (!mon_storage) + return NULL; + + hash_add_rcu(da_monitor_ht, &mon_storage->node, id); + mon_storage->id = id; + return mon_storage; +} + +/* + * da_create_storage - create the per-object storage + * + * The caller is responsible to synchronise writers, either with locks or + * implicitly. For instance, if da_create_storage is only called from a single + * event for target (e.g. sched_switch), it's safe to call this without locks. + */ +static inline struct da_monitor *da_create_storage(da_id_type id, + monitor_target target, + struct da_monitor *da_mon) +{ + struct da_monitor_storage *mon_storage; + + if (da_mon) + return da_mon; + + mon_storage = da_create_empty_storage(id); + if (!mon_storage) + return NULL; + + mon_storage->target = target; + return &mon_storage->rv.da_mon; +} + +/* + * __da_get_mon_storage - get the monitor storage from the hash table + */ +static inline struct da_monitor_storage *__da_get_mon_storage(da_id_type id) +{ + struct da_monitor_storage *mon_storage; + + lockdep_assert_in_rcu_read_lock(); + hash_for_each_possible_rcu(da_monitor_ht, mon_storage, node, id) { + if (mon_storage->id == id) + return mon_storage; + } + + return NULL; +} + +/* + * da_get_monitor - return the monitor for target + */ +static struct da_monitor *da_get_monitor(da_id_type id, monitor_target target) +{ + struct da_monitor_storage *mon_storage; + + mon_storage = __da_get_mon_storage(id); + return mon_storage ? &mon_storage->rv.da_mon : NULL; +} + +/* + * da_get_target - return the object associated to the monitor + */ +static inline monitor_target da_get_target(struct da_monitor *da_mon) +{ + return container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target; +} + +/* + * da_get_id - return the id associated to the monitor + */ +static inline da_id_type da_get_id(struct da_monitor *da_mon) +{ + return container_of(da_mon, struct da_monitor_storage, rv.da_mon)->id; +} + +/* + * da_create_or_get - create the per-object storage if not already there + * + * This needs a lookup so should be guarded by RCU, the condition is checked + * directly in da_create_storage() + */ +static inline void da_create_or_get(da_id_type id, monitor_target target) +{ + guard(rcu)(); + da_create_storage(id, target, da_get_monitor(id, target)); +} + +/* + * da_fill_empty_storage - store the target in a pre-allocated storage + * + * Can be used as a substitute of da_create_storage when starting a monitor in + * an environment where allocation is unsafe. + */ +static inline struct da_monitor *da_fill_empty_storage(da_id_type id, + monitor_target target, + struct da_monitor *da_mon) +{ + if (unlikely(da_mon && !da_get_target(da_mon))) + container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target = target; + return da_mon; +} + +/* + * da_get_target_by_id - return the object associated to the id + */ +static inline monitor_target da_get_target_by_id(da_id_type id) +{ + struct da_monitor_storage *mon_storage; + + guard(rcu)(); + mon_storage = __da_get_mon_storage(id); + + if (unlikely(!mon_storage)) + return NULL; + return mon_storage->target; +} + +/* + * da_destroy_storage - destroy the per-object storage + * + * The caller is responsible to synchronise writers, either with locks or + * implicitly. For instance, if da_destroy_storage is called at sched_exit and + * da_create_storage can never occur after that, it's safe to call this without + * locks. + * This function includes an RCU read-side critical section to synchronise + * against da_monitor_destroy(). + */ +static inline void da_destroy_storage(da_id_type id) +{ + struct da_monitor_storage *mon_storage; + + guard(rcu)(); + mon_storage = __da_get_mon_storage(id); + + if (!mon_storage) + return; + da_monitor_reset_hook(&mon_storage->rv.da_mon); + hash_del_rcu(&mon_storage->node); + kfree_rcu(mon_storage, rcu); +} + +static void da_monitor_reset_all(void) +{ + struct da_monitor_storage *mon_storage; + int bkt; + + rcu_read_lock(); + hash_for_each_rcu(da_monitor_ht, bkt, mon_storage, node) + da_monitor_reset(&mon_storage->rv.da_mon); + rcu_read_unlock(); +} + +static inline int da_monitor_init(void) +{ + hash_init(da_monitor_ht); + return 0; +} + +static inline void da_monitor_destroy(void) +{ + struct da_monitor_storage *mon_storage; + struct hlist_node *tmp; + int bkt; + + /* + * This function is called after all probes are disabled, we need only + * worry about concurrency against old events. + */ + synchronize_rcu(); + hash_for_each_safe(da_monitor_ht, bkt, tmp, mon_storage, node) { + da_monitor_reset_hook(&mon_storage->rv.da_mon); + hash_del_rcu(&mon_storage->node); + kfree(mon_storage); + } +} + +/* + * Allow the per-object monitors to run allocation manually, necessary if the + * start condition is in a context problematic for allocation (e.g. scheduling). + * In such case, if the storage was pre-allocated without a target, set it now. + */ +#ifdef DA_SKIP_AUTO_ALLOC +#define da_prepare_storage da_fill_empty_storage +#else +#define da_prepare_storage da_create_storage +#endif /* DA_SKIP_AUTO_ALLOC */ + #endif /* RV_MON_TYPE */ #if RV_MON_TYPE == RV_MON_GLOBAL || RV_MON_TYPE == RV_MON_PER_CPU @@ -342,9 +562,9 @@ static inline da_id_type da_get_id(struct da_monitor *da_mon) return 0; } -#elif RV_MON_TYPE == RV_MON_PER_TASK +#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ /* - * Trace events for per_task monitors, report the PID of the task. + * Trace events for per_task/per_object monitors, report the target id. */ static inline void da_trace_event(struct da_monitor *da_mon, @@ -525,6 +745,76 @@ static inline bool da_handle_start_run_event(struct task_struct *tsk, { return __da_handle_start_run_event(da_get_monitor(tsk), event, tsk->pid); } + +#elif RV_MON_TYPE == RV_MON_PER_OBJ +/* + * Handle event for per object. + */ + +/* + * da_handle_event - handle an event + */ +static inline void da_handle_event(da_id_type id, monitor_target target, enum events event) +{ + struct da_monitor *da_mon; + + guard(rcu)(); + da_mon = da_get_monitor(id, target); + if (likely(da_mon)) + __da_handle_event(da_mon, event, id); +} + +/* + * da_handle_start_event - start monitoring or handle event + * + * This function is used to notify the monitor that the system is returning + * to the initial state, so the monitor can start monitoring in the next event. + * Thus: + * + * If the monitor already started, handle the event. + * If the monitor did not start yet, start the monitor but skip the event. + */ +static inline bool da_handle_start_event(da_id_type id, monitor_target target, + enum events event) +{ + struct da_monitor *da_mon; + + guard(rcu)(); + da_mon = da_get_monitor(id, target); + da_mon = da_prepare_storage(id, target, da_mon); + if (unlikely(!da_mon)) + return 0; + return __da_handle_start_event(da_mon, event, id); +} + +/* + * da_handle_start_run_event - start monitoring and handle event + * + * This function is used to notify the monitor that the system is in the + * initial state, so the monitor can start monitoring and handling event. + */ +static inline bool da_handle_start_run_event(da_id_type id, monitor_target target, + enum events event) +{ + struct da_monitor *da_mon; + + guard(rcu)(); + da_mon = da_get_monitor(id, target); + da_mon = da_prepare_storage(id, target, da_mon); + if (unlikely(!da_mon)) + return 0; + return __da_handle_start_run_event(da_mon, event, id); +} + +static inline void da_reset(da_id_type id, monitor_target target) +{ + struct da_monitor *da_mon; + + guard(rcu)(); + da_mon = da_get_monitor(id, target); + if (likely(da_mon)) + da_monitor_reset(da_mon); +} #endif /* RV_MON_TYPE */ #endif diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h index b6cf3b2ba989..d59507e8cb30 100644 --- a/include/rv/ha_monitor.h +++ b/include/rv/ha_monitor.h @@ -190,7 +190,10 @@ static inline void ha_trace_error_env(struct ha_monitor *ha_mon, { CONCATENATE(trace_error_env_, MONITOR_NAME)(curr_state, event, env); } -#elif RV_MON_TYPE == RV_MON_PER_TASK +#elif RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ + +#define ha_get_target(ha_mon) da_get_target(&ha_mon->da_mon) + static inline void ha_trace_error_env(struct ha_monitor *ha_mon, char *curr_state, char *event, char *env, da_id_type id) -- cgit v1.2.3 From 820725b0eb59f6011e379cc526ae90a6f3efeb50 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 30 Mar 2026 13:10:08 +0200 Subject: sched: Add deadline tracepoints Add the following tracepoints: * sched_dl_throttle(dl_se, cpu, type): Called when a deadline entity is throttled * sched_dl_replenish(dl_se, cpu, type): Called when a deadline entity's runtime is replenished * sched_dl_update(dl_se, cpu, type): Called when a deadline entity updates without throttle or replenish * sched_dl_server_start(dl_se, cpu, type): Called when a deadline server is started * sched_dl_server_stop(dl_se, cpu, type): Called when a deadline server is stopped Those tracepoints can be useful to validate the deadline scheduler with RV and are not exported to tracefs. Reviewed-by: Phil Auld Acked-by: Juri Lelli Link: https://lore.kernel.org/r/20260330111010.153663-11-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/trace/events/sched.h | 26 ++++++++++++++++++++++++++ kernel/sched/core.c | 5 +++++ kernel/sched/deadline.c | 23 +++++++++++++++++++++++ 3 files changed, 54 insertions(+) (limited to 'include') diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h index 7b2645b50e78..535860581f15 100644 --- a/include/trace/events/sched.h +++ b/include/trace/events/sched.h @@ -896,6 +896,32 @@ DECLARE_TRACE(sched_set_need_resched, TP_PROTO(struct task_struct *tsk, int cpu, int tif), TP_ARGS(tsk, cpu, tif)); +#define DL_OTHER 0 +#define DL_TASK 1 +#define DL_SERVER_FAIR 2 +#define DL_SERVER_EXT 3 + +DECLARE_TRACE(sched_dl_throttle, + TP_PROTO(struct sched_dl_entity *dl_se, int cpu, u8 type), + TP_ARGS(dl_se, cpu, type)); + +DECLARE_TRACE(sched_dl_replenish, + TP_PROTO(struct sched_dl_entity *dl_se, int cpu, u8 type), + TP_ARGS(dl_se, cpu, type)); + +/* Call to update_curr_dl_se not involving throttle or replenish */ +DECLARE_TRACE(sched_dl_update, + TP_PROTO(struct sched_dl_entity *dl_se, int cpu, u8 type), + TP_ARGS(dl_se, cpu, type)); + +DECLARE_TRACE(sched_dl_server_start, + TP_PROTO(struct sched_dl_entity *dl_se, int cpu, u8 type), + TP_ARGS(dl_se, cpu, type)); + +DECLARE_TRACE(sched_dl_server_stop, + TP_PROTO(struct sched_dl_entity *dl_se, int cpu, u8 type), + TP_ARGS(dl_se, cpu, type)); + #endif /* _TRACE_SCHED_H */ /* This part must be outside protection */ diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 496dff740dca..6a043f11b79d 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -122,6 +122,11 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp); EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp); EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp); EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_throttle_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_replenish_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_update_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_server_start_tp); +EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_server_stop_tp); DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues); DEFINE_PER_CPU(struct rnd_state, sched_rnd_state); diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index d08b00429323..e511e36916bd 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -115,6 +115,19 @@ static inline bool is_dl_boosted(struct sched_dl_entity *dl_se) } #endif /* !CONFIG_RT_MUTEXES */ +static inline u8 dl_get_type(struct sched_dl_entity *dl_se, struct rq *rq) +{ + if (!dl_server(dl_se)) + return DL_TASK; + if (dl_se == &rq->fair_server) + return DL_SERVER_FAIR; +#ifdef CONFIG_SCHED_CLASS_EXT + if (dl_se == &rq->ext_server) + return DL_SERVER_EXT; +#endif + return DL_OTHER; +} + static inline struct dl_bw *dl_bw_of(int i) { RCU_LOCKDEP_WARN(!rcu_read_lock_sched_held(), @@ -733,6 +746,7 @@ static inline void replenish_dl_new_period(struct sched_dl_entity *dl_se, dl_se->dl_throttled = 1; dl_se->dl_defer_armed = 1; } + trace_sched_dl_replenish_tp(dl_se, cpu_of(rq), dl_get_type(dl_se, rq)); } /* @@ -848,6 +862,8 @@ static void replenish_dl_entity(struct sched_dl_entity *dl_se) if (dl_se->dl_throttled) dl_se->dl_throttled = 0; + trace_sched_dl_replenish_tp(dl_se, cpu_of(rq), dl_get_type(dl_se, rq)); + /* * If this is the replenishment of a deferred reservation, * clear the flag and return. @@ -1345,6 +1361,7 @@ static inline void dl_check_constrained_dl(struct sched_dl_entity *dl_se) dl_time_before(rq_clock(rq), dl_next_period(dl_se))) { if (unlikely(is_dl_boosted(dl_se) || !start_dl_timer(dl_se))) return; + trace_sched_dl_throttle_tp(dl_se, cpu_of(rq), dl_get_type(dl_se, rq)); dl_se->dl_throttled = 1; if (dl_se->runtime > 0) dl_se->runtime = 0; @@ -1508,6 +1525,7 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64 throttle: if (dl_runtime_exceeded(dl_se) || dl_se->dl_yielded) { + trace_sched_dl_throttle_tp(dl_se, cpu_of(rq), dl_get_type(dl_se, rq)); dl_se->dl_throttled = 1; /* If requested, inform the user about runtime overruns. */ @@ -1532,6 +1550,8 @@ throttle: if (!is_leftmost(dl_se, &rq->dl)) resched_curr(rq); + } else { + trace_sched_dl_update_tp(dl_se, cpu_of(rq), dl_get_type(dl_se, rq)); } /* @@ -1810,6 +1830,7 @@ void dl_server_start(struct sched_dl_entity *dl_se) if (WARN_ON_ONCE(!cpu_online(cpu_of(rq)))) return; + trace_sched_dl_server_start_tp(dl_se, cpu_of(rq), dl_get_type(dl_se, rq)); dl_se->dl_server_active = 1; enqueue_dl_entity(dl_se, ENQUEUE_WAKEUP); if (!dl_task(dl_se->rq->curr) || dl_entity_preempt(dl_se, &rq->curr->dl)) @@ -1821,6 +1842,8 @@ void dl_server_stop(struct sched_dl_entity *dl_se) if (!dl_server(dl_se) || !dl_server_active(dl_se)) return; + trace_sched_dl_server_stop_tp(dl_se, cpu_of(dl_se->rq), + dl_get_type(dl_se, dl_se->rq)); dequeue_dl_entity(dl_se, DEQUEUE_SLEEP); hrtimer_try_to_cancel(&dl_se->dl_timer); dl_se->dl_defer_armed = 0; -- cgit v1.2.3 From c85dbddad705babfbddfef182495994f7f5262c9 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 30 Mar 2026 13:10:09 +0200 Subject: sched/deadline: Move some utility functions to deadline.h Some utility functions on sched_dl_entity can be useful outside of deadline.c , for instance for modelling, without relying on raw structure fields. Move functions like dl_task_of and dl_is_implicit to deadline.h to make them available outside. Acked-by: Juri Lelli Link: https://lore.kernel.org/r/20260330111010.153663-12-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/linux/sched/deadline.h | 27 +++++++++++++++++++++++++++ kernel/sched/deadline.c | 28 +--------------------------- 2 files changed, 28 insertions(+), 27 deletions(-) (limited to 'include') diff --git a/include/linux/sched/deadline.h b/include/linux/sched/deadline.h index c40115d4e34d..1198138cb839 100644 --- a/include/linux/sched/deadline.h +++ b/include/linux/sched/deadline.h @@ -37,4 +37,31 @@ extern void dl_clear_root_domain_cpu(int cpu); extern u64 dl_cookie; extern bool dl_bw_visited(int cpu, u64 cookie); +static inline bool dl_server(struct sched_dl_entity *dl_se) +{ + return dl_se->dl_server; +} + +static inline struct task_struct *dl_task_of(struct sched_dl_entity *dl_se) +{ + BUG_ON(dl_server(dl_se)); + return container_of(dl_se, struct task_struct, dl); +} + +/* + * Regarding the deadline, a task with implicit deadline has a relative + * deadline == relative period. A task with constrained deadline has a + * relative deadline <= relative period. + * + * We support constrained deadline tasks. However, there are some restrictions + * applied only for tasks which do not have an implicit deadline. See + * update_dl_entity() to know more about such restrictions. + * + * The dl_is_implicit() returns true if the task has an implicit deadline. + */ +static inline bool dl_is_implicit(struct sched_dl_entity *dl_se) +{ + return dl_se->dl_deadline == dl_se->dl_period; +} + #endif /* _LINUX_SCHED_DEADLINE_H */ diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index e511e36916bd..c10415c1aa4a 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -18,6 +18,7 @@ #include #include +#include #include #include "sched.h" #include "pelt.h" @@ -57,17 +58,6 @@ static int __init sched_dl_sysctl_init(void) late_initcall(sched_dl_sysctl_init); #endif /* CONFIG_SYSCTL */ -static bool dl_server(struct sched_dl_entity *dl_se) -{ - return dl_se->dl_server; -} - -static inline struct task_struct *dl_task_of(struct sched_dl_entity *dl_se) -{ - BUG_ON(dl_server(dl_se)); - return container_of(dl_se, struct task_struct, dl); -} - static inline struct rq *rq_of_dl_rq(struct dl_rq *dl_rq) { return container_of(dl_rq, struct rq, dl); @@ -990,22 +980,6 @@ update_dl_revised_wakeup(struct sched_dl_entity *dl_se, struct rq *rq) dl_se->runtime = (dl_se->dl_density * laxity) >> BW_SHIFT; } -/* - * Regarding the deadline, a task with implicit deadline has a relative - * deadline == relative period. A task with constrained deadline has a - * relative deadline <= relative period. - * - * We support constrained deadline tasks. However, there are some restrictions - * applied only for tasks which do not have an implicit deadline. See - * update_dl_entity() to know more about such restrictions. - * - * The dl_is_implicit() returns true if the task has an implicit deadline. - */ -static inline bool dl_is_implicit(struct sched_dl_entity *dl_se) -{ - return dl_se->dl_deadline == dl_se->dl_period; -} - /* * When a deadline entity is placed in the runqueue, its runtime and deadline * might need to be updated. This is done by a CBS wake up rule. There are two -- cgit v1.2.3