From ae2e47b177478b794e29db389fce262cbf3ab9c6 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:04 +0200 Subject: [PATCH 01/40] verification/rvgen: Switch LTL parser to Lark The LTL parser is built using Ply. However, Ply is no longer maintained [1]. Switch to use Lark instead. In addition to being actively maintained, Lark also offers additional features (namely, automatically creating the abstract syntax tree) which make the parser simpler. Link: https://github.com/dabeaz/ply/commit/9d7c40099e23ff78f9d86ef69a26c1e8a83e706a [1] Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/73d149221d96090342c7de408d032573de9cb6c4.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/__main__.py | 5 +- tools/verification/rvgen/rvgen/ltl2ba.py | 184 +++++++++-------------- 2 files changed, 73 insertions(+), 116 deletions(-) diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py index 5c923dc10d0f..0915cf86e43b 100644 --- a/tools/verification/rvgen/__main__.py +++ b/tools/verification/rvgen/__main__.py @@ -14,6 +14,7 @@ if __name__ == '__main__': from rvgen.container import Container from rvgen.ltl2k import ltl2k from rvgen.automata import AutomataError + from rvgen.ltl2ba import LTLError import argparse import sys @@ -57,8 +58,8 @@ if __name__ == '__main__': sys.exit(1) else: monitor = Container(vars(params)) - except AutomataError as e: - print(f"There was an error processing {params.spec}: {e}", file=sys.stderr) + except (AutomataError, LTLError) as e: + print(f"There was an error processing {params.spec}:\n{e}", file=sys.stderr) sys.exit(1) print(f"Writing the monitor into the directory {monitor.name}") diff --git a/tools/verification/rvgen/rvgen/ltl2ba.py b/tools/verification/rvgen/rvgen/ltl2ba.py index 016e7cf93bbb..7cebda61bce8 100644 --- a/tools/verification/rvgen/rvgen/ltl2ba.py +++ b/tools/verification/rvgen/rvgen/ltl2ba.py @@ -7,9 +7,7 @@ # https://doi.org/10.1007/978-0-387-34892-6_1 # With extra optimizations -from ply.lex import lex -from ply.yacc import yacc -from .automata import AutomataError +import lark # Grammar: # ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl @@ -30,42 +28,41 @@ from .automata import AutomataError # imply # equivalent -tokens = ( - 'AND', - 'OR', - 'IMPLY', - 'UNTIL', - 'ALWAYS', - 'EVENTUALLY', - 'NEXT', - 'VARIABLE', - 'LITERAL', - 'NOT', - 'LPAREN', - 'RPAREN', - 'ASSIGN', -) +GRAMMAR = r''' +start: assign+ -t_AND = r'and' -t_OR = r'or' -t_IMPLY = r'imply' -t_UNTIL = r'until' -t_ALWAYS = r'always' -t_NEXT = r'next' -t_EVENTUALLY = r'eventually' -t_VARIABLE = r'[A-Z_0-9]+' -t_LITERAL = r'true|false' -t_NOT = r'not' -t_LPAREN = r'\(' -t_RPAREN = r'\)' -t_ASSIGN = r'=' -t_ignore_COMMENT = r'\#.*' -t_ignore = ' \t\n' +assign: VARIABLE "=" _ltl -def t_error(t): - raise AutomataError(f"Illegal character '{t.value[0]}'") +_ltl: _opd | binop | unop -lexer = lex() +_opd : VARIABLE + | LITERAL + | "(" _ltl ")" + +unop: UNOP _ltl +UNOP: "always" + | "eventually" + | "next" + | "not" + +binop: _opd BINOP _ltl +BINOP: "until" + | "and" + | "or" + | "imply" + +VARIABLE: /[A-Z_][A-Z0-9_]*/ +LITERAL: "true" | "false" + +COMMENT: "#" /.*/ "\n" +%ignore COMMENT + +%import common.WS +%ignore WS +''' + +class LTLError(Exception): + "Exception raised for malformed linear temporal logic" class GraphNode: uid = 0 @@ -97,7 +94,7 @@ class GraphNode: return self.id < other.id class ASTNode: - uid = 1 + uid = 0 def __init__(self, op): self.op = op @@ -433,90 +430,49 @@ class Literal: node.old |= {n} return node.expand(node_set) -def p_spec(p): - ''' - spec : assign - | assign spec - ''' - if len(p) == 3: - p[2].append(p[1]) - p[0] = p[2] - else: - p[0] = [p[1]] +class Transform(lark.visitors.Transformer): + def unop(self, node): + if node[0] == "always": + return ASTNode(AlwaysOp(node[1])) + if node[0] == "eventually": + return ASTNode(EventuallyOp(node[1])) + if node[0] == "next": + return ASTNode(NextOp(node[1])) + if node[0] == "not": + return ASTNode(NotOp(node[1])) + raise ValueError("Unknown operator %s" % node[0]) -def p_assign(p): - ''' - assign : VARIABLE ASSIGN ltl - ''' - p[0] = (p[1], p[3]) + def binop(self, node): + if node[1] == "until": + return ASTNode(UntilOp(node[0], node[2])) + if node[1] == "and": + return ASTNode(AndOp(node[0], node[2])) + if node[1] == "or": + return ASTNode(OrOp(node[0], node[2])) + if node[1] == "imply": + return ASTNode(ImplyOp(node[0], node[2])) + raise ValueError("Unknown operator %s" % node[1]) -def p_ltl(p): - ''' - ltl : opd - | binop - | unop - ''' - p[0] = p[1] + def VARIABLE(self, args): + return ASTNode(Variable(args)) -def p_opd(p): - ''' - opd : VARIABLE - | LITERAL - | LPAREN ltl RPAREN - ''' - if p[1] == "true": - p[0] = ASTNode(Literal(True)) - elif p[1] == "false": - p[0] = ASTNode(Literal(False)) - elif p[1] == '(': - p[0] = p[2] - else: - p[0] = ASTNode(Variable(p[1])) + def LITERAL(self, args): + return ASTNode(Literal(args == "true")) -def p_unop(p): - ''' - unop : ALWAYS ltl - | EVENTUALLY ltl - | NEXT ltl - | NOT ltl - ''' - if p[1] == "always": - op = AlwaysOp(p[2]) - elif p[1] == "eventually": - op = EventuallyOp(p[2]) - elif p[1] == "next": - op = NextOp(p[2]) - elif p[1] == "not": - op = NotOp(p[2]) - else: - raise AutomataError(f"Invalid unary operator {p[1]}") + def start(self, node): + return node - p[0] = ASTNode(op) + def assign(self, node): + return node[0].op.name, node[1] -def p_binop(p): - ''' - binop : opd UNTIL ltl - | opd AND ltl - | opd OR ltl - | opd IMPLY ltl - ''' - if p[2] == "and": - op = AndOp(p[1], p[3]) - elif p[2] == "until": - op = UntilOp(p[1], p[3]) - elif p[2] == "or": - op = OrOp(p[1], p[3]) - elif p[2] == "imply": - op = ImplyOp(p[1], p[3]) - else: - raise AutomataError(f"Invalid binary operator {p[2]}") - - p[0] = ASTNode(op) - -parser = yacc() +parser = lark.Lark(GRAMMAR) def parse_ltl(s: str) -> ASTNode: - spec = parser.parse(s) + try: + spec = parser.parse(s) + except lark.exceptions.UnexpectedInput as e: + raise LTLError(str(e)) + spec = Transform().transform(spec) rule = None subexpr = {} @@ -528,7 +484,7 @@ def parse_ltl(s: str) -> ASTNode: subexpr[assign[0]] = assign[1] if rule is None: - raise AutomataError("Please define your specification in the \"RULE = \" format") + raise LTLError("Please define your specification in the \"RULE = \" format") for node in rule: if not isinstance(node.op, Variable): From eb46aecc1269ccc7e3a50129bde179a9d244c67f Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:05 +0200 Subject: [PATCH 02/40] verification/rvgen: Introduce a parse tree for automata using Lark The DOT parsing scripts directly parse the raw text and they are quite fragile. If the input dot files' formats are slightly changed (for instance, by breaking long some lines which is allowed by the DOT language defined by graphviz), the scripts would fail. To make the scripts robust, the parser should be implemented based on the dot language specification, not based on how the existing dot files look. As a first step, use Lark to implement a Parser based on the graphviz dot language specification. The resulting parse tree is not used yet, but the existing scripts will be converted one by one to use this new parse tree in the follow-up commits. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/f816bce4cc7c48d0b6b6a28a7029459df69d6a71.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/automata.py | 186 +++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py index b9f8149f7118..8649d982383d 100644 --- a/tools/verification/rvgen/rvgen/automata.py +++ b/tools/verification/rvgen/rvgen/automata.py @@ -13,6 +13,191 @@ import re from typing import Iterator from itertools import islice +import lark + +class ParseTree: + # based on https://graphviz.org/doc/info/lang.html + # with the irrelevant stuffs (port and compass) removed + grammar = r''' + start: "strict"? ("graph" | "digraph") ID? "{" stmt_list "}" + + stmt_list: (stmt ";"? stmt_list)? + + stmt: node_stmt + | edge_stmt + | attr_stmt + | ID "=" ID + | subgraph + + attr_stmt: attr_type attr_list + + attr_type: "graph" -> graph + | "node" -> node + | "edge" -> edge + + attr_list: "[" a_list? "]" attr_list? + + a_list: ID "=" ID (";" | ",")? a_list? + + edge_stmt: (node_id | subgraph) edgerhs attr_list? + + edgerhs: edgeop (node_id | subgraph) edgerhs? + + edgeop: "->" | "--" + + node_stmt: node_id attr_list? + + node_id: ID + + subgraph: ("subgraph" ID?)? "{" stmt_list "}" + + ID: CNAME + | /-?(\.[0-9]+|[0-9]+(\.[0-9]*))/ + | ESCAPED_STRING + + %import common.CNAME + %import common.ESCAPED_STRING + %import common.WS + %ignore WS + ''' + + @staticmethod + def parse_edge(tree: lark.Tree) -> tuple[str, str]: + # only support a simple node-to-node edge + nodes = [] + for node in tree.iter_subtrees_topdown(): + if node.data == "node_id": + nodes.append(node.children[0].strip('"')) + + if len(nodes) != 2: + raise AutomataError("Only state-to-state transition is supported") + + return tuple(nodes) + + class ParseNodes(lark.visitors.Visitor): + def __init__(self, *args, **kwargs): + self.nodes = set() + super().__init__(*args, **kwargs) + + def node_stmt(self, tree): + node_id = tree.children[0] + node = node_id.children[0].strip('"') + self.nodes.add(node) + + class ParseEdges(lark.visitors.Visitor): + def __init__(self, *args, **kwargs): + self.edges = set() + super().__init__(*args, **kwargs) + + def edge_stmt(self, tree): + edge = ParseTree.parse_edge(tree) + self.edges.add(edge) + + class ParseAttributes(lark.visitors.Interpreter): + def __init__(self, *args, **kwargs): + ''' + Stacks of default attributes. [0] is the default + attributes for the outermost scope, while [-1] is the + default attributes for the current scope. + ''' + self.default_node_attrs = [{}] + self.default_edge_attrs = [{}] + + self.node_attrs = {} + self.edge_attrs = {} + + super().__init__(*args, **kwargs) + + @staticmethod + def __get_attrs(stmt: lark.Tree) -> dict[str, str]: + attrs = {} + + for node in stmt.iter_subtrees(): + if node.data == "a_list": + attrs[node.children[0]] = node.children[1].strip('"') + + return attrs + + + def subgraph(self, tree): + # We are entering a new scope, inherit the default + # attributes of the outer scope + self.default_node_attrs.append(self.default_node_attrs[-1].copy()) + self.default_edge_attrs.append(self.default_edge_attrs[-1].copy()) + + children = self.visit_children(tree) + + # Exiting the scope + del self.default_node_attrs[-1] + del self.default_edge_attrs[-1] + + return children + + def node_stmt(self, tree): + node_id = tree.children[0] + node = node_id.children[0].strip('"') + + attrs = self.default_node_attrs[-1].copy() + attrs |= self.__get_attrs(tree) + + if attrs: + if node in self.node_attrs: + self.node_attrs[node] = attrs | self.node_attrs[node] + else: + self.node_attrs[node] = attrs + + return self.visit_children(tree) + + def edge_stmt(self, tree): + edge = ParseTree.parse_edge(tree) + + attrs = self.default_edge_attrs[-1].copy() + attrs |= self.__get_attrs(tree) + + if attrs: + if edge in self.edge_attrs: + self.edge_attrs[edge] = attrs | self.edge_attrs[edge] + else: + self.edge_attrs[edge] = attrs + + return self.visit_children(tree) + + def attr_stmt(self, tree): + attr_type = tree.children[0].data + attrs = self.__get_attrs(tree) + + if attr_type == "node": + self.default_node_attrs[-1] |= attrs + elif attr_type == "edge": + self.default_edge_attrs[-1] |= attrs + else: + # graph attributes are irrelevant + pass + + self.visit_children(tree) + + def __init__(self, dot_file): + parser = lark.Lark(self.grammar, parser='lalr') + node_parser = self.ParseNodes() + edge_parser = self.ParseEdges() + attributes_parser = self.ParseAttributes() + + try: + with open(dot_file, "r") as f: + tree = parser.parse(f.read()) + attributes_parser.visit(tree) + node_parser.visit(tree) + edge_parser.visit(tree) + except OSError as exc: + raise AutomataError(exc.strerror) from exc + except lark.exceptions.UnexpectedInput as exc: + raise AutomataError(str(exc)) + + self.nodes = node_parser.nodes + self.edges = edge_parser.edges + self.node_attrs = attributes_parser.node_attrs + self.edge_attrs = attributes_parser.edge_attrs + class _ConstraintKey: """Base class for constraint keys.""" @@ -66,6 +251,7 @@ class Automata: self.__dot_path = file_path self.name = model_name or self.__get_model_name() self.__dot_lines = self.__open_dot() + self.__parse_tree = ParseTree(file_path) self.states, self.initial_state, self.final_states = self.__get_state_variables() self.env_types = {} self.env_stored = set() From a9fb612daf5c916c709da32cbe00dea26e5f6322 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:06 +0200 Subject: [PATCH 03/40] verification/rvgen: Implement state and transition parser based on Lark The DOT parsing scripts directly parse the raw text and they are quite fragile. If the input dot files' formats are slightly changed (for instance, by breaking long some lines which is allowed by the DOT language), the scripts would fail. Prepare to move away from the raw text processing, implement parsers based on Lark which parse states, transitions and constraints. The parse results are not used yet. The existing scripts will be converted one by one to them, and the raw text processing will eventually be removed. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/3ef60fdb03154abb9d9718ea106484213e1a4598.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/automata.py | 216 +++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py index 8649d982383d..ea7eabd4c173 100644 --- a/tools/verification/rvgen/rvgen/automata.py +++ b/tools/verification/rvgen/rvgen/automata.py @@ -198,6 +198,164 @@ class ParseTree: self.node_attrs = attributes_parser.node_attrs self.edge_attrs = attributes_parser.edge_attrs +class ConstraintCondition: + def __init__(self, env: str, op: str, val: str, unit=None): + self.env = env + self.op = op + self.val = val + self.unit = unit + if unit is None: + # try to infer unit from constants or parameters + val_for_unit = val.lower().replace("()", "") + if val_for_unit.endswith("_ns"): + self.unit = "ns" + if val_for_unit.endswith("_jiffies"): + self.unit = "j" + +class ConstraintRule: + grammar = r''' + rule: condition (OP condition)* + + OP: "&&" | "||" + + condition: ENV CMP_OP VAL UNIT? + + ENV: CNAME + + CMP_OP: "==" | "!=" | "<=" | "<" | ">=" | ">" + + VAL: /[0-9]+/ + | /[A-Z_]+\(\)/ + | /[A-Z_]+/ + | /[a-z_]+\(\)/ + | /[a-z_]+/ + + UNIT: "ns" | "us" | "ms" | "s" | "j" + ''' + + def __init__(self, c: ConstraintCondition): + ''' + A list of pairs of + - the condition (e.g. is_constr_dl == 1) + - the logical operator ("||" or "&&") combining this + condition with the next one if it exists, otherwise None + + TODO: Perhaps use an abstract syntax tree instead, because + this representation cannot capture precedence + ''' + self.rules = [[c, None]] + + def chain(self, op: str, c: ConstraintCondition): + self.rules[-1][1] = op + self.rules.append([c, None]) + +class ConstraintReset: + def __init__(self, env): + self.env = env + +class StateLabelParser: + grammar = r''' + label: CNAME ("\\n" condition)? + + %import common.CNAME + %import common.WS + %ignore WS + ''' + ConstraintRule.grammar + + parser = lark.Lark(grammar, parser='lalr', start="label") + + def __init__(self, label: str): + try: + tree = self.parser.parse(label) + except lark.exceptions.UnexpectedInput as exc: + raise(AutomataError(f"Unrecognised state \"{label}\"\n{exc}")) + + self.state = tree.children[0] + self.constraint = None + + if len(tree.children) == 2: + self.constraint = ConstraintCondition(*tree.children[1].children) + if self.constraint.op not in ("<", "<="): + raise AutomataError("State constraints must be clock expirations like" + f" clk tuple[list[str], str, list[str]]: # wait for node declaration states = [] From 75092d5a48ed827e6ac5ba0b1f3cd0459b686276 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:07 +0200 Subject: [PATCH 04/40] verification/rvgen: Convert __fill_verify_invariants_func() to Lark Convert __fill_verify_invariants_func() to use the parsed states information from Lark, prepare to remove the old raw text parsing code. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/dce2a7dd9e9644e1c4c7ddf696c1b695b14157eb.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/dot2k.py | 32 ++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index 110cfd69e53a..0595bfcd232e 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -12,6 +12,7 @@ from collections import deque from .dot2c import Dot2c from .generator import Monitor from .automata import _EventConstraintKey, _StateConstraintKey, AutomataError +from .automata import ConstraintCondition class dot2k(Monitor, Dot2c): @@ -177,6 +178,14 @@ class ha2k(dot2k): raise AutomataError("Detected deterministic automaton, use the 'da' class") self.trace_h = self._read_template_file("trace_hybrid.h") self.__parse_constraints() + self.has_invariant = False + self.has_guard = False + for state in self._states: + if state.inv: + self.has_invariant = True + for transition in self.transitions: + if transition.rule or transition.reset: + self.has_guard = True def fill_monitor_class_type(self) -> str: if self._is_id_monitor(): @@ -218,14 +227,13 @@ class ha2k(dot2k): assert env.removesuffix(f"_{self.name}") in self.envs return env - def __start_to_invariant_check(self, constr: str) -> str: + def __start_to_invariant_check(self, inv: ConstraintCondition) -> str: # by default assume the timer has ns expiration - env = self.__get_constraint_env(constr) clock_type = "ns" - if self.env_types.get(env.removesuffix(f"_{self.name}")) == "j": + if inv.unit == "j": clock_type = "jiffy" - return f"return ha_check_invariant_{clock_type}(ha_mon, {env}, time_ns)" + return f"return ha_check_invariant_{clock_type}(ha_mon, {inv.env}_{self.name}, time_ns)" def __start_to_conv(self, constr: str) -> str: """ @@ -320,20 +328,22 @@ class ha2k(dot2k): self.invariants[key] = rules[0] def __fill_verify_invariants_func(self) -> list[str]: - buff = [] - if not self.invariants: + if not self.has_invariant: return [] - buff.append( + buff = [ f"""static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, \t\t\t\t\tenum {self.enum_states_def} curr_state, enum {self.enum_events_def} event, \t\t\t\t\tenum {self.enum_states_def} next_state, u64 time_ns) -{{""") +{{"""] _else = "" - for state, constr in sorted(self.invariants.items()): - check_str = self.__start_to_invariant_check(constr) - buff.append(f"\t{_else}if (curr_state == {self.states[state]}{self.enum_suffix})") + for state in self._states: + if not state.inv: + continue + + check_str = self.__start_to_invariant_check(state.inv) + buff.append(f"\t{_else}if (curr_state == {state.name}{self.enum_suffix})") buff.append(f"\t\t{check_str};") _else = "else " From beb3a26e23fb65f634ace4a05259759e0eae4ca9 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:08 +0200 Subject: [PATCH 05/40] verification/rvgen: Convert __fill_setup_invariants_func() to Lark Prepare for self.invariants and __parse_constraints() to be removed. convert __fill_setup_invariants_func() to use the new parsed states from Lark. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/1e6e95ae085b21155f9ba97359659fa231d1b803.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/dot2k.py | 48 ++++++++++++++++++++----- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index 0595bfcd232e..93674505f07b 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -250,6 +250,30 @@ class ha2k(dot2k): return (f"ha_start_timer_{clock_type}(ha_mon, {rule["env"]}{self.enum_suffix}," f" {value}, time_ns)") + def __parse_invariant(self, inv): + # by default assume the timer has ns expiration + clock_type = "ns" + if inv.unit == "j": + clock_type = "jiffy" + + env = inv.env + self.enum_suffix + try: + val = int(inv.val) + except ValueError: + # it's a constant, a parameter or a function + val = inv.val.replace("()", "(ha_mon)") + + match inv.unit: + case "us": + val *= 10**3 + case "ms": + val *= 10**6 + case "s": + val *= 10**9 + + return (f"ha_start_timer_{clock_type}(ha_mon, {env}," + f" {val}, time_ns)") + def __format_guard_rules(self, rules: list[str]) -> list[str]: """ Merge guard constraints as a single C return statement. @@ -463,15 +487,14 @@ f"""static inline bool ha_verify_guards(struct ha_monitor *ha_mon, return conflict_guards, conflict_invs def __fill_setup_invariants_func(self) -> list[str]: - buff = [] - if not self.invariants: + if not self.has_invariant: return [] - buff.append( + buff = [ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon, \t\t\t\t enum {self.enum_states_def} curr_state, enum {self.enum_events_def} event, \t\t\t\t enum {self.enum_states_def} next_state, u64 time_ns) -{{""") +{{"""] conditions = ["next_state == curr_state"] conditions += [f"event != {e}{self.enum_suffix}" @@ -480,13 +503,20 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon, buff.append(f"\tif ({condition_str})\n\t\treturn;") _else = "" - for state, constr in sorted(self.invariants.items()): - buff.append(f"\t{_else}if (next_state == {self.states[state]}{self.enum_suffix})") - buff.append(f"\t\t{constr};") + for state in self._states: + inv = state.inv + if not inv: + continue + inv = self.__parse_invariant(inv) + buff.append(f"\t{_else}if (next_state == {state.name}{self.enum_suffix})") + buff.append(f"\t\t{inv};") _else = "else " - for state in self.invariants: - buff.append(f"\telse if (curr_state == {self.states[state]}{self.enum_suffix})") + for state in self._states: + inv = state.inv + if not inv: + continue + buff.append(f"\telse if (curr_state == {state.name}{self.enum_suffix})") buff.append("\t\tha_cancel_timer(ha_mon);") buff.append("}\n") From e0235729b0ab7e1f994e82ef908381bbdb8684bd Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:09 +0200 Subject: [PATCH 06/40] verification/rvgen: Convert __fill_verify_guards_func() to Lark Prepare to remove self.guards and self.__parse_constraints(), convert __fill_verify_guards_func() to use the parsed transitions from Lark. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/4f18c30b60d7c7138c0016cd6985d14a898b1eec.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/dot2k.py | 38 +++++++++++++++++++------ 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index 93674505f07b..ced4e6288ff4 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -221,6 +221,19 @@ class ha2k(dot2k): def __parse_single_constraint(self, rule: dict, value: str) -> str: return f"ha_get_env(ha_mon, {rule["env"]}{self.enum_suffix}, time_ns) {rule["op"]} {value}" + def __parse_guard_rule(self, rule) -> list[str]: + buff = [] + for c, sep in rule.rules: + env = c.env + self.enum_suffix + op = c.op + val = self.__adjust_value(c.val, c.unit) + + cond = f"ha_get_env(ha_mon, {env}, time_ns) {op} {val}" + if sep: + cond += f" {sep}" + buff.append(cond) + return buff + def __get_constraint_env(self, constr: str) -> str: """Extract the second argument from an ha_ function""" env = constr.split("(")[1].split()[1].rstrip(")").rstrip(",") @@ -291,7 +304,7 @@ class ha2k(dot2k): rules = invalid_checks + rules separator = "\n\t\t " if sum(len(r) for r in rules) > 80 else " " - return ["res = " + separator.join(rules)] + return ["res = " + separator.join(rules) + ";"] def __validate_constraint(self, key: tuple[int, int] | int, constr: str, rule, reset) -> None: @@ -410,7 +423,8 @@ f"""static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon, def __fill_verify_guards_func(self) -> list[str]: buff = [] - if not self.guards: + + if not self.has_guard: return [] buff.append( @@ -422,14 +436,22 @@ f"""static inline bool ha_verify_guards(struct ha_monitor *ha_mon, """) _else = "" - for edge, constr in sorted(self.guards.items()): + for transition in self.transitions: + if not transition.rule and not transition.reset: + continue + buff.append(f"\t{_else}if (curr_state == " - f"{self.states[edge[0]]}{self.enum_suffix} && " - f"event == {self.events[edge[1]]}{self.enum_suffix})") - if constr.count(";") > 0: + f"{transition.src}{self.enum_suffix} && " + f"event == {transition.event}{self.enum_suffix})") + rule = transition.rule + reset = transition.reset + if rule and reset: buff[-1] += " {" - buff += [f"\t\t{c};" for c in constr.split(";")] - if constr.count(";") > 0: + if rule: + buff.append("\t\t" + self.__format_guard_rules(self.__parse_guard_rule(rule))[0]) + if reset: + buff.append(f"\t\tha_reset_env(ha_mon, {reset.env}{self.enum_suffix}, time_ns);") + if rule and reset: _else = "} else " else: _else = "else " From ab2900ae252b2a3dc2641bea627d1cb14a5d1bcb Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:10 +0200 Subject: [PATCH 07/40] rv: Simplify hybrid automata monitors's clock variables Hybrid automata monitors's clock variables have two different representations: - The invariant representation, which is the timestamp when the invariant expires - The guard representation, which is the timestamp when the clock is last reset This dual representation makes the logic quite difficult to follow (well, at least for me). It also complicates the monitors and the generation tool, as it requires conversion back and forth between the representation. Simplify by using the clock variables for a single purpose: storing the time stamp since the clock is last reset. This also allows simplifying rvgen, which will be done in a follow-up commit. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/c0f600dcbf3d8b487c944406851a39146f4d91fa.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- include/rv/ha_monitor.h | 64 ++++++------------------ kernel/trace/rv/monitors/nomiss/nomiss.c | 18 +------ kernel/trace/rv/monitors/stall/stall.c | 2 +- 3 files changed, 19 insertions(+), 65 deletions(-) diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h index 28d3c74cabfc..9144b4c06f3f 100644 --- a/include/rv/ha_monitor.h +++ b/include/rv/ha_monitor.h @@ -327,19 +327,8 @@ static inline void __ha_monitor_timer_callback(struct ha_monitor *ha_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. + * The clock variables store the time epoch - the timestamp when the clock was last reset. + * They are read by subtracting the time epoch from the current time. */ /* @@ -353,31 +342,21 @@ static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64 { 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) +static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon, enum envs env, + u64 time_ns, u64 expire_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; + return READ_ONCE(ha_mon->env_store[env]) >= time_ns - expire_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) +static inline u64 ha_invariant_passed_ns(struct ha_monitor *ha_mon, enum envs env, 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; + return ha_get_env(ha_mon, env, time_ns); } /* @@ -391,32 +370,21 @@ 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) +static inline bool ha_check_invariant_jiffy(struct ha_monitor *ha_mon, enum envs env, + u64 time_ns, u64 expire_jiffy) { - 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()); - + return time_after64(READ_ONCE(ha_mon->env_store[env]), get_jiffies_64() - expire_jiffy); } /* * 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) +static inline u64 ha_invariant_passed_jiffy(struct ha_monitor *ha_mon, enum envs env, 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; + return ha_get_env(ha_mon, env, time_ns); } /* @@ -463,14 +431,14 @@ static inline void ha_setup_timer(struct ha_monitor *ha_mon) 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); + u64 passed = ha_invariant_passed_jiffy(ha_mon, env, 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); + u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns); ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED, nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns); @@ -516,7 +484,7 @@ 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); + u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns); if (RV_MON_TYPE == RV_MON_PER_CPU) mode |= HRTIMER_MODE_PINNED; @@ -525,7 +493,7 @@ static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env, 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); + u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns); ha_start_timer_ns(ha_mon, ENV_MAX_STORED, jiffies_to_nsecs(expire - passed), time_ns); diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c index 8ead8783c29f..515ece5ce0ca 100644 --- a/kernel/trace/rv/monitors/nomiss/nomiss.c +++ b/kernel/trace/rv/monitors/nomiss/nomiss.c @@ -57,24 +57,12 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, enum states next_state, u64 time_ns) { if (curr_state == ready_nomiss) - return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns); + return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon)); else if (curr_state == running_nomiss) - return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns); + return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns, DEADLINE_NS(ha_mon)); return true; } -static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon, - enum states curr_state, enum events event, - enum states next_state, u64 time_ns) -{ - if (curr_state == next_state) - return; - if (curr_state == ready_nomiss) - ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns); - else if (curr_state == running_nomiss) - ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns); -} - static inline bool ha_verify_guards(struct ha_monitor *ha_mon, enum states curr_state, enum events event, enum states next_state, u64 time_ns) @@ -122,8 +110,6 @@ static bool ha_verify_constraint(struct ha_monitor *ha_mon, if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns)) return false; - ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns); - if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns)) return false; diff --git a/kernel/trace/rv/monitors/stall/stall.c b/kernel/trace/rv/monitors/stall/stall.c index 3c38fb1a0159..b265578f845c 100644 --- a/kernel/trace/rv/monitors/stall/stall.c +++ b/kernel/trace/rv/monitors/stall/stall.c @@ -38,7 +38,7 @@ static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, enum states next_state, u64 time_ns) { if (curr_state == enqueued_stall) - return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns); + return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns, threshold_jiffies); return true; } From 62247351315bddbc1c7e749e1443c21643a56359 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:11 +0200 Subject: [PATCH 08/40] verification/rvgen: Simplify the generation for clock variables Hybrid automata monitors's clock variables have been changed to have only a single representation. Now there is no need to generate code to convert between the two representations. Delete __fill_convert_inv_guard_func() and its associates. Update __start_to_invariant_check() to how invariants now work. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/0d2a8e4bf90a9ed959289ddd2190b1152e4bbadf.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/dot2k.py | 96 +------------------------ 1 file changed, 3 insertions(+), 93 deletions(-) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index ced4e6288ff4..4ea1ecc55c80 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -246,7 +246,9 @@ class ha2k(dot2k): if inv.unit == "j": clock_type = "jiffy" - return f"return ha_check_invariant_{clock_type}(ha_mon, {inv.env}_{self.name}, time_ns)" + value = self.__adjust_value(inv.val, inv.unit) + + return f"return ha_check_invariant_{clock_type}(ha_mon, {inv.env}_{self.name}, time_ns, {value})" def __start_to_conv(self, constr: str) -> str: """ @@ -387,40 +389,6 @@ f"""static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, buff.append("\treturn true;\n}\n") return buff - def __fill_convert_inv_guard_func(self) -> list[str]: - buff = [] - if not self.invariants: - return [] - - conflict_guards, conflict_invs = self.__find_inv_conflicts() - if not conflict_guards and not conflict_invs: - return [] - - buff.append( -f"""static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon, -\t\t\t\t\tenum {self.enum_states_def} curr_state, enum {self.enum_events_def} event, -\t\t\t\t\tenum {self.enum_states_def} next_state, u64 time_ns) -{{""") - buff.append("\tif (curr_state == next_state)\n\t\treturn;") - - _else = "" - for state, constr in sorted(self.invariants.items()): - # a state with invariant can reach us without reset - # multiple conflicts must have the same invariant, otherwise we cannot - # know how to reset the value - conf_i = [start for start, end in conflict_invs if end == state] - # we can reach a guard without reset - conf_g = [e for s, e in conflict_guards if s == state] - if not conf_i and not conf_g: - continue - buff.append(f"\t{_else}if (curr_state == {self.states[state]}{self.enum_suffix})") - - buff.append(f"\t\t{self.__start_to_conv(constr)};") - _else = "else " - - buff.append("}\n") - return buff - def __fill_verify_guards_func(self) -> list[str]: buff = [] @@ -460,54 +428,6 @@ f"""static inline bool ha_verify_guards(struct ha_monitor *ha_mon, buff.append("\treturn res;\n}\n") return buff - def __find_inv_conflicts(self) -> tuple[set[tuple[int, _EventConstraintKey]], - set[tuple[int, _StateConstraintKey]]]: - """ - Run a breadth first search from all states with an invariant. - Find any conflicting constraints reachable from there, this can be - another state with an invariant or an edge with a non-reset guard. - Stop when we find a reset. - - Return the set of conflicting guards and invariants as tuples of - conflicting state and constraint key. - """ - conflict_guards: set[tuple[int, _EventConstraintKey]] = set() - conflict_invs: set[tuple[int, _StateConstraintKey]] = set() - for start_idx in self.invariants: - queue = deque([(start_idx, 0)]) # (state_idx, distance) - env = self.__get_constraint_env(self.invariants[start_idx]) - - while queue: - curr_idx, distance = queue.popleft() - - # Check state condition - if curr_idx != start_idx and curr_idx in self.invariants: - conflict_invs.add((start_idx, _StateConstraintKey(curr_idx))) - continue - - # Check if we should stop - if distance > len(self.states): - break - if curr_idx != start_idx and distance > 1: - continue - - for event_idx, next_state_name in enumerate(self.function[curr_idx]): - if next_state_name == self.invalid_state_str: - continue - curr_guard = self.guards.get((curr_idx, event_idx), "") - if "reset" in curr_guard and env in curr_guard: - continue - - if env in curr_guard: - conflict_guards.add((start_idx, - _EventConstraintKey(curr_idx, event_idx))) - continue - - next_idx = self.states.index(next_state_name) - queue.append((next_idx, distance + 1)) - - return conflict_guards, conflict_invs - def __fill_setup_invariants_func(self) -> list[str]: if not self.has_invariant: return [] @@ -558,16 +478,9 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon, * the next state has a constraint, cancel it in any other case and to check * that it didn't expire before the callback run. Transitions to the same state * without a reset never affect timers. - * Due to the different representations between invariants and guards, there is - * a function to convert it in case invariants or guards are reachable from - * another invariant without reset. Those are not present if not required in - * the model. This is all automatic but is worth checking because it may show - * errors in the model (e.g. missing resets). */""") buff += self.__fill_verify_invariants_func() - inv_conflicts = self.__fill_convert_inv_guard_func() - buff += inv_conflicts buff += self.__fill_verify_guards_func() buff += self.__fill_setup_invariants_func() @@ -580,9 +493,6 @@ f"""static bool ha_verify_constraint(struct ha_monitor *ha_mon, if self.invariants: buff.append("\tif (!ha_verify_invariants(ha_mon, curr_state, " "event, next_state, time_ns))\n\t\treturn false;\n") - if inv_conflicts: - buff.append("\tha_convert_inv_guard(ha_mon, curr_state, event, " - "next_state, time_ns);\n") if self.guards: buff.append("\tif (!ha_verify_guards(ha_mon, curr_state, event, " From 35266cc333303fbe98288bd1b8afcc4633efe51c Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:12 +0200 Subject: [PATCH 09/40] verification/rvgen: Delete __parse_constraint() All previous users of self.invariants and self.guards have been converted to the Lark parser, delete __parse_constraints() and its associates. Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/b22a5a3822fe53afb8e2cf1df623a0e4c9ed5f49.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/dot2k.py | 67 ++----------------------- 1 file changed, 4 insertions(+), 63 deletions(-) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index 4ea1ecc55c80..f1f5fa297adb 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -177,7 +177,6 @@ class ha2k(dot2k): if not self.is_hybrid_automata(): raise AutomataError("Detected deterministic automaton, use the 'da' class") self.trace_h = self._read_template_file("trace_hybrid.h") - self.__parse_constraints() self.has_invariant = False self.has_guard = False for state in self._states: @@ -308,64 +307,6 @@ class ha2k(dot2k): separator = "\n\t\t " if sum(len(r) for r in rules) > 80 else " " return ["res = " + separator.join(rules) + ";"] - def __validate_constraint(self, key: tuple[int, int] | int, constr: str, - rule, reset) -> None: - # event constrains are tuples and allow both rules and reset - # state constraints are only used for expirations (e.g. clk None: - self.guards: dict[_EventConstraintKey, str] = {} - self.invariants: dict[_StateConstraintKey, str] = {} - for key, constraint in self.constraints.items(): - rules = [] - resets = [] - for c, sep in self._split_constraint_expr(constraint): - rule = self.constraint_rule.search(c) - reset = self.constraint_reset.search(c) - self.__validate_constraint(key, c, rule, reset) - if rule: - value = rule["val"] - value_len = len(rule["val"]) - unit = None - if rule.groupdict().get("unit"): - value_len += len(rule["unit"]) - unit = rule["unit"] - c = c[:-(value_len)] - value = self.__adjust_value(value, unit) - if self.is_event_constraint(key): - c = self.__parse_single_constraint(rule, value) - if sep: - c += f" {sep}" - else: - c = self.__parse_timer_constraint(rule, value) - rules.append(c) - if reset: - c = f"ha_reset_env(ha_mon, {reset["env"]}{self.enum_suffix}, time_ns)" - resets.append(c) - if self.is_event_constraint(key): - res = self.__format_guard_rules(rules) + resets - self.guards[key] = ";".join(res) - else: - self.invariants[key] = rules[0] - def __fill_verify_invariants_func(self) -> list[str]: if not self.has_invariant: return [] @@ -490,15 +431,15 @@ f"""static bool ha_verify_constraint(struct ha_monitor *ha_mon, \t\t\t\t enum {self.enum_states_def} next_state, u64 time_ns) {{""") - if self.invariants: + if self.has_invariant: buff.append("\tif (!ha_verify_invariants(ha_mon, curr_state, " "event, next_state, time_ns))\n\t\treturn false;\n") - if self.guards: + if self.has_guard: buff.append("\tif (!ha_verify_guards(ha_mon, curr_state, event, " "next_state, time_ns))\n\t\treturn false;\n") - if self.invariants: + if self.has_invariant: buff.append("\tha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);\n") buff.append("\treturn true;\n}\n") @@ -575,7 +516,7 @@ f"""static bool ha_verify_constraint(struct ha_monitor *ha_mon, return self.__fill_hybrid_get_reset_functions() + self.__fill_constr_func() def _fill_timer_type(self) -> list: - if self.invariants: + if self.has_invariant: return [ "/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */", "#define HA_TIMER_TYPE HA_TIMER_HRTIMER" From 8aa51cb4b1838b01a977c3d50ad4a7f893b5fbb0 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:13 +0200 Subject: [PATCH 10/40] verification/rvgen: Switch __get_event_variables() to Lark Switch __get_event_variables() to use the parsed results from Lark, instead of raw text processing. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/03f6457b4fcaa64199ffe73edb2a9fc48e76a839.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/automata.py | 76 +++++----------------- 1 file changed, 18 insertions(+), 58 deletions(-) diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py index ea7eabd4c173..c34e916516ba 100644 --- a/tools/verification/rvgen/rvgen/automata.py +++ b/tools/verification/rvgen/rvgen/automata.py @@ -591,45 +591,22 @@ class Automata: def __get_event_variables(self) -> tuple[list[str], list[str]]: events: list[str] = [] envs: list[str] = [] - # here we are at the begin of transitions, take a note, we will return later. - cursor = self.__get_cursor_begin_events() - for line in map(str.lstrip, islice(self.__dot_lines, cursor, None)): - if not line.startswith('"'): - break + for transition in self.transitions: + events.append(transition.event) - # transitions have the format: - # "all_fired" -> "both_fired" [ label = "disable_irq" ]; - # ------------ event is here ------------^^^^^ - split_line = line.split() - if len(split_line) > 1 and split_line[1] == "->": - event = "".join(split_line[split_line.index("label") + 2:-1]).replace('"', '') + if transition.reset: + envs.append(transition.reset.env) + self.env_stored.add(transition.reset.env) + if transition.rule: + for c, _ in transition.rule.rules: + envs.append(c.env) + self.__extract_env_var(c) - # when a transition has more than one label, they are like this - # "local_irq_enable\nhw_local_irq_enable_n" - # so split them. - - for i in event.split("\\n"): - # if the event contains a constraint (hybrid automata), - # it will be separated by a ";": - # "sched_switch;x<1000;reset(x)" - ev, *constr = i.split(";") - if constr: - if len(constr) > 2: - raise AutomataError("Only 1 constraint and 1 reset are supported") - envs += self.__extract_env_var(constr) - events.append(ev) - else: - # state labels have the format: - # "enable_fired" [label = "enable_fired\ncondition"]; - # ----- label is here -----^^^^^ - # label and node name must be the same, condition is optional - state = line.split("label")[1].split('"')[1] - _, *constr = state.split("\\n") - if constr: - if len(constr) > 1: - raise AutomataError("Only 1 constraint is supported in the state") - envs += self.__extract_env_var([constr[0].replace(" ", "")]) + for state in self._states: + if state.inv: + envs.append(state.inv.env) + self.__extract_env_var(state.inv) return sorted(set(events)), sorted(set(envs)) @@ -653,28 +630,11 @@ class Automata: seps.append(None) return zip(exprs, seps) - def __extract_env_var(self, constraint: list[str]) -> list[str]: - env = [] - for c, _ in self._split_constraint_expr(constraint): - rule = self.constraint_rule.search(c) - reset = self.constraint_reset.search(c) - if rule: - env.append(rule["env"]) - if rule.groupdict().get("unit"): - self.env_types[rule["env"]] = rule["unit"] - if rule["val"][0].isalpha(): - self.constraint_vars.add(rule["val"]) - # try to infer unit from constants or parameters - val_for_unit = rule["val"].lower().replace("()", "") - if val_for_unit.endswith("_ns"): - self.env_types[rule["env"]] = "ns" - if val_for_unit.endswith("_jiffies"): - self.env_types[rule["env"]] = "j" - if reset: - env.append(reset["env"]) - # environment variables that are reset need a storage - self.env_stored.add(reset["env"]) - return env + def __extract_env_var(self, constraint: ConstraintCondition): + if constraint.unit: + self.env_types[constraint.env] = constraint.unit + if constraint.val[0].isalpha(): + self.constraint_vars.add(constraint.val) def __create_matrix(self) -> tuple[list[list[str]], dict[_ConstraintKey, list[str]]]: # transform the array into a dictionary From cad252db78a91b6da269cafd91637acd47ab70ea Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:14 +0200 Subject: [PATCH 11/40] verification/rvgen: Switch __create_matrix() to Lark Switch __create_matrix() to use the transitions parsed by Lark to avoid all the raw text parsing. Also stop parsing constraints in __create_matrix(), that is not used anymore. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/36e76b64049b7fef1cf5c2855fea310c0452ee38.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/automata.py | 45 ++++++---------------- tools/verification/rvgen/rvgen/dot2k.py | 2 +- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py index c34e916516ba..1726e82546a7 100644 --- a/tools/verification/rvgen/rvgen/automata.py +++ b/tools/verification/rvgen/rvgen/automata.py @@ -418,7 +418,7 @@ class Automata: self.constraint_vars = set() self.self_loop_reset_events = set() self.events, self.envs = self.__get_event_variables() - self.function, self.constraints = self.__create_matrix() + self.function = self.__create_matrix() self.events_start, self.events_start_run = self.__store_init_events() self.env_stored = sorted(self.env_stored) self.constraint_vars = sorted(self.constraint_vars) @@ -636,10 +636,10 @@ class Automata: if constraint.val[0].isalpha(): self.constraint_vars.add(constraint.val) - def __create_matrix(self) -> tuple[list[list[str]], dict[_ConstraintKey, list[str]]]: + def __create_matrix(self) -> list[list[str]]: # transform the array into a dictionary events = self.events - states = self.states + states = [s.name for s in self._states] events_dict = {} states_dict = {} nr_event = 0 @@ -654,39 +654,16 @@ class Automata: # declare the matrix.... matrix = [[self.invalid_state_str for _ in range(nr_event)] for _ in range(nr_state)] - constraints: dict[_ConstraintKey, list[str]] = {} - # and we are back! Let's fill the matrix - cursor = self.__get_cursor_begin_events() + for transition in self.transitions: + src, dst = transition.src, transition.dst + event = transition.event + if src == dst and transition.reset: + # those events reset also on self loops + self.self_loop_reset_events.add(event) + matrix[states_dict[src]][events_dict[event]] = dst - for line in map(str.lstrip, - islice(self.__dot_lines, cursor, None)): - - if not line or line[0] != '"': - break - - split_line = line.split() - - if len(split_line) > 2 and split_line[1] == "->": - origin_state = split_line[0].replace('"', '').replace(',', '_') - dest_state = split_line[2].replace('"', '').replace(',', '_') - possible_events = "".join(split_line[split_line.index("label") + 2:-1]).replace('"', '') - for event in possible_events.split("\\n"): - event, *constr = event.split(";") - if constr: - key = _EventConstraintKey(states_dict[origin_state], events_dict[event]) - constraints[key] = constr - # those events reset also on self loops - if origin_state == dest_state and "reset" in "".join(constr): - self.self_loop_reset_events.add(event) - matrix[states_dict[origin_state]][events_dict[event]] = dest_state - else: - state = line.split("label")[1].split('"')[1] - state, *constr = state.replace(" ", "").split("\\n") - if constr: - constraints[_StateConstraintKey(states_dict[state])] = constr - - return matrix, constraints + return matrix def __store_init_events(self) -> tuple[list[bool], list[bool]]: events_start = [False] * len(self.events) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index f1f5fa297adb..03141e9ef45d 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -407,7 +407,7 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon, def __fill_constr_func(self) -> list[str]: buff = [] - if not self.constraints: + if not self.has_invariant and not self.has_guard: return [] buff.append( From 7edaba052d9f0047ffabe10e07f9a295e15b111b Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:15 +0200 Subject: [PATCH 12/40] verification/rvgen: Remove the old state variables The state variables (states, initial_state, final_states) only capture the states' names and have less information than their Lark-based counterparts. Switch to use the new state variables and delete these old ones. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/c1e214623f83a6d8a97b6ffa54ce8ec106b11c65.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/automata.py | 9 ++++----- tools/verification/rvgen/rvgen/dot2c.py | 10 +++++----- tools/verification/rvgen/rvgen/dot2k.py | 8 ++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py index 1726e82546a7..2cb7443ea00f 100644 --- a/tools/verification/rvgen/rvgen/automata.py +++ b/tools/verification/rvgen/rvgen/automata.py @@ -411,8 +411,7 @@ class Automata: self.__dot_lines = self.__open_dot() self.__parse_tree = ParseTree(file_path) self.transitions = self.__parse_transitions() - self._states, self._initial_state, self._final_states = self.__parse_states() - self.states, self.initial_state, self.final_states = self.__get_state_variables() + self.states, self.initial_state, self.final_states = self.__parse_states() self.env_types = {} self.env_stored = set() self.constraint_vars = set() @@ -603,7 +602,7 @@ class Automata: envs.append(c.env) self.__extract_env_var(c) - for state in self._states: + for state in self.states: if state.inv: envs.append(state.inv.env) self.__extract_env_var(state.inv) @@ -639,7 +638,7 @@ class Automata: def __create_matrix(self) -> list[list[str]]: # transform the array into a dictionary events = self.events - states = [s.name for s in self._states] + states = [s.name for s in self.states] events_dict = {} states_dict = {} nr_event = 0 @@ -675,7 +674,7 @@ class Automata: for j in range(len(self.states)): if self.function[j][i] != self.invalid_state_str: curr_event_used += 1 - if self.function[j][i] == self.initial_state: + if self.function[j][i] == self.initial_state.name: curr_event_will_init += 1 if self.function[0][i] != self.invalid_state_str: curr_event_from_init = True diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py index fc85ba1f649e..22938ce1bf6c 100644 --- a/tools/verification/rvgen/rvgen/dot2c.py +++ b/tools/verification/rvgen/rvgen/dot2c.py @@ -29,10 +29,10 @@ class Dot2c(Automata): def __get_enum_states_content(self) -> list[str]: buff = [] - buff.append(f"\t{self.initial_state}{self.enum_suffix},") + buff.append(f"\t{self.initial_state.name}{self.enum_suffix},") for state in self.states: if state != self.initial_state: - buff.append(f"\t{state}{self.enum_suffix},") + buff.append(f"\t{state.name}{self.enum_suffix},") buff.append(f"\tstate_max{self.enum_suffix},") return buff @@ -142,7 +142,7 @@ class Dot2c(Automata): def format_aut_init_states_string(self) -> list[str]: buff = [] buff.append("\t.state_names = {") - buff.append(self.__get_string_vector_per_line_content(self.states)) + buff.append(self.__get_string_vector_per_line_content([s.name for s in self.states])) buff.append("\t},") return buff @@ -159,7 +159,7 @@ class Dot2c(Automata): return buff def __get_max_strlen_of_states(self) -> int: - max_state_name = len(max(self.states, key=len)) + max_state_name = max((len(s.name) for s in self.states)) return max(max_state_name, len(self.invalid_state_str)) def get_aut_init_function(self) -> str: @@ -199,7 +199,7 @@ class Dot2c(Automata): return buff def get_aut_init_initial_state(self) -> str: - return self.initial_state + return self.initial_state.name def format_aut_init_initial_state(self) -> list[str]: buff = [] diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index 03141e9ef45d..b93d0ccc9bdf 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -179,7 +179,7 @@ class ha2k(dot2k): self.trace_h = self._read_template_file("trace_hybrid.h") self.has_invariant = False self.has_guard = False - for state in self._states: + for state in self.states: if state.inv: self.has_invariant = True for transition in self.transitions: @@ -318,7 +318,7 @@ f"""static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, {{"""] _else = "" - for state in self._states: + for state in self.states: if not state.inv: continue @@ -386,7 +386,7 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon, buff.append(f"\tif ({condition_str})\n\t\treturn;") _else = "" - for state in self._states: + for state in self.states: inv = state.inv if not inv: continue @@ -395,7 +395,7 @@ f"""static inline void ha_setup_invariants(struct ha_monitor *ha_mon, buff.append(f"\t\t{inv};") _else = "else " - for state in self._states: + for state in self.states: inv = state.inv if not inv: continue From fc0663ec311296aa4eacabbcb513e44141fabdb9 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 07:52:16 +0200 Subject: [PATCH 13/40] verification/rvgen: Remove dead code The conversion to use Lark left some dead code behind. Remove them. Reviewed-by: Gabriele Monaco Signed-off-by: Nam Cao Link: https://lore.kernel.org/r/98115605a49c819adae9329823d4010bf181c3b7.1781847583.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/automata.py | 157 --------------------- tools/verification/rvgen/rvgen/dot2k.py | 29 +--- 2 files changed, 1 insertion(+), 185 deletions(-) diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py index 2cb7443ea00f..fd37ce304276 100644 --- a/tools/verification/rvgen/rvgen/automata.py +++ b/tools/verification/rvgen/rvgen/automata.py @@ -9,9 +9,6 @@ # Documentation/trace/rv/deterministic_automata.rst import ntpath -import re -from typing import Iterator -from itertools import islice import lark @@ -356,19 +353,6 @@ class State: self.name = name self.inv = inv -class _ConstraintKey: - """Base class for constraint keys.""" - -class _StateConstraintKey(_ConstraintKey, int): - """Key for a state constraint. Under the hood just state_id.""" - def __new__(cls, state_id: int): - return super().__new__(cls, state_id) - -class _EventConstraintKey(_ConstraintKey, tuple): - """Key for an event constraint. Under the hood just tuple(state_id,event_id).""" - def __new__(cls, state_id: int, event_id: int): - return super().__new__(cls, (state_id, event_id)) - class AutomataError(Exception): """Exception raised for errors in automata parsing and validation. @@ -387,28 +371,10 @@ class Automata: invalid_state_str = "INVALID_STATE" init_marker = "__init_" - node_marker = "{node" - # val can be numerical, uppercase (constant or macro), lowercase (parameter or function) - # only numerical values should have units - constraint_rule = re.compile(r""" - ^ - (?P[a-zA-Z_][a-zA-Z0-9_]+) # C-like identifier for the env var - (?P[!<=>]{1,2}) # operator - (?P - [0-9]+ | # numerical value - [A-Z_]+\(\) | # macro - [A-Z_]+ | # constant - [a-z_]+\(\) | # function - [a-z_]+ # parameter - ) - (?P[a-z]{1,2})? # optional unit for numerical values - """, re.VERBOSE) - constraint_reset = re.compile(r"^reset\((?P[a-zA-Z_][a-zA-Z0-9_]+)\)") def __init__(self, file_path, model_name=None): self.__dot_path = file_path self.name = model_name or self.__get_model_name() - self.__dot_lines = self.__open_dot() self.__parse_tree = ParseTree(file_path) self.transitions = self.__parse_transitions() self.states, self.initial_state, self.final_states = self.__parse_states() @@ -435,57 +401,6 @@ class Automata: return model_name - def __open_dot(self) -> list[str]: - dot_lines = [] - try: - with open(self.__dot_path) as dot_file: - dot_lines = dot_file.readlines() - except OSError as exc: - raise AutomataError(exc.strerror) from exc - - if not dot_lines: - raise AutomataError(f"{self.__dot_path} is empty") - - # checking the first line: - line = dot_lines[0].split() - - if len(line) < 2 or line[0] != "digraph" or line[1] != "state_automaton": - raise AutomataError(f"Not a valid .dot format: {self.__dot_path}") - - return dot_lines - - def __get_cursor_begin_states(self) -> int: - for cursor, line in enumerate(self.__dot_lines): - split_line = line.split() - - if len(split_line) and split_line[0] == self.node_marker: - return cursor - - raise AutomataError("Could not find a beginning state") - - def __get_cursor_begin_events(self) -> int: - state = 0 - cursor = 0 # make pyright happy - - for cursor, line in enumerate(self.__dot_lines): - line = line.split() - if not line: - continue - - if state == 0: - if line[0] == self.node_marker: - state = 1 - elif line[0] != self.node_marker: - break - else: - raise AutomataError("Could not find beginning event") - - cursor += 1 # skip initial state transition - if cursor == len(self.__dot_lines): - raise AutomataError("Dot file ended after event beginning") - - return cursor - def __parse_transitions(self): transitions = [] @@ -542,51 +457,6 @@ class Automata: states.insert(0, initial_state) return states, initial_state, final_states - def __get_state_variables(self) -> tuple[list[str], str, list[str]]: - # wait for node declaration - states = [] - final_states = [] - initial_state = "" - - has_final_states = False - cursor = self.__get_cursor_begin_states() - - # process nodes - for line in islice(self.__dot_lines, cursor, None): - split_line = line.split() - if not split_line or split_line[0] != self.node_marker: - break - - raw_state = split_line[-1] - - # "enabled_fired"}; -> enabled_fired - state = raw_state.replace('"', '').replace('};', '').replace(',', '_') - if state.startswith(self.init_marker): - initial_state = state[len(self.init_marker):] - else: - states.append(state) - if "doublecircle" in line: - final_states.append(state) - has_final_states = True - - if "ellipse" in line: - final_states.append(state) - has_final_states = True - - if not initial_state: - raise AutomataError("The automaton doesn't have an initial state") - - states = sorted(set(states)) - states.remove(initial_state) - - # Insert the initial state at the beginning of the states - states.insert(0, initial_state) - - if not has_final_states: - final_states.append(initial_state) - - return states, initial_state, final_states - def __get_event_variables(self) -> tuple[list[str], list[str]]: events: list[str] = [] envs: list[str] = [] @@ -609,26 +479,6 @@ class Automata: return sorted(set(events)), sorted(set(envs)) - def _split_constraint_expr(self, constr: list[str]) -> Iterator[tuple[str, - str | None]]: - """ - Get a list of strings of the type constr1 && constr2 and returns a list of - constraints and separators: [[constr1,"&&"],[constr2,None]] - """ - exprs = [] - seps = [] - for c in constr: - while "&&" in c or "||" in c: - a = c.find("&&") - o = c.find("||") - pos = a if o < 0 or 0 < a < o else o - exprs.append(c[:pos].replace(" ", "")) - seps.append(c[pos:pos + 2].replace(" ", "")) - c = c[pos + 2:].replace(" ", "") - exprs.append(c) - seps.append(None) - return zip(exprs, seps) - def __extract_env_var(self, constraint: ConstraintCondition): if constraint.unit: self.env_types[constraint.env] = constraint.unit @@ -697,10 +547,3 @@ class Automata: def is_hybrid_automata(self) -> bool: return bool(self.envs) - - def is_event_constraint(self, key: _ConstraintKey) -> bool: - """ - Given the key in self.constraints return true if it is an event - constraint, false if it is a state constraint - """ - return isinstance(key, _EventConstraintKey) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index b93d0ccc9bdf..4d39f229c970 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -8,12 +8,9 @@ # For further information, see: # Documentation/trace/rv/da_monitor_synthesis.rst -from collections import deque from .dot2c import Dot2c from .generator import Monitor -from .automata import _EventConstraintKey, _StateConstraintKey, AutomataError -from .automata import ConstraintCondition - +from .automata import ConstraintCondition, AutomataError class dot2k(Monitor, Dot2c): template_dir = "dot2k" @@ -217,9 +214,6 @@ class ha2k(dot2k): value *= 10**9 return str(value) + "ull" - def __parse_single_constraint(self, rule: dict, value: str) -> str: - return f"ha_get_env(ha_mon, {rule["env"]}{self.enum_suffix}, time_ns) {rule["op"]} {value}" - def __parse_guard_rule(self, rule) -> list[str]: buff = [] for c, sep in rule.rules: @@ -233,12 +227,6 @@ class ha2k(dot2k): buff.append(cond) return buff - def __get_constraint_env(self, constr: str) -> str: - """Extract the second argument from an ha_ function""" - env = constr.split("(")[1].split()[1].rstrip(")").rstrip(",") - assert env.removesuffix(f"_{self.name}") in self.envs - return env - def __start_to_invariant_check(self, inv: ConstraintCondition) -> str: # by default assume the timer has ns expiration clock_type = "ns" @@ -249,21 +237,6 @@ class ha2k(dot2k): return f"return ha_check_invariant_{clock_type}(ha_mon, {inv.env}_{self.name}, time_ns, {value})" - def __start_to_conv(self, constr: str) -> str: - """ - Undo the storage conversion done by ha_start_timer_ - """ - return "ha_inv_to_guard" + constr[constr.find("("):] - - def __parse_timer_constraint(self, rule: dict, value: str) -> str: - # by default assume the timer has ns expiration - clock_type = "ns" - if self.env_types.get(rule["env"]) == "j": - clock_type = "jiffy" - - return (f"ha_start_timer_{clock_type}(ha_mon, {rule["env"]}{self.enum_suffix}," - f" {value}, time_ns)") - def __parse_invariant(self, inv): # by default assume the timer has ns expiration clock_type = "ns" From da245fae4041774bc467fc62fbbfc36a57442e4c Mon Sep 17 00:00:00 2001 From: Yu Chuanyu Date: Thu, 18 Jun 2026 21:45:25 +0800 Subject: [PATCH 14/40] rv: Update rvgen monitor synthesis documentation path The rvgen source comments still refer to da_monitor_synthesis.rst, which no longer exists. The documentation is now available in monitor_synthesis.rst. Update both references to point to the current file. Signed-off-by: Yu Chuanyu Acked-by: Gabriele Monaco Link: https://lore.kernel.org/r/20260618-rvgen-doc-path-v1-1-9bcf0148417a@gmail.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/__main__.py | 2 +- tools/verification/rvgen/rvgen/dot2k.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py index 0915cf86e43b..019839ed9205 100644 --- a/tools/verification/rvgen/__main__.py +++ b/tools/verification/rvgen/__main__.py @@ -6,7 +6,7 @@ # dot2k: transform dot files into a monitor for the Linux kernel. # # For further information, see: -# Documentation/trace/rv/da_monitor_synthesis.rst +# Documentation/trace/rv/monitor_synthesis.rst if __name__ == '__main__': from rvgen.dot2k import da2k, ha2k diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index 4d39f229c970..787f81ef3b83 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -6,7 +6,7 @@ # dot2k: transform dot files into a monitor for the Linux kernel. # # For further information, see: -# Documentation/trace/rv/da_monitor_synthesis.rst +# Documentation/trace/rv/monitor_synthesis.rst from .dot2c import Dot2c from .generator import Monitor From 3fc5d9bb0989996a143af03686efb32f22b46884 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 1 Jun 2026 17:38:39 +0200 Subject: [PATCH 15/40] rv: Fix read_lock scope in per-task DA cleanup The da_monitor_reset_all() function for per-task monitors takes tasklist_lock while iterating over tasks, then keeps it also while iterating over idle tasks (one per CPU). The latter is not necessary since the lock needs to guard only for_each_process_thread(). Use a scoped_guard for more compact syntax and adjust the scope only where the lock is necessary. Reviewed-by: Wen Yang Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260601153840.124372-13-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/rv/da_monitor.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h index 34b8fba9ecd4..08e5d0c59926 100644 --- a/include/rv/da_monitor.h +++ b/include/rv/da_monitor.h @@ -334,12 +334,12 @@ static void __da_monitor_reset_all(void (*reset)(struct da_monitor *)) struct task_struct *g, *p; int cpu; - read_lock(&tasklist_lock); - for_each_process_thread(g, p) - reset(da_get_monitor(p)); + scoped_guard(read_lock, &tasklist_lock) { + for_each_process_thread(g, p) + reset(da_get_monitor(p)); + } for_each_present_cpu(cpu) reset(da_get_monitor(idle_task(cpu))); - read_unlock(&tasklist_lock); } static void da_monitor_reset_all(void) From 2a8cd68cc0ba9930ee966ef07d6e1a6a4b0e5b0f Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 1 Jun 2026 17:38:40 +0200 Subject: [PATCH 16/40] verification/rvgen: Generate cleanup hook for per-obj monitor Per-object monitors can allocate memory dynamically and such memory is required for the lifetime of the object, then it should be freed with the appropriate call. Force the generation scripts to add a cleanup function the user will need to wire to the appropriate event (e.g. sched_process_exit for tasks). This can be safely removed if the object will never cease to exist before disabling the monitor (e.g. if following only static variables). Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260601153840.124372-14-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/dot2k.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py index 787f81ef3b83..fd3254ea5b4d 100644 --- a/tools/verification/rvgen/rvgen/dot2k.py +++ b/tools/verification/rvgen/rvgen/dot2k.py @@ -15,6 +15,9 @@ from .automata import ConstraintCondition, AutomataError class dot2k(Monitor, Dot2c): template_dir = "dot2k" + # only needed for the per-obj cleanup hook + cleanup_marker = "obj_cleanup" + def __init__(self, file_path, MonitorType, extra_params={}): self.monitor_type = MonitorType Monitor.__init__(self, extra_params) @@ -54,18 +57,30 @@ class dot2k(Monitor, Dot2c): buff.append(f"\tda_{handle}({event}{self.enum_suffix});") buff.append("}") buff.append("") + if self.monitor_type == "per_obj": + buff.append("/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */") + buff.append(f"static void handle_{self.cleanup_marker}(void *data, /* XXX: fill header */)") + buff.append("{") + buff.append("\tint id = /* XXX: how do I get the id? */;") + buff.append("\tda_destroy_storage(id);") + buff.append("}") + buff.append("") return '\n'.join(buff) def fill_tracepoint_attach_probe(self) -> str: buff = [] for event in self.events: buff.append(f"\trv_attach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_{event});") + if self.monitor_type == "per_obj": + buff.append(f"\trv_attach_trace_probe(\"{self.name}\", /* XXX: cleanup tracepoint */, handle_{self.cleanup_marker});") return '\n'.join(buff) def fill_tracepoint_detach_helper(self) -> str: buff = [] for event in self.events: buff.append(f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_{event});") + if self.monitor_type == "per_obj": + buff.append(f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: cleanup tracepoint */, handle_{self.cleanup_marker});") return '\n'.join(buff) def fill_model_h_header(self) -> list[str]: From b255fc56f4e85fb34a491be9aa31bece3f4f3958 Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Wed, 15 Jul 2026 09:58:24 +0800 Subject: [PATCH 17/40] rv: Simplify task monitor slot management The slot array already tracks allocation and task_monitor_count duplicates that state. On an invalid second release, the old code warns but still decrements the counter, corrupting later allocations. Use the slot array as the sole source of truth. Return after warning about an unused slot, and return -EBUSY when no slot is free. Reviewed-by: Gabriele Monaco Signed-off-by: Li Qiang Link: https://lore.kernel.org/r/20260715015825.1413822-1-liqiang01@kylinos.cn Signed-off-by: Gabriele Monaco --- kernel/trace/rv/rv.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c index ee4e68102f17..187d87d5991c 100644 --- a/kernel/trace/rv/rv.c +++ b/kernel/trace/rv/rv.c @@ -164,7 +164,6 @@ struct dentry *get_monitors_root(void) */ LIST_HEAD(rv_monitors_list); -static int task_monitor_count; static bool task_monitor_slots[CONFIG_RV_PER_TASK_MONITORS]; int rv_get_task_monitor_slot(void) @@ -173,21 +172,14 @@ int rv_get_task_monitor_slot(void) lockdep_assert_held(&rv_interface_lock); - if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS) - return -EBUSY; - - task_monitor_count++; - for (i = 0; i < CONFIG_RV_PER_TASK_MONITORS; i++) { - if (task_monitor_slots[i] == false) { + if (!task_monitor_slots[i]) { task_monitor_slots[i] = true; return i; } } - WARN_ONCE(1, "RV task_monitor_count and slots are out of sync\n"); - - return -EINVAL; + return -EBUSY; } void rv_put_task_monitor_slot(int slot) @@ -199,10 +191,10 @@ void rv_put_task_monitor_slot(int slot) return; } - WARN_ONCE(!task_monitor_slots[slot], "RV releasing unused task_monitor_slots: %d\n", - slot); + if (WARN_ONCE(!task_monitor_slots[slot], + "RV releasing unused task monitor slot: %d\n", slot)) + return; - task_monitor_count--; task_monitor_slots[slot] = false; } From 42545589e36390e74a848c517a62eafaa6b8526c Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:19 +0200 Subject: [PATCH 18/40] rv/rtapp/sleep: Make the error more informative for user The rtapp/sleep monitor detects real-time tasks which go to sleep in an real-time-unsafe manner. If this happen, the monitor triggers a trace event in the sched_wakeup tracepoint's handler. However, the invoking context of that trace event is not the most informative, because of the stack trace of that event is the wakeup's code path which is not very helpful: 74.669317: rv:error_sleep: condvar[254]: violation detected ltl_validate+0x345 ([kernel.kallsyms]) handle_sched_wakeup+0x34 ([kernel.kallsyms]) ttwu_do_activate+0xff ([kernel.kallsyms]) sched_ttwu_pending+0x104 ([kernel.kallsyms]) __flush_smp_call_function_queue+0x15b ([kernel.kallsyms]) __sysvec_call_function_single+0x18 ([kernel.kallsyms]) sysvec_call_function_single+0x66 ([kernel.kallsyms]) asm_sysvec_call_function_single+0x1a ([kernel.kallsyms]) pv_native_safe_halt+0xf ([kernel.kallsyms]) default_idle+0x9 ([kernel.kallsyms]) default_idle_call+0x33 ([kernel.kallsyms]) do_idle+0x234 ([kernel.kallsyms]) cpu_startup_entry+0x24 ([kernel.kallsyms]) start_secondary+0xf8 ([kernel.kallsyms]) common_startup_64+0x13e ([kernel.kallsyms]) What would be much more valuable is the stack trace of the task itself. Instead of using the sched_wakeup tracepoint, use the sched_exit tracepoint. This makes the event happen in the task's context, making the stack trace far more informative for user: rv:error_sleep: condvar[254]: violation detected ltl_validate+0x345 ([kernel.kallsyms]) handle_sched_exit+0x39 ([kernel.kallsyms]) __schedule+0x80f ([kernel.kallsyms]) schedule+0x22 ([kernel.kallsyms]) futex_do_wait+0x33 ([kernel.kallsyms]) __futex_wait+0x8c ([kernel.kallsyms]) futex_wait+0x73 ([kernel.kallsyms]) do_futex+0xc6 ([kernel.kallsyms]) __x64_sys_futex+0x121 ([kernel.kallsyms]) do_syscall_64+0xf3 ([kernel.kallsyms]) entry_SYSCALL_64_after_hwframe+0x77 ([kernel.kallsyms]) __futex_abstimed_wait_common64+0xc6 (inlined) __futex_abstimed_wait_common+0xc6 (/usr/lib/x86_64-linux-gnu/libc.so.6) Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/d97b4b5c476e5792b6875ec9bbf8dc214f999516.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 2 +- kernel/trace/rv/monitors/sleep/sleep.c | 10 +++++----- kernel/trace/rv/monitors/sleep/sleep.h | 14 +++++++------- tools/verification/models/rtapp/sleep.ltl | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index c8104eda924a..01656bf7080a 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -95,7 +95,7 @@ The monitor's specification is:: RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) - and ((not WAKE) until RT_FRIENDLY_WAKE) + and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT or RT_FRIENDLY_NANOSLEEP diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index 8dfe5ec13e19..d6b677fab8f8 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -36,7 +36,7 @@ static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) { ltl_atom_set(mon, LTL_SLEEP, false); - ltl_atom_set(mon, LTL_WAKE, false); + ltl_atom_set(mon, LTL_SCHEDULE_IN, false); ltl_atom_set(mon, LTL_ABORT_SLEEP, false); ltl_atom_set(mon, LTL_WOKEN_BY_HARDIRQ, false); ltl_atom_set(mon, LTL_WOKEN_BY_NMI, false); @@ -92,9 +92,9 @@ static void handle_sched_set_state(void *data, struct task_struct *task, int sta ltl_atom_pulse(task, LTL_ABORT_SLEEP, true); } -static void handle_sched_wakeup(void *data, struct task_struct *task) +static void handle_sched_exit(void *data, bool is_switch) { - ltl_atom_pulse(task, LTL_WAKE, true); + ltl_atom_pulse(current, LTL_SCHEDULE_IN, true); } static void handle_sched_waking(void *data, struct task_struct *task) @@ -200,7 +200,7 @@ static int enable_sleep(void) return retval; rv_attach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking); - rv_attach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup); + rv_attach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit); rv_attach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_attach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_attach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); @@ -213,7 +213,7 @@ static int enable_sleep(void) static void disable_sleep(void) { rv_detach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking); - rv_detach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup); + rv_detach_trace_probe("rtapp_sleep", sched_exit_tp, handle_sched_exit); rv_detach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_detach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_detach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h index 95dc2727c059..403dc2852c52 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.h +++ b/kernel/trace/rv/monitors/sleep/sleep.h @@ -24,10 +24,10 @@ enum ltl_atom { LTL_NANOSLEEP_CLOCK_TAI, LTL_NANOSLEEP_TIMER_ABSTIME, LTL_RT, + LTL_SCHEDULE_IN, LTL_SLEEP, LTL_TASK_IS_MIGRATION, LTL_TASK_IS_RCU, - LTL_WAKE, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, LTL_WOKEN_BY_HARDIRQ, LTL_WOKEN_BY_NMI, @@ -50,10 +50,10 @@ static const char *ltl_atom_str(enum ltl_atom atom) "na_cl_ta", "na_ti_ab", "rt", - "sl", + "sch_in", + "sle", "ta_mi", "ta_rc", - "wak", "wo_eq_hi_pr", "wo_ha", "wo_nm", @@ -81,10 +81,10 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool wake = test_bit(LTL_WAKE, mon->atoms); bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); + bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); @@ -104,7 +104,7 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool val35 = woken_by_nmi || val34; bool val36 = woken_by_hardirq || val35; bool val14 = woken_by_equal_or_higher_prio || val36; - bool val13 = !wake; + bool val13 = !schedule_in; bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; bool val27 = nanosleep_timer_abstime && val26; bool val18 = clock_nanosleep && val27; @@ -132,10 +132,10 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool wake = test_bit(LTL_WAKE, mon->atoms); bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); + bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); @@ -155,7 +155,7 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool val35 = woken_by_nmi || val34; bool val36 = woken_by_hardirq || val35; bool val14 = woken_by_equal_or_higher_prio || val36; - bool val13 = !wake; + bool val13 = !schedule_in; bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; bool val27 = nanosleep_timer_abstime && val26; bool val18 = clock_nanosleep && val27; diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl index 6f26c4810f78..464c84b9df87 100644 --- a/tools/verification/models/rtapp/sleep.ltl +++ b/tools/verification/models/rtapp/sleep.ltl @@ -1,7 +1,7 @@ RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) - and ((not WAKE) until RT_FRIENDLY_WAKE) + and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT or RT_FRIENDLY_NANOSLEEP From 8fc4e16c75c012c93721a5b87f35d9f7e198dd01 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:20 +0200 Subject: [PATCH 19/40] rv/rtapp/sleep: Update nanosleep rule CLOCK_REALTIME is the only clock that often is misused in real-time applications. The other clocks either are safe for real-time uses (CLOCK_TAI, CLOCK_MONOTONIC, CLOCK_BOOTTIME) or are unlikely to be misused (CLOCK_AUX, CLOCK_PROCESS_CPUTIME_ID). Update the monitor to only warn about CLOCK_REALTIME. While at it, update the out-of-sync documentation. Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/c7ceb5c6263ee8f43a2676acae669cf486b0d903.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 17 +++++--- kernel/trace/rv/monitors/sleep/sleep.c | 12 ++---- kernel/trace/rv/monitors/sleep/sleep.h | 52 +++++++++++------------ tools/verification/models/rtapp/sleep.ltl | 2 +- 4 files changed, 39 insertions(+), 44 deletions(-) diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index 01656bf7080a..570be67a8f3b 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -51,12 +51,13 @@ The `sleep` monitor reports real-time threads sleeping in a manner that may cause undesirable latency. Real-time applications should only put a real-time thread to sleep for one of the following reasons: - - Cyclic work: real-time thread sleeps waiting for the next cycle. For this - case, only the `clock_nanosleep` syscall should be used with `TIMER_ABSTIME` - (to avoid time drift) and `CLOCK_MONOTONIC` (to avoid the clock being - changed). No other method is safe for real-time. For example, threads - waiting for timerfd can be woken by softirq which provides no real-time - guarantee. + - Cyclic work: real-time thread sleeps waiting for the next + cycle. For this case, only the `clock_nanosleep` syscall should be + used with `TIMER_ABSTIME` (to avoid time drift). Additionally, + `CLOCK_REALTIME` should not be used (to avoid the clock being + changed). No other method is safe for real-time. For example, + threads waiting for timerfd can be woken by softirq which provides + no real-time guarantee. - Real-time thread waiting for something to happen (e.g. another thread releasing shared resources, or a completion signal from another thread). In this case, only futexes (FUTEX_LOCK_PI, FUTEX_LOCK_PI2 or one of @@ -99,14 +100,16 @@ The monitor's specification is:: RT_VALID_SLEEP_REASON = FUTEX_WAIT or RT_FRIENDLY_NANOSLEEP + or EPOLL_WAIT RT_FRIENDLY_NANOSLEEP = CLOCK_NANOSLEEP and NANOSLEEP_TIMER_ABSTIME - and NANOSLEEP_CLOCK_MONOTONIC + and not NANOSLEEP_CLOCK_REALTIME RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO or WOKEN_BY_HARDIRQ or WOKEN_BY_NMI + or ABORT_SLEEP or KTHREAD_SHOULD_STOP ALLOWLIST = BLOCK_ON_RT_MUTEX diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index d6b677fab8f8..638be7d8747f 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -44,8 +44,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo if (task_creation) { ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); ltl_atom_set(mon, LTL_FUTEX_WAIT, false); @@ -60,8 +59,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo /* kernel tasks do not do syscall */ ltl_atom_set(mon, LTL_FUTEX_WAIT, false); ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); ltl_atom_set(mon, LTL_EPOLL_WAIT, false); @@ -136,8 +134,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id) case __NR_clock_nanosleep_time64: #endif syscall_get_arguments(current, regs, args); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, args[0] == CLOCK_MONOTONIC); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, args[0] == CLOCK_TAI); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, args[0] == CLOCK_REALTIME); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME); ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true); break; @@ -178,8 +175,7 @@ static void handle_sys_exit(void *data, struct pt_regs *regs, long ret) ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); ltl_atom_set(mon, LTL_FUTEX_WAIT, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false); + ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_EPOLL_WAIT, false); ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false); diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h index 403dc2852c52..2fe2ec7edae8 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.h +++ b/kernel/trace/rv/monitors/sleep/sleep.h @@ -20,8 +20,7 @@ enum ltl_atom { LTL_FUTEX_WAIT, LTL_KERNEL_THREAD, LTL_KTHREAD_SHOULD_STOP, - LTL_NANOSLEEP_CLOCK_MONOTONIC, - LTL_NANOSLEEP_CLOCK_TAI, + LTL_NANOSLEEP_CLOCK_REALTIME, LTL_NANOSLEEP_TIMER_ABSTIME, LTL_RT, LTL_SCHEDULE_IN, @@ -46,8 +45,7 @@ static const char *ltl_atom_str(enum ltl_atom atom) "fu_wa", "ker_th", "kth_sh_st", - "na_cl_mo", - "na_cl_ta", + "na_cl_re", "na_ti_ab", "rt", "sch_in", @@ -87,8 +85,7 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); - bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); - bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms); + bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); @@ -97,17 +94,17 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val42 = task_is_rcu || task_is_migration; - bool val43 = futex_lock_pi || val42; - bool val5 = block_on_rt_mutex || val43; - bool val34 = abort_sleep || kthread_should_stop; - bool val35 = woken_by_nmi || val34; - bool val36 = woken_by_hardirq || val35; - bool val14 = woken_by_equal_or_higher_prio || val36; + bool val41 = task_is_rcu || task_is_migration; + bool val42 = futex_lock_pi || val41; + bool val5 = block_on_rt_mutex || val42; + bool val33 = abort_sleep || kthread_should_stop; + bool val34 = woken_by_nmi || val33; + bool val35 = woken_by_hardirq || val34; + bool val14 = woken_by_equal_or_higher_prio || val35; bool val13 = !schedule_in; - bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; - bool val27 = nanosleep_timer_abstime && val26; - bool val18 = clock_nanosleep && val27; + bool val25 = !nanosleep_clock_realtime; + bool val26 = nanosleep_timer_abstime && val25; + bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; bool val9 = futex_wait || val20; bool val11 = val9 || kernel_thread; @@ -138,8 +135,7 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); - bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms); - bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms); + bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); @@ -148,17 +144,17 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val42 = task_is_rcu || task_is_migration; - bool val43 = futex_lock_pi || val42; - bool val5 = block_on_rt_mutex || val43; - bool val34 = abort_sleep || kthread_should_stop; - bool val35 = woken_by_nmi || val34; - bool val36 = woken_by_hardirq || val35; - bool val14 = woken_by_equal_or_higher_prio || val36; + bool val41 = task_is_rcu || task_is_migration; + bool val42 = futex_lock_pi || val41; + bool val5 = block_on_rt_mutex || val42; + bool val33 = abort_sleep || kthread_should_stop; + bool val34 = woken_by_nmi || val33; + bool val35 = woken_by_hardirq || val34; + bool val14 = woken_by_equal_or_higher_prio || val35; bool val13 = !schedule_in; - bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai; - bool val27 = nanosleep_timer_abstime && val26; - bool val18 = clock_nanosleep && val27; + bool val25 = !nanosleep_clock_realtime; + bool val26 = nanosleep_timer_abstime && val25; + bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; bool val9 = futex_wait || val20; bool val11 = val9 || kernel_thread; diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl index 464c84b9df87..5923e58d7810 100644 --- a/tools/verification/models/rtapp/sleep.ltl +++ b/tools/verification/models/rtapp/sleep.ltl @@ -9,7 +9,7 @@ RT_VALID_SLEEP_REASON = FUTEX_WAIT RT_FRIENDLY_NANOSLEEP = CLOCK_NANOSLEEP and NANOSLEEP_TIMER_ABSTIME - and (NANOSLEEP_CLOCK_MONOTONIC or NANOSLEEP_CLOCK_TAI) + and not NANOSLEEP_CLOCK_REALTIME RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO or WOKEN_BY_HARDIRQ From 28e68d3cdc8adf6215e6334222b710d07fb1c1c5 Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:21 +0200 Subject: [PATCH 20/40] rv/rtapp/sleep: Stop monitoring kernel threads The rtapp/sleep monitor's primary purpose is detecting common mistakes with user-space real-time design. Monitoring real-time issues with kernel threads is a bonus. However, accomodating kernel threads complicates the monitor due to the edge cases which is seen by the monitor as lower-priority task waking higher-priority task: - kthread_stop() wakes up the task in order to stop it. - The rcu thread and migration thread can be woken by any task. - The ktimerd thread is woken near the end of irq_exit_rcu(), where the preempt counter is "broken" and falsely says this is task context. This requires the monitor to use the hardirq_context flag instead of the preempt counter. Beside complicating the monitor, the final case also requires enabling CONFIG_TRACE_IRQFLAGS (so that "hardirq_context" can be used). This adds overhead to the kernel even when the monitor is not active. This may be an obstacle to enabling this monitor in distros' kernels. Furthermore, kernel threads usually are started before the monitor is enabled. Consequently, the threads' states (i.o.w. the monitor's atomic propositions for the threads) are not fully known to the monitor. As a result, the kernel threads mostly cannot be monitored. Overall, the downsides of accomodating kernel threads outweights the benefits. Thus, exclude kernel threads to simplify the monitor. Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/eec2ca5224bcdacc45b8e1eb2f0e68109e1cae7a.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 18 +--- kernel/trace/rv/monitors/sleep/Kconfig | 1 - kernel/trace/rv/monitors/sleep/sleep.c | 39 +------- kernel/trace/rv/monitors/sleep/sleep.h | 104 +++++++++------------- tools/verification/models/rtapp/sleep.ltl | 7 +- 5 files changed, 52 insertions(+), 117 deletions(-) diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index 570be67a8f3b..502d3ea412eb 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -93,9 +93,9 @@ assessment. The monitor's specification is:: - RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) + RULE = always ((RT and SLEEP and USER_THREAD) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) - RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) + RT_FRIENDLY_SLEEP = RT_VALID_SLEEP_REASON and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT @@ -110,23 +110,13 @@ The monitor's specification is:: or WOKEN_BY_HARDIRQ or WOKEN_BY_NMI or ABORT_SLEEP - or KTHREAD_SHOULD_STOP ALLOWLIST = BLOCK_ON_RT_MUTEX or FUTEX_LOCK_PI - or TASK_IS_RCU - or TASK_IS_MIGRATION -Beside the scenarios described above, this specification also handle some -special cases: +Beside the scenarios described above, this specification also defines an allow list +to handle some special cases: - - `KERNEL_THREAD`: kernel tasks do not have any pattern that can be recognized - as valid real-time sleeping reasons. Therefore sleeping reason is not - checked for kernel tasks. - - `KTHREAD_SHOULD_STOP`: a non-real-time thread may stop a real-time kernel - thread by waking it and waiting for it to exit (`kthread_stop()`). This - wakeup is safe for real-time. - - `ALLOWLIST`: to handle known false positives with the kernel. - `BLOCK_ON_RT_MUTEX` is included in the allowlist due to its implementation. In the release path of rt_mutex, a boosted task is de-boosted before waking the rt_mutex's waiter. Consequently, the monitor may see a real-time-unsafe diff --git a/kernel/trace/rv/monitors/sleep/Kconfig b/kernel/trace/rv/monitors/sleep/Kconfig index 6b7a122e7b47..d6ec3e9a91b6 100644 --- a/kernel/trace/rv/monitors/sleep/Kconfig +++ b/kernel/trace/rv/monitors/sleep/Kconfig @@ -5,7 +5,6 @@ config RV_MON_SLEEP select RV_LTL_MONITOR depends on HAVE_SYSCALL_TRACEPOINTS depends on RV_MON_RTAPP - select TRACE_IRQFLAGS default y select LTL_MON_EVENTS_ID bool "sleep monitor" diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index 638be7d8747f..aa5a984853b5 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -43,7 +43,6 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo ltl_atom_set(mon, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, false); if (task_creation) { - ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false); ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); @@ -53,33 +52,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false); } - if (task->flags & PF_KTHREAD) { - ltl_atom_set(mon, LTL_KERNEL_THREAD, true); - - /* kernel tasks do not do syscall */ - ltl_atom_set(mon, LTL_FUTEX_WAIT, false); - ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); - ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); - ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); - ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false); - ltl_atom_set(mon, LTL_EPOLL_WAIT, false); - - if (strstarts(task->comm, "migration/")) - ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, true); - else - ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false); - - if (strstarts(task->comm, "rcu")) - ltl_atom_set(mon, LTL_TASK_IS_RCU, true); - else - ltl_atom_set(mon, LTL_TASK_IS_RCU, false); - } else { - ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false); - ltl_atom_set(mon, LTL_KERNEL_THREAD, false); - ltl_atom_set(mon, LTL_TASK_IS_RCU, false); - ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false); - } - + ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD)); } static void handle_sched_set_state(void *data, struct task_struct *task, int state) @@ -97,7 +70,7 @@ static void handle_sched_exit(void *data, bool is_switch) static void handle_sched_waking(void *data, struct task_struct *task) { - if (this_cpu_read(hardirq_context)) { + if (in_hardirq()) { ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true); } else if (in_task()) { if (current->prio <= task->prio) @@ -181,12 +154,6 @@ static void handle_sys_exit(void *data, struct pt_regs *regs, long ret) ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false); } -static void handle_kthread_stop(void *data, struct task_struct *task) -{ - /* FIXME: this could race with other tracepoint handlers */ - ltl_atom_update(task, LTL_KTHREAD_SHOULD_STOP, true); -} - static int enable_sleep(void) { int retval; @@ -200,7 +167,6 @@ static int enable_sleep(void) rv_attach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_attach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_attach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); - rv_attach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop); rv_attach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter); rv_attach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit); return 0; @@ -213,7 +179,6 @@ static void disable_sleep(void) rv_detach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state); rv_detach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin); rv_detach_trace_probe("rtapp_sleep", contention_end, handle_contention_end); - rv_detach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop); rv_detach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter); rv_detach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit); diff --git a/kernel/trace/rv/monitors/sleep/sleep.h b/kernel/trace/rv/monitors/sleep/sleep.h index 2fe2ec7edae8..44e593f41e6a 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.h +++ b/kernel/trace/rv/monitors/sleep/sleep.h @@ -18,15 +18,12 @@ enum ltl_atom { LTL_EPOLL_WAIT, LTL_FUTEX_LOCK_PI, LTL_FUTEX_WAIT, - LTL_KERNEL_THREAD, - LTL_KTHREAD_SHOULD_STOP, LTL_NANOSLEEP_CLOCK_REALTIME, LTL_NANOSLEEP_TIMER_ABSTIME, LTL_RT, LTL_SCHEDULE_IN, LTL_SLEEP, - LTL_TASK_IS_MIGRATION, - LTL_TASK_IS_RCU, + LTL_USER_THREAD, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, LTL_WOKEN_BY_HARDIRQ, LTL_WOKEN_BY_NMI, @@ -43,15 +40,12 @@ static const char *ltl_atom_str(enum ltl_atom atom) "ep_wa", "fu_lo_pi", "fu_wa", - "ker_th", - "kth_sh_st", "na_cl_re", "na_ti_ab", "rt", "sch_in", "sle", - "ta_mi", - "ta_rc", + "us_th", "wo_eq_hi_pr", "wo_ha", "wo_nm", @@ -79,46 +73,41 @@ static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); - bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); - bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); - bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms); bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val41 = task_is_rcu || task_is_migration; - bool val42 = futex_lock_pi || val41; - bool val5 = block_on_rt_mutex || val42; - bool val33 = abort_sleep || kthread_should_stop; - bool val34 = woken_by_nmi || val33; - bool val35 = woken_by_hardirq || val34; - bool val14 = woken_by_equal_or_higher_prio || val35; + bool val7 = block_on_rt_mutex || futex_lock_pi; + bool val32 = woken_by_nmi || abort_sleep; + bool val33 = woken_by_hardirq || val32; + bool val14 = woken_by_equal_or_higher_prio || val33; bool val13 = !schedule_in; bool val25 = !nanosleep_clock_realtime; bool val26 = nanosleep_timer_abstime && val25; bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; - bool val9 = futex_wait || val20; - bool val11 = val9 || kernel_thread; + bool val11 = futex_wait || val20; + bool val3 = !user_thread; bool val2 = !sleep; + bool val4 = val2 || val3; bool val1 = !rt; - bool val3 = val1 || val2; + bool val5 = val1 || val4; - if (val3) + if (val5) __set_bit(S0, mon->states); if (val11 && val13) __set_bit(S1, mon->states); if (val11 && val14) __set_bit(S4, mon->states); - if (val5) + if (val7) __set_bit(S5, mon->states); } @@ -129,130 +118,125 @@ ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned l bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms); bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, mon->atoms); - bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms); - bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); bool sleep = test_bit(LTL_SLEEP, mon->atoms); bool schedule_in = test_bit(LTL_SCHEDULE_IN, mon->atoms); bool rt = test_bit(LTL_RT, mon->atoms); bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms); bool nanosleep_clock_realtime = test_bit(LTL_NANOSLEEP_CLOCK_REALTIME, mon->atoms); - bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms); - bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms); bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms); bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms); bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms); bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms); - bool val41 = task_is_rcu || task_is_migration; - bool val42 = futex_lock_pi || val41; - bool val5 = block_on_rt_mutex || val42; - bool val33 = abort_sleep || kthread_should_stop; - bool val34 = woken_by_nmi || val33; - bool val35 = woken_by_hardirq || val34; - bool val14 = woken_by_equal_or_higher_prio || val35; + bool val7 = block_on_rt_mutex || futex_lock_pi; + bool val32 = woken_by_nmi || abort_sleep; + bool val33 = woken_by_hardirq || val32; + bool val14 = woken_by_equal_or_higher_prio || val33; bool val13 = !schedule_in; bool val25 = !nanosleep_clock_realtime; bool val26 = nanosleep_timer_abstime && val25; bool val18 = clock_nanosleep && val26; bool val20 = val18 || epoll_wait; - bool val9 = futex_wait || val20; - bool val11 = val9 || kernel_thread; + bool val11 = futex_wait || val20; + bool val3 = !user_thread; bool val2 = !sleep; + bool val4 = val2 || val3; bool val1 = !rt; - bool val3 = val1 || val2; + bool val5 = val1 || val4; switch (state) { case S0: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S1: if (val11 && val13) __set_bit(S1, next); - if (val13 && val3) + if (val13 && val5) __set_bit(S2, next); - if (val14 && val3) + if (val14 && val5) __set_bit(S3, next); if (val11 && val14) __set_bit(S4, next); - if (val13 && val5) + if (val13 && val7) __set_bit(S6, next); - if (val14 && val5) + if (val14 && val7) __set_bit(S7, next); break; case S2: if (val11 && val13) __set_bit(S1, next); - if (val13 && val3) + if (val13 && val5) __set_bit(S2, next); - if (val14 && val3) + if (val14 && val5) __set_bit(S3, next); if (val11 && val14) __set_bit(S4, next); - if (val13 && val5) + if (val13 && val7) __set_bit(S6, next); - if (val14 && val5) + if (val14 && val7) __set_bit(S7, next); break; case S3: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S4: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S5: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; case S6: if (val11 && val13) __set_bit(S1, next); - if (val13 && val3) + if (val13 && val5) __set_bit(S2, next); - if (val14 && val3) + if (val14 && val5) __set_bit(S3, next); if (val11 && val14) __set_bit(S4, next); - if (val13 && val5) + if (val13 && val7) __set_bit(S6, next); - if (val14 && val5) + if (val14 && val7) __set_bit(S7, next); break; case S7: - if (val3) + if (val5) __set_bit(S0, next); if (val11 && val13) __set_bit(S1, next); if (val11 && val14) __set_bit(S4, next); - if (val5) + if (val7) __set_bit(S5, next); break; } diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl index 5923e58d7810..4d78fdd204c0 100644 --- a/tools/verification/models/rtapp/sleep.ltl +++ b/tools/verification/models/rtapp/sleep.ltl @@ -1,6 +1,6 @@ -RULE = always ((RT and SLEEP) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) +RULE = always ((RT and SLEEP and USER_THREAD) imply (RT_FRIENDLY_SLEEP or ALLOWLIST)) -RT_FRIENDLY_SLEEP = (RT_VALID_SLEEP_REASON or KERNEL_THREAD) +RT_FRIENDLY_SLEEP = RT_VALID_SLEEP_REASON and ((not SCHEDULE_IN) until RT_FRIENDLY_WAKE) RT_VALID_SLEEP_REASON = FUTEX_WAIT @@ -15,9 +15,6 @@ RT_FRIENDLY_WAKE = WOKEN_BY_EQUAL_OR_HIGHER_PRIO or WOKEN_BY_HARDIRQ or WOKEN_BY_NMI or ABORT_SLEEP - or KTHREAD_SHOULD_STOP ALLOWLIST = BLOCK_ON_RT_MUTEX or FUTEX_LOCK_PI - or TASK_IS_RCU - or TASK_IS_MIGRATION From 6fdaab4e163609772bcc617b052a62435e89d77c Mon Sep 17 00:00:00 2001 From: Nam Cao Date: Fri, 19 Jun 2026 09:21:22 +0200 Subject: [PATCH 21/40] rv/rtapp: Add wakeup monitor Add a wakeup monitor to detect a lower-priority task waking up a higher-priority task. The rtapp/sleep monitor already detects this. However, that monitor triggers an error in the context of the wakee task and user only gets the stacktrace of that task. It is also extremely useful to get the stacktrace of the waker task, which this monitor offers. In other words, this monitor complements the rtapp/sleep monitor. Signed-off-by: Nam Cao Reviewed-by: Gabriele Monaco Link: https://lore.kernel.org/r/ba5658fa13e49ada466b84a2c211f233037180b5.1781852967.git.namcao@linutronix.de Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 20 +++ kernel/trace/rv/Kconfig | 1 + kernel/trace/rv/Makefile | 1 + kernel/trace/rv/monitors/rtapp/Kconfig | 2 +- kernel/trace/rv/monitors/wakeup/Kconfig | 16 ++ kernel/trace/rv/monitors/wakeup/wakeup.c | 153 ++++++++++++++++++ kernel/trace/rv/monitors/wakeup/wakeup.h | 92 +++++++++++ .../trace/rv/monitors/wakeup/wakeup_trace.h | 14 ++ kernel/trace/rv/rv_trace.h | 1 + tools/verification/models/rtapp/wakeup.ltl | 5 + 10 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 kernel/trace/rv/monitors/wakeup/Kconfig create mode 100644 kernel/trace/rv/monitors/wakeup/wakeup.c create mode 100644 kernel/trace/rv/monitors/wakeup/wakeup.h create mode 100644 kernel/trace/rv/monitors/wakeup/wakeup_trace.h create mode 100644 tools/verification/models/rtapp/wakeup.ltl diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index 502d3ea412eb..238b59395ff5 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -124,3 +124,23 @@ to handle some special cases: real-time-safe because preemption is disabled for the duration. - `FUTEX_LOCK_PI` is included in the allowlist for the same reason as `BLOCK_ON_RT_MUTEX`. + +Monitor wakeup +++++++++++++++ + +The `wakeup` monitor reports real-time threads being woken by lower-priority threads, +which is a hint of priority inversion. Its specification is:: + + RULE = always (((RT and USER_THREAD) imply + (not (WOKEN_BY_LOWER_PRIO or WOKEN_BY_SOFTIRQ)) or ALLOWLIST)) + + ALLOWLIST = BLOCK_ON_RT_MUTEX + or FUTEX_LOCK_PI + +The `sleep` monitor already reports this type of problem. The difference is the +context in which the problem is reported. While the `sleep` monitor reports the problem +in the context of the wakee, this `wakeup` monitor reports the problem in the context of +the waker. This monitor complement the `sleep` monitor, giving user better +understanding of the issue. For instance, to debug a lower-priority task waking a +higher-priority task scenario, user can enable both `wakeup` monitor and `sleep` +monitor to get the stack traces of both tasks. diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig index 3884b14df375..4d3a14a0bac2 100644 --- a/kernel/trace/rv/Kconfig +++ b/kernel/trace/rv/Kconfig @@ -76,6 +76,7 @@ source "kernel/trace/rv/monitors/opid/Kconfig" source "kernel/trace/rv/monitors/rtapp/Kconfig" source "kernel/trace/rv/monitors/pagefault/Kconfig" source "kernel/trace/rv/monitors/sleep/Kconfig" +source "kernel/trace/rv/monitors/wakeup/Kconfig" # Add new rtapp monitors here source "kernel/trace/rv/monitors/stall/Kconfig" diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile index 94498da35b37..c2c0e4142eb4 100644 --- a/kernel/trace/rv/Makefile +++ b/kernel/trace/rv/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o +obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o # Add new monitors here obj-$(CONFIG_RV_REACTORS) += rv_reactors.o obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o diff --git a/kernel/trace/rv/monitors/rtapp/Kconfig b/kernel/trace/rv/monitors/rtapp/Kconfig index 1ce9370a9ba8..1fcd7a400ded 100644 --- a/kernel/trace/rv/monitors/rtapp/Kconfig +++ b/kernel/trace/rv/monitors/rtapp/Kconfig @@ -1,6 +1,6 @@ config RV_MON_RTAPP depends on RV - depends on RV_PER_TASK_MONITORS >= 2 + depends on RV_PER_TASK_MONITORS >= 3 bool "rtapp monitor" help Collection of monitors to check for common problems with real-time diff --git a/kernel/trace/rv/monitors/wakeup/Kconfig b/kernel/trace/rv/monitors/wakeup/Kconfig new file mode 100644 index 000000000000..98f618f0e01d --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_WAKEUP + depends on RV + depends on RV_MON_RTAPP + depends on HAVE_SYSCALL_TRACEPOINTS + default y + select LTL_MON_EVENTS_ID + bool "wakeup monitor" + help + This monitor detects a lower-priority task waking up a + higher-priority task. The RV_MON_SLEEP monitor already + detects this case, but this monitor detects in the context + of the waker task instead. This and RV_MON_SLEEP can be + enabled together to get the stacktrace of both the waker + task and the wakee task. diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.c b/kernel/trace/rv/monitors/wakeup/wakeup.c new file mode 100644 index 000000000000..01b47416f24e --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/wakeup.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "wakeup" + +#include +#include +#include +#include + +#include +#include + + +#ifndef __NR_futex +#define __NR_futex (-__COUNTER__) +#endif +#ifndef __NR_futex_time64 +#define __NR_futex_time64 (-__COUNTER__) +#endif + +#include "wakeup.h" +#include + +static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) +{ + /* + * This includes "actual" real-time tasks and also PI-boosted + * tasks. A task being PI-boosted means it is blocking an "actual" + * real-task, therefore it should also obey the monitor's rule, + * otherwise the "actual" real-task may be delayed. + */ + ltl_atom_set(mon, LTL_RT, rt_or_dl_task(task)); +} + +static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) +{ + ltl_atom_set(mon, LTL_WOKEN_BY_LOWER_PRIO, false); + ltl_atom_set(mon, LTL_WOKEN_BY_SOFTIRQ, false); + + if (task_creation) { + ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false); + ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); + } + + ltl_atom_set(mon, LTL_USER_THREAD, !(task->flags & PF_KTHREAD)); +} + +static void handle_sched_waking(void *data, struct task_struct *task) +{ + if (in_task()) { + if (current->prio > task->prio) + ltl_atom_pulse(task, LTL_WOKEN_BY_LOWER_PRIO, true); + } else if (in_serving_softirq()) { + ltl_atom_pulse(task, LTL_WOKEN_BY_SOFTIRQ, true); + } +} + +static void handle_contention_begin(void *data, void *lock, unsigned int flags) +{ + if (flags & LCB_F_RT) + ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true); +} + +static void handle_contention_end(void *data, void *lock, int ret) +{ + ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false); +} + +static void handle_sys_enter(void *data, struct pt_regs *regs, long id) +{ + unsigned long args[6]; + int op, cmd; + + switch (id) { + case __NR_futex: + case __NR_futex_time64: + syscall_get_arguments(current, regs, args); + op = args[1]; + cmd = op & FUTEX_CMD_MASK; + + switch (cmd) { + case FUTEX_LOCK_PI: + case FUTEX_LOCK_PI2: + ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true); + break; + } + break; + } +} + +static void handle_sys_exit(void *data, struct pt_regs *regs, long ret) +{ + ltl_atom_update(current, LTL_FUTEX_LOCK_PI, false); +} + +static int enable_wakeup(void) +{ + int retval; + + retval = ltl_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking); + rv_attach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin); + rv_attach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end); + rv_attach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter); + rv_attach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit); + + return 0; +} + +static void disable_wakeup(void) +{ + rv_detach_trace_probe("rtapp_wakeup", sched_waking, handle_sched_waking); + rv_detach_trace_probe("rtapp_wakeup", contention_begin, handle_contention_begin); + rv_detach_trace_probe("rtapp_wakeup", contention_end, handle_contention_end); + rv_detach_trace_probe("rtapp_wakeup", sys_enter, handle_sys_enter); + rv_detach_trace_probe("rtapp_wakeup", sys_exit, handle_sys_exit); + + ltl_monitor_destroy(); +} + +static struct rv_monitor rv_wakeup = { + .name = "wakeup", + .description = "Monitor that real-time tasks are not woken by lower-priority tasks", + .enable = enable_wakeup, + .disable = disable_wakeup, +}; + +static int __init register_wakeup(void) +{ + return rv_register_monitor(&rv_wakeup, &rv_rtapp); +} + +static void __exit unregister_wakeup(void) +{ + rv_unregister_monitor(&rv_wakeup); +} + +module_init(register_wakeup); +module_exit(unregister_wakeup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nam Cao "); +MODULE_DESCRIPTION("Monitor that real-time tasks are not woken by lower-priority tasks"); diff --git a/kernel/trace/rv/monitors/wakeup/wakeup.h b/kernel/trace/rv/monitors/wakeup/wakeup.h new file mode 100644 index 000000000000..6f80da64e0e1 --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/wakeup.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * C implementation of Buchi automaton, automatically generated by + * tools/verification/rvgen from the linear temporal logic specification. + * For further information, see kernel documentation: + * Documentation/trace/rv/linear_temporal_logic.rst + */ + +#include + +#define MONITOR_NAME wakeup + +enum ltl_atom { + LTL_BLOCK_ON_RT_MUTEX, + LTL_FUTEX_LOCK_PI, + LTL_RT, + LTL_USER_THREAD, + LTL_WOKEN_BY_LOWER_PRIO, + LTL_WOKEN_BY_SOFTIRQ, + LTL_NUM_ATOM +}; +static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM); + +static const char *ltl_atom_str(enum ltl_atom atom) +{ + static const char *const names[] = { + "bl_on_rt_mu", + "fu_lo_pi", + "rt", + "us_th", + "wo_lo_pr", + "wo_so", + }; + + return names[atom]; +} + +enum ltl_buchi_state { + S0, + RV_NUM_BA_STATES +}; +static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES); + +static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) +{ + bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms); + bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); + bool rt = test_bit(LTL_RT, mon->atoms); + bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); + bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); + bool val9 = block_on_rt_mutex || futex_lock_pi; + bool val6 = !woken_by_softirq; + bool val5 = !woken_by_lower_prio; + bool val8 = val5 && val6; + bool val10 = val8 || val9; + bool val3 = !user_thread; + bool val2 = !rt; + bool val4 = val2 || val3; + bool val11 = val4 || val10; + + if (val11) + __set_bit(S0, mon->states); +} + +static void +ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next) +{ + bool woken_by_softirq = test_bit(LTL_WOKEN_BY_SOFTIRQ, mon->atoms); + bool woken_by_lower_prio = test_bit(LTL_WOKEN_BY_LOWER_PRIO, mon->atoms); + bool user_thread = test_bit(LTL_USER_THREAD, mon->atoms); + bool rt = test_bit(LTL_RT, mon->atoms); + bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms); + bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms); + bool val9 = block_on_rt_mutex || futex_lock_pi; + bool val6 = !woken_by_softirq; + bool val5 = !woken_by_lower_prio; + bool val8 = val5 && val6; + bool val10 = val8 || val9; + bool val3 = !user_thread; + bool val2 = !rt; + bool val4 = val2 || val3; + bool val11 = val4 || val10; + + switch (state) { + case S0: + if (val11) + __set_bit(S0, next); + break; + } +} diff --git a/kernel/trace/rv/monitors/wakeup/wakeup_trace.h b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h new file mode 100644 index 000000000000..7e056183f920 --- /dev/null +++ b/kernel/trace/rv/monitors/wakeup/wakeup_trace.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_WAKEUP +DEFINE_EVENT(event_ltl_monitor_id, event_wakeup, + TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next), + TP_ARGS(task, states, atoms, next)); +DEFINE_EVENT(error_ltl_monitor_id, error_wakeup, + TP_PROTO(struct task_struct *task), + TP_ARGS(task)); +#endif /* CONFIG_RV_MON_WAKEUP */ diff --git a/kernel/trace/rv/rv_trace.h b/kernel/trace/rv/rv_trace.h index 9622c269789c..2f8a932432c9 100644 --- a/kernel/trace/rv/rv_trace.h +++ b/kernel/trace/rv/rv_trace.h @@ -241,6 +241,7 @@ DECLARE_EVENT_CLASS(error_ltl_monitor_id, ); #include #include +#include // Add new monitors based on CONFIG_LTL_MON_EVENTS_ID here #endif /* CONFIG_LTL_MON_EVENTS_ID */ diff --git a/tools/verification/models/rtapp/wakeup.ltl b/tools/verification/models/rtapp/wakeup.ltl new file mode 100644 index 000000000000..a5d63ca0811a --- /dev/null +++ b/tools/verification/models/rtapp/wakeup.ltl @@ -0,0 +1,5 @@ +RULE = always (((RT and USER_THREAD) imply + (not (WOKEN_BY_LOWER_PRIO or WOKEN_BY_SOFTIRQ)) or ALLOWLIST)) + +ALLOWLIST = BLOCK_ON_RT_MUTEX + or FUTEX_LOCK_PI From d9e4c61a12dd4ac58a780bc8a6b3bb1a9a8e2120 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:18 +0200 Subject: [PATCH 22/40] rv: Use generic rv_this for the rv_monitor variable in LTL Align the rv_monitor variable name in LTL to the generic rv_this as it is already done for DA/HA monitors. This improves consistency and eases assumptions across model classes. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-2-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/rv/ltl_monitor.h | 5 ++--- kernel/trace/rv/monitors/pagefault/pagefault.c | 6 +++--- kernel/trace/rv/monitors/sleep/sleep.c | 6 +++--- tools/verification/rvgen/rvgen/templates/ltl2k/main.c | 6 +++--- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h index 38e792401f76..56e83edcf0c4 100644 --- a/include/rv/ltl_monitor.h +++ b/include/rv/ltl_monitor.h @@ -16,8 +16,7 @@ #error "Please include $(MODEL_NAME).h generated by rvgen" #endif -#define RV_MONITOR_NAME CONCATENATE(rv_, MONITOR_NAME) -static struct rv_monitor RV_MONITOR_NAME; +static struct rv_monitor rv_this; static int ltl_monitor_slot = RV_PER_TASK_MONITOR_INIT; @@ -85,7 +84,7 @@ static void ltl_monitor_destroy(void) static void ltl_illegal_state(struct task_struct *task, struct ltl_monitor *mon) { CONCATENATE(trace_error_, MONITOR_NAME)(task); - rv_react(&RV_MONITOR_NAME, "rv: "__stringify(MONITOR_NAME)": %s[%d]: violation detected\n", + rv_react(&rv_this, "rv: "__stringify(MONITOR_NAME)": %s[%d]: violation detected\n", task->comm, task->pid); } diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c index 9fe6123b2200..5e1a2a606783 100644 --- a/kernel/trace/rv/monitors/pagefault/pagefault.c +++ b/kernel/trace/rv/monitors/pagefault/pagefault.c @@ -63,7 +63,7 @@ static void disable_pagefault(void) ltl_monitor_destroy(); } -static struct rv_monitor rv_pagefault = { +static struct rv_monitor rv_this = { .name = "pagefault", .description = "Monitor that RT tasks do not raise page faults", .enable = enable_pagefault, @@ -72,12 +72,12 @@ static struct rv_monitor rv_pagefault = { static int __init register_pagefault(void) { - return rv_register_monitor(&rv_pagefault, &rv_rtapp); + return rv_register_monitor(&rv_this, &rv_rtapp); } static void __exit unregister_pagefault(void) { - rv_unregister_monitor(&rv_pagefault); + rv_unregister_monitor(&rv_this); } module_init(register_pagefault); diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index aa5a984853b5..4fd5e20151a8 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -185,7 +185,7 @@ static void disable_sleep(void) ltl_monitor_destroy(); } -static struct rv_monitor rv_sleep = { +static struct rv_monitor rv_this = { .name = "sleep", .description = "Monitor that RT tasks do not undesirably sleep", .enable = enable_sleep, @@ -194,12 +194,12 @@ static struct rv_monitor rv_sleep = { static int __init register_sleep(void) { - return rv_register_monitor(&rv_sleep, &rv_rtapp); + return rv_register_monitor(&rv_this, &rv_rtapp); } static void __exit unregister_sleep(void) { - rv_unregister_monitor(&rv_sleep); + rv_unregister_monitor(&rv_this); } module_init(register_sleep); diff --git a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c index f85d076fbf78..31258b9ea083 100644 --- a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c +++ b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c @@ -77,7 +77,7 @@ static void disable_%%MODEL_NAME%%(void) /* * This is the monitor register section. */ -static struct rv_monitor rv_%%MODEL_NAME%% = { +static struct rv_monitor rv_this = { .name = "%%MODEL_NAME%%", .description = "%%DESCRIPTION%%", .enable = enable_%%MODEL_NAME%%, @@ -86,12 +86,12 @@ static struct rv_monitor rv_%%MODEL_NAME%% = { static int __init register_%%MODEL_NAME%%(void) { - return rv_register_monitor(&rv_%%MODEL_NAME%%, %%PARENT%%); + return rv_register_monitor(&rv_this, %%PARENT%%); } static void __exit unregister_%%MODEL_NAME%%(void) { - rv_unregister_monitor(&rv_%%MODEL_NAME%%); + rv_unregister_monitor(&rv_this); } module_init(register_%%MODEL_NAME%%); From bc4eca3f24e2806c0117215c671013f5297141b6 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:19 +0200 Subject: [PATCH 23/40] tools/rv: Fix exit status when monitor execution fails When running "rv mon" on a monitor that is already enabled, the tool fails to start but incorrectly exits with a success status (0). Fix the exit condition to ensure it returns a failure code on any execution error. Also use the standard EXIT_SUCCESS/EXIT_FAILURE macros throughout the file. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-3-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rv/src/rv.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c index b8fe24a87d97..09e0d8598619 100644 --- a/tools/verification/rv/src/rv.c +++ b/tools/verification/rv/src/rv.c @@ -50,23 +50,23 @@ static void rv_list(int argc, char **argv) " [container]: list only monitors in this container", NULL, }; - int i, print_help = 0, retval = 0; + int i, print_help = 0, retval = EXIT_SUCCESS; char *container = NULL; if (argc == 2) { if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { print_help = 1; - retval = 0; + retval = EXIT_SUCCESS; } else if (argv[1][0] == '-') { /* assume invalid option */ print_help = 1; - retval = 1; + retval = EXIT_FAILURE; } else container = argv[1]; } else if (argc > 2) { /* more than 2 is always usage */ print_help = 1; - retval = 1; + retval = EXIT_FAILURE; } if (print_help) { fprintf(stderr, "rv version %s\n", VERSION); @@ -77,7 +77,7 @@ static void rv_list(int argc, char **argv) ikm_list_monitors(container); - exit(0); + exit(EXIT_SUCCESS); } /* @@ -108,14 +108,14 @@ static void rv_mon(int argc, char **argv) for (i = 0; usage[i]; i++) fprintf(stderr, "%s\n", usage[i]); - exit(1); + exit(EXIT_FAILURE); } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { fprintf(stderr, "rv version %s\n", VERSION); for (i = 0; usage[i]; i++) fprintf(stderr, "%s\n", usage[i]); - exit(0); + exit(EXIT_SUCCESS); } monitor_name = argv[1]; @@ -127,7 +127,7 @@ static void rv_mon(int argc, char **argv) if (!run) err_msg("rv: monitor %s does not exist\n", monitor_name); - exit(!run); + exit(run > 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void usage(int exit_val, const char *fmt, ...) @@ -174,13 +174,13 @@ static void usage(int exit_val, const char *fmt, ...) int main(int argc, char **argv) { if (geteuid()) - usage(1, "%s needs root permission", argv[0]); + usage(EXIT_FAILURE, "%s needs root permission", argv[0]); if (argc <= 1) - usage(1, "%s requires a command", argv[0]); + usage(EXIT_FAILURE, "%s requires a command", argv[0]); if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) - usage(0, "help"); + usage(EXIT_SUCCESS, "help"); if (!strcmp(argv[1], "list")) rv_list(--argc, &argv[1]); @@ -197,5 +197,5 @@ int main(int argc, char **argv) } /* invalid sub-command */ - usage(1, "%s does not know the %s command, old version?", argv[0], argv[1]); + usage(EXIT_FAILURE, "%s does not know the %s command, old version?", argv[0], argv[1]); } From c737b725b1ac601816dab5c5072f80ddd413aa6e Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:20 +0200 Subject: [PATCH 24/40] verification/rvgen: Improve rv_dir discovery in RVGenerator The RVGenerator class can find the RV directory (kernel/trace/rv) in the kernel tree to do some auto patching. This works by assuming PWD is either the kernel tree or tools/verification, which isn't always the case (e.g. when running from selftests). Make discovery more robust by relying on the absolute path of the current script and traversing backwards the right number of times. This should work from any location if rvgen is in the kernel tree. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-4-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/generator.py | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py index 56f3bd8db850..1c20f7d1905c 100644 --- a/tools/verification/rvgen/rvgen/generator.py +++ b/tools/verification/rvgen/rvgen/generator.py @@ -7,6 +7,7 @@ import platform import os +from pathlib import Path class RVGenerator: @@ -25,27 +26,29 @@ class RVGenerator: self.__fill_rv_kernel_dir() def __fill_rv_kernel_dir(self): + # find the kernel tree root relative to this file's location + resolved_path = Path(__file__).resolve() + if len(resolved_path.parents) > 4: + kernel_root = resolved_path.parents[4] + kernel_path = kernel_root / self.rv_dir - # first try if we are running in the kernel tree root - if os.path.exists(self.rv_dir): - return + if kernel_path.exists(): + self.rv_dir = str(kernel_path) + return - # offset if we are running inside the kernel tree from verification/dot2 - kernel_path = os.path.join("../..", self.rv_dir) - - if os.path.exists(kernel_path): - self.rv_dir = kernel_path + # best effort if rvgen is installed and we are at the root of a kernel tree + if Path(self.rv_dir).exists(): return if platform.system() != "Linux": raise OSError("I can only run on Linux.") - kernel_path = os.path.join(f"/lib/modules/{platform.release()}/build", self.rv_dir) + kernel_path = Path(f"/lib/modules/{platform.release()}/build") / self.rv_dir # if the current kernel is from a distro this may not be a full kernel tree # verify that one of the files we are going to modify is available - if os.path.exists(os.path.join(kernel_path, "rv_trace.h")): - self.rv_dir = kernel_path + if (kernel_path / "rv_trace.h").exists(): + self.rv_dir = str(kernel_path) return raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?") From 85b43f84547c5c33d86d022c886b34c670262cc1 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:21 +0200 Subject: [PATCH 25/40] verification/rvgen: Use pathlib instead of os.path Migrate to the newer patlib library, bundled with python since 3.4 to increase readability over using os.path. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-5-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/generator.py | 22 +++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py index 1c20f7d1905c..f1b37d34b1e9 100644 --- a/tools/verification/rvgen/rvgen/generator.py +++ b/tools/verification/rvgen/rvgen/generator.py @@ -6,7 +6,6 @@ # Abstract class for generating kernel runtime verification monitors from specification file import platform -import os from pathlib import Path @@ -17,7 +16,7 @@ class RVGenerator: self.name = extra_params.get("model_name") self.parent = extra_params.get("parent") self.abs_template_dir = \ - os.path.join(os.path.dirname(__file__), "templates", self.template_dir) + Path(__file__).resolve().parent / "templates" / self.template_dir self.main_c = self._read_template_file("main.c") self.kconfig = self._read_template_file("Kconfig") self.description = extra_params.get("description", self.name) or "auto-generated" @@ -60,12 +59,12 @@ class RVGenerator: def _read_template_file(self, file): try: - path = os.path.join(self.abs_template_dir, file) + path = self.abs_template_dir / file return self._read_file(path) except OSError: # Specific template file not found. Try the generic template file in the template/ # directory, which is one level up - path = os.path.join(self.abs_template_dir, "..", file) + path = self.abs_template_dir.parent / file return self._read_file(path) def fill_parent(self): @@ -136,7 +135,7 @@ class RVGenerator: def _patch_file(self, file, marker, line): assert self.auto_patch - file_to_patch = os.path.join(self.rv_dir, file) + file_to_patch = Path(self.rv_dir) / file content = self._read_file(file_to_patch) content = content.replace(marker, line + "\n" + marker) self.__write_file(file_to_patch, content) @@ -190,22 +189,19 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o return f" - Move {self.name}/ to the kernel's monitor directory ({self.rv_dir}/monitors)" def __create_directory(self): - path = self.name + path = Path(self.name) if self.auto_patch: - path = os.path.join(self.rv_dir, "monitors", path) - try: - os.mkdir(path) - except FileExistsError: - return + path = Path(self.rv_dir) / "monitors" / path + path.mkdir(exist_ok=True) def __write_file(self, file_name, content): with open(file_name, 'w') as file: file.write(content) def _create_file(self, file_name, content): - path = f"{self.name}/{file_name}" + path = Path(self.name) / file_name if self.auto_patch: - path = os.path.join(self.rv_dir, "monitors", path) + path = Path(self.rv_dir) / "monitors" / self.name / file_name self.__write_file(path, content) def print_files(self): From 0ab69fdd4384138c63794788ed1c29c31f1923e0 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:22 +0200 Subject: [PATCH 26/40] verification/rvgen: Improve consistency in template files Template files for rvgen had minor inconsistencies in their placeholders for default author and default tracepoint examples. The user needs to modify those anyway but keeping consistency may help in bulk editing or checking. Change default author from "dot2k: auto-generated" (for DA/containers) or /* TODO */ (for LTL) to the general "rvgen: auto-generated". Align the sample tracepoint handler name in LTL template to handle_example_event, consistently with the rest of the file. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-6-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/rvgen/ltl2k.py | 2 +- tools/verification/rvgen/rvgen/templates/container/main.c | 2 +- tools/verification/rvgen/rvgen/templates/dot2k/main.c | 2 +- tools/verification/rvgen/rvgen/templates/ltl2k/main.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/verification/rvgen/rvgen/ltl2k.py b/tools/verification/rvgen/rvgen/ltl2k.py index 81fd1f5ea5ea..f3781a3e0856 100644 --- a/tools/verification/rvgen/rvgen/ltl2k.py +++ b/tools/verification/rvgen/rvgen/ltl2k.py @@ -222,7 +222,7 @@ class ltl2k(generator.Monitor): return f"\trv_attach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_example_event);" def fill_tracepoint_detach_helper(self): - return f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_sample_event);" + return f"\trv_detach_trace_probe(\"{self.name}\", /* XXX: tracepoint */, handle_example_event);" def fill_atoms_init(self): buff = [] diff --git a/tools/verification/rvgen/rvgen/templates/container/main.c b/tools/verification/rvgen/rvgen/templates/container/main.c index 5fc89b46f279..e6a20d74886c 100644 --- a/tools/verification/rvgen/rvgen/templates/container/main.c +++ b/tools/verification/rvgen/rvgen/templates/container/main.c @@ -31,5 +31,5 @@ module_init(register_%%MODEL_NAME%%); module_exit(unregister_%%MODEL_NAME%%); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("dot2k: auto-generated"); +MODULE_AUTHOR("rvgen: auto-generated"); MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%"); diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main.c b/tools/verification/rvgen/rvgen/templates/dot2k/main.c index 889446760e3c..bd3e0aab9cc5 100644 --- a/tools/verification/rvgen/rvgen/templates/dot2k/main.c +++ b/tools/verification/rvgen/rvgen/templates/dot2k/main.c @@ -79,5 +79,5 @@ module_init(register_%%MODEL_NAME%%); module_exit(unregister_%%MODEL_NAME%%); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("dot2k: auto-generated"); +MODULE_AUTHOR("rvgen: auto-generated"); MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%"); diff --git a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c index 31258b9ea083..c33f21535a7a 100644 --- a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c +++ b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c @@ -98,5 +98,5 @@ module_init(register_%%MODEL_NAME%%); module_exit(unregister_%%MODEL_NAME%%); MODULE_LICENSE("GPL"); -MODULE_AUTHOR(/* TODO */); +MODULE_AUTHOR("rvgen: auto-generated"); MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%"); From 92f0ce55299082a37ac88c12b552059b278afcbf Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:23 +0200 Subject: [PATCH 27/40] tools/rv: Add selftests The rv tool needs automated testing to catch regressions and verify correct functionality across different usage scenarios. Add selftests that validate monitor listing (including containers and nested monitors), monitor execution with different configurations (reactors, verbose output, tracing), and trace output format for both per-task and per-cpu monitors. Error handling paths are also tested. Tests use a shared engine for common patterns. Acked-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-7-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rv/Makefile | 5 +- tools/verification/rv/tests/rv_list.t | 48 +++++++++ tools/verification/rv/tests/rv_mon.t | 95 +++++++++++++++++ tools/verification/tests/engine.sh | 141 ++++++++++++++++++++++++++ 4 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 tools/verification/rv/tests/rv_list.t create mode 100644 tools/verification/rv/tests/rv_mon.t create mode 100644 tools/verification/tests/engine.sh diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile index 5b898360ba48..8ae5fc0d1d17 100644 --- a/tools/verification/rv/Makefile +++ b/tools/verification/rv/Makefile @@ -78,4 +78,7 @@ clean: doc_clean fixdep-clean $(Q)rm -f rv rv-static fixdep FEATURE-DUMP rv-* $(Q)rm -rf feature -.PHONY: FORCE clean +check: $(RV) + RV=$(RV) prove -o --directives -f tests/ + +.PHONY: FORCE clean check diff --git a/tools/verification/rv/tests/rv_list.t b/tools/verification/rv/tests/rv_list.t new file mode 100644 index 000000000000..201af33a52cc --- /dev/null +++ b/tools/verification/rv/tests/rv_list.t @@ -0,0 +1,48 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source ../tests/engine.sh +test_begin + +set_timeout 30s + +RVDIR=/sys/kernel/tracing/rv/ + +# Help and basic tests +check "verify help page" \ + "$RV --help" 0 "usage: rv command" + +check "verify list subcommand help" \ + "$RV list --help" 0 "list all available monitors" + +all_nested=$(grep : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g') +all_non_nested=$(grep -v : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g') +sched_monitors=$(grep sched: $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g') +description_state="[[:space:]]\+[[:print:]]\+\[\(OFF\|ON\)\]" +line_nested=" - \($all_nested\)${description_state}" +line_non_nested="\($all_non_nested\)${description_state}" + +# List monitors and containers +check "list all monitors" \ + "$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\)$" + +check_if_exists "list container" \ + "$RV list sched" "$RVDIR/monitors/sched" \ + "" "-- No monitor found in container sched --" \ + "^\($sched_monitors\)${description_state}$" + +check_if_exists "list non-container" \ + "$RV list wwnr" "$RVDIR/monitors/wwnr" \ + "-- No monitor found in container wwnr --" \ + "^\( - \)\?[[:alnum:]]\+${description_state}$" + +check "list incomplete container name" \ + "$RV list s" 0 "-- No monitor found in container s --" + +# Error handling tests +check "no command" \ + "$RV" 1 "rv requires a command" + +check "invalid command" \ + "$RV invalid" 1 "rv does not know the invalid command" + +test_end diff --git a/tools/verification/rv/tests/rv_mon.t b/tools/verification/rv/tests/rv_mon.t new file mode 100644 index 000000000000..cbc346c74c71 --- /dev/null +++ b/tools/verification/rv/tests/rv_mon.t @@ -0,0 +1,95 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source ../tests/engine.sh +test_begin + +set_timeout 30s + +RVDIR=/sys/kernel/tracing/rv/ + +# Help and basic tests +check "verify mon subcommand help" \ + "$RV mon --help" 0 "run a monitor" + +# Error handling tests +check "mon without monitor name" \ + "$RV mon" 1 "usage: rv mon" + +check "invalid monitor name" \ + "$RV mon invalid" 1 "monitor invalid does not exist" + +if [ -d $RVDIR/monitors/wwnr ]; then + +check "invalid reactor name" \ + "$RV mon wwnr -r invalid" 1 "failed to set invalid reactor, is it available?" + +check "monitor name is substring of another monitor" \ + "$RV mon nr" 1 "monitor nr does not exist" + +check "already enabled monitor returns error" \ + "echo 1 > $RVDIR/monitors/wwnr/enable; $RV mon wwnr" 1 \ + "monitor wwnr (in-kernel) is already enabled" +echo 0 > $RVDIR/monitors/wwnr/enable + +fi + +# rv mon runs until terminated +set_expected_timeout 2s + +# Run monitors with different configurations +check_if_exists "run the monitor without parameters" \ + "$RV mon wwnr" "$RVDIR/monitors/wwnr" "" "." + +check_if_exists "run the monitor as verbose" \ + "$RV mon wwnr -v" "$RVDIR/monitors/wwnr" \ + "my pid is \$pid" "\(event\|error\)" + +check_if_exists "run the monitor with a reactor" \ + "$RV mon wwnr -r printk & sleep .5 && cat $RVDIR/monitors/wwnr/reactors && wait" \ + "$RVDIR/monitors/wwnr/reactors" "\[printk\]" + +check_if_exists "reactor is restored after exit" \ + "cat $RVDIR/monitors/wwnr/reactors" \ + "$RVDIR/monitors/wwnr/reactors" "\[nop\]" + +check_if_exists "run a nested monitor with a reactor" \ + "$RV mon snroc -r printk & sleep .5 && cat $RVDIR/monitors/sched/snroc/reactors && wait" \ + "$RVDIR/monitors/sched/snroc/reactors" "\[printk\]" + +check_if_exists "run an explicitly nested monitor with a reactor" \ + "$RV mon sched:sssw -r printk & sleep .5 && cat $RVDIR/monitors/sched/sssw/reactors && wait" \ + "$RVDIR/monitors/sched/sssw/reactors" "\[printk\]" + +check_if_exists "run container monitor" \ + "$RV mon sched & sleep .5 && cat $RVDIR/monitors/sched/{sssw,sco}/enable && wait" \ + "$RVDIR/monitors/sched" "1" "0" "^1$" + +# Regexes for the trace +header="^[[:space:]]\+\(\([][A-Z_x<>-]\+\||\)[[:space:]]*\)\+$" +type="\(event\|error\)[[:space:]]\+" +genpid="[0-9]\+[[:space:]]\+" +selfpid="\$pid[[:space:]]\+" +cpu="\[[0-9]\{3\}\][[:space:]]\+" +state="[a-z_]\+ " +trace_task="${genpid}${cpu}${type}${genpid}${state}" +trace_task_self="${genpid}${cpu}${type}${selfpid}${state}" +trace_cpu="${genpid}${cpu}${type}${state}" +trace_cpu_self="${selfpid}${cpu}${type}${state}" + +check_if_exists "run per-task monitor with tracing" \ + "$RV mon sssw -t" "$RVDIR/monitors/sched/sssw" \ + "$header" "$trace_task_self" "\($header\|$trace_task\)" + +check_if_exists "run per-task monitor tracing also self" \ + "$RV mon sched:sssw -t -s" "$RVDIR/monitors/sched/sssw" \ + "$trace_task_self" "" "\($header\|$trace_task\)" + +check_if_exists "run per-cpu monitor with tracing" \ + "$RV mon sched:sco -t" "$RVDIR/monitors/sched/sco" \ + "$header" "$trace_cpu_self" "\($header\|$trace_cpu\)" + +check_if_exists "run per-cpu monitor tracing also self" \ + "$RV mon sco -t -s" "$RVDIR/monitors/sched/sco" \ + "$trace_cpu_self" "" "\($header\|$trace_cpu\)" + +test_end diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh new file mode 100644 index 000000000000..57e16dc980b1 --- /dev/null +++ b/tools/verification/tests/engine.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +test_begin() { + # Count tests to allow the test harness to double-check if all were + # included correctly. + ctr=0 + [ -z "$RV" ] && RV="../rv/rv" + [ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT" +} + +failure() { + fail=1 + if [ $# -gt 0 ]; then + failbuf+="$1" + failbuf+=$'\n' + fi +} + +report() { + local desc="$1" + + if [ "$fail" -eq 0 ]; then + echo "ok $ctr - $desc" + else + # Add output and exit code as comments in case of failure + echo "not ok $ctr - $desc" + echo -n "$failbuf" + echo "$result" | col -b | while read -r line; do echo "# $line"; done + printf "#\n# exit code %s\n" "$exitcode" + fi +} + +_check() { + local command=$2 + local expected_exitcode=${3:-0} + local expected_output=$4 + local unexpected_output=$5 + local all_lines_pattern=$6 + local patterns="$expected_output $unexpected_output $all_lines_pattern" + local bgpid pid + + eval "$TIMEOUT" "$command" &> check_output.$$ & + bgpid=$! + + if grep -q "\$pid" <<< "$patterns"; then + for _ in {1..30}; do + pid=$(pgrep -f "${command%%[|;&>]*}" | tail -n1) + [ -n "$pid" ] && break + sleep 0.1 + done + fi + + wait $bgpid + exitcode=$? + result=$(tr -d '\0' < check_output.$$) + rm -f check_output.$$ + + failbuf='' + fail=0 + + # Suppress any other error if a needed pid is empty + if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns"; then + result='' + failure "# Empty pid for $command" + return 1 + fi + + expected_output="${expected_output//\$pid/$pid}" + unexpected_output="${unexpected_output//\$pid/$pid}" + all_lines_pattern="${all_lines_pattern//\$pid/$pid}" + + # Test if the results matches if requested + if [ -n "$expected_output" ] && ! grep -qe "$expected_output" <<< "$result"; then + failure "# Output match failed: \"$expected_output\"" + fi + + if [ -n "$unexpected_output" ] && grep -qe "$unexpected_output" <<< "$result"; then + failure "# Output non-match failed: \"$unexpected_output\"" + fi + + if [ -n "$all_lines_pattern" ] && grep -vqe "$all_lines_pattern" <<< "$result"; then + failure "# All-lines pattern failed: \"$all_lines_pattern\"" + fi + + if [ $exitcode -ne "$expected_exitcode" ]; then + failure "# Expected exit code $expected_exitcode" + fi +} + +check() { + # Simple check: run the command with given arguments and test exit code. + # If TEST_COUNT is set, run the test. Otherwise, just count. + ctr=$((ctr + 1)) + if [ -n "$TEST_COUNT" ]; then + _check "$@" + report "$1" + fi +} + +check_if_exists() { + # Conditional check that skips if a file or folder doesn't exist + local desc=$1 + local command=$2 + local file=$3 + local expected_output=$4 + local unexpected_output=$5 + local all_lines_pattern=$6 + + ctr=$((ctr + 1)) + if [ -n "$TEST_COUNT" ]; then + if [ ! -e "$file" ]; then + echo "ok $ctr - $desc # SKIP file not found: $file" + else + _check "$desc" "$command" 0 "$expected_output" \ + "$unexpected_output" "$all_lines_pattern" + report "$desc" + fi + fi +} + +set_timeout() { + TIMEOUT="timeout -v -k 30s $1" +} + +set_expected_timeout() { + TIMEOUT="timeout --preserve-status -k 30s $1" +} + +unset_timeout() { + unset TIMEOUT +} + +test_end() { + # If running without TEST_COUNT, tests are not actually run, just + # counted. In that case, re-run the test with the correct count. + [ -z "$TEST_COUNT" ] && TEST_COUNT=$ctr exec bash "$0" || true +} + +# Avoid any environmental discrepancies +export LC_ALL=C +unset_timeout From 655d48096d481be8b1836257248db802b84a42d8 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:24 +0200 Subject: [PATCH 28/40] verification/rvgen: Add golden and spec folders for tests Create reference models specifications and generated files in the golded folder. Those can be used as reference to validate rvgen still generates files as expected in automated tests. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-8-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- .../rvgen/tests/golden/da_global/Kconfig | 9 + .../rvgen/tests/golden/da_global/da_global.c | 95 ++++++++ .../rvgen/tests/golden/da_global/da_global.h | 47 ++++ .../tests/golden/da_global/da_global_trace.h | 15 ++ .../tests/golden/da_perobj_parent/Kconfig | 11 + .../da_perobj_parent/da_perobj_parent.c | 119 +++++++++ .../da_perobj_parent/da_perobj_parent.h | 64 +++++ .../da_perobj_parent/da_perobj_parent_trace.h | 15 ++ .../tests/golden/da_pertask_desc/Kconfig | 9 + .../golden/da_pertask_desc/da_pertask_desc.c | 105 ++++++++ .../golden/da_pertask_desc/da_pertask_desc.h | 64 +++++ .../da_pertask_desc/da_pertask_desc_trace.h | 15 ++ .../rvgen/tests/golden/ha_percpu/Kconfig | 9 + .../rvgen/tests/golden/ha_percpu/ha_percpu.c | 227 +++++++++++++++++ .../rvgen/tests/golden/ha_percpu/ha_percpu.h | 72 ++++++ .../tests/golden/ha_percpu/ha_percpu_trace.h | 19 ++ .../rvgen/tests/golden/ltl_pertask/Kconfig | 9 + .../tests/golden/ltl_pertask/ltl_pertask.c | 107 ++++++++ .../tests/golden/ltl_pertask/ltl_pertask.h | 108 ++++++++ .../golden/ltl_pertask/ltl_pertask_trace.h | 14 ++ .../rvgen/tests/golden/test_container/Kconfig | 5 + .../golden/test_container/test_container.c | 35 +++ .../golden/test_container/test_container.h | 3 + .../rvgen/tests/golden/test_da/Kconfig | 9 + .../rvgen/tests/golden/test_da/test_da.c | 95 ++++++++ .../rvgen/tests/golden/test_da/test_da.h | 47 ++++ .../tests/golden/test_da/test_da_trace.h | 15 ++ .../rvgen/tests/golden/test_ha/Kconfig | 9 + .../rvgen/tests/golden/test_ha/test_ha.c | 230 ++++++++++++++++++ .../rvgen/tests/golden/test_ha/test_ha.h | 72 ++++++ .../tests/golden/test_ha/test_ha_trace.h | 19 ++ .../rvgen/tests/golden/test_ltl/Kconfig | 11 + .../rvgen/tests/golden/test_ltl/test_ltl.c | 108 ++++++++ .../rvgen/tests/golden/test_ltl/test_ltl.h | 108 ++++++++ .../tests/golden/test_ltl/test_ltl_trace.h | 14 ++ .../rvgen/tests/specs/test_da.dot | 16 ++ .../rvgen/tests/specs/test_da2.dot | 19 ++ .../rvgen/tests/specs/test_ha.dot | 27 ++ .../rvgen/tests/specs/test_invalid.dot | 8 + .../rvgen/tests/specs/test_invalid.ltl | 1 + .../rvgen/tests/specs/test_invalid_ha.dot | 16 ++ .../rvgen/tests/specs/test_ltl.ltl | 1 + 42 files changed, 2001 insertions(+) create mode 100644 tools/verification/rvgen/tests/golden/da_global/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.c create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global.h create mode 100644 tools/verification/rvgen/tests/golden/da_global/da_global_trace.h create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h create mode 100644 tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h create mode 100644 tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h create mode 100644 tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h create mode 100644 tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h create mode 100644 tools/verification/rvgen/tests/golden/test_container/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.c create mode 100644 tools/verification/rvgen/tests/golden/test_container/test_container.h create mode 100644 tools/verification/rvgen/tests/golden/test_da/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.c create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da.h create mode 100644 tools/verification/rvgen/tests/golden/test_da/test_da_trace.h create mode 100644 tools/verification/rvgen/tests/golden/test_ha/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.c create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha.h create mode 100644 tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h create mode 100644 tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h create mode 100644 tools/verification/rvgen/tests/specs/test_da.dot create mode 100644 tools/verification/rvgen/tests/specs/test_da2.dot create mode 100644 tools/verification/rvgen/tests/specs/test_ha.dot create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.dot create mode 100644 tools/verification/rvgen/tests/specs/test_invalid.ltl create mode 100644 tools/verification/rvgen/tests/specs/test_invalid_ha.dot create mode 100644 tools/verification/rvgen/tests/specs/test_ltl.ltl diff --git a/tools/verification/rvgen/tests/golden/da_global/Kconfig b/tools/verification/rvgen/tests/golden/da_global/Kconfig new file mode 100644 index 000000000000..799fbf11c3ac --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_global/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_DA_GLOBAL + depends on RV + # XXX: add dependencies if there + select DA_MON_EVENTS_IMPLICIT + bool "da_global monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.c b/tools/verification/rvgen/tests/golden/da_global/da_global.c new file mode 100644 index 000000000000..71b26ae2d51c --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_global/da_global.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "da_global" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_GLOBAL +#include "da_global.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +static void handle_event_1(void *data, /* XXX: fill header */) +{ + da_handle_event(event_1_da_global); +} + +static void handle_event_2(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event always leads to the initial state */ + da_handle_start_event(event_2_da_global); +} + +static int enable_da_global(void) +{ + int retval; + + retval = da_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1); + rv_attach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2); + + return 0; +} + +static void disable_da_global(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_1); + rv_detach_trace_probe("da_global", /* XXX: tracepoint */, handle_event_2); + + da_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "da_global", + .description = "auto-generated", + .enable = enable_da_global, + .disable = disable_da_global, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_da_global(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_da_global(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_da_global); +module_exit(unregister_da_global); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("da_global: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global.h b/tools/verification/rvgen/tests/golden/da_global/da_global.h new file mode 100644 index 000000000000..40b1f1c0c681 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_global/da_global.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of da_global automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME da_global + +enum states_da_global { + state_a_da_global, + state_b_da_global, + state_max_da_global, +}; + +#define INVALID_STATE state_max_da_global + +enum events_da_global { + event_1_da_global, + event_2_da_global, + event_max_da_global, +}; + +struct automaton_da_global { + char *state_names[state_max_da_global]; + char *event_names[event_max_da_global]; + unsigned char function[state_max_da_global][event_max_da_global]; + unsigned char initial_state; + bool final_states[state_max_da_global]; +}; + +static const struct automaton_da_global automaton_da_global = { + .state_names = { + "state_a", + "state_b", + }, + .event_names = { + "event_1", + "event_2", + }, + .function = { + { state_b_da_global, state_a_da_global }, + { INVALID_STATE, state_a_da_global }, + }, + .initial_state = state_a_da_global, + .final_states = { 1, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h new file mode 100644 index 000000000000..4d2730b71dd0 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_global/da_global_trace.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_DA_GLOBAL +DEFINE_EVENT(event_da_monitor, event_da_global, + TP_PROTO(char *state, char *event, char *next_state, bool final_state), + TP_ARGS(state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor, error_da_global, + TP_PROTO(char *state, char *event), + TP_ARGS(state, event)); +#endif /* CONFIG_RV_MON_DA_GLOBAL */ diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig new file mode 100644 index 000000000000..249ba3aee8d7 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_DA_PEROBJ_PARENT + depends on RV + # XXX: add dependencies if there + depends on RV_MON_PARENT_MON + default y + select DA_MON_EVENTS_ID + bool "da_perobj_parent monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c new file mode 100644 index 000000000000..5c5d300b4183 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "da_perobj_parent" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_PER_OBJ +typedef /* XXX: define the target type */ *monitor_target; +#include "da_perobj_parent.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +static void handle_event_1(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event is only valid in the initial state */ + int id = /* XXX: how do I get the id? */; + monitor_target t = /* XXX: how do I get t? */; + da_handle_start_run_event(id, t, event_1_da_perobj_parent); +} + +static void handle_event_2(void *data, /* XXX: fill header */) +{ + int id = /* XXX: how do I get the id? */; + monitor_target t = /* XXX: how do I get t? */; + da_handle_event(id, t, event_2_da_perobj_parent); +} + +static void handle_event_3(void *data, /* XXX: fill header */) +{ + int id = /* XXX: how do I get the id? */; + monitor_target t = /* XXX: how do I get t? */; + da_handle_event(id, t, event_3_da_perobj_parent); +} + +/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */ +static void handle_obj_cleanup(void *data, /* XXX: fill header */) +{ + int id = /* XXX: how do I get the id? */; + da_destroy_storage(id); +} + +static int enable_da_perobj_parent(void) +{ + int retval; + + retval = da_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1); + rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2); + rv_attach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3); + rv_attach_trace_probe("da_perobj_parent", /* XXX: cleanup tracepoint */, handle_obj_cleanup); + + return 0; +} + +static void disable_da_perobj_parent(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_1); + rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_2); + rv_detach_trace_probe("da_perobj_parent", /* XXX: tracepoint */, handle_event_3); + rv_detach_trace_probe("da_perobj_parent", /* XXX: cleanup tracepoint */, handle_obj_cleanup); + + da_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "da_perobj_parent", + .description = "auto-generated", + .enable = enable_da_perobj_parent, + .disable = disable_da_perobj_parent, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_da_perobj_parent(void) +{ + return rv_register_monitor(&rv_this, &rv_parent_mon); +} + +static void __exit unregister_da_perobj_parent(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_da_perobj_parent); +module_exit(unregister_da_perobj_parent); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("da_perobj_parent: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h new file mode 100644 index 000000000000..3c8dc3b22443 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of da_perobj_parent automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME da_perobj_parent + +enum states_da_perobj_parent { + state_a_da_perobj_parent, + state_b_da_perobj_parent, + state_c_da_perobj_parent, + state_max_da_perobj_parent, +}; + +#define INVALID_STATE state_max_da_perobj_parent + +enum events_da_perobj_parent { + event_1_da_perobj_parent, + event_2_da_perobj_parent, + event_3_da_perobj_parent, + event_max_da_perobj_parent, +}; + +struct automaton_da_perobj_parent { + char *state_names[state_max_da_perobj_parent]; + char *event_names[event_max_da_perobj_parent]; + unsigned char function[state_max_da_perobj_parent][event_max_da_perobj_parent]; + unsigned char initial_state; + bool final_states[state_max_da_perobj_parent]; +}; + +static const struct automaton_da_perobj_parent automaton_da_perobj_parent = { + .state_names = { + "state_a", + "state_b", + "state_c", + }, + .event_names = { + "event_1", + "event_2", + "event_3", + }, + .function = { + { + state_b_da_perobj_parent, + state_c_da_perobj_parent, + INVALID_STATE, + }, + { + INVALID_STATE, + state_a_da_perobj_parent, + state_c_da_perobj_parent, + }, + { + INVALID_STATE, + INVALID_STATE, + INVALID_STATE, + }, + }, + .initial_state = state_a_da_perobj_parent, + .final_states = { 1, 0, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h new file mode 100644 index 000000000000..59bfca8f73d2 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_perobj_parent/da_perobj_parent_trace.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_DA_PEROBJ_PARENT +DEFINE_EVENT(event_da_monitor_id, event_da_perobj_parent, + TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state), + TP_ARGS(id, state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor_id, error_da_perobj_parent, + TP_PROTO(int id, char *state, char *event), + TP_ARGS(id, state, event)); +#endif /* CONFIG_RV_MON_DA_PEROBJ_PARENT */ diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig new file mode 100644 index 000000000000..c6f350179098 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_DA_PERTASK_DESC + depends on RV + # XXX: add dependencies if there + select DA_MON_EVENTS_ID + bool "da_pertask_desc monitor" + help + Custom description for testing diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c new file mode 100644 index 000000000000..c1ae5078c4f9 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "da_pertask_desc" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_PER_TASK +#include "da_pertask_desc.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +static void handle_event_1(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event is only valid in the initial state */ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_start_run_event(p, event_1_da_pertask_desc); +} + +static void handle_event_2(void *data, /* XXX: fill header */) +{ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_event(p, event_2_da_pertask_desc); +} + +static void handle_event_3(void *data, /* XXX: fill header */) +{ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_event(p, event_3_da_pertask_desc); +} + +static int enable_da_pertask_desc(void) +{ + int retval; + + retval = da_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1); + rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2); + rv_attach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3); + + return 0; +} + +static void disable_da_pertask_desc(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_1); + rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_2); + rv_detach_trace_probe("da_pertask_desc", /* XXX: tracepoint */, handle_event_3); + + da_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "da_pertask_desc", + .description = "Custom description for testing", + .enable = enable_da_pertask_desc, + .disable = disable_da_pertask_desc, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_da_pertask_desc(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_da_pertask_desc(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_da_pertask_desc); +module_exit(unregister_da_pertask_desc); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("da_pertask_desc: Custom description for testing"); diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h new file mode 100644 index 000000000000..837b238754b0 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of da_pertask_desc automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME da_pertask_desc + +enum states_da_pertask_desc { + state_a_da_pertask_desc, + state_b_da_pertask_desc, + state_c_da_pertask_desc, + state_max_da_pertask_desc, +}; + +#define INVALID_STATE state_max_da_pertask_desc + +enum events_da_pertask_desc { + event_1_da_pertask_desc, + event_2_da_pertask_desc, + event_3_da_pertask_desc, + event_max_da_pertask_desc, +}; + +struct automaton_da_pertask_desc { + char *state_names[state_max_da_pertask_desc]; + char *event_names[event_max_da_pertask_desc]; + unsigned char function[state_max_da_pertask_desc][event_max_da_pertask_desc]; + unsigned char initial_state; + bool final_states[state_max_da_pertask_desc]; +}; + +static const struct automaton_da_pertask_desc automaton_da_pertask_desc = { + .state_names = { + "state_a", + "state_b", + "state_c", + }, + .event_names = { + "event_1", + "event_2", + "event_3", + }, + .function = { + { + state_b_da_pertask_desc, + state_c_da_pertask_desc, + INVALID_STATE, + }, + { + INVALID_STATE, + state_a_da_pertask_desc, + state_c_da_pertask_desc, + }, + { + INVALID_STATE, + INVALID_STATE, + INVALID_STATE, + }, + }, + .initial_state = state_a_da_pertask_desc, + .final_states = { 1, 0, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h new file mode 100644 index 000000000000..4e6086c4d86e --- /dev/null +++ b/tools/verification/rvgen/tests/golden/da_pertask_desc/da_pertask_desc_trace.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_DA_PERTASK_DESC +DEFINE_EVENT(event_da_monitor_id, event_da_pertask_desc, + TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state), + TP_ARGS(id, state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor_id, error_da_pertask_desc, + TP_PROTO(int id, char *state, char *event), + TP_ARGS(id, state, event)); +#endif /* CONFIG_RV_MON_DA_PERTASK_DESC */ diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig new file mode 100644 index 000000000000..0cc185ccfddf --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ha_percpu/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_HA_PERCPU + depends on RV + # XXX: add dependencies if there + select HA_MON_EVENTS_IMPLICIT + bool "ha_percpu monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c new file mode 100644 index 000000000000..3a72e867122c --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "ha_percpu" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_PER_CPU +/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */ +#define HA_TIMER_TYPE HA_TIMER_HRTIMER +#include "ha_percpu.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */ + +#define FOO_NS /* XXX: what is FOO_NS? */ + +static inline u64 bar_ns(struct ha_monitor *ha_mon) +{ + return /* XXX: what is bar_ns(ha_mon)? */; +} + +static u64 foo_ns = /* XXX: default value */; +module_param(foo_ns, ullong, 0644); + +/* + * These functions define how to read and reset the environment variable. + * + * Common environment variables like ns-based and jiffy-based clocks have + * pre-define getters and resetters you can use. The parser can infer the type + * of the environment variable if you supply a measure unit in the constraint. + * If you define your own functions, make sure to add appropriate memory + * barriers if required. + * Some environment variables don't require a storage as they read a system + * state (e.g. preemption count). Those variables are never reset, so we don't + * define a reset function on monitors only relying on this type of variables. + */ +static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns) +{ + if (env == clk_ha_percpu) + return ha_get_clk_ns(ha_mon, env, time_ns); + else if (env == env1_ha_percpu) + return /* XXX: how do I read env1? */ + else if (env == env2_ha_percpu) + return /* XXX: how do I read env2? */ + return ENV_INVALID_VALUE; +} + +static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_ha_percpu env, u64 time_ns) +{ + if (env == clk_ha_percpu) + ha_reset_clk_ns(ha_mon, env, time_ns); +} + +/* + * These functions are used to validate state transitions. + * + * They are generated by parsing the model, there is usually no need to change them. + * If the monitor requires a timer, there are functions responsible to arm it when + * the next state has a constraint, cancel it in any other case and to check + * that it didn't expire before the callback run. Transitions to the same state + * without a reset never affect timers. + */ +static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (curr_state == S0_ha_percpu) + return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns, bar_ns(ha_mon)); + else if (curr_state == S2_ha_percpu) + return ha_check_invariant_ns(ha_mon, clk_ha_percpu, time_ns, BAR_NS(ha_mon)); + return true; +} + +static inline bool ha_verify_guards(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + bool res = true; + + if (curr_state == S0_ha_percpu && event == event0_ha_percpu) + ha_reset_env(ha_mon, clk_ha_percpu, time_ns); + else if (curr_state == S0_ha_percpu && event == event1_ha_percpu) + ha_reset_env(ha_mon, clk_ha_percpu, time_ns); + else if (curr_state == S1_ha_percpu && event == event0_ha_percpu) + ha_reset_env(ha_mon, clk_ha_percpu, time_ns); + else if (curr_state == S1_ha_percpu && event == event2_ha_percpu) { + res = ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 0ull; + ha_reset_env(ha_mon, clk_ha_percpu, time_ns); + } else if (curr_state == S2_ha_percpu && event == event1_ha_percpu) + res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) || + ha_get_env(ha_mon, clk_ha_percpu, time_ns) < foo_ns; + else if (curr_state == S3_ha_percpu && event == event0_ha_percpu) + res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) || + (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < FOO_NS && + ha_get_env(ha_mon, env2_ha_percpu, time_ns) == 0ull); + else if (curr_state == S3_ha_percpu && event == event1_ha_percpu) { + res = ha_monitor_env_invalid(ha_mon, clk_ha_percpu) || + (ha_get_env(ha_mon, clk_ha_percpu, time_ns) < 5000ull && + ha_get_env(ha_mon, env1_ha_percpu, time_ns) == 1ull); + ha_reset_env(ha_mon, clk_ha_percpu, time_ns); + } + return res; +} + +static inline void ha_setup_invariants(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (next_state == curr_state && event != event0_ha_percpu) + return; + if (next_state == S0_ha_percpu) + ha_start_timer_ns(ha_mon, clk_ha_percpu, bar_ns(ha_mon), time_ns); + else if (next_state == S2_ha_percpu) + ha_start_timer_ns(ha_mon, clk_ha_percpu, BAR_NS(ha_mon), time_ns); + else if (curr_state == S0_ha_percpu) + ha_cancel_timer(ha_mon); + else if (curr_state == S2_ha_percpu) + ha_cancel_timer(ha_mon); +} + +static bool ha_verify_constraint(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns)) + return false; + + if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns)) + return false; + + ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns); + + return true; +} + +static void handle_event0(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event always leads to the initial state */ + da_handle_start_event(event0_ha_percpu); +} + +static void handle_event1(void *data, /* XXX: fill header */) +{ + da_handle_event(event1_ha_percpu); +} + +static void handle_event2(void *data, /* XXX: fill header */) +{ + da_handle_event(event2_ha_percpu); +} + +static int enable_ha_percpu(void) +{ + int retval; + + retval = ha_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0); + rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1); + rv_attach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2); + + return 0; +} + +static void disable_ha_percpu(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event0); + rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event1); + rv_detach_trace_probe("ha_percpu", /* XXX: tracepoint */, handle_event2); + + ha_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "ha_percpu", + .description = "auto-generated", + .enable = enable_ha_percpu, + .disable = disable_ha_percpu, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_ha_percpu(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_ha_percpu(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_ha_percpu); +module_exit(unregister_ha_percpu); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("ha_percpu: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h new file mode 100644 index 000000000000..2538db4f6a26 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of ha_percpu automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME ha_percpu + +enum states_ha_percpu { + S0_ha_percpu, + S1_ha_percpu, + S2_ha_percpu, + S3_ha_percpu, + state_max_ha_percpu, +}; + +#define INVALID_STATE state_max_ha_percpu + +enum events_ha_percpu { + event0_ha_percpu, + event1_ha_percpu, + event2_ha_percpu, + event_max_ha_percpu, +}; + +enum envs_ha_percpu { + clk_ha_percpu, + env1_ha_percpu, + env2_ha_percpu, + env_max_ha_percpu, + env_max_stored_ha_percpu = env1_ha_percpu, +}; + +_Static_assert(env_max_stored_ha_percpu <= MAX_HA_ENV_LEN, "Not enough slots"); +#define HA_CLK_NS + +struct automaton_ha_percpu { + char *state_names[state_max_ha_percpu]; + char *event_names[event_max_ha_percpu]; + char *env_names[env_max_ha_percpu]; + unsigned char function[state_max_ha_percpu][event_max_ha_percpu]; + unsigned char initial_state; + bool final_states[state_max_ha_percpu]; +}; + +static const struct automaton_ha_percpu automaton_ha_percpu = { + .state_names = { + "S0", + "S1", + "S2", + "S3", + }, + .event_names = { + "event0", + "event1", + "event2", + }, + .env_names = { + "clk", + "env1", + "env2", + }, + .function = { + { S0_ha_percpu, S1_ha_percpu, INVALID_STATE }, + { S0_ha_percpu, INVALID_STATE, S2_ha_percpu }, + { INVALID_STATE, S2_ha_percpu, S3_ha_percpu }, + { S0_ha_percpu, S1_ha_percpu, INVALID_STATE }, + }, + .initial_state = S0_ha_percpu, + .final_states = { 1, 0, 0, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h new file mode 100644 index 000000000000..074ddff6a60d --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ha_percpu/ha_percpu_trace.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_HA_PERCPU +DEFINE_EVENT(event_da_monitor, event_ha_percpu, + TP_PROTO(char *state, char *event, char *next_state, bool final_state), + TP_ARGS(state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor, error_ha_percpu, + TP_PROTO(char *state, char *event), + TP_ARGS(state, event)); + +DEFINE_EVENT(error_env_da_monitor, error_env_ha_percpu, + TP_PROTO(char *state, char *event, char *env), + TP_ARGS(state, event, env)); +#endif /* CONFIG_RV_MON_HA_PERCPU */ diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig new file mode 100644 index 000000000000..b37f46670bfd --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ltl_pertask/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_LTL_PERTASK + depends on RV + # XXX: add dependencies if there + select LTL_MON_EVENTS_ID + bool "ltl_pertask monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c new file mode 100644 index 000000000000..2c60b5c5b4e0 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "ltl_pertask" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#include "ltl_pertask.h" +#include + +static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) +{ + /* + * This is called everytime the Buchi automaton is triggered. + * + * This function could be used to fetch the atomic propositions which + * are expensive to trace. It is possible only if the atomic proposition + * does not need to be updated at precise time. + * + * It is recommended to use tracepoints and ltl_atom_update() instead. + */ +} + +static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) +{ + /* + * This should initialize as many atomic propositions as possible. + * + * @task_creation indicates whether the task is being created. This is + * false if the task is already running before the monitor is enabled. + */ + ltl_atom_set(mon, LTL_EVENT_A, true/false); + ltl_atom_set(mon, LTL_EVENT_B, true/false); +} + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + */ +static void handle_example_event(void *data, /* XXX: fill header */) +{ + ltl_atom_update(task, LTL_EVENT_A, true/false); +} + +static int enable_ltl_pertask(void) +{ + int retval; + + retval = ltl_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_example_event); + + return 0; +} + +static void disable_ltl_pertask(void) +{ + rv_detach_trace_probe("ltl_pertask", /* XXX: tracepoint */, handle_example_event); + + ltl_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "ltl_pertask", + .description = "auto-generated", + .enable = enable_ltl_pertask, + .disable = disable_ltl_pertask, +}; + +static int __init register_ltl_pertask(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_ltl_pertask(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_ltl_pertask); +module_exit(unregister_ltl_pertask); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("ltl_pertask: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h new file mode 100644 index 000000000000..7e5de351b8fa --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * C implementation of Buchi automaton, automatically generated by + * tools/verification/rvgen from the linear temporal logic specification. + * For further information, see kernel documentation: + * Documentation/trace/rv/linear_temporal_logic.rst + */ + +#include + +#define MONITOR_NAME ltl_pertask + +enum ltl_atom { + LTL_EVENT_A, + LTL_EVENT_B, + LTL_NUM_ATOM +}; +static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM); + +static const char *ltl_atom_str(enum ltl_atom atom) +{ + static const char *const names[] = { + "ev_a", + "ev_b", + }; + + return names[atom]; +} + +enum ltl_buchi_state { + S0, + S1, + S2, + S3, + S4, + RV_NUM_BA_STATES +}; +static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES); + +static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + if (val1) + __set_bit(S0, mon->states); + if (true) + __set_bit(S1, mon->states); + if (event_b) + __set_bit(S4, mon->states); +} + +static void +ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + switch (state) { + case S0: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S1: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S2: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S3: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S4: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + } +} diff --git a/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h new file mode 100644 index 000000000000..ebd53621a5b1 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/ltl_pertask/ltl_pertask_trace.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_LTL_PERTASK +DEFINE_EVENT(event_ltl_monitor_id, event_ltl_pertask, + TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next), + TP_ARGS(task, states, atoms, next)); +DEFINE_EVENT(error_ltl_monitor_id, error_ltl_pertask, + TP_PROTO(struct task_struct *task), + TP_ARGS(task)); +#endif /* CONFIG_RV_MON_LTL_PERTASK */ diff --git a/tools/verification/rvgen/tests/golden/test_container/Kconfig b/tools/verification/rvgen/tests/golden/test_container/Kconfig new file mode 100644 index 000000000000..2becb65dddad --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_container/Kconfig @@ -0,0 +1,5 @@ +config RV_MON_TEST_CONTAINER + depends on RV + bool "test_container monitor" + help + Test container for grouping monitors diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.c b/tools/verification/rvgen/tests/golden/test_container/test_container.c new file mode 100644 index 000000000000..e7e34592c6c5 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_container/test_container.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +#define MODULE_NAME "test_container" + +#include "test_container.h" + +struct rv_monitor rv_test_container = { + .name = "test_container", + .description = "Test container for grouping monitors", + .enable = NULL, + .disable = NULL, + .reset = NULL, + .enabled = 0, +}; + +static int __init register_test_container(void) +{ + return rv_register_monitor(&rv_test_container, NULL); +} + +static void __exit unregister_test_container(void) +{ + rv_unregister_monitor(&rv_test_container); +} + +module_init(register_test_container); +module_exit(unregister_test_container); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_container: Test container for grouping monitors"); diff --git a/tools/verification/rvgen/tests/golden/test_container/test_container.h b/tools/verification/rvgen/tests/golden/test_container/test_container.h new file mode 100644 index 000000000000..83e434432650 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_container/test_container.h @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +extern struct rv_monitor rv_test_container; diff --git a/tools/verification/rvgen/tests/golden/test_da/Kconfig b/tools/verification/rvgen/tests/golden/test_da/Kconfig new file mode 100644 index 000000000000..0143a148ef34 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_TEST_DA + depends on RV + # XXX: add dependencies if there + select DA_MON_EVENTS_IMPLICIT + bool "test_da monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.c b/tools/verification/rvgen/tests/golden/test_da/test_da.c new file mode 100644 index 000000000000..59b8dfabbbf1 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da/test_da.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "test_da" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_PER_CPU +#include "test_da.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +static void handle_event_1(void *data, /* XXX: fill header */) +{ + da_handle_event(event_1_test_da); +} + +static void handle_event_2(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event always leads to the initial state */ + da_handle_start_event(event_2_test_da); +} + +static int enable_test_da(void) +{ + int retval; + + retval = da_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1); + rv_attach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2); + + return 0; +} + +static void disable_test_da(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_1); + rv_detach_trace_probe("test_da", /* XXX: tracepoint */, handle_event_2); + + da_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "test_da", + .description = "auto-generated", + .enable = enable_test_da, + .disable = disable_test_da, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_test_da(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_test_da(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_test_da); +module_exit(unregister_test_da); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_da: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da.h b/tools/verification/rvgen/tests/golden/test_da/test_da.h new file mode 100644 index 000000000000..d55795efbb61 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da/test_da.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of test_da automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME test_da + +enum states_test_da { + state_a_test_da, + state_b_test_da, + state_max_test_da, +}; + +#define INVALID_STATE state_max_test_da + +enum events_test_da { + event_1_test_da, + event_2_test_da, + event_max_test_da, +}; + +struct automaton_test_da { + char *state_names[state_max_test_da]; + char *event_names[event_max_test_da]; + unsigned char function[state_max_test_da][event_max_test_da]; + unsigned char initial_state; + bool final_states[state_max_test_da]; +}; + +static const struct automaton_test_da automaton_test_da = { + .state_names = { + "state_a", + "state_b", + }, + .event_names = { + "event_1", + "event_2", + }, + .function = { + { state_b_test_da, state_a_test_da }, + { INVALID_STATE, state_a_test_da }, + }, + .initial_state = state_a_test_da, + .final_states = { 1, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h new file mode 100644 index 000000000000..8bd67115d244 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da/test_da_trace.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_TEST_DA +DEFINE_EVENT(event_da_monitor, event_test_da, + TP_PROTO(char *state, char *event, char *next_state, bool final_state), + TP_ARGS(state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor, error_test_da, + TP_PROTO(char *state, char *event), + TP_ARGS(state, event)); +#endif /* CONFIG_RV_MON_TEST_DA */ diff --git a/tools/verification/rvgen/tests/golden/test_ha/Kconfig b/tools/verification/rvgen/tests/golden/test_ha/Kconfig new file mode 100644 index 000000000000..f4048290c774 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_TEST_HA + depends on RV + # XXX: add dependencies if there + select HA_MON_EVENTS_ID + bool "test_ha monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.c b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c new file mode 100644 index 000000000000..9047ff725546 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "test_ha" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_PER_TASK +/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */ +#define HA_TIMER_TYPE HA_TIMER_HRTIMER +#include "test_ha.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */ + +#define FOO_NS /* XXX: what is FOO_NS? */ + +static inline u64 bar_ns(struct ha_monitor *ha_mon) +{ + return /* XXX: what is bar_ns(ha_mon)? */; +} + +static u64 foo_ns = /* XXX: default value */; +module_param(foo_ns, ullong, 0644); + +/* + * These functions define how to read and reset the environment variable. + * + * Common environment variables like ns-based and jiffy-based clocks have + * pre-define getters and resetters you can use. The parser can infer the type + * of the environment variable if you supply a measure unit in the constraint. + * If you define your own functions, make sure to add appropriate memory + * barriers if required. + * Some environment variables don't require a storage as they read a system + * state (e.g. preemption count). Those variables are never reset, so we don't + * define a reset function on monitors only relying on this type of variables. + */ +static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns) +{ + if (env == clk_test_ha) + return ha_get_clk_ns(ha_mon, env, time_ns); + else if (env == env1_test_ha) + return /* XXX: how do I read env1? */ + else if (env == env2_test_ha) + return /* XXX: how do I read env2? */ + return ENV_INVALID_VALUE; +} + +static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_test_ha env, u64 time_ns) +{ + if (env == clk_test_ha) + ha_reset_clk_ns(ha_mon, env, time_ns); +} + +/* + * These functions are used to validate state transitions. + * + * They are generated by parsing the model, there is usually no need to change them. + * If the monitor requires a timer, there are functions responsible to arm it when + * the next state has a constraint, cancel it in any other case and to check + * that it didn't expire before the callback run. Transitions to the same state + * without a reset never affect timers. + */ +static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (curr_state == S0_test_ha) + return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns, bar_ns(ha_mon)); + else if (curr_state == S2_test_ha) + return ha_check_invariant_ns(ha_mon, clk_test_ha, time_ns, BAR_NS(ha_mon)); + return true; +} + +static inline bool ha_verify_guards(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + bool res = true; + + if (curr_state == S0_test_ha && event == event0_test_ha) + ha_reset_env(ha_mon, clk_test_ha, time_ns); + else if (curr_state == S0_test_ha && event == event1_test_ha) + ha_reset_env(ha_mon, clk_test_ha, time_ns); + else if (curr_state == S1_test_ha && event == event0_test_ha) + ha_reset_env(ha_mon, clk_test_ha, time_ns); + else if (curr_state == S1_test_ha && event == event2_test_ha) { + res = ha_get_env(ha_mon, env1_test_ha, time_ns) == 0ull; + ha_reset_env(ha_mon, clk_test_ha, time_ns); + } else if (curr_state == S2_test_ha && event == event1_test_ha) + res = ha_monitor_env_invalid(ha_mon, clk_test_ha) || + ha_get_env(ha_mon, clk_test_ha, time_ns) < foo_ns; + else if (curr_state == S3_test_ha && event == event0_test_ha) + res = ha_monitor_env_invalid(ha_mon, clk_test_ha) || + (ha_get_env(ha_mon, clk_test_ha, time_ns) < FOO_NS && + ha_get_env(ha_mon, env2_test_ha, time_ns) == 0ull); + else if (curr_state == S3_test_ha && event == event1_test_ha) { + res = ha_monitor_env_invalid(ha_mon, clk_test_ha) || + (ha_get_env(ha_mon, clk_test_ha, time_ns) < 5000ull && + ha_get_env(ha_mon, env1_test_ha, time_ns) == 1ull); + ha_reset_env(ha_mon, clk_test_ha, time_ns); + } + return res; +} + +static inline void ha_setup_invariants(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (next_state == curr_state && event != event0_test_ha) + return; + if (next_state == S0_test_ha) + ha_start_timer_ns(ha_mon, clk_test_ha, bar_ns(ha_mon), time_ns); + else if (next_state == S2_test_ha) + ha_start_timer_ns(ha_mon, clk_test_ha, BAR_NS(ha_mon), time_ns); + else if (curr_state == S0_test_ha) + ha_cancel_timer(ha_mon); + else if (curr_state == S2_test_ha) + ha_cancel_timer(ha_mon); +} + +static bool ha_verify_constraint(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns)) + return false; + + if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns)) + return false; + + ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns); + + return true; +} + +static void handle_event0(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event always leads to the initial state */ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_start_event(p, event0_test_ha); +} + +static void handle_event1(void *data, /* XXX: fill header */) +{ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_event(p, event1_test_ha); +} + +static void handle_event2(void *data, /* XXX: fill header */) +{ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_event(p, event2_test_ha); +} + +static int enable_test_ha(void) +{ + int retval; + + retval = ha_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0); + rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1); + rv_attach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2); + + return 0; +} + +static void disable_test_ha(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event0); + rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event1); + rv_detach_trace_probe("test_ha", /* XXX: tracepoint */, handle_event2); + + ha_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "test_ha", + .description = "auto-generated", + .enable = enable_test_ha, + .disable = disable_test_ha, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_test_ha(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_test_ha(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_test_ha); +module_exit(unregister_test_ha); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_ha: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h new file mode 100644 index 000000000000..949fa4453403 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of test_ha automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME test_ha + +enum states_test_ha { + S0_test_ha, + S1_test_ha, + S2_test_ha, + S3_test_ha, + state_max_test_ha, +}; + +#define INVALID_STATE state_max_test_ha + +enum events_test_ha { + event0_test_ha, + event1_test_ha, + event2_test_ha, + event_max_test_ha, +}; + +enum envs_test_ha { + clk_test_ha, + env1_test_ha, + env2_test_ha, + env_max_test_ha, + env_max_stored_test_ha = env1_test_ha, +}; + +_Static_assert(env_max_stored_test_ha <= MAX_HA_ENV_LEN, "Not enough slots"); +#define HA_CLK_NS + +struct automaton_test_ha { + char *state_names[state_max_test_ha]; + char *event_names[event_max_test_ha]; + char *env_names[env_max_test_ha]; + unsigned char function[state_max_test_ha][event_max_test_ha]; + unsigned char initial_state; + bool final_states[state_max_test_ha]; +}; + +static const struct automaton_test_ha automaton_test_ha = { + .state_names = { + "S0", + "S1", + "S2", + "S3", + }, + .event_names = { + "event0", + "event1", + "event2", + }, + .env_names = { + "clk", + "env1", + "env2", + }, + .function = { + { S0_test_ha, S1_test_ha, INVALID_STATE }, + { S0_test_ha, INVALID_STATE, S2_test_ha }, + { INVALID_STATE, S2_test_ha, S3_test_ha }, + { S0_test_ha, S1_test_ha, INVALID_STATE }, + }, + .initial_state = S0_test_ha, + .final_states = { 1, 0, 0, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h new file mode 100644 index 000000000000..381bafcb3322 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha/test_ha_trace.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_TEST_HA +DEFINE_EVENT(event_da_monitor_id, event_test_ha, + TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state), + TP_ARGS(id, state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor_id, error_test_ha, + TP_PROTO(int id, char *state, char *event), + TP_ARGS(id, state, event)); + +DEFINE_EVENT(error_env_da_monitor_id, error_env_test_ha, + TP_PROTO(int id, char *state, char *event, char *env), + TP_ARGS(id, state, event, env)); +#endif /* CONFIG_RV_MON_TEST_HA */ diff --git a/tools/verification/rvgen/tests/golden/test_ltl/Kconfig b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig new file mode 100644 index 000000000000..e2d0e721f180 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_TEST_LTL + depends on RV + # XXX: add dependencies if there + depends on RV_MON_LTL_PARENT + default y + select LTL_MON_EVENTS_ID + bool "test_ltl monitor" + help + Simple description diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c new file mode 100644 index 000000000000..dd961c5dc8ad --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "test_ltl" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include +#include + + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#include "test_ltl.h" +#include + +static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) +{ + /* + * This is called everytime the Buchi automaton is triggered. + * + * This function could be used to fetch the atomic propositions which + * are expensive to trace. It is possible only if the atomic proposition + * does not need to be updated at precise time. + * + * It is recommended to use tracepoints and ltl_atom_update() instead. + */ +} + +static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) +{ + /* + * This should initialize as many atomic propositions as possible. + * + * @task_creation indicates whether the task is being created. This is + * false if the task is already running before the monitor is enabled. + */ + ltl_atom_set(mon, LTL_EVENT_A, true/false); + ltl_atom_set(mon, LTL_EVENT_B, true/false); +} + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + */ +static void handle_example_event(void *data, /* XXX: fill header */) +{ + ltl_atom_update(task, LTL_EVENT_A, true/false); +} + +static int enable_test_ltl(void) +{ + int retval; + + retval = ltl_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_example_event); + + return 0; +} + +static void disable_test_ltl(void) +{ + rv_detach_trace_probe("test_ltl", /* XXX: tracepoint */, handle_example_event); + + ltl_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "test_ltl", + .description = "Simple description", + .enable = enable_test_ltl, + .disable = disable_test_ltl, +}; + +static int __init register_test_ltl(void) +{ + return rv_register_monitor(&rv_this, &rv_ltl_parent); +} + +static void __exit unregister_test_ltl(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_test_ltl); +module_exit(unregister_test_ltl); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_ltl: Simple description"); diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h new file mode 100644 index 000000000000..7895f2e233e8 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * C implementation of Buchi automaton, automatically generated by + * tools/verification/rvgen from the linear temporal logic specification. + * For further information, see kernel documentation: + * Documentation/trace/rv/linear_temporal_logic.rst + */ + +#include + +#define MONITOR_NAME test_ltl + +enum ltl_atom { + LTL_EVENT_A, + LTL_EVENT_B, + LTL_NUM_ATOM +}; +static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM); + +static const char *ltl_atom_str(enum ltl_atom atom) +{ + static const char *const names[] = { + "ev_a", + "ev_b", + }; + + return names[atom]; +} + +enum ltl_buchi_state { + S0, + S1, + S2, + S3, + S4, + RV_NUM_BA_STATES +}; +static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES); + +static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + if (val1) + __set_bit(S0, mon->states); + if (true) + __set_bit(S1, mon->states); + if (event_b) + __set_bit(S4, mon->states); +} + +static void +ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + switch (state) { + case S0: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S1: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S2: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S3: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S4: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + } +} diff --git a/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h new file mode 100644 index 000000000000..3571b004c114 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl/test_ltl_trace.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_TEST_LTL +DEFINE_EVENT(event_ltl_monitor_id, event_test_ltl, + TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next), + TP_ARGS(task, states, atoms, next)); +DEFINE_EVENT(error_ltl_monitor_id, error_test_ltl, + TP_PROTO(struct task_struct *task), + TP_ARGS(task)); +#endif /* CONFIG_RV_MON_TEST_LTL */ diff --git a/tools/verification/rvgen/tests/specs/test_da.dot b/tools/verification/rvgen/tests/specs/test_da.dot new file mode 100644 index 000000000000..e555c239b221 --- /dev/null +++ b/tools/verification/rvgen/tests/specs/test_da.dot @@ -0,0 +1,16 @@ +digraph state_automaton { + {node [shape = circle] "state_b"}; + {node [shape = plaintext, style=invis, label=""] "__init_state_a"}; + {node [shape = doublecircle] "state_a"}; + {node [shape = circle] "state_a"}; + "__init_state_a" -> "state_a"; + "state_a" [label = "state_a"]; + "state_a" -> "state_a" [ label = "event_2" ]; + "state_a" -> "state_b" [ label = "event_1" ]; + "state_b" [label = "state_b"]; + "state_b" -> "state_a" [ label = "event_2" ]; + { rank = min ; + "__init_state_a"; + "state_a"; + } +} diff --git a/tools/verification/rvgen/tests/specs/test_da2.dot b/tools/verification/rvgen/tests/specs/test_da2.dot new file mode 100644 index 000000000000..cdd4192f58ae --- /dev/null +++ b/tools/verification/rvgen/tests/specs/test_da2.dot @@ -0,0 +1,19 @@ +digraph state_automaton { + {node [shape = circle] "state_b"}; + {node [shape = circle] "state_c"}; + {node [shape = plaintext, style=invis, label=""] "__init_state_a"}; + {node [shape = doublecircle] "state_a"}; + {node [shape = circle] "state_a"}; + "__init_state_a" -> "state_a"; + "state_a" [label = "state_a"]; + "state_a" -> "state_b" [ label = "event_1" ]; + "state_a" -> "state_c" [ label = "event_2" ]; + "state_b" [label = "state_b"]; + "state_b" -> "state_a" [ label = "event_2" ]; + "state_b" -> "state_c" [ label = "event_3" ]; + "state_c" [label = "state_c"]; + { rank = min ; + "__init_state_a"; + "state_a"; + } +} diff --git a/tools/verification/rvgen/tests/specs/test_ha.dot b/tools/verification/rvgen/tests/specs/test_ha.dot new file mode 100644 index 000000000000..af18ad7389ec --- /dev/null +++ b/tools/verification/rvgen/tests/specs/test_ha.dot @@ -0,0 +1,27 @@ +digraph state_automaton { + center = true; + size = "7,11"; + {node [shape = circle] "S1"}; + {node [shape = plaintext, style=invis, label=""] "__init_S0"}; + {node [shape = doublecircle] "S0"}; + {node [shape = circle] "S0"}; + {node [shape = circle] "S2"}; + {node [shape = circle] "S3"}; + "__init_S0" -> "S0"; + "S0" [label = "S0\nclk < bar_ns()", color = green3]; + "S1" [label = "S1"]; + "S2" [label = "S2\nclk < BAR_NS()"]; + "S3" [label = "S3"]; + "S1" -> "S0" [ label = "event0;reset(clk)" ]; + "S0" -> "S1" [ label = "event1;reset(clk)" ]; + "S0" -> "S0" [ label = "event0;reset(clk)" ]; + "S1" -> "S2" [ label = "event2;env1 == 0;reset(clk)" ]; + "S2" -> "S3" [ label = "event2" ]; + "S2" -> "S2" [ label = "event1;clk < foo_ns" ]; + "S3" -> "S0" [ label = "event0;clk < FOO_NS && env2 == 0" ]; + "S3" -> "S1" [ label = "event1;clk < 5us && env1 == 1;reset(clk)" ]; + { rank = min ; + "__init_S0"; + "S0"; + } +} diff --git a/tools/verification/rvgen/tests/specs/test_invalid.dot b/tools/verification/rvgen/tests/specs/test_invalid.dot new file mode 100644 index 000000000000..17c63fc57f17 --- /dev/null +++ b/tools/verification/rvgen/tests/specs/test_invalid.dot @@ -0,0 +1,8 @@ +digraph invalid { + {node [shape = circle] "init"}; + {node [shape = circle] "state1"}; + "init" [label = "init"]; + "init" -> "state1" [ label = "event_a" ]; + "state1" [label = "state1"]; + "state1" -> "init" [ label = "event_b" ]; +} diff --git a/tools/verification/rvgen/tests/specs/test_invalid.ltl b/tools/verification/rvgen/tests/specs/test_invalid.ltl new file mode 100644 index 000000000000..cf36307e003c --- /dev/null +++ b/tools/verification/rvgen/tests/specs/test_invalid.ltl @@ -0,0 +1 @@ +RULE = A invalid B diff --git a/tools/verification/rvgen/tests/specs/test_invalid_ha.dot b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot new file mode 100644 index 000000000000..06de6aa8709f --- /dev/null +++ b/tools/verification/rvgen/tests/specs/test_invalid_ha.dot @@ -0,0 +1,16 @@ +digraph state_automaton { + {node [shape = circle] "state_b"}; + {node [shape = plaintext, style=invis, label=""] "__init_state_a"}; + {node [shape = doublecircle] "state_a"}; + {node [shape = circle] "state_a"}; + "__init_state_a" -> "state_a"; + "state_a" [label = "state_a;clk < 1"]; + "state_a" -> "state_a" [ label = "event_2;reset(clk)" ]; + "state_a" -> "state_b" [ label = "event_1;wrong_constraint" ]; + "state_b" [label = "state_b"]; + "state_b" -> "state_a" [ label = "event_2" ]; + { rank = min ; + "__init_state_a"; + "state_a"; + } +} diff --git a/tools/verification/rvgen/tests/specs/test_ltl.ltl b/tools/verification/rvgen/tests/specs/test_ltl.ltl new file mode 100644 index 000000000000..5ed658abd69c --- /dev/null +++ b/tools/verification/rvgen/tests/specs/test_ltl.ltl @@ -0,0 +1 @@ +RULE = always (EVENT_A imply eventually EVENT_B) From 36c1af42e794bd33732eaa90deee40ced894908e Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:25 +0200 Subject: [PATCH 29/40] verification/rvgen: Add selftests The rvgen code generator needs validation to ensure it produces correct monitor implementations from input specifications. Add selftests with golden reference outputs covering all monitor classes (DA, HA, LTL) and types (global, per_cpu, per_task, per_obj), including optional features like descriptions and parent monitors. Container generation and error handling (missing files, invalid specifications, missing arguments) are also validated against expected output. Acked-by: Nam Cao Reviewed-by: Wen Yang Link: https://lore.kernel.org/r/20260723074534.43521-9-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/Makefile | 4 + .../rvgen/tests/rvgen_container.t | 20 +++++ .../verification/rvgen/tests/rvgen_monitor.t | 87 +++++++++++++++++++ tools/verification/tests/engine.sh | 34 ++++++++ 4 files changed, 145 insertions(+) create mode 100644 tools/verification/rvgen/tests/rvgen_container.t create mode 100644 tools/verification/rvgen/tests/rvgen_monitor.t diff --git a/tools/verification/rvgen/Makefile b/tools/verification/rvgen/Makefile index cfc4056c1e87..2a2b9e64ea42 100644 --- a/tools/verification/rvgen/Makefile +++ b/tools/verification/rvgen/Makefile @@ -13,6 +13,10 @@ all: .PHONY: clean clean: +.PHONY: check +check: + prove -o --directives -f tests/ + .PHONY: install install: $(INSTALL) rvgen/automata.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/automata.py diff --git a/tools/verification/rvgen/tests/rvgen_container.t b/tools/verification/rvgen/tests/rvgen_container.t new file mode 100644 index 000000000000..fa4fb3db8288 --- /dev/null +++ b/tools/verification/rvgen/tests/rvgen_container.t @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source ../tests/engine.sh +test_begin + +set_timeout 30s + +# Help tests +check "verify container subcommand help" \ + "$RVGEN container -h" 0 "model_name" "class" + +check_and_compare_folder "container with description" \ + "$RVGEN container -n test_container -D 'Test container for grouping monitors'" \ + "test_container" "Writing the monitor into the directory test_container" + +# Error handling tests +check "missing required model_name" \ + "$RVGEN container" 2 "the following arguments are required: -n/--model_name" + +test_end diff --git a/tools/verification/rvgen/tests/rvgen_monitor.t b/tools/verification/rvgen/tests/rvgen_monitor.t new file mode 100644 index 000000000000..5f2562600bad --- /dev/null +++ b/tools/verification/rvgen/tests/rvgen_monitor.t @@ -0,0 +1,87 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source ../tests/engine.sh +test_begin + +set_timeout 30s + +# Help and basic tests +check "verify help page" \ + "$RVGEN --help" 0 "Generate kernel rv monitor" + +check "verify monitor subcommand help" \ + "$RVGEN monitor --help" 0 "Monitor class" + +# DA monitor tests - test all monitor types +check_and_compare_folder "DA per_cpu (default name)" \ + "$RVGEN monitor -c da -s tests/specs/test_da.dot -t per_cpu" \ + "test_da" "obj-\$(CONFIG_RV_MON_TEST_DA) += monitors/test_da/test_da.o" + +check_and_compare_folder "DA global type" \ + "$RVGEN monitor -c da -s tests/specs/test_da.dot -t global -n da_global" \ + "da_global" "DA_MON_EVENTS_IMPLICIT" + +check_and_compare_folder "DA per_task with description" \ + "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_task -n da_pertask_desc -D 'Custom description for testing'" \ + "da_pertask_desc" "#include " + +check_and_compare_folder "DA per_obj with parent" \ + "$RVGEN monitor -c da -s tests/specs/test_da2.dot -t per_obj -n da_perobj_parent -p parent_mon" \ + "da_perobj_parent" "DA_MON_EVENTS_ID" + +# HA monitor tests +check_and_compare_folder "HA per_task (default name)" \ + "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_task" \ + "test_ha" "HA_MON_EVENTS_ID" + +check_and_compare_folder "HA per_cpu type" \ + "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_cpu -n ha_percpu" \ + "ha_percpu" "HA_MON_EVENTS_IMPLICIT" + +# LTL monitor test +check_and_compare_folder "LTL per_task" \ + "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n ltl_pertask" \ + "ltl_pertask" "source \"kernel/trace/rv/monitors/ltl_pertask/Kconfig\"" + +check_and_compare_folder "LTL per_task with parent and description (default name)" \ + "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -p ltl_parent -D 'Simple description'" \ + "test_ltl" "LTL_MON_EVENTS_ID" + +# Error handling tests +check "missing required spec argument" \ + "$RVGEN monitor -c da -t per_cpu" 2 \ + "the following arguments are required: -s/--spec" "Traceback (most recent call last)" + +check "missing required monitor type" \ + "$RVGEN monitor -c da -s tests/specs/test_da.dot" 2 \ + "the following arguments are required: -t/--monitor_type" "Traceback (most recent call last)" + +check "missing required monitor class" \ + "$RVGEN monitor -s tests/specs/test_da.dot -t per_cpu" 2 \ + "the following arguments are required: -c/--class" "Traceback (most recent call last)" + +check "invalid monitor class" \ + "$RVGEN monitor -c invalid -s tests/specs/test_da.dot -t per_cpu" 1 \ + "Unknown monitor class" "Traceback (most recent call last)" + +check "missing dot file" \ + "$RVGEN monitor -c da -s tests/specs/nonexistent.dot -t per_cpu" 1 \ + "No such file or directory" "Traceback (most recent call last)" + +check "missing ltl file" \ + "$RVGEN monitor -c ltl -s tests/specs/nonexistent.ltl -t per_task" 1 \ + "No such file or directory" "Traceback (most recent call last)" + +check "invalid dot file syntax" \ + "$RVGEN monitor -c da -s tests/specs/test_invalid.dot -t per_cpu" 1 \ + "The automaton doesn't have an initial state" "Traceback (most recent call last)" + +check "invalid ha file syntax" \ + "$RVGEN monitor -c ha -s tests/specs/test_invalid_ha.dot -t per_obj" 1 \ + "Unrecognised event" "Traceback (most recent call last)" + +check "invalid ltl file syntax" \ + "$RVGEN monitor -c ltl -s tests/specs/test_invalid.ltl -t per_task" 1 \ + "No terminal matches 'i'" "Traceback (most recent call last)" + +test_end diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh index 57e16dc980b1..cfdf2180aad8 100644 --- a/tools/verification/tests/engine.sh +++ b/tools/verification/tests/engine.sh @@ -5,6 +5,8 @@ test_begin() { # included correctly. ctr=0 [ -z "$RV" ] && RV="../rv/rv" + [ -z "$RVGEN" ] && RVGEN="python3 ../rvgen" + [ -z "$GOLDEN_DIR" ] && GOLDEN_DIR="tests/golden" [ -n "$TEST_COUNT" ] && echo "1..$TEST_COUNT" } @@ -118,6 +120,38 @@ check_if_exists() { fi } +check_and_compare_folder() { + # Run command, compare generated folder to golden, and cleanup + local desc=$1 + local command=$2 + local generated_dir=$3 + local expected_output=$4 + local unexpected_output=$5 + local golden_dir="$GOLDEN_DIR/$generated_dir" + + ctr=$((ctr + 1)) + if [ -n "$TEST_COUNT" ]; then + rm -rf "$generated_dir" + _check "$desc" "$command" 0 "$expected_output" "$unexpected_output" + + if [ "$fail" -eq 0 ] && [ ! -d "$generated_dir" ]; then + failure "# Generated directory not found: $generated_dir" + fi + + if [ "$fail" -ne 0 ]; then + : + elif ! diff -r "$generated_dir" "$golden_dir" &> /dev/null; then + failure "# Directories differ:" + failbuf+=$(diff -r "$generated_dir" "$golden_dir" 2>&1 | sed 's/^/# /') + failbuf+=$'\n' + fi + + report "$1" + + rm -rf "$generated_dir" + fi +} + set_timeout() { TIMEOUT="timeout -v -k 30s $1" } From 5b4b6ef9cd941b4da02e066eea29a5578a0fa2be Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:26 +0200 Subject: [PATCH 30/40] verification/rvgen: Add the rvgen kunit subcommand Add the rvgen kunit subcommand to patch an already generated monitor for kunit support. It parses the handlers and create the necessary structs and initialisations. The only remaining manual steps are importing the test in the runner and writing the test itself. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-10-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- tools/verification/rvgen/Makefile | 1 + tools/verification/rvgen/__main__.py | 15 +- tools/verification/rvgen/rvgen/generator.py | 4 +- tools/verification/rvgen/rvgen/kunit.py | 194 ++++++++++++++++++ .../rvgen/rvgen/templates/kunit.c | 33 +++ 5 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 tools/verification/rvgen/rvgen/kunit.py create mode 100644 tools/verification/rvgen/rvgen/templates/kunit.c diff --git a/tools/verification/rvgen/Makefile b/tools/verification/rvgen/Makefile index 2a2b9e64ea42..48d0376a5cc4 100644 --- a/tools/verification/rvgen/Makefile +++ b/tools/verification/rvgen/Makefile @@ -23,6 +23,7 @@ install: $(INSTALL) rvgen/dot2c.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2c.py $(INSTALL) dot2c -D -m 755 $(DESTDIR)$(bindir)/ $(INSTALL) rvgen/dot2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2k.py + $(INSTALL) rvgen/kunit.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/kunit.py $(INSTALL) rvgen/container.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/container.py $(INSTALL) rvgen/generator.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/generator.py $(INSTALL) rvgen/ltl2ba.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2ba.py diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py index 019839ed9205..246b43fa29f1 100644 --- a/tools/verification/rvgen/__main__.py +++ b/tools/verification/rvgen/__main__.py @@ -13,6 +13,7 @@ if __name__ == '__main__': from rvgen.generator import Monitor from rvgen.container import Container from rvgen.ltl2k import ltl2k + from rvgen.kunit import KUnit, KUnitError from rvgen.automata import AutomataError from rvgen.ltl2ba import LTLError import argparse @@ -42,6 +43,11 @@ if __name__ == '__main__': container_parser = subparsers.add_parser("container", parents=[parent_parser]) container_parser.add_argument('-n', "--model_name", dest="model_name", required=True) + kunit_parser = subparsers.add_parser("kunit", parents=[parent_parser]) + kunit_parser.add_argument('-n', "--model_name", dest="model_name", required=True) + kunit_parser.add_argument('-l', "--local", dest="local", action="store_true", required=False, + help="Force looking for the monitor in the current directory only") + params = parser.parse_args() try: @@ -56,11 +62,18 @@ if __name__ == '__main__': else: print("Unknown monitor class:", params.monitor_class) sys.exit(1) - else: + elif params.subcmd == "container": monitor = Container(vars(params)) + elif params.subcmd == "kunit": + monitor = KUnit(vars(params)) + monitor.print_files() + sys.exit(0) except (AutomataError, LTLError) as e: print(f"There was an error processing {params.spec}:\n{e}", file=sys.stderr) sys.exit(1) + except KUnitError as e: + print(f"There was an error generating KUnit files:\n{e}", file=sys.stderr) + sys.exit(1) print(f"Writing the monitor into the directory {monitor.name}") monitor.print_files() diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py index f1b37d34b1e9..45e2bab26cb5 100644 --- a/tools/verification/rvgen/rvgen/generator.py +++ b/tools/verification/rvgen/rvgen/generator.py @@ -22,9 +22,9 @@ class RVGenerator: self.description = extra_params.get("description", self.name) or "auto-generated" self.auto_patch = extra_params.get("auto_patch") if self.auto_patch: - self.__fill_rv_kernel_dir() + self._fill_rv_kernel_dir() - def __fill_rv_kernel_dir(self): + def _fill_rv_kernel_dir(self): # find the kernel tree root relative to this file's location resolved_path = Path(__file__).resolve() if len(resolved_path.parents) > 4: diff --git a/tools/verification/rvgen/rvgen/kunit.py b/tools/verification/rvgen/rvgen/kunit.py new file mode 100644 index 000000000000..ed2082d7d3bc --- /dev/null +++ b/tools/verification/rvgen/rvgen/kunit.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco +# +# Generator for runtime verification kunit files + +import re +from pathlib import Path +from . import generator + + +class KUnitError(Exception): + """Exception raised for errors in KUnit generation and file handling.""" + + +class KUnit(generator.RVGenerator): + template_dir = "" + + def __init__(self, extra_params={}): + super().__init__(extra_params) + self.local = extra_params.get("local", False) + self.kunit_c = self._read_template_file("kunit.c") + if not self.local: + self._fill_rv_kernel_dir() + try: + self.monitor_path = self.__find_monitor_c_file() + with open(self.monitor_path, 'r') as f: + self.content = f.read() + except OSError as e: + raise KUnitError(e) from e + self.monitor_class = self.__detect_monitor_class() + + def _read_template_file(self, file): + if file in ("main.c", "Kconfig"): + return "" + return super()._read_template_file(file) + + def __find_monitor_c_file(self) -> str: + """Look for the monitor file in the kernel tree or in the current folder.""" + if not self.local: + path = Path(self.rv_dir) / "monitors" / self.name / f"{self.name}.c" + if path.exists(): + return str(path) + + path = Path(self.name) / f"{self.name}.c" + if path.exists(): + return str(path) + + raise FileNotFoundError(f"Could not find monitor C file for '{self.name}'") + + def __extract_function_args(self, handler_name: str) -> str: + pattern = re.compile( + r'^\s*(.*?)\b' + re.escape(handler_name) + r'\(([^)]*)\)', + re.MULTILINE | re.DOTALL + ) + match = pattern.search(self.content) + if not match: + return "/* XXX: fill handlers argument. */" + + return match.group(2).strip() + + def __parse_attach_handlers(self) -> list[str]: + """Find handlers by parsing when they are attached to tracepoints.""" + probe_pattern = re.compile( + r'rv_attach_trace_probe\(.*, ([a-zA-Z0-9_]+)\)' + ) + handlers = [] + for match in probe_pattern.finditer(self.content): + handler = match.group(1) + if handler not in handlers: + handlers.append(handler) + return handlers + + def __detect_monitor_class(self) -> str: + for c in ("da", "ha", "ltl"): + if f"{c}_monitor.h" in self.content: + return c + return "da" + + def __fill_kunit_c(self, struct_name: str) -> str: + kunit_c = self.kunit_c + kunit_c = kunit_c.replace("%%MODEL_NAME%%", self.name) + kunit_c = kunit_c.replace("%%MODEL_NAME_UP%%", self.name.upper()) + kunit_c = kunit_c.replace("%%MONITOR_CLASS%%", self.monitor_class) + kunit_c = kunit_c.replace("%%STRUCT_NAME%%", struct_name) + return kunit_c + + def __fill_kunit_h(self, struct_name, prototypes) -> str: + return f"""/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __{self.name.upper()}_KUNIT_H +#define __{self.name.upper()}_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct {struct_name} {{ +\tstruct rv_kunit_mon mon; +\t{"\n\t".join(prototypes)} +}} {struct_name}; +#endif + +#endif /* __{self.name.upper()}_KUNIT_H */ +""" + + def __fill_monitor_handlers(self, struct_name, assignments): + struct_definition = f"""#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "{self.name}_kunit.h" + +const struct {struct_name} {struct_name} = {{ +\t.mon = RV_MON_OPS_INIT(), +\t{"\n\t".join(assignments)} +}}; +EXPORT_SYMBOL_IF_KUNIT({struct_name}); +#endif""" + + if self.auto_patch: + try: + with open(self.monitor_path, 'w') as f: + f.write(f"{self.content}\n{struct_definition}\n") + except OSError as e: + raise KUnitError(f"Error patching monitor file {self.monitor_path}: {e}") from e + else: + print(f"Append the following to {self.name}.c:\n") + print(struct_definition) + print("Now complete the test and add it to rv_monitors_test.c") + + def print_files(self): + + handlers = self.__parse_attach_handlers() + + if not handlers: + raise KUnitError(f"No handlers found in {self.monitor_path}") + + prototypes = [] + assignments = [] + for handler in handlers: + arguments = self.__extract_function_args(handler) + + prototypes.append(f"void (*{handler})({arguments});") + assignments.append(f".{handler} = {handler},") + + struct_name = f"rv_{self.name}_ops" + + self.__fill_monitor_handlers(struct_name, assignments) + + dir_path = Path(self.monitor_path).parent + + header_file_path = dir_path / f"{self.name}_kunit.h" + kunit_c_file_path = dir_path / f"{self.name}_kunit.c" + + use_backup = True + if header_file_path.exists() or kunit_c_file_path.exists(): + try: + response = input("KUnit file(s) already exist. Backup? [Y/n] ") + if response.strip().lower() in ("n", "no"): + use_backup = False + except EOFError: + print("Non-interactive session detected, backing up existing files.") + else: + use_backup = False + + if use_backup: + for path in (header_file_path, kunit_c_file_path): + if path.exists(): + try: + path.rename(path.with_suffix(path.suffix + ".bak")) + except OSError as e: + raise KUnitError(f"Error backing up file {path}: {e}") from e + + header_content = self.__fill_kunit_h(struct_name, prototypes) + try: + with open(header_file_path, 'w') as f: + f.write(header_content) + print(f"Successfully created KUnit header file: {header_file_path}") + except OSError as e: + raise KUnitError(f"Error writing to file {header_file_path}: {e}") from e + + kunit_c_content = self.__fill_kunit_c(struct_name) + try: + with open(kunit_c_file_path, 'w') as f: + f.write(kunit_c_content) + print(f"Successfully created KUnit C file: {kunit_c_file_path}") + except OSError as e: + raise KUnitError(f"Error writing to file {kunit_c_file_path}: {e}") from e diff --git a/tools/verification/rvgen/rvgen/templates/kunit.c b/tools/verification/rvgen/rvgen/templates/kunit.c new file mode 100644 index 000000000000..62092b5cc6d4 --- /dev/null +++ b/tools/verification/rvgen/rvgen/templates/kunit.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +/* + * XXX: include required headers, e.g., + * #include + */ +#include "%%MODEL_NAME%%_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_%%MODEL_NAME_UP%%) + +static void rv_test_%%MODEL_NAME%%(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + /* + * If you need to create task_structs with rv_kunit_alloc_mock_task() + * do it BEFORE preparing the test. + */ + + prepare_test(test, &%%STRUCT_NAME%%.mon); + + /* + * XXX: write the test here + * e.g. + * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + * %%STRUCT_NAME%%.handle_event(args); + */ +} + +#else +#define rv_test_%%MODEL_NAME%% rv_test_stub +#endif From 7b6246294eb091c821078468523594c640804988 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:27 +0200 Subject: [PATCH 31/40] verification/rvgen: Add selftests for rvgen kunit The rvgen kunit command patches monitor files and adds necessary definitions for kunit tests. Add a test case validating its behaviour on dummy generated files and comparing it against reference files, like it's done for rvgen monitor. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-11-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- .../rvgen/tests/golden/test_bak_kunit/Kconfig | 9 + .../golden/test_bak_kunit/test_bak_kunit.c | 107 ++++++++ .../golden/test_bak_kunit/test_bak_kunit.h | 108 ++++++++ .../test_bak_kunit/test_bak_kunit_kunit.c | 33 +++ .../test_bak_kunit/test_bak_kunit_kunit.c.bak | 1 + .../test_bak_kunit/test_bak_kunit_kunit.h | 22 ++ .../test_bak_kunit/test_bak_kunit_trace.h | 14 + .../rvgen/tests/golden/test_da_kunit/Kconfig | 9 + .../golden/test_da_kunit/test_da_kunit.c | 107 ++++++++ .../golden/test_da_kunit/test_da_kunit.h | 47 ++++ .../test_da_kunit/test_da_kunit_kunit.c | 33 +++ .../test_da_kunit/test_da_kunit_kunit.h | 23 ++ .../test_da_kunit/test_da_kunit_trace.h | 15 ++ .../rvgen/tests/golden/test_ha_kunit/Kconfig | 9 + .../golden/test_ha_kunit/test_ha_kunit.c | 243 ++++++++++++++++++ .../golden/test_ha_kunit/test_ha_kunit.h | 88 +++++++ .../test_ha_kunit/test_ha_kunit_kunit.c | 33 +++ .../test_ha_kunit/test_ha_kunit_kunit.h | 24 ++ .../test_ha_kunit/test_ha_kunit_trace.h | 19 ++ .../rvgen/tests/golden/test_ltl_kunit/Kconfig | 9 + .../golden/test_ltl_kunit/test_ltl_kunit.c | 107 ++++++++ .../golden/test_ltl_kunit/test_ltl_kunit.h | 108 ++++++++ .../test_ltl_kunit/test_ltl_kunit_kunit.c | 33 +++ .../test_ltl_kunit/test_ltl_kunit_kunit.h | 22 ++ .../test_ltl_kunit/test_ltl_kunit_trace.h | 14 + tools/verification/rvgen/tests/rvgen_kunit.t | 41 +++ 26 files changed, 1278 insertions(+) create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h create mode 100644 tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h create mode 100644 tools/verification/rvgen/tests/rvgen_kunit.t diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig new file mode 100644 index 000000000000..175a416f8b18 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_TEST_BAK_KUNIT + depends on RV + # XXX: add dependencies if there + select LTL_MON_EVENTS_ID + bool "test_bak_kunit monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c new file mode 100644 index 000000000000..16579c1c6910 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "test_bak_kunit" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#include "test_bak_kunit.h" +#include + +static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) +{ + /* + * This is called everytime the Buchi automaton is triggered. + * + * This function could be used to fetch the atomic propositions which + * are expensive to trace. It is possible only if the atomic proposition + * does not need to be updated at precise time. + * + * It is recommended to use tracepoints and ltl_atom_update() instead. + */ +} + +static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) +{ + /* + * This should initialize as many atomic propositions as possible. + * + * @task_creation indicates whether the task is being created. This is + * false if the task is already running before the monitor is enabled. + */ + ltl_atom_set(mon, LTL_EVENT_A, true/false); + ltl_atom_set(mon, LTL_EVENT_B, true/false); +} + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + */ +static void handle_example_event(void *data, /* XXX: fill header */) +{ + ltl_atom_update(task, LTL_EVENT_A, true/false); +} + +static int enable_test_bak_kunit(void) +{ + int retval; + + retval = ltl_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("test_bak_kunit", /* XXX: tracepoint */, handle_example_event); + + return 0; +} + +static void disable_test_bak_kunit(void) +{ + rv_detach_trace_probe("test_bak_kunit", /* XXX: tracepoint */, handle_example_event); + + ltl_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "test_bak_kunit", + .description = "auto-generated", + .enable = enable_test_bak_kunit, + .disable = disable_test_bak_kunit, +}; + +static int __init register_test_bak_kunit(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_test_bak_kunit(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_test_bak_kunit); +module_exit(unregister_test_bak_kunit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_bak_kunit: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h new file mode 100644 index 000000000000..2bfe4e37cea7 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * C implementation of Buchi automaton, automatically generated by + * tools/verification/rvgen from the linear temporal logic specification. + * For further information, see kernel documentation: + * Documentation/trace/rv/linear_temporal_logic.rst + */ + +#include + +#define MONITOR_NAME test_bak_kunit + +enum ltl_atom { + LTL_EVENT_A, + LTL_EVENT_B, + LTL_NUM_ATOM +}; +static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM); + +static const char *ltl_atom_str(enum ltl_atom atom) +{ + static const char *const names[] = { + "ev_a", + "ev_b", + }; + + return names[atom]; +} + +enum ltl_buchi_state { + S0, + S1, + S2, + S3, + S4, + RV_NUM_BA_STATES +}; +static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES); + +static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + if (val1) + __set_bit(S0, mon->states); + if (true) + __set_bit(S1, mon->states); + if (event_b) + __set_bit(S4, mon->states); +} + +static void +ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + switch (state) { + case S0: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S1: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S2: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S3: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S4: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + } +} diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c new file mode 100644 index 000000000000..e2b9354034cc --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +/* + * XXX: include required headers, e.g., + * #include + */ +#include "test_bak_kunit_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_TEST_BAK_KUNIT) + +static void rv_test_test_bak_kunit(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + /* + * If you need to create task_structs with rv_kunit_alloc_mock_task() + * do it BEFORE preparing the test. + */ + + prepare_test(test, &rv_test_bak_kunit_ops.mon); + + /* + * XXX: write the test here + * e.g. + * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + * rv_test_bak_kunit_ops.handle_event(args); + */ +} + +#else +#define rv_test_test_bak_kunit rv_test_stub +#endif diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak new file mode 100644 index 000000000000..f747925bf542 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.c.bak @@ -0,0 +1 @@ +DUMMY diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h new file mode 100644 index 000000000000..585c4803be23 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_kunit.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __TEST_BAK_KUNIT_KUNIT_H +#define __TEST_BAK_KUNIT_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_test_bak_kunit_ops { + struct rv_kunit_mon mon; + void (*handle_example_event)(void *data, /* XXX: fill header */); +} rv_test_bak_kunit_ops; +#endif + +#endif /* __TEST_BAK_KUNIT_KUNIT_H */ diff --git a/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h new file mode 100644 index 000000000000..b984208838c4 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_bak_kunit/test_bak_kunit_trace.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_TEST_BAK_KUNIT +DEFINE_EVENT(event_ltl_monitor_id, event_test_bak_kunit, + TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next), + TP_ARGS(task, states, atoms, next)); +DEFINE_EVENT(error_ltl_monitor_id, error_test_bak_kunit, + TP_PROTO(struct task_struct *task), + TP_ARGS(task)); +#endif /* CONFIG_RV_MON_TEST_BAK_KUNIT */ diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig new file mode 100644 index 000000000000..6d664ba5624d --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da_kunit/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_TEST_DA_KUNIT + depends on RV + # XXX: add dependencies if there + select DA_MON_EVENTS_IMPLICIT + bool "test_da_kunit monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c new file mode 100644 index 000000000000..effd26548b07 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "test_da_kunit" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_PER_CPU +#include "test_da_kunit.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +static void handle_event_1(void *data, /* XXX: fill header */) +{ + da_handle_event(event_1_test_da_kunit); +} + +static void handle_event_2(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event always leads to the initial state */ + da_handle_start_event(event_2_test_da_kunit); +} + +static int enable_test_da_kunit(void) +{ + int retval; + + retval = da_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_1); + rv_attach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_2); + + return 0; +} + +static void disable_test_da_kunit(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_1); + rv_detach_trace_probe("test_da_kunit", /* XXX: tracepoint */, handle_event_2); + + da_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "test_da_kunit", + .description = "auto-generated", + .enable = enable_test_da_kunit, + .disable = disable_test_da_kunit, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_test_da_kunit(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_test_da_kunit(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_test_da_kunit); +module_exit(unregister_test_da_kunit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_da_kunit: auto-generated"); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "test_da_kunit_kunit.h" + +const struct rv_test_da_kunit_ops rv_test_da_kunit_ops = { + .mon = RV_MON_OPS_INIT(), + .handle_event_1 = handle_event_1, + .handle_event_2 = handle_event_2, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_test_da_kunit_ops); +#endif diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h new file mode 100644 index 000000000000..290a9454caa4 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of test_da_kunit automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME test_da_kunit + +enum states_test_da_kunit { + state_a_test_da_kunit, + state_b_test_da_kunit, + state_max_test_da_kunit, +}; + +#define INVALID_STATE state_max_test_da_kunit + +enum events_test_da_kunit { + event_1_test_da_kunit, + event_2_test_da_kunit, + event_max_test_da_kunit, +}; + +struct automaton_test_da_kunit { + char *state_names[state_max_test_da_kunit]; + char *event_names[event_max_test_da_kunit]; + unsigned char function[state_max_test_da_kunit][event_max_test_da_kunit]; + unsigned char initial_state; + bool final_states[state_max_test_da_kunit]; +}; + +static const struct automaton_test_da_kunit automaton_test_da_kunit = { + .state_names = { + "state_a", + "state_b", + }, + .event_names = { + "event_1", + "event_2", + }, + .function = { + { state_b_test_da_kunit, state_a_test_da_kunit }, + { INVALID_STATE, state_a_test_da_kunit }, + }, + .initial_state = state_a_test_da_kunit, + .final_states = { 1, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c new file mode 100644 index 000000000000..17826a5c47c8 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +/* + * XXX: include required headers, e.g., + * #include + */ +#include "test_da_kunit_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_TEST_DA_KUNIT) + +static void rv_test_test_da_kunit(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + /* + * If you need to create task_structs with rv_kunit_alloc_mock_task() + * do it BEFORE preparing the test. + */ + + prepare_test(test, &rv_test_da_kunit_ops.mon); + + /* + * XXX: write the test here + * e.g. + * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + * rv_test_da_kunit_ops.handle_event(args); + */ +} + +#else +#define rv_test_test_da_kunit rv_test_stub +#endif diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h new file mode 100644 index 000000000000..0094215ff4fb --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_kunit.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __TEST_DA_KUNIT_KUNIT_H +#define __TEST_DA_KUNIT_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_test_da_kunit_ops { + struct rv_kunit_mon mon; + void (*handle_event_1)(void *data, /* XXX: fill header */); + void (*handle_event_2)(void *data, /* XXX: fill header */); +} rv_test_da_kunit_ops; +#endif + +#endif /* __TEST_DA_KUNIT_KUNIT_H */ diff --git a/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h new file mode 100644 index 000000000000..16804a79e834 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_da_kunit/test_da_kunit_trace.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_TEST_DA_KUNIT +DEFINE_EVENT(event_da_monitor, event_test_da_kunit, + TP_PROTO(char *state, char *event, char *next_state, bool final_state), + TP_ARGS(state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor, error_test_da_kunit, + TP_PROTO(char *state, char *event), + TP_ARGS(state, event)); +#endif /* CONFIG_RV_MON_TEST_DA_KUNIT */ diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig new file mode 100644 index 000000000000..6c48770ace1a --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_TEST_HA_KUNIT + depends on RV + # XXX: add dependencies if there + select HA_MON_EVENTS_ID + bool "test_ha_kunit monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c new file mode 100644 index 000000000000..239b0539df18 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "test_ha_kunit" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#define RV_MON_TYPE RV_MON_PER_TASK +/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */ +#define HA_TIMER_TYPE HA_TIMER_HRTIMER +#include "test_ha_kunit.h" +#include + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + * + */ +#define BAR_NS(ha_mon) /* XXX: what is BAR_NS(ha_mon)? */ + +#define FOO_NS /* XXX: what is FOO_NS? */ + +static inline u64 bar_ns(struct ha_monitor *ha_mon) +{ + return /* XXX: what is bar_ns(ha_mon)? */; +} + +static u64 foo_ns = /* XXX: default value */; +module_param(foo_ns, ullong, 0644); + +/* + * These functions define how to read and reset the environment variable. + * + * Common environment variables like ns-based and jiffy-based clocks have + * pre-define getters and resetters you can use. The parser can infer the type + * of the environment variable if you supply a measure unit in the constraint. + * If you define your own functions, make sure to add appropriate memory + * barriers if required. + * Some environment variables don't require a storage as they read a system + * state (e.g. preemption count). Those variables are never reset, so we don't + * define a reset function on monitors only relying on this type of variables. + */ +static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_test_ha_kunit env, u64 time_ns) +{ + if (env == clk_test_ha_kunit) + return ha_get_clk_ns(ha_mon, env, time_ns); + else if (env == env1_test_ha_kunit) + return /* XXX: how do I read env1? */ + else if (env == env2_test_ha_kunit) + return /* XXX: how do I read env2? */ + return ENV_INVALID_VALUE; +} + +static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_test_ha_kunit env, u64 time_ns) +{ + if (env == clk_test_ha_kunit) + ha_reset_clk_ns(ha_mon, env, time_ns); +} + +/* + * These functions are used to validate state transitions. + * + * They are generated by parsing the model, there is usually no need to change them. + * If the monitor requires a timer, there are functions responsible to arm it when + * the next state has a constraint, cancel it in any other case and to check + * that it didn't expire before the callback run. Transitions to the same state + * without a reset never affect timers. + */ +static inline bool ha_verify_invariants(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (curr_state == S0_test_ha_kunit) + return ha_check_invariant_ns(ha_mon, clk_test_ha_kunit, time_ns, bar_ns(ha_mon)); + else if (curr_state == S2_test_ha_kunit) + return ha_check_invariant_ns(ha_mon, clk_test_ha_kunit, time_ns, BAR_NS(ha_mon)); + return true; +} + +static inline bool ha_verify_guards(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + bool res = true; + + if (curr_state == S0_test_ha_kunit && event == event0_test_ha_kunit) + ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns); + else if (curr_state == S0_test_ha_kunit && event == event1_test_ha_kunit) + ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns); + else if (curr_state == S1_test_ha_kunit && event == event0_test_ha_kunit) + ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns); + else if (curr_state == S1_test_ha_kunit && event == event2_test_ha_kunit) { + res = ha_get_env(ha_mon, env1_test_ha_kunit, time_ns) == 0ull; + ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns); + } else if (curr_state == S2_test_ha_kunit && event == event1_test_ha_kunit) + res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) || + ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < foo_ns; + else if (curr_state == S3_test_ha_kunit && event == event0_test_ha_kunit) + res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) || + (ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < FOO_NS && + ha_get_env(ha_mon, env2_test_ha_kunit, time_ns) == 0ull); + else if (curr_state == S3_test_ha_kunit && event == event1_test_ha_kunit) { + res = ha_monitor_env_invalid(ha_mon, clk_test_ha_kunit) || + (ha_get_env(ha_mon, clk_test_ha_kunit, time_ns) < 5000ull && + ha_get_env(ha_mon, env1_test_ha_kunit, time_ns) == 1ull); + ha_reset_env(ha_mon, clk_test_ha_kunit, time_ns); + } + return res; +} + +static inline void ha_setup_invariants(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (next_state == curr_state && event != event0_test_ha_kunit) + return; + if (next_state == S0_test_ha_kunit) + ha_start_timer_ns(ha_mon, clk_test_ha_kunit, bar_ns(ha_mon), time_ns); + else if (next_state == S2_test_ha_kunit) + ha_start_timer_ns(ha_mon, clk_test_ha_kunit, BAR_NS(ha_mon), time_ns); + else if (curr_state == S0_test_ha_kunit) + ha_cancel_timer(ha_mon); + else if (curr_state == S2_test_ha_kunit) + ha_cancel_timer(ha_mon); +} + +static bool ha_verify_constraint(struct ha_monitor *ha_mon, + enum states curr_state, enum events event, + enum states next_state, u64 time_ns) +{ + if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns)) + return false; + + if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns)) + return false; + + ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns); + + return true; +} + +static void handle_event0(void *data, /* XXX: fill header */) +{ + /* XXX: validate that this event always leads to the initial state */ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_start_event(p, event0_test_ha_kunit); +} + +static void handle_event1(void *data, /* XXX: fill header */) +{ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_event(p, event1_test_ha_kunit); +} + +static void handle_event2(void *data, /* XXX: fill header */) +{ + struct task_struct *p = /* XXX: how do I get p? */; + da_handle_event(p, event2_test_ha_kunit); +} + +static int enable_test_ha_kunit(void) +{ + int retval; + + retval = ha_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event0); + rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event1); + rv_attach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event2); + + return 0; +} + +static void disable_test_ha_kunit(void) +{ + rv_this.enabled = 0; + + rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event0); + rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event1); + rv_detach_trace_probe("test_ha_kunit", /* XXX: tracepoint */, handle_event2); + + ha_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "test_ha_kunit", + .description = "auto-generated", + .enable = enable_test_ha_kunit, + .disable = disable_test_ha_kunit, + .reset = da_monitor_reset_all, + .enabled = 0, +}; + +static int __init register_test_ha_kunit(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_test_ha_kunit(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_test_ha_kunit); +module_exit(unregister_test_ha_kunit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_ha_kunit: auto-generated"); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "test_ha_kunit_kunit.h" + +const struct rv_test_ha_kunit_ops rv_test_ha_kunit_ops = { + .mon = RV_MON_OPS_INIT(), + .handle_event0 = handle_event0, + .handle_event1 = handle_event1, + .handle_event2 = handle_event2, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_test_ha_kunit_ops); +#endif diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h new file mode 100644 index 000000000000..5c428f818bdf --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Automatically generated C representation of test_ha_kunit automaton + * For further information about this format, see kernel documentation: + * Documentation/trace/rv/deterministic_automata.rst + */ + +#define MONITOR_NAME test_ha_kunit + +enum states_test_ha_kunit { + S0_test_ha_kunit, + S1_test_ha_kunit, + S2_test_ha_kunit, + S3_test_ha_kunit, + state_max_test_ha_kunit, +}; + +#define INVALID_STATE state_max_test_ha_kunit + +enum events_test_ha_kunit { + event0_test_ha_kunit, + event1_test_ha_kunit, + event2_test_ha_kunit, + event_max_test_ha_kunit, +}; + +enum envs_test_ha_kunit { + clk_test_ha_kunit, + env1_test_ha_kunit, + env2_test_ha_kunit, + env_max_test_ha_kunit, + env_max_stored_test_ha_kunit = env1_test_ha_kunit, +}; + +_Static_assert(env_max_stored_test_ha_kunit <= MAX_HA_ENV_LEN, "Not enough slots"); +#define HA_CLK_NS + +struct automaton_test_ha_kunit { + char *state_names[state_max_test_ha_kunit]; + char *event_names[event_max_test_ha_kunit]; + char *env_names[env_max_test_ha_kunit]; + unsigned char function[state_max_test_ha_kunit][event_max_test_ha_kunit]; + unsigned char initial_state; + bool final_states[state_max_test_ha_kunit]; +}; + +static const struct automaton_test_ha_kunit automaton_test_ha_kunit = { + .state_names = { + "S0", + "S1", + "S2", + "S3", + }, + .event_names = { + "event0", + "event1", + "event2", + }, + .env_names = { + "clk", + "env1", + "env2", + }, + .function = { + { + S0_test_ha_kunit, + S1_test_ha_kunit, + INVALID_STATE, + }, + { + S0_test_ha_kunit, + INVALID_STATE, + S2_test_ha_kunit, + }, + { + INVALID_STATE, + S2_test_ha_kunit, + S3_test_ha_kunit, + }, + { + S0_test_ha_kunit, + S1_test_ha_kunit, + INVALID_STATE, + }, + }, + .initial_state = S0_test_ha_kunit, + .final_states = { 1, 0, 0, 0 }, +}; diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c new file mode 100644 index 000000000000..6214a4aa6d25 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +/* + * XXX: include required headers, e.g., + * #include + */ +#include "test_ha_kunit_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_TEST_HA_KUNIT) + +static void rv_test_test_ha_kunit(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + /* + * If you need to create task_structs with rv_kunit_alloc_mock_task() + * do it BEFORE preparing the test. + */ + + prepare_test(test, &rv_test_ha_kunit_ops.mon); + + /* + * XXX: write the test here + * e.g. + * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + * rv_test_ha_kunit_ops.handle_event(args); + */ +} + +#else +#define rv_test_test_ha_kunit rv_test_stub +#endif diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h new file mode 100644 index 000000000000..0b2030cb644a --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_kunit.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __TEST_HA_KUNIT_KUNIT_H +#define __TEST_HA_KUNIT_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_test_ha_kunit_ops { + struct rv_kunit_mon mon; + void (*handle_event0)(void *data, /* XXX: fill header */); + void (*handle_event1)(void *data, /* XXX: fill header */); + void (*handle_event2)(void *data, /* XXX: fill header */); +} rv_test_ha_kunit_ops; +#endif + +#endif /* __TEST_HA_KUNIT_KUNIT_H */ diff --git a/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h new file mode 100644 index 000000000000..6c13ee0068d3 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ha_kunit/test_ha_kunit_trace.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_TEST_HA_KUNIT +DEFINE_EVENT(event_da_monitor_id, event_test_ha_kunit, + TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state), + TP_ARGS(id, state, event, next_state, final_state)); + +DEFINE_EVENT(error_da_monitor_id, error_test_ha_kunit, + TP_PROTO(int id, char *state, char *event), + TP_ARGS(id, state, event)); + +DEFINE_EVENT(error_env_da_monitor_id, error_env_test_ha_kunit, + TP_PROTO(int id, char *state, char *event, char *env), + TP_ARGS(id, state, event, env)); +#endif /* CONFIG_RV_MON_TEST_HA_KUNIT */ diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig b/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig new file mode 100644 index 000000000000..3e334c344261 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +config RV_MON_TEST_LTL_KUNIT + depends on RV + # XXX: add dependencies if there + select LTL_MON_EVENTS_ID + bool "test_ltl_kunit monitor" + help + auto-generated diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c new file mode 100644 index 000000000000..c1d58ce435a8 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "test_ltl_kunit" + +/* + * XXX: include required tracepoint headers, e.g., + * #include + */ +#include + + +/* + * This is the self-generated part of the monitor. Generally, there is no need + * to touch this section. + */ +#include "test_ltl_kunit.h" +#include + +static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon) +{ + /* + * This is called everytime the Buchi automaton is triggered. + * + * This function could be used to fetch the atomic propositions which + * are expensive to trace. It is possible only if the atomic proposition + * does not need to be updated at precise time. + * + * It is recommended to use tracepoints and ltl_atom_update() instead. + */ +} + +static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation) +{ + /* + * This should initialize as many atomic propositions as possible. + * + * @task_creation indicates whether the task is being created. This is + * false if the task is already running before the monitor is enabled. + */ + ltl_atom_set(mon, LTL_EVENT_A, true/false); + ltl_atom_set(mon, LTL_EVENT_B, true/false); +} + +/* + * This is the instrumentation part of the monitor. + * + * This is the section where manual work is required. Here the kernel events + * are translated into model's event. + */ +static void handle_example_event(void *data, /* XXX: fill header */) +{ + ltl_atom_update(task, LTL_EVENT_A, true/false); +} + +static int enable_test_ltl_kunit(void) +{ + int retval; + + retval = ltl_monitor_init(); + if (retval) + return retval; + + rv_attach_trace_probe("test_ltl_kunit", /* XXX: tracepoint */, handle_example_event); + + return 0; +} + +static void disable_test_ltl_kunit(void) +{ + rv_detach_trace_probe("test_ltl_kunit", /* XXX: tracepoint */, handle_example_event); + + ltl_monitor_destroy(); +} + +/* + * This is the monitor register section. + */ +static struct rv_monitor rv_this = { + .name = "test_ltl_kunit", + .description = "auto-generated", + .enable = enable_test_ltl_kunit, + .disable = disable_test_ltl_kunit, +}; + +static int __init register_test_ltl_kunit(void) +{ + return rv_register_monitor(&rv_this, NULL); +} + +static void __exit unregister_test_ltl_kunit(void) +{ + rv_unregister_monitor(&rv_this); +} + +module_init(register_test_ltl_kunit); +module_exit(unregister_test_ltl_kunit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("rvgen: auto-generated"); +MODULE_DESCRIPTION("test_ltl_kunit: auto-generated"); diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h new file mode 100644 index 000000000000..acc503b56e87 --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * C implementation of Buchi automaton, automatically generated by + * tools/verification/rvgen from the linear temporal logic specification. + * For further information, see kernel documentation: + * Documentation/trace/rv/linear_temporal_logic.rst + */ + +#include + +#define MONITOR_NAME test_ltl_kunit + +enum ltl_atom { + LTL_EVENT_A, + LTL_EVENT_B, + LTL_NUM_ATOM +}; +static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM); + +static const char *ltl_atom_str(enum ltl_atom atom) +{ + static const char *const names[] = { + "ev_a", + "ev_b", + }; + + return names[atom]; +} + +enum ltl_buchi_state { + S0, + S1, + S2, + S3, + S4, + RV_NUM_BA_STATES +}; +static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES); + +static void ltl_start(struct task_struct *task, struct ltl_monitor *mon) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + if (val1) + __set_bit(S0, mon->states); + if (true) + __set_bit(S1, mon->states); + if (event_b) + __set_bit(S4, mon->states); +} + +static void +ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next) +{ + bool event_b = test_bit(LTL_EVENT_B, mon->atoms); + bool event_a = test_bit(LTL_EVENT_A, mon->atoms); + bool val1 = !event_a; + + switch (state) { + case S0: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S1: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S2: + if (true) + __set_bit(S1, next); + if (true && val1) + __set_bit(S2, next); + if (event_b && val1) + __set_bit(S3, next); + if (event_b) + __set_bit(S4, next); + break; + case S3: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + case S4: + if (val1) + __set_bit(S0, next); + if (true) + __set_bit(S1, next); + if (event_b) + __set_bit(S4, next); + break; + } +} diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c new file mode 100644 index 000000000000..37dab5dfdebc --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +/* + * XXX: include required headers, e.g., + * #include + */ +#include "test_ltl_kunit_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_TEST_LTL_KUNIT) + +static void rv_test_test_ltl_kunit(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + /* + * If you need to create task_structs with rv_kunit_alloc_mock_task() + * do it BEFORE preparing the test. + */ + + prepare_test(test, &rv_test_ltl_kunit_ops.mon); + + /* + * XXX: write the test here + * e.g. + * RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + * rv_test_ltl_kunit_ops.handle_event(args); + */ +} + +#else +#define rv_test_test_ltl_kunit rv_test_stub +#endif diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h new file mode 100644 index 000000000000..b2ca34be327f --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_kunit.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __TEST_LTL_KUNIT_KUNIT_H +#define __TEST_LTL_KUNIT_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_test_ltl_kunit_ops { + struct rv_kunit_mon mon; + void (*handle_example_event)(void *data, /* XXX: fill header */); +} rv_test_ltl_kunit_ops; +#endif + +#endif /* __TEST_LTL_KUNIT_KUNIT_H */ diff --git a/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h new file mode 100644 index 000000000000..a054d5b2c0ea --- /dev/null +++ b/tools/verification/rvgen/tests/golden/test_ltl_kunit/test_ltl_kunit_trace.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Snippet to be included in rv_trace.h + */ + +#ifdef CONFIG_RV_MON_TEST_LTL_KUNIT +DEFINE_EVENT(event_ltl_monitor_id, event_test_ltl_kunit, + TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next), + TP_ARGS(task, states, atoms, next)); +DEFINE_EVENT(error_ltl_monitor_id, error_test_ltl_kunit, + TP_PROTO(struct task_struct *task), + TP_ARGS(task)); +#endif /* CONFIG_RV_MON_TEST_LTL_KUNIT */ diff --git a/tools/verification/rvgen/tests/rvgen_kunit.t b/tools/verification/rvgen/tests/rvgen_kunit.t new file mode 100644 index 000000000000..d27d9175f562 --- /dev/null +++ b/tools/verification/rvgen/tests/rvgen_kunit.t @@ -0,0 +1,41 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +source ../tests/engine.sh +test_begin + +set_timeout 30s + +# Help tests +check "verify kunit subcommand help" \ + "$RVGEN kunit -h" 0 "model_name" "spec" + +check_and_compare_folder "KUnit generation with local lookup and test_da_kunit" \ + "$RVGEN monitor -c da -s tests/specs/test_da.dot -t per_cpu -n test_da_kunit && $RVGEN kunit -a -l -n test_da_kunit" \ + "test_da_kunit" "Now complete the test and add it to rv_monitors_test.c" "RV_MON_OPS_INIT" + +check_and_compare_folder "KUnit generation with local lookup and test_ha_kunit" \ + "$RVGEN monitor -c ha -s tests/specs/test_ha.dot -t per_task -n test_ha_kunit && $RVGEN kunit -a -l -n test_ha_kunit" \ + "test_ha_kunit" "Successfully created KUnit" "Append the following to" + +check_and_compare_folder "KUnit generation with local lookup and test_ltl_kunit" \ + "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n test_ltl_kunit && $RVGEN kunit -l -n test_ltl_kunit" \ + "test_ltl_kunit" "RV_MON_OPS_INIT" + +check_and_compare_folder "KUnit generation with backup file" \ + "$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -n test_bak_kunit && echo DUMMY > test_bak_kunit/test_bak_kunit_kunit.c && $RVGEN kunit -l -n test_bak_kunit" \ + "test_bak_kunit" "KUnit file(s) already exist.*backing up existing files" + +# Error handling tests +check "missing required model_name" \ + "$RVGEN kunit" 2 "the following arguments are required: -n/--model_name" + +check "non-existent model_name with auto_patch" \ + "$RVGEN kunit -a -n nonexistent" 1 \ + "Could not find monitor C file" "Traceback (most recent call last)" + +check "monitor without handlers" \ + "mkdir -p nohandler ; echo DUMMY > nohandler/nohandler.c ; $RVGEN kunit -l -n nohandler" 1 \ + "No handlers found" "Traceback (most recent call last)" +rm -rf nohandler + +test_end From be22e55b37bc3a2d9cc9dc60ede5940131d4c873 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:28 +0200 Subject: [PATCH 32/40] rv: Export task monitor slot and react symbols Export rv_get_task_monitor_slot, rv_put_task_monitor_slot, and rv_react to GPL modules so they can be accessed by KUnit and future monitors built as kernel modules. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-12-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- kernel/trace/rv/rv.c | 2 ++ kernel/trace/rv/rv_reactors.c | 1 + 2 files changed, 3 insertions(+) diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c index 187d87d5991c..6d7b93fa8146 100644 --- a/kernel/trace/rv/rv.c +++ b/kernel/trace/rv/rv.c @@ -181,6 +181,7 @@ int rv_get_task_monitor_slot(void) return -EBUSY; } +EXPORT_SYMBOL_GPL(rv_get_task_monitor_slot); void rv_put_task_monitor_slot(int slot) { @@ -197,6 +198,7 @@ void rv_put_task_monitor_slot(int slot) task_monitor_slots[slot] = false; } +EXPORT_SYMBOL_GPL(rv_put_task_monitor_slot); /* * Monitors with a parent are nested, diff --git a/kernel/trace/rv/rv_reactors.c b/kernel/trace/rv/rv_reactors.c index 460af07f7aba..2f5fc8d18dea 100644 --- a/kernel/trace/rv/rv_reactors.c +++ b/kernel/trace/rv/rv_reactors.c @@ -479,3 +479,4 @@ void rv_react(struct rv_monitor *monitor, const char *msg, ...) va_end(args); } +EXPORT_SYMBOL_GPL(rv_react); From 8da2a88383658dac97769ed8f807ef6100a69480 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:29 +0200 Subject: [PATCH 33/40] rv: Add KUnit tests for some DA/HA monitors Validate the functionality of DA monitors by injecting events in a controlled environment (KUnit) and expecting reactions. Events handlers are exported directly from the monitor source files without using system events and with dummy arguments (e.g. no real tasks). If the provided sequence of events incurs a violation, the test expects the stub version of rv_react() to be called. This testing method can validate the entire monitor implementation since it sits between the monitor and the system (in place of the tracepoints). All sorts of system and timing events can be emulated without affecting the running kernel. Handlers and monitor functions are exported as part of a struct to simplify the process of running KUnit tests from kernel modules. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-13-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/rv/da_monitor.h | 22 +++ include/rv/ha_monitor.h | 21 +++ include/rv/kunit.h | 61 ++++++ kernel/trace/rv/Kconfig | 11 ++ kernel/trace/rv/Makefile | 1 + kernel/trace/rv/monitors/nomiss/nomiss.c | 18 ++ .../trace/rv/monitors/nomiss/nomiss_kunit.c | 38 ++++ .../trace/rv/monitors/nomiss/nomiss_kunit.h | 35 ++++ kernel/trace/rv/monitors/opid/opid.c | 12 ++ kernel/trace/rv/monitors/opid/opid_kunit.c | 33 ++++ kernel/trace/rv/monitors/opid/opid_kunit.h | 23 +++ kernel/trace/rv/monitors/sco/sco.c | 13 ++ kernel/trace/rv/monitors/sco/sco_kunit.c | 29 +++ kernel/trace/rv/monitors/sco/sco_kunit.h | 24 +++ kernel/trace/rv/monitors/sssw/sssw.c | 14 ++ kernel/trace/rv/monitors/sssw/sssw_kunit.c | 33 ++++ kernel/trace/rv/monitors/sssw/sssw_kunit.h | 30 +++ kernel/trace/rv/monitors/sts/sts.c | 19 ++ kernel/trace/rv/monitors/sts/sts_kunit.c | 39 ++++ kernel/trace/rv/monitors/sts/sts_kunit.h | 33 ++++ kernel/trace/rv/rv.c | 40 ++++ kernel/trace/rv/rv_monitors_test.c | 177 ++++++++++++++++++ 22 files changed, 726 insertions(+) create mode 100644 include/rv/kunit.h create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_kunit.c create mode 100644 kernel/trace/rv/monitors/nomiss/nomiss_kunit.h create mode 100644 kernel/trace/rv/monitors/opid/opid_kunit.c create mode 100644 kernel/trace/rv/monitors/opid/opid_kunit.h create mode 100644 kernel/trace/rv/monitors/sco/sco_kunit.c create mode 100644 kernel/trace/rv/monitors/sco/sco_kunit.h create mode 100644 kernel/trace/rv/monitors/sssw/sssw_kunit.c create mode 100644 kernel/trace/rv/monitors/sssw/sssw_kunit.h create mode 100644 kernel/trace/rv/monitors/sts/sts_kunit.c create mode 100644 kernel/trace/rv/monitors/sts/sts_kunit.h create mode 100644 kernel/trace/rv/rv_monitors_test.c diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h index 08e5d0c59926..db57a8a704f9 100644 --- a/include/rv/da_monitor.h +++ b/include/rv/da_monitor.h @@ -311,6 +311,11 @@ static inline struct da_monitor *da_get_monitor(struct task_struct *tsk) return &tsk->rv[task_mon_slot].da_mon; } +static inline void da_reset(struct task_struct *tsk) +{ + da_monitor_reset(da_get_monitor(tsk)); +} + /* * da_get_target - return the task associated to the monitor */ @@ -908,4 +913,21 @@ static inline void da_reset(da_id_type id, monitor_target target) } #endif /* RV_MON_TYPE */ +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#if RV_MON_TYPE == RV_MON_PER_TASK +#define RV_MON_OPS_INIT() { \ + .rv_this = &rv_this, \ + .is_per_task = true, \ + .task_slot = &task_mon_slot, \ + .task_reset = da_reset, \ +} +#else +#define RV_MON_OPS_INIT() { \ + .rv_this = &rv_this, \ + .monitor_init = da_monitor_init, \ + .monitor_destroy = da_monitor_destroy, \ +} +#endif /* RV_MON_TYPE */ +#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */ + #endif diff --git a/include/rv/ha_monitor.h b/include/rv/ha_monitor.h index 9144b4c06f3f..6e1c7fe5449a 100644 --- a/include/rv/ha_monitor.h +++ b/include/rv/ha_monitor.h @@ -526,4 +526,25 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon) static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon) { } #endif +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#ifdef RV_MON_OPS_INIT +#undef RV_MON_OPS_INIT +#endif + +#if RV_MON_TYPE == RV_MON_PER_TASK +#define RV_MON_OPS_INIT() { \ + .rv_this = &rv_this, \ + .is_per_task = true, \ + .task_slot = &task_mon_slot, \ + .task_reset = da_reset, \ +} +#else +#define RV_MON_OPS_INIT() { \ + .rv_this = &rv_this, \ + .monitor_init = ha_monitor_init, \ + .monitor_destroy = ha_monitor_destroy, \ +} +#endif /* RV_MON_TYPE */ +#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */ + #endif diff --git a/include/rv/kunit.h b/include/rv/kunit.h new file mode 100644 index 000000000000..ff98b5137285 --- /dev/null +++ b/include/rv/kunit.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco + * + * Declaration of utilities to run KUnit tests. + */ + +#ifndef _RV_KUNIT_H +#define _RV_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include +#include + +int rv_set_testing(struct kunit_suite *suite); +void rv_clear_testing(struct kunit_suite *suite); + +#define RV_KUNIT_MAX_MOCK_TASKS 8 + +struct rv_kunit_ctx { + int reactions, expected; + int mock_task_count; + struct task_struct *mock_tasks[RV_KUNIT_MAX_MOCK_TASKS]; +}; + +#define RV_KUNIT_EXPECT_REACTION(test, ctx) \ + do { \ + KUNIT_EXPECT_EQ(test, ctx->reactions, ++ctx->expected); \ + if (ctx->reactions != ctx->expected) \ + ctx->expected = ctx->reactions; \ + } while (0) + +#define RV_KUNIT_EXPECT_NO_REACTION(test, ctx) \ + do { \ + KUNIT_EXPECT_EQ(test, ctx->reactions, ctx->expected); \ + if (ctx->reactions != ctx->expected) \ + ctx->expected = ctx->reactions; \ + } while (0) + +#define RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) \ + for (int __done = ({ RV_KUNIT_EXPECT_NO_REACTION(test, ctx); 0; }); \ + !__done; \ + __done = ({ RV_KUNIT_EXPECT_REACTION(test, ctx); 1; })) + +struct rv_kunit_mon { + struct rv_monitor *rv_this; + int (*monitor_init)(void); + void (*monitor_destroy)(void); + bool is_per_task; + int *task_slot; + void (*task_reset)(struct task_struct *task); +}; + +void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon); +void teardown_test(void *arg); +struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test); + +#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */ +#endif /* _RV_KUNIT_H */ diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig index 4d3a14a0bac2..5608314de06b 100644 --- a/kernel/trace/rv/Kconfig +++ b/kernel/trace/rv/Kconfig @@ -112,3 +112,14 @@ config RV_REACT_PANIC help Enables the panic reactor. The panic reactor emits a printk() message if an exception is found and panic()s the system. + +config RV_MONITORS_KUNIT_TEST + tristate "KUnit tests for RV monitors" if !KUNIT_ALL_TESTS + depends on KUNIT && RV && RV_REACTORS + default KUNIT_ALL_TESTS + help + Enable KUnit tests for the RV (Runtime Verification) monitors. + These tests verify that monitors correctly detect violations by + triggering fake events and validating the expected reactions. + + If unsure, say N. diff --git a/kernel/trace/rv/Makefile b/kernel/trace/rv/Makefile index c2c0e4142eb4..cdbf68c84f5a 100644 --- a/kernel/trace/rv/Makefile +++ b/kernel/trace/rv/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o obj-$(CONFIG_RV_REACTORS) += rv_reactors.o obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o +obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o diff --git a/kernel/trace/rv/monitors/nomiss/nomiss.c b/kernel/trace/rv/monitors/nomiss/nomiss.c index 515ece5ce0ca..6e47d379f777 100644 --- a/kernel/trace/rv/monitors/nomiss/nomiss.c +++ b/kernel/trace/rv/monitors/nomiss/nomiss.c @@ -277,3 +277,21 @@ module_exit(unregister_nomiss); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Gabriele Monaco "); MODULE_DESCRIPTION("nomiss: dl entities run to completion before their deadline."); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "nomiss_kunit.h" + +const struct rv_nomiss_ops rv_nomiss_ops = { + .mon = RV_MON_OPS_INIT(), + .deadline_thresh = &deadline_thresh, + .handle_dl_replenish = handle_dl_replenish, + .handle_dl_throttle = handle_dl_throttle, + .handle_dl_server_stop = handle_dl_server_stop, + .handle_sched_switch = handle_sched_switch, + .handle_sched_wakeup = handle_sched_wakeup, + .handle_sys_enter = handle_sys_enter, + .handle_newtask = handle_newtask, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_nomiss_ops); +#endif diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c new file mode 100644 index 000000000000..1f64249dfcce --- /dev/null +++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include "nomiss_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_NOMISS) + +static void rv_test_nomiss(struct kunit *test) +{ + struct task_struct *target = rv_kunit_alloc_mock_task(test); + struct task_struct *other = rv_kunit_alloc_mock_task(test); + struct rv_kunit_ctx *ctx = test->priv; + + prepare_test(test, &rv_nomiss_ops.mon); + + target->pid = 99; + target->policy = SCHED_DEADLINE; + target->dl.runtime = 10000; + target->dl.dl_deadline = 20000; + + rv_nomiss_ops.handle_newtask(NULL, target, 0); + + /* Task gets preempted and can't terminate before deadline */ + rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING); + rv_nomiss_ops.handle_dl_replenish(NULL, &target->dl, 0, DL_TASK); + udelay(10); + rv_nomiss_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING); + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) { + udelay(15 + *rv_nomiss_ops.deadline_thresh / 1000); + rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING); + } +} + +#else +#define rv_test_nomiss rv_test_stub +#endif diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h new file mode 100644 index 000000000000..2be779c5dbaa --- /dev/null +++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __NOMISS_KUNIT_H +#define __NOMISS_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_nomiss_ops { + struct rv_kunit_mon mon; + const u64 *deadline_thresh; + void (*handle_dl_replenish)(void *data, struct sched_dl_entity *dl_se, + int cpu, u8 type); + void (*handle_dl_throttle)(void *data, struct sched_dl_entity *dl_se, + int cpu, u8 type); + void (*handle_dl_server_stop)(void *data, struct sched_dl_entity *dl_se, + int cpu, u8 type); + void (*handle_sched_switch)(void *data, bool preempt, + struct task_struct *prev, + struct task_struct *next, + unsigned int prev_state); + void (*handle_sched_wakeup)(void *data, struct task_struct *tsk); + void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id); + void (*handle_newtask)(void *data, struct task_struct *task, u64 flags); +} rv_nomiss_ops; +#endif + +#endif /* __NOMISS_KUNIT_H */ diff --git a/kernel/trace/rv/monitors/opid/opid.c b/kernel/trace/rv/monitors/opid/opid.c index 3b6a85e815b8..9ae619f176fa 100644 --- a/kernel/trace/rv/monitors/opid/opid.c +++ b/kernel/trace/rv/monitors/opid/opid.c @@ -115,3 +115,15 @@ module_exit(unregister_opid); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Gabriele Monaco "); MODULE_DESCRIPTION("opid: operations with preemption and irq disabled."); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "opid_kunit.h" + +const struct rv_opid_ops rv_opid_ops = { + .mon = RV_MON_OPS_INIT(), + .handle_sched_need_resched = handle_sched_need_resched, + .handle_sched_waking = handle_sched_waking, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_opid_ops); +#endif diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.c b/kernel/trace/rv/monitors/opid/opid_kunit.c new file mode 100644 index 000000000000..3cb087a74241 --- /dev/null +++ b/kernel/trace/rv/monitors/opid/opid_kunit.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include "opid_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_OPID) + +static void rv_test_opid(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + + prepare_test(test, &rv_opid_ops.mon); + + /* Ensure we keep the same per-cpu monitor */ + guard(migrate)(); + KUNIT_EXPECT_TRUE(test, preemptible()); + + /* Wakeup with preemption and interrupts enabled */ + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_opid_ops.handle_sched_waking(NULL, NULL); + + /* Need resched with interrupts enabled */ + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) { + scoped_guard(preempt) + rv_opid_ops.handle_sched_need_resched(NULL, NULL, 0, TIF_NEED_RESCHED); + } +} + +#else +#define rv_test_opid rv_test_stub +#endif diff --git a/kernel/trace/rv/monitors/opid/opid_kunit.h b/kernel/trace/rv/monitors/opid/opid_kunit.h new file mode 100644 index 000000000000..4969c6175957 --- /dev/null +++ b/kernel/trace/rv/monitors/opid/opid_kunit.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __OPID_KUNIT_H +#define __OPID_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_opid_ops { + struct rv_kunit_mon mon; + void (*handle_sched_need_resched)(void *data, struct task_struct *tsk, int cpu, int tif); + void (*handle_sched_waking)(void *data, struct task_struct *p); +} rv_opid_ops; +#endif + +#endif /* __OPID_KUNIT_H */ diff --git a/kernel/trace/rv/monitors/sco/sco.c b/kernel/trace/rv/monitors/sco/sco.c index 5a3bd5e16e62..1ef1b96e859d 100644 --- a/kernel/trace/rv/monitors/sco/sco.c +++ b/kernel/trace/rv/monitors/sco/sco.c @@ -83,3 +83,16 @@ module_exit(unregister_sco); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Gabriele Monaco "); MODULE_DESCRIPTION("sco: scheduling context operations."); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "sco_kunit.h" + +const struct rv_sco_ops rv_sco_ops = { + .mon = RV_MON_OPS_INIT(), + .handle_sched_set_state = handle_sched_set_state, + .handle_schedule_entry = handle_schedule_entry, + .handle_schedule_exit = handle_schedule_exit, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_sco_ops); +#endif diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.c b/kernel/trace/rv/monitors/sco/sco_kunit.c new file mode 100644 index 000000000000..5e59bcbfcf0b --- /dev/null +++ b/kernel/trace/rv/monitors/sco/sco_kunit.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include "sco_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_SCO) + +static void rv_test_sco(struct kunit *test) +{ + struct task_struct *target = rv_kunit_alloc_mock_task(test); + struct rv_kunit_ctx *ctx = test->priv; + + prepare_test(test, &rv_sco_ops.mon); + + /* Ensure we keep the same per-cpu monitor */ + guard(migrate)(); + + /* Set state while scheduling */ + rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE); + rv_sco_ops.handle_schedule_entry(NULL, false); + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_sco_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE); +} + +#else +#define rv_test_sco rv_test_stub +#endif diff --git a/kernel/trace/rv/monitors/sco/sco_kunit.h b/kernel/trace/rv/monitors/sco/sco_kunit.h new file mode 100644 index 000000000000..567757df6b1d --- /dev/null +++ b/kernel/trace/rv/monitors/sco/sco_kunit.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __SCO_KUNIT_H +#define __SCO_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_sco_ops { + struct rv_kunit_mon mon; + void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state); + void (*handle_schedule_entry)(void *data, bool preempt); + void (*handle_schedule_exit)(void *data, bool is_switch); +} rv_sco_ops; +#endif + +#endif /* __SCO_KUNIT_H */ diff --git a/kernel/trace/rv/monitors/sssw/sssw.c b/kernel/trace/rv/monitors/sssw/sssw.c index a91321c890cd..fbfde32dc136 100644 --- a/kernel/trace/rv/monitors/sssw/sssw.c +++ b/kernel/trace/rv/monitors/sssw/sssw.c @@ -112,3 +112,17 @@ module_exit(unregister_sssw); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Gabriele Monaco "); MODULE_DESCRIPTION("sssw: set state sleep and wakeup."); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "sssw_kunit.h" + +const struct rv_sssw_ops rv_sssw_ops = { + .mon = RV_MON_OPS_INIT(), + .handle_sched_set_state = handle_sched_set_state, + .handle_sched_switch = handle_sched_switch, + .handle_sched_wakeup = handle_sched_wakeup, + .handle_signal_deliver = handle_signal_deliver, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_sssw_ops); +#endif diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.c b/kernel/trace/rv/monitors/sssw/sssw_kunit.c new file mode 100644 index 000000000000..a95faf859c60 --- /dev/null +++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include "sssw_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_SSSW) + +static void rv_test_sssw(struct kunit *test) +{ + struct task_struct *target = rv_kunit_alloc_mock_task(test); + struct task_struct *other = rv_kunit_alloc_mock_task(test); + struct rv_kunit_ctx *ctx = test->priv; + + prepare_test(test, &rv_sssw_ops.mon); + + /* Suspend without setting to sleepable */ + rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_RUNNING); + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE); + + /* Switch in after suspension without wakeup */ + rv_sssw_ops.handle_sched_wakeup(NULL, target); + rv_sssw_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE); + rv_sssw_ops.handle_sched_switch(NULL, 0, target, other, TASK_INTERRUPTIBLE); + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_sssw_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING); +} + +#else +#define rv_test_sssw rv_test_stub +#endif diff --git a/kernel/trace/rv/monitors/sssw/sssw_kunit.h b/kernel/trace/rv/monitors/sssw/sssw_kunit.h new file mode 100644 index 000000000000..6513daa7afba --- /dev/null +++ b/kernel/trace/rv/monitors/sssw/sssw_kunit.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __SSSW_KUNIT_H +#define __SSSW_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_sssw_ops { + struct rv_kunit_mon mon; + void (*handle_sched_set_state)(void *data, struct task_struct *tsk, int state); + void (*handle_sched_switch)(void *data, bool preempt, + struct task_struct *prev, + struct task_struct *next, + unsigned int prev_state); + void (*handle_sched_wakeup)(void *data, struct task_struct *p); + void (*handle_signal_deliver)(void *data, int sig, + struct kernel_siginfo *info, + struct k_sigaction *ka); +} rv_sssw_ops; +#endif + +#endif /* __SSSW_KUNIT_H */ diff --git a/kernel/trace/rv/monitors/sts/sts.c b/kernel/trace/rv/monitors/sts/sts.c index ce031cbf202a..2a044cf925b1 100644 --- a/kernel/trace/rv/monitors/sts/sts.c +++ b/kernel/trace/rv/monitors/sts/sts.c @@ -152,3 +152,22 @@ module_exit(unregister_sts); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Gabriele Monaco "); MODULE_DESCRIPTION("sts: schedule implies task switch."); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "sts_kunit.h" + +const struct rv_sts_ops rv_sts_ops = { + .mon = RV_MON_OPS_INIT(), +#ifdef CONFIG_X86_LOCAL_APIC + .handle_vector_irq_entry = handle_vector_irq_entry, +#endif + .handle_irq_disable = handle_irq_disable, + .handle_irq_enable = handle_irq_enable, + .handle_irq_entry = handle_irq_entry, + .handle_sched_switch = handle_sched_switch, + .handle_schedule_entry = handle_schedule_entry, + .handle_schedule_exit = handle_schedule_exit, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_sts_ops); +#endif diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.c b/kernel/trace/rv/monitors/sts/sts_kunit.c new file mode 100644 index 000000000000..a07316fff091 --- /dev/null +++ b/kernel/trace/rv/monitors/sts/sts_kunit.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include "sts_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_STS) + +static void rv_test_sts(struct kunit *test) +{ + struct task_struct *target = rv_kunit_alloc_mock_task(test); + struct task_struct *other = rv_kunit_alloc_mock_task(test); + struct rv_kunit_ctx *ctx = test->priv; + + prepare_test(test, &rv_sts_ops.mon); + /* Per-CPU monitor, make sure we don't change CPU mid-test */ + guard(migrate)(); + + /* Switch without disabling interrupts */ + rv_sts_ops.handle_schedule_exit(NULL, false); + rv_sts_ops.handle_schedule_entry(NULL, false); + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING); + + rv_sts_ops.handle_schedule_exit(NULL, false); + + /* Schedule from interrupt context */ + rv_sts_ops.handle_schedule_entry(NULL, false); + rv_sts_ops.handle_irq_disable(NULL, 0, 0); + rv_sts_ops.handle_irq_entry(NULL, 0, NULL); + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_sts_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING); + rv_sts_ops.handle_irq_enable(NULL, 0, 0); +} + +#else +#define rv_test_sts rv_test_stub +#endif diff --git a/kernel/trace/rv/monitors/sts/sts_kunit.h b/kernel/trace/rv/monitors/sts/sts_kunit.h new file mode 100644 index 000000000000..dede4e098c1f --- /dev/null +++ b/kernel/trace/rv/monitors/sts/sts_kunit.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __STS_KUNIT_H +#define __STS_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_sts_ops { + struct rv_kunit_mon mon; +#ifdef CONFIG_X86_LOCAL_APIC + void (*handle_vector_irq_entry)(void *data, int vector); +#endif + void (*handle_irq_disable)(void *data, unsigned long ip, unsigned long parent_ip); + void (*handle_irq_enable)(void *data, unsigned long ip, unsigned long parent_ip); + void (*handle_irq_entry)(void *data, int irq, struct irqaction *action); + void (*handle_sched_switch)(void *data, bool preempt, + struct task_struct *prev, + struct task_struct *next, + unsigned int prev_state); + void (*handle_schedule_entry)(void *data, bool preempt); + void (*handle_schedule_exit)(void *data, bool is_switch); +} rv_sts_ops; +#endif + +#endif /* __STS_KUNIT_H */ diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c index 6d7b93fa8146..4f577d9b5ba8 100644 --- a/kernel/trace/rv/rv.c +++ b/kernel/trace/rv/rv.c @@ -846,3 +846,43 @@ int __init rv_init_interface(void) return 0; } + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include + +/* + * rv_set_testing - ensure mutual exclusion between KUnit tests and real monitors + * + * KUnit tests for RV monitors rely on stubs that are incompatible with + * the execution of real monitors. Ensure mutual exclusion by acquiring + * the rv_interface_lock for the duration of the suite. + * + * Returns 0 on success, -EBUSY if any real monitor is already enabled. + */ +int rv_set_testing(struct kunit_suite *suite) +{ + struct rv_monitor *mon; + + mutex_lock(&rv_interface_lock); + + list_for_each_entry(mon, &rv_monitors_list, list) { + if (mon->enabled) { + mutex_unlock(&rv_interface_lock); + return -EBUSY; + } + } + + return 0; +} +EXPORT_SYMBOL_IF_KUNIT(rv_set_testing); + +/* + * rv_clear_testing - allow real monitors to run again after KUnit tests + */ +void rv_clear_testing(struct kunit_suite *suite) +{ + mutex_unlock(&rv_interface_lock); +} +EXPORT_SYMBOL_IF_KUNIT(rv_clear_testing); +#endif diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c new file mode 100644 index 000000000000..be440bf4b4a7 --- /dev/null +++ b/kernel/trace/rv/rv_monitors_test.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco + * + * RV monitor kunit tests: + * Tests the RV monitors by triggering fake events to verify monitor + * behavior and reactions. Tests start from the first defined event and + * trigger events in order to verify error detection. + */ +#include +#include +#include +#include +#include "rv.h" + +/* + * An easy way to pass the context is to use kunit_get_current_test()->priv, + * but this doesn't always work (e.g. a reactor running from another context + * like softirq). Store the current value here whenever a test is running. + */ +static struct rv_kunit_ctx *active_ctx; + +__printf(1, 0) +static void rv_kunit_mock_react(const char *msg, va_list args) +{ + if (active_ctx) + ++active_ctx->reactions; +} + +/* + * teardown_test - Disable the monitor for a kunit test + * + * Since per-task monitors are special, make sure we reset all the ones we + * started manually here, if required. + */ +void teardown_test(void *arg) +{ + const struct rv_kunit_mon *mon = arg; + struct kunit *test = kunit_get_current_test(); + + if (test) { + struct rv_kunit_ctx *ctx = test->priv; + + RV_KUNIT_EXPECT_NO_REACTION(test, ctx); + + if (mon->is_per_task && mon->task_reset) { + for (int i = 0; i < ctx->mock_task_count; i++) + mon->task_reset(ctx->mock_tasks[i]); + synchronize_rcu(); + } + } + + mon->rv_this->enabled = 0; + + if (mon->rv_this->reactor) + mon->rv_this->react = mon->rv_this->reactor->react; + else + mon->rv_this->react = NULL; + active_ctx = NULL; + + if (mon->is_per_task) + *mon->task_slot = RV_PER_TASK_MONITOR_INIT; + else + mon->monitor_destroy(); +} + +/* + * prepare_test - Enable the monitor for a kunit test + * + * Do the bare minimum to set up the monitor, per-task monitors are special as + * "real" initialisation/destruction iterates over real tasks, and may register + * handlers. All we need is to select the right slot in the task_struct. + */ +void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon) +{ + KUNIT_ASSERT_FALSE(test, mon->rv_this->enabled); + + active_ctx = test->priv; + mon->rv_this->react = rv_kunit_mock_react; + + if (mon->is_per_task) + *mon->task_slot = 0; + else + KUNIT_ASSERT_EQ(test, mon->monitor_init(), 0); + + mon->rv_this->enabled = 1; + + KUNIT_ASSERT_EQ(test, 0, + kunit_add_action_or_reset(test, teardown_test, (void *)mon)); +} + +struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + struct task_struct *tsk; + + KUNIT_ASSERT_LT(test, ctx->mock_task_count, RV_KUNIT_MAX_MOCK_TASKS); + + tsk = kunit_kzalloc(test, sizeof(struct task_struct), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tsk); + + if (!IS_ENABLED(CONFIG_THREAD_INFO_IN_TASK)) { + tsk->stack = kunit_kzalloc(test, sizeof(struct thread_info), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tsk->stack); + } + + ctx->mock_tasks[ctx->mock_task_count++] = tsk; + return tsk; +} + +static int rv_mon_test_init(struct kunit *test) +{ + struct rv_kunit_ctx *ctx; + + ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx); + + test->priv = ctx; + + return 0; +} + +static void __maybe_unused rv_test_stub(struct kunit *test) +{ + kunit_skip(test, "Monitor not enabled\n"); +} + +/* + * rv_test_dummy - test reactions work as expected + */ +static void rv_test_dummy(struct kunit *test) +{ + struct rv_kunit_ctx *ctx = test->priv; + static struct rv_monitor dummy_monitor = { + .name = "dummy", + .react = rv_kunit_mock_react, + }; + + active_ctx = ctx; + + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_react(&dummy_monitor, "dummy"); + RV_KUNIT_EXPECT_NO_REACTION(test, ctx); + + active_ctx = NULL; +} + +#include "monitors/sco/sco_kunit.c" +#include "monitors/sssw/sssw_kunit.c" +#include "monitors/sts/sts_kunit.c" +#include "monitors/opid/opid_kunit.c" +#include "monitors/nomiss/nomiss_kunit.c" + +static struct kunit_case rv_mon_test_cases[] = { + KUNIT_CASE(rv_test_dummy), + KUNIT_CASE(rv_test_sco), + KUNIT_CASE(rv_test_sssw), + KUNIT_CASE(rv_test_sts), + KUNIT_CASE(rv_test_opid), + KUNIT_CASE(rv_test_nomiss), + {} +}; + +static struct kunit_suite rv_mon_test_suite = { + .name = "rv_mon", + .suite_init = rv_set_testing, + .suite_exit = rv_clear_testing, + .init = rv_mon_test_init, + .test_cases = rv_mon_test_cases, +}; + +kunit_test_suites(&rv_mon_test_suite); + +MODULE_AUTHOR("Gabriele Monaco "); +MODULE_DESCRIPTION("RV monitor kunit tests: test monitors by triggering reactions"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); From cf8f191c06546dff223df12010d4a21f7b631ae3 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:30 +0200 Subject: [PATCH 34/40] rv: Add KUnit mock for current Some monitors do not only rely on tracepoint arguments but also on the currently executing task. This makes it more challenging to mock events in KUnit. Define wrapper functions around current, the functionality is mocked only during KUnit, an additional function call is avoided using a static branch unless any (even unrelated) KUnit test is running. Rely on a global mock_current variable that is set only by the RV KUnit tests and cleared on teardown. Unrelated KUnit tests that happen to trigger RV handlers would see it null and use current. Reviewed-by: Nam Cao Reviewed-by: Wen Yang Link: https://lore.kernel.org/r/20260723074534.43521-14-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/rv/da_monitor.h | 1 + include/rv/kunit.h | 14 +++++++++- include/rv/ltl_monitor.h | 1 + kernel/trace/rv/Kconfig | 3 +++ .../trace/rv/monitors/pagefault/pagefault.c | 2 +- kernel/trace/rv/monitors/sleep/sleep.c | 26 +++++++++---------- kernel/trace/rv/rv.c | 26 +++++++++++++++++++ kernel/trace/rv/rv_monitors_test.c | 1 + 8 files changed, 59 insertions(+), 15 deletions(-) diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h index db57a8a704f9..e3cf85c9ce55 100644 --- a/include/rv/da_monitor.h +++ b/include/rv/da_monitor.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include diff --git a/include/rv/kunit.h b/include/rv/kunit.h index ff98b5137285..31e0b93c40ea 100644 --- a/include/rv/kunit.h +++ b/include/rv/kunit.h @@ -2,7 +2,10 @@ /* * Copyright (C) 2026-2029 Red Hat, Inc. Gabriele Monaco * - * Declaration of utilities to run KUnit tests. + * Declaration of wrappers to allow mocking core functionality, like current, + * and other testing utilities. + * Necessary only when mocking may be needed. If the RV KUnit test is + * enabled, the wrappers incur an additional function call overhead. */ #ifndef _RV_KUNIT_H @@ -57,5 +60,14 @@ void prepare_test(struct kunit *test, const struct rv_kunit_mon *mon); void teardown_test(void *arg); struct task_struct *rv_kunit_alloc_mock_task(struct kunit *test); +void rv_mock_current(struct task_struct *tsk); +struct task_struct *rv_get_mock_current(void); + +#define rv_get_current() (unlikely(kunit_get_current_test()) ? rv_get_mock_current() : current) + +#else /* !CONFIG_RV_MONITORS_KUNIT_TEST */ + +#define rv_get_current() current + #endif /* CONFIG_RV_MONITORS_KUNIT_TEST */ #endif /* _RV_KUNIT_H */ diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h index 56e83edcf0c4..d7dc01db4dd9 100644 --- a/include/rv/ltl_monitor.h +++ b/include/rv/ltl_monitor.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/kernel/trace/rv/Kconfig b/kernel/trace/rv/Kconfig index 5608314de06b..efa930f94ea4 100644 --- a/kernel/trace/rv/Kconfig +++ b/kernel/trace/rv/Kconfig @@ -122,4 +122,7 @@ config RV_MONITORS_KUNIT_TEST These tests verify that monitors correctly detect violations by triggering fake events and validating the expected reactions. + Enabling this may slightly increase overhead of some monitors if any + unrelated KUnit test is running. + If unsure, say N. diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c index 5e1a2a606783..e52500fd2de0 100644 --- a/kernel/trace/rv/monitors/pagefault/pagefault.c +++ b/kernel/trace/rv/monitors/pagefault/pagefault.c @@ -38,7 +38,7 @@ static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bo static void handle_page_fault(void *data, unsigned long address, struct pt_regs *regs, unsigned long error_code) { - ltl_atom_pulse(current, LTL_PAGEFAULT, true); + ltl_atom_pulse(rv_get_current(), LTL_PAGEFAULT, true); } static int enable_pagefault(void) diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index 4fd5e20151a8..a1b569a4b183 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -65,7 +65,7 @@ static void handle_sched_set_state(void *data, struct task_struct *task, int sta static void handle_sched_exit(void *data, bool is_switch) { - ltl_atom_pulse(current, LTL_SCHEDULE_IN, true); + ltl_atom_pulse(rv_get_current(), LTL_SCHEDULE_IN, true); } static void handle_sched_waking(void *data, struct task_struct *task) @@ -73,7 +73,7 @@ static void handle_sched_waking(void *data, struct task_struct *task) if (in_hardirq()) { ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true); } else if (in_task()) { - if (current->prio <= task->prio) + if (rv_get_current()->prio <= task->prio) ltl_atom_pulse(task, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, true); } else if (in_nmi()) { ltl_atom_pulse(task, LTL_WOKEN_BY_NMI, true); @@ -83,12 +83,12 @@ static void handle_sched_waking(void *data, struct task_struct *task) static void handle_contention_begin(void *data, void *lock, unsigned int flags) { if (flags & LCB_F_RT) - ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true); + ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, true); } static void handle_contention_end(void *data, void *lock, int ret) { - ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false); + ltl_atom_update(rv_get_current(), LTL_BLOCK_ON_RT_MUTEX, false); } static void handle_sys_enter(void *data, struct pt_regs *regs, long id) @@ -97,7 +97,7 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id) unsigned long args[6]; int op, cmd; - mon = ltl_get_monitor(current); + mon = ltl_get_monitor(rv_get_current()); switch (id) { #ifdef __NR_clock_nanosleep @@ -106,10 +106,10 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id) #ifdef __NR_clock_nanosleep_time64 case __NR_clock_nanosleep_time64: #endif - syscall_get_arguments(current, regs, args); + syscall_get_arguments(rv_get_current(), regs, args); ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, args[0] == CLOCK_REALTIME); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME); - ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true); + ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, true); break; #ifdef __NR_futex @@ -118,25 +118,25 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id) #ifdef __NR_futex_time64 case __NR_futex_time64: #endif - syscall_get_arguments(current, regs, args); + syscall_get_arguments(rv_get_current(), regs, args); op = args[1]; cmd = op & FUTEX_CMD_MASK; switch (cmd) { case FUTEX_LOCK_PI: case FUTEX_LOCK_PI2: - ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true); + ltl_atom_update(rv_get_current(), LTL_FUTEX_LOCK_PI, true); break; case FUTEX_WAIT: case FUTEX_WAIT_BITSET: case FUTEX_WAIT_REQUEUE_PI: - ltl_atom_update(current, LTL_FUTEX_WAIT, true); + ltl_atom_update(rv_get_current(), LTL_FUTEX_WAIT, true); break; } break; #ifdef __NR_epoll_wait case __NR_epoll_wait: - ltl_atom_update(current, LTL_EPOLL_WAIT, true); + ltl_atom_update(rv_get_current(), LTL_EPOLL_WAIT, true); break; #endif } @@ -144,14 +144,14 @@ static void handle_sys_enter(void *data, struct pt_regs *regs, long id) static void handle_sys_exit(void *data, struct pt_regs *regs, long ret) { - struct ltl_monitor *mon = ltl_get_monitor(current); + struct ltl_monitor *mon = ltl_get_monitor(rv_get_current()); ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false); ltl_atom_set(mon, LTL_FUTEX_WAIT, false); ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_REALTIME, false); ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false); ltl_atom_set(mon, LTL_EPOLL_WAIT, false); - ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false); + ltl_atom_update(rv_get_current(), LTL_CLOCK_NANOSLEEP, false); } static int enable_sleep(void) diff --git a/kernel/trace/rv/rv.c b/kernel/trace/rv/rv.c index 4f577d9b5ba8..29f155c6968b 100644 --- a/kernel/trace/rv/rv.c +++ b/kernel/trace/rv/rv.c @@ -885,4 +885,30 @@ void rv_clear_testing(struct kunit_suite *suite) mutex_unlock(&rv_interface_lock); } EXPORT_SYMBOL_IF_KUNIT(rv_clear_testing); + +/* + * rv_get_mock_current() is called only if we are running from a KUnit test. + * This can occur from a legitimate RV test or any unrelated test running when + * a real RV monitor is active and triggering events. + * We assume the former case is the only one where mock_current is not NULL and + * can occur only sequentially (KUnit doesn't run tests in parallel). + * We cannot rely on the test's context because there is no way to safely + * understand from which test we are running and KUnit utilities require + * locking, which is unsafe from NMI or scheduling context. + * Note that it is not possible for a real RV monitor to run when the RV KUnit + * tests are running (see rv_set_testing()). + */ +static struct task_struct *mock_current; + +void rv_mock_current(struct task_struct *tsk) +{ + mock_current = tsk; +} +EXPORT_SYMBOL_IF_KUNIT(rv_mock_current); + +struct task_struct *rv_get_mock_current(void) +{ + return mock_current ?: current; +} +EXPORT_SYMBOL_GPL(rv_get_mock_current); #endif diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c index be440bf4b4a7..e58bd677ae0b 100644 --- a/kernel/trace/rv/rv_monitors_test.c +++ b/kernel/trace/rv/rv_monitors_test.c @@ -57,6 +57,7 @@ void teardown_test(void *arg) else mon->rv_this->react = NULL; active_ctx = NULL; + rv_mock_current(NULL); if (mon->is_per_task) *mon->task_slot = RV_PER_TASK_MONITOR_INIT; From 51f3fe704a880c7061057d9b22ada8964bc4c9de Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:31 +0200 Subject: [PATCH 35/40] rv: Add KUnit tests for some LTL monitors Validate the functionality of LTL monitors by injecting events in a controlled environment (KUnit) and expecting reactions, just like it is done in DA monitors. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-15-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- include/rv/ltl_monitor.h | 8 +++ .../trace/rv/monitors/pagefault/pagefault.c | 12 ++++ .../rv/monitors/pagefault/pagefault_kunit.c | 34 +++++++++++ .../rv/monitors/pagefault/pagefault_kunit.h | 24 ++++++++ kernel/trace/rv/monitors/sleep/sleep.c | 18 ++++++ kernel/trace/rv/monitors/sleep/sleep_kunit.c | 59 +++++++++++++++++++ kernel/trace/rv/monitors/sleep/sleep_kunit.h | 29 +++++++++ kernel/trace/rv/rv_monitors_test.c | 4 ++ 8 files changed, 188 insertions(+) create mode 100644 kernel/trace/rv/monitors/pagefault/pagefault_kunit.c create mode 100644 kernel/trace/rv/monitors/pagefault/pagefault_kunit.h create mode 100644 kernel/trace/rv/monitors/sleep/sleep_kunit.c create mode 100644 kernel/trace/rv/monitors/sleep/sleep_kunit.h diff --git a/include/rv/ltl_monitor.h b/include/rv/ltl_monitor.h index d7dc01db4dd9..e9fd8265a3da 100644 --- a/include/rv/ltl_monitor.h +++ b/include/rv/ltl_monitor.h @@ -172,3 +172,11 @@ static void __maybe_unused ltl_atom_pulse(struct task_struct *task, enum ltl_ato ltl_atom_set(mon, atom, !value); ltl_validate(task, mon); } + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#define RV_MON_OPS_INIT() { \ + .rv_this = &rv_this, \ + .is_per_task = true, \ + .task_slot = <l_monitor_slot, \ +} +#endif /* CONFIG_RV_MONITORS_KUNIT_TEST */ diff --git a/kernel/trace/rv/monitors/pagefault/pagefault.c b/kernel/trace/rv/monitors/pagefault/pagefault.c index e52500fd2de0..c599fc19fc88 100644 --- a/kernel/trace/rv/monitors/pagefault/pagefault.c +++ b/kernel/trace/rv/monitors/pagefault/pagefault.c @@ -86,3 +86,15 @@ module_exit(unregister_pagefault); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Nam Cao "); MODULE_DESCRIPTION("pagefault: Monitor that RT tasks do not raise page faults"); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "pagefault_kunit.h" + +const struct rv_pagefault_ops rv_pagefault_ops = { + .mon = RV_MON_OPS_INIT(), + .handle_page_fault = handle_page_fault, + .handle_task_newtask = handle_task_newtask, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_pagefault_ops); +#endif diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c new file mode 100644 index 000000000000..06369960b008 --- /dev/null +++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include "pagefault_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_PAGEFAULT) + +static void rv_test_pagefault(struct kunit *test) +{ + struct task_struct *target = rv_kunit_alloc_mock_task(test); + struct rv_kunit_ctx *ctx = test->priv; + + prepare_test(test, &rv_pagefault_ops.mon); + + /* Initial pagefault when non-RT to start the model without failure */ + target->policy = SCHED_NORMAL; + target->prio = MAX_RT_PRIO + 20; + rv_pagefault_ops.handle_task_newtask(NULL, target, 0); + rv_mock_current(target); + rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0); + + /* RT task has a page fault */ + target->policy = SCHED_FIFO; + target->prio = MAX_RT_PRIO - 1; + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_pagefault_ops.handle_page_fault(NULL, 0, NULL, 0); +} + +#else +#define rv_test_pagefault rv_test_stub +#endif diff --git a/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h new file mode 100644 index 000000000000..2f9652f08b3f --- /dev/null +++ b/kernel/trace/rv/monitors/pagefault/pagefault_kunit.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __PAGEFAULT_KUNIT_H +#define __PAGEFAULT_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_pagefault_ops { + struct rv_kunit_mon mon; + void (*handle_page_fault)(void *data, unsigned long address, struct pt_regs *regs, + unsigned long error_code); + void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags); +} rv_pagefault_ops; +#endif + +#endif /* __PAGEFAULT_KUNIT_H */ diff --git a/kernel/trace/rv/monitors/sleep/sleep.c b/kernel/trace/rv/monitors/sleep/sleep.c index a1b569a4b183..b82537251e09 100644 --- a/kernel/trace/rv/monitors/sleep/sleep.c +++ b/kernel/trace/rv/monitors/sleep/sleep.c @@ -208,3 +208,21 @@ module_exit(unregister_sleep); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Nam Cao "); MODULE_DESCRIPTION("sleep: Monitor that RT tasks do not undesirably sleep"); + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) +#include +#include "sleep_kunit.h" + +const struct rv_sleep_ops rv_sleep_ops = { + .mon = RV_MON_OPS_INIT(), + .handle_sched_waking = handle_sched_waking, + .handle_sched_exit = handle_sched_exit, + .handle_sched_set_state = handle_sched_set_state, + .handle_contention_begin = handle_contention_begin, + .handle_contention_end = handle_contention_end, + .handle_sys_enter = handle_sys_enter, + .handle_sys_exit = handle_sys_exit, + .handle_task_newtask = handle_task_newtask, +}; +EXPORT_SYMBOL_IF_KUNIT(rv_sleep_ops); +#endif diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.c b/kernel/trace/rv/monitors/sleep/sleep_kunit.c new file mode 100644 index 000000000000..17df5baf1ec2 --- /dev/null +++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include "sleep_kunit.h" + +#if IS_REACHABLE(CONFIG_RV_MON_SLEEP) + +static void rv_test_sleep(struct kunit *test) +{ + struct task_struct *target = rv_kunit_alloc_mock_task(test); + struct task_struct *other = rv_kunit_alloc_mock_task(test); + struct rv_kunit_ctx *ctx = test->priv; + unsigned long args[6] = {0}; + struct pt_regs regs = {0}; + + prepare_test(test, &rv_sleep_ops.mon); + target->policy = SCHED_FIFO; + target->prio = MAX_RT_PRIO - 2; + other->policy = SCHED_FIFO; + other->prio = MAX_RT_PRIO - 1; + rv_sleep_ops.handle_task_newtask(NULL, target, 0); + + /* RT task sleeps on a non RT-friendly nanosleep */ + rv_mock_current(target); + args[0] = CLOCK_REALTIME; + syscall_set_arguments(target, ®s, args); +#ifdef __NR_clock_nanosleep + rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_clock_nanosleep); +#elif defined(__NR_clock_nanosleep_time64) + rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_clock_nanosleep_time64); +#endif + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE); + rv_sleep_ops.handle_sys_exit(NULL, NULL, 0); + + /* RT task woken up by lower priority task */ + args[1] = FUTEX_WAIT; + syscall_set_arguments(target, ®s, args); + rv_mock_current(target); +#ifdef __NR_futex + rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_futex); +#elif defined(__NR_futex_time64) + rv_sleep_ops.handle_sys_enter(NULL, ®s, __NR_futex_time64); +#endif + rv_sleep_ops.handle_sched_set_state(NULL, target, TASK_INTERRUPTIBLE); + rv_mock_current(other); + rv_sleep_ops.handle_sched_waking(NULL, target); + rv_mock_current(target); + RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) + rv_sleep_ops.handle_sched_exit(NULL, true); +} + +#else +#define rv_test_sleep rv_test_stub +#endif diff --git a/kernel/trace/rv/monitors/sleep/sleep_kunit.h b/kernel/trace/rv/monitors/sleep/sleep_kunit.h new file mode 100644 index 000000000000..3ebf8d2699f2 --- /dev/null +++ b/kernel/trace/rv/monitors/sleep/sleep_kunit.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Automatically generated by rvgen kunit. + * May need manual intervention for function prototypes that couldn't be + * found (e.g. are in another file) or variables to be exported. + */ + +#ifndef __SLEEP_KUNIT_H +#define __SLEEP_KUNIT_H + +#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST) + +#include +#include + +extern const struct rv_sleep_ops { + struct rv_kunit_mon mon; + void (*handle_sched_waking)(void *data, struct task_struct *task); + void (*handle_sched_exit)(void *data, bool is_switch); + void (*handle_sched_set_state)(void *data, struct task_struct *task, int state); + void (*handle_contention_begin)(void *data, void *lock, unsigned int flags); + void (*handle_contention_end)(void *data, void *lock, int ret); + void (*handle_sys_enter)(void *data, struct pt_regs *regs, long id); + void (*handle_sys_exit)(void *data, struct pt_regs *regs, long ret); + void (*handle_task_newtask)(void *data, struct task_struct *task, u64 flags); +} rv_sleep_ops; +#endif + +#endif /* __SLEEP_KUNIT_H */ diff --git a/kernel/trace/rv/rv_monitors_test.c b/kernel/trace/rv/rv_monitors_test.c index e58bd677ae0b..3ad11195e664 100644 --- a/kernel/trace/rv/rv_monitors_test.c +++ b/kernel/trace/rv/rv_monitors_test.c @@ -151,6 +151,8 @@ static void rv_test_dummy(struct kunit *test) #include "monitors/sts/sts_kunit.c" #include "monitors/opid/opid_kunit.c" #include "monitors/nomiss/nomiss_kunit.c" +#include "monitors/pagefault/pagefault_kunit.c" +#include "monitors/sleep/sleep_kunit.c" static struct kunit_case rv_mon_test_cases[] = { KUNIT_CASE(rv_test_dummy), @@ -159,6 +161,8 @@ static struct kunit_case rv_mon_test_cases[] = { KUNIT_CASE(rv_test_sts), KUNIT_CASE(rv_test_opid), KUNIT_CASE(rv_test_nomiss), + KUNIT_CASE(rv_test_pagefault), + KUNIT_CASE(rv_test_sleep), {} }; From 7c700dcd74640b9f0f5eaafdc6fdc4d5741666e2 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:32 +0200 Subject: [PATCH 36/40] selftests/verification: Fix wrong errexit assumption RV selftest rely on bash errexit (set -e) to terminate with error, when a step is expected to return false, the following syntax is used: ! cmd This however prevents the test from exiting when cmd is false (desired) but doesn't exit if cmd is true, since commands prefixed with ! are explicitly excluded from errexit. Use the syntax ! cmd || false Which ends up checking the exit value of ! cmd and supplies a false command for errexit to evaluate. Reviewed-by: Wen Yang Acked-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-16-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- .../verification/test.d/rv_monitor_enable_disable.tc | 10 +++++----- .../verification/test.d/rv_monitor_reactor.tc | 4 ++-- .../selftests/verification/test.d/rv_wwnr_printk.tc | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc b/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc index f29236defb5a..61e2c8b54d9a 100644 --- a/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc +++ b/tools/testing/selftests/verification/test.d/rv_monitor_enable_disable.tc @@ -10,7 +10,7 @@ test_simple_monitor() { grep -q "$monitor$" enabled_monitors echo 0 > "monitors/$prefix$monitor/enable" - ! grep -q "$monitor$" enabled_monitors + ! grep -q "$monitor$" enabled_monitors || false echo "$monitor" >> enabled_monitors grep -q 1 "monitors/$prefix$monitor/enable" @@ -34,12 +34,12 @@ test_container_monitor() { test -n "$nested" echo 0 > "monitors/$monitor/enable" - ! grep -q "^$monitor$" enabled_monitors + ! grep -q "^$monitor$" enabled_monitors || false for nested_dir in "monitors/$monitor"/*; do [ -d "$nested_dir" ] || continue nested=$(basename "$nested_dir") - ! grep -q "^$monitor:$nested$" enabled_monitors + ! grep -q "^$monitor:$nested$" enabled_monitors || false done echo "$monitor" >> enabled_monitors @@ -71,5 +71,5 @@ for monitor_dir in monitors/*; do fi done -! echo non_existent_monitor > enabled_monitors -! grep -q "^non_existent_monitor$" enabled_monitors +! echo non_existent_monitor > enabled_monitors || false +! grep -q "^non_existent_monitor$" enabled_monitors || false diff --git a/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc b/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc index 2958bf849338..516a20971390 100644 --- a/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc +++ b/tools/testing/selftests/verification/test.d/rv_monitor_reactor.tc @@ -64,5 +64,5 @@ done monitor=$(ls /sys/kernel/tracing/rv/monitors -1 | head -n 1) test -f "monitors/$monitor/reactors" -! echo non_existent_reactor > "monitors/$monitor/reactors" -! grep -q "\\[non_existent_reactor\\]" "monitors/$monitor/reactors" +! echo non_existent_reactor > "monitors/$monitor/reactors" || false +! grep -q "\\[non_existent_reactor\\]" "monitors/$monitor/reactors" || false diff --git a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc index 5a59432b1d93..96de95edb530 100644 --- a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc +++ b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc @@ -17,13 +17,13 @@ echo printk > monitors/wwnr/reactors load echo 0 > monitoring_on -! load +! load || false echo 1 > monitoring_on load echo 0 > reacting_on -! load +! load || false echo 1 > reacting_on echo nop > monitors/wwnr/reactors From 572f3d94fd4fb7ffb0f6918fc7f98548817f476d Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:33 +0200 Subject: [PATCH 37/40] selftests/verification: Rearrange the wwnr_printk test The wwnr_printk test expects no reactions in some situations, after fixing the bash assertion, the test is failing because expecting no reaction after a previous step had reactions is flaky without making sure all buffers are flushed. Wait for reactions to be over when expected by polling dmesg for an interval without any rv message. Also simplify the load function to stop loads as soon as a reaction occurs, this limits the number of lines to flush and makes tests overall faster and more stable. Reviewed-by: Wen Yang Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-17-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- .../verification/test.d/rv_wwnr_printk.tc | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc index 96de95edb530..17e1edfb3902 100644 --- a/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc +++ b/tools/testing/selftests/verification/test.d/rv_wwnr_printk.tc @@ -4,11 +4,29 @@ # requires: available_reactors wwnr:monitor printk:reactor stress-ng:program load() { # returns true if there was a reaction - local lines_before num + local lines_before num load_pid ret num=$((($(nproc) + 1) / 2)) lines_before=$(dmesg | wc -l) - stress-ng --cpu-sched "$num" --timer "$num" -t 5 -q - dmesg | tail -n $((lines_before + 1)) | grep -q "rv: monitor wwnr does not allow event" + stress-ng --cpu-sched "$num" --timer "$num" -t 5 -q & + load_pid=$! + timeout 5 dmesg -w | tail -n +$((lines_before + 1)) | \ + grep -m 1 -q "rv: monitor wwnr does not allow event" + ret=$? + kill "$load_pid" || true + wait "$load_pid" || true + return $ret +} + +# loads may flood the ringbuffer, wait for all pending printks (timeout at 2 minutes) +wait_dmesg_flush() { + local last_before last_after + for _ in $(seq 400); do + last_before=$last_after + last_after=$(dmesg | grep "rv:" | tail -n 1 || true) + [ "$last_before" = "$last_after" ] && return 0 + sleep .3 + done + return 1 } echo 1 > monitors/wwnr/enable @@ -17,12 +35,16 @@ echo printk > monitors/wwnr/reactors load echo 0 > monitoring_on +wait_dmesg_flush + ! load || false echo 1 > monitoring_on load echo 0 > reacting_on +wait_dmesg_flush + ! load || false echo 1 > reacting_on From 984b5a36fd12d1849511a45fda32036fe2b0d004 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 23 Jul 2026 09:45:34 +0200 Subject: [PATCH 38/40] selftests/verification: Add selftests for deadline and stall monitors Add selftests to verify deadline monitors don't fail under expected conditions and the stall monitor report violations only when expected. Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260723074534.43521-18-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- .../verification/test.d/rv_deadline.tc | 23 +++++++++++++ .../selftests/verification/test.d/rv_stall.tc | 33 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tools/testing/selftests/verification/test.d/rv_deadline.tc create mode 100644 tools/testing/selftests/verification/test.d/rv_stall.tc diff --git a/tools/testing/selftests/verification/test.d/rv_deadline.tc b/tools/testing/selftests/verification/test.d/rv_deadline.tc new file mode 100644 index 000000000000..fc95267dbb82 --- /dev/null +++ b/tools/testing/selftests/verification/test.d/rv_deadline.tc @@ -0,0 +1,23 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# description: Test deadline monitors trigger no reaction +# requires: available_reactors deadline:monitor printk:reactor stress-ng:program + +load() { # returns true if there was a reaction + local lines_before + lines_before=$(dmesg | wc -l) + stress-ng --cpu 2 --sched deadline --sched-period 100000000 \ + --sched-deadline 100000000 --sched-runtime 20000000 -t 5 & + stress-ng --cpu 2 --sched rr --sched-prio 50 --cyclic 1 \ + --cyclic-policy rr --cyclic-prio 50 -t 5 & + wait + dmesg | tail -n +$((lines_before + 1)) | grep -q "rv: monitor [a-z]\+ does not allow event" +} + +echo 1 > monitors/deadline/enable +echo printk > monitors/deadline/reactors + +! load || false + +echo nop > monitors/deadline/reactors +echo 0 > monitors/deadline/enable diff --git a/tools/testing/selftests/verification/test.d/rv_stall.tc b/tools/testing/selftests/verification/test.d/rv_stall.tc new file mode 100644 index 000000000000..515a10263ca1 --- /dev/null +++ b/tools/testing/selftests/verification/test.d/rv_stall.tc @@ -0,0 +1,33 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# description: Test stall monitor +# requires: available_reactors stall:monitor printk:reactor stress-ng:program + +THRESHOLD=/sys/module/stall/parameters/threshold_jiffies +ORIG_THRESHOLD=$(cat $THRESHOLD) +trap 'echo $ORIG_THRESHOLD > $THRESHOLD' EXIT + +load() { # returns true if there was a reaction + local lines_before cpu + cpu=$(($(nproc) - 1)) + lines_before=$(dmesg | wc -l) + stress-ng --cpu 1 --taskset "$cpu" --sched rr --sched-prio 1 -t 3 & + stress-ng --cpu 5 --taskset "$cpu" -t 3 & + wait + dmesg | tail -n +$((lines_before + 1)) | grep -q "rv: monitor stall does not allow event" +} + +echo 5000 > $THRESHOLD +echo 1 > monitors/stall/enable +echo printk > monitors/stall/reactors + +! load || false + +echo 0 > monitors/stall/enable +echo 70 > $THRESHOLD +echo 1 > monitors/stall/enable + +load + +echo nop > monitors/stall/reactors +echo 0 > monitors/stall/enable From 785095112f4198de49760552374f364043c8dbdf Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Mon, 3 Aug 2026 17:06:22 +0200 Subject: [PATCH 39/40] rv: Fix 32-bit build of nomiss KUnit test Commit 8da2a8838365 ("rv: Add KUnit tests for some DA/HA monitors") introduced a division of a 64-bit value by 1000 in the nomiss KUnit test. This does not compile on 32-bit systems, as standard division of 64-bit values leads to an undefined reference to __udivdi3. Fix the build on 32-bit systems by using div_u64(). Fixes: 8da2a8838365 ("rv: Add KUnit tests for some DA/HA monitors") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608020311.hYjqOG5k-lkp@intel.com Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260803150622.322806-1-gmonaco@redhat.com Signed-off-by: Gabriele Monaco --- kernel/trace/rv/monitors/nomiss/nomiss_kunit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c index 1f64249dfcce..763129e2a990 100644 --- a/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c +++ b/kernel/trace/rv/monitors/nomiss/nomiss_kunit.c @@ -28,7 +28,7 @@ static void rv_test_nomiss(struct kunit *test) udelay(10); rv_nomiss_ops.handle_sched_switch(NULL, 0, target, other, TASK_RUNNING); RV_KUNIT_EXPECT_REACTION_HERE(test, ctx) { - udelay(15 + *rv_nomiss_ops.deadline_thresh / 1000); + udelay(15 + div_u64(*rv_nomiss_ops.deadline_thresh, 1000)); rv_nomiss_ops.handle_sched_switch(NULL, 0, other, target, TASK_RUNNING); } } From 7d81675d1bb2cc6db61a2d93b1e0dc7fb0929f9c Mon Sep 17 00:00:00 2001 From: Chao Liu Date: Wed, 29 Jul 2026 16:11:02 +0800 Subject: [PATCH 40/40] Documentation/rv: Explain epoll and aborted sleeps epoll_wait() is a valid sleeping reason for real-time tasks because it uses PI-aware locking, but the rtapp sleep monitor documentation only discusses clock_nanosleep() and futexes. Document it. ABORT_SLEEP represents a task restoring TASK_RUNNING before entering the scheduler. Since the task does not actually block, it becomes runnable again without a wakeup sequence unsafe for real-time. Document this behavior. Signed-off-by: Chao Liu Reviewed-by: Gabriele Monaco Reviewed-by: Nam Cao Link: https://lore.kernel.org/r/20260729081102.73138-1-chao.liu@processmission.com Signed-off-by: Gabriele Monaco --- Documentation/trace/rv/monitor_rtapp.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/trace/rv/monitor_rtapp.rst b/Documentation/trace/rv/monitor_rtapp.rst index 238b59395ff5..b95994ade14a 100644 --- a/Documentation/trace/rv/monitor_rtapp.rst +++ b/Documentation/trace/rv/monitor_rtapp.rst @@ -67,6 +67,8 @@ thread to sleep for one of the following reasons: variables as safe for real-time. As an alternative, the librtpi library exists to provide a conditional variable implementation that is correct for real-time applications in Linux. + - Real-time thread waiting for events using `epoll_wait`, which is a + real-time-safe syscall for sleeping as it uses PI-aware locking. Beside the reason for sleeping, the eventual waker should also be real-time-safe. Namely, one of: @@ -114,6 +116,10 @@ The monitor's specification is:: ALLOWLIST = BLOCK_ON_RT_MUTEX or FUTEX_LOCK_PI +`ABORT_SLEEP` represents a task restoring its state to `TASK_RUNNING` before +entering the scheduler. In this case, the task does not actually block, so the +task is back to runnable without any wakeup sequence unsafe for real-time. + Beside the scenarios described above, this specification also defines an allow list to handle some special cases: