[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <817f3cb62b2632286e5c6fffde3163592a110eee.camel@redhat.com>
Date: Fri, 09 May 2025 16:50:29 +0200
From: Gabriele Monaco <gmonaco@...hat.com>
To: Nam Cao <namcao@...utronix.de>
Cc: john.ogness@...utronix.de, Steven Rostedt <rostedt@...dmis.org>,
linux-trace-kernel@...r.kernel.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH v7 13/22] rv: Add support for LTL monitors
On Fri, 2025-05-09 at 10:12 +0200, Nam Cao wrote:
> While attempting to implement DA monitors for some complex
> specifications,
> deterministic automaton is found to be inappropriate as the
> specification
> language. The automaton is complicated, hard to understand, and
> error-prone.
>
> For these cases, linear temporal logic is more suitable as the
> specification language.
>
> Add support for linear temporal logic runtime verification monitor.
>
> For all the details, see the documentations added by this commit.
>
> Signed-off-by: Nam Cao <namcao@...utronix.de>
> ---
I tried generating some less than trivial monitors and I'm getting a few
errors, please find below some questions/comments.
There are also some typos in the references.
> diff --git a/Documentation/trace/rv/linear_temporal_logic.rst b/Documentation/trace/rv/linear_temporal_logic.rst
> new file mode 100644
> index 000000000000..6bf10d8e3761
> --- /dev/null
> +++ b/Documentation/trace/rv/linear_temporal_logic.rst
> @@ -0,0 +1,122 @@
> +Linear temporal logic
> +=====================
> +
> +Introduction
> +------------
> +
> +Runtime verification monitor is a verification technique which checks that the kernel follows a
> +specification. It does so by using tracepoints to monitor the kernel's execution trace, and
> +verifying that the execution trace sastifies the specification.
> +
> +Initially, the specification can only be written in the form of deterministic automaton (DA).
> +However, while attempting to implement DA monitors for some complex specifications, deterministic
> +automaton is found to be inappropriate as the specification language. The automaton is complicated,
> +hard to understand, and error-prone.
> +
> +Thus, RV monitors based on linear temporal logic (LTL) are introduced. This type of monitor uses LTL
> +as specification instead of DA. For some cases, writing the specification as LTL is more concise and
> +intuitive.
> +
> +Many materials explain LTL in details. One book is::
> +
> + Christel Baier **aund** Joost-Pieter Katoen: Principles of Model Checking, The MIT Press, 2008.
Typo here and also in the references at the end of the page (omitted for brevity).
+ Christel Baier and Joost-Pieter Katoen: Principles of Model Checking, The MIT Press, 2008.
> [...]
> diff --git a/tools/verification/rvgen/rvgen/ltl2ba.py
> b/tools/verification/rvgen/rvgen/ltl2ba.py
> new file mode 100644
> index 000000000000..aa5c3339aab8
> --- /dev/null
> +++ b/tools/verification/rvgen/rvgen/ltl2ba.py
> @@ -0,0 +1,558 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Implementation based on
> +# Gerth, R., Peled, D., Vardi, M.Y., Wolper, P. (1996).
> +# Simple On-the-fly Automatic Verification of Linear Temporal Logic.
> +# https://doi.org/10.1007/978-0-387-34892-6_1
> +# With extra optimizations
> +
> +from ply.lex import lex
> +from ply.yacc import yacc
> +
> +# Grammar:
> +# ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl
> +#
> +# Operands (opd):
> +# true, false, user-defined names
> +#
> +# Unary Operators (unop):
> +# always
> +# eventually
> +# not
> +#
> +# Binary Operators (binop):
> +# until
> +# and
> +# or
> +# imply
> +# equivalent
> +
> +tokens = (
> + 'AND',
> + 'OR',
> + 'IMPLY',
> + 'UNTIL',
> + 'ALWAYS',
> + 'EVENTUALLY',
> + 'VARIABLE',
> + 'LITERAL',
> + 'NOT',
> + 'LPAREN',
> + 'RPAREN',
> + 'ASSIGN',
> +)
> +
> +t_AND = r'and'
> +t_OR = r'or'
> +t_IMPLY = r'imply'
> +t_UNTIL = r'until'
> +t_ALWAYS = r'always'
> +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'
> +
> +def t_error(t):
> + raise ValueError("Illegal character '%s'" % t.value[0])
> +
> +lexer = lex()
> +
> +class GraphNode:
> + uid = 0
> +
> + def __init__(self, incoming: set['GraphNode'], new, old, _next):
> + self.init = False
> + self.outgoing = set()
> + self.labels = set()
> + self.incoming = incoming.copy()
> + self.new = new.copy()
> + self.old = old.copy()
> + self.next = _next.copy()
> + self.id = GraphNode.uid
> + GraphNode.uid += 1
> +
> + def expand(self, node_set):
> + if not self.new:
> + for nd in node_set:
> + if nd.old == self.old and nd.next == self.next:
> + nd.incoming |= self.incoming
> + return node_set
> +
> + new_current_node = GraphNode({self}, self.next, set(),
> set())
> + return new_current_node.expand({self} | node_set)
> + n = self.new.pop()
> + return n.expand(self, node_set)
> +
> + def __lt__(self, other):
> + return self.id < other.id
> +
> +class ASTNode:
> + uid = 1
> +
> + def __init__(self, op):
> + self.op = op
> + self.id = ASTNode.uid
> + ASTNode.uid += 1
> +
> + def __hash__(self):
> + return hash(self.op)
> +
> + def __eq__(self, other):
> + return self is other
> +
> + def __iter__(self):
> + yield self
> + yield from self.op
> +
> + def negate(self):
> + self.op = self.op.negate()
> + return self
> +
> + def expand(self, node, node_set):
> + return self.op.expand(self, node, node_set)
> +
> + def __str__(self):
> + if isinstance(self.op, Literal):
> + return str(self.op.value)
> + elif isinstance(self.op, Variable):
> + return self.op.name.lower()
> + return "val" + str(self.id)
> +
> + def normalize(self):
> + # Get rid of:
> + # - ALWAYS
> + # - EVENTUALLY
> + # - IMPLY
> + # And move all the NOT to be inside
> + self.op = self.op.normalize()
> + return self
> +
> +class BinaryOp:
> + op_str = "not_supported"
> +
> + def __init__(self, left: ASTNode, right: ASTNode):
> + self.left = left
> + self.right = right
> +
> + def __hash__(self):
> + return hash((self.left, self.right))
> +
> + def __iter__(self):
> + yield from self.left
> + yield from self.right
> +
> + def normalize(self):
> + raise NotImplementedError
> +
> + def negate(self):
> + raise NotImplementedError
> +
> + def _is_temporal(self):
> + raise NotImplementedError
> +
> + def is_temporal(self):
> + if self.left.op.is_temporal():
> + return True
> + if self.right.op.is_temporal():
> + return True
> + return self._is_temporal()
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + raise NotImplementedError
> +
> +class AndOp(BinaryOp):
> + op_str = '&&'
> +
> + def __init__(self, left, right):
> + super().__init__(left, right)
> +
> + def normalize(self):
> + return self
> +
> + def negate(self):
> + return OrOp(self.left.negate(), self.right.negate())
> +
> + def _is_temporal(self):
> + return False
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + if not n.op.is_temporal():
> + node.old.add(n)
> + return node.expand(node_set)
> +
> + tmp = GraphNode(node.incoming,
> + node.new | ({n.op.left, n.op.right} -
> node.old),
> + node.old | {n},
> + node.next)
> + return tmp.expand(node_set)
> +
> +class OrOp(BinaryOp):
> + op_str = '||'
> +
> + def __init__(self, left, right):
> + super().__init__(left, right)
> +
> + def normalize(self):
> + return self
> +
> + def negate(self):
> + return AndOp(self.left.negate(), self.right.negate())
> +
> + def _is_temporal(self):
> + return False
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + if not n.op.is_temporal():
> + node.old |= {n}
> + return node.expand(node_set)
> +
> + node1 = GraphNode(node.incoming,
> + node.new | ({n.op.left} - node.old),
> + node.old | {n},
> + node.next)
> + node2 = GraphNode(node.incoming,
> + node.new | ({n.op.right} - node.old),
> + node.old | {n},
> + node.next)
> + return node2.expand(node1.expand(node_set))
> +
> +class UntilOp(BinaryOp):
> + def __init__(self, left, right):
> + super().__init__(left, right)
> +
> + def normalize(self):
> + return self
> +
> + def negate(self):
> + return VOp(self.left.negate(), self.right.negate())
> +
> + def _is_temporal(self):
> + return True
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + node1 = GraphNode(node.incoming,
> + node.new | ({n.op.left} - node.old),
> + node.old | {n},
> + node.next | {n})
> + node2 = GraphNode(node.incoming,
> + node.new | ({n.op.right} - node.old),
> + node.old | {n},
> + node.next)
> + return node2.expand(node1.expand(node_set))
> +
> +class VOp(BinaryOp):
> + def __init__(self, left, right):
> + super().__init__(left, right)
> +
> + def normalize(self):
> + return self
> +
> + def negate(self):
> + return UntilOp(self.left.negate(), self.right.negate())
> +
> + def _is_temporal(self):
> + return True
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + node1 = GraphNode(node.incoming,
> + node.new | ({n.op.right} - node.old),
> + node.old | {n},
> + node.next | {n})
> + node2 = GraphNode(node.incoming,
> + node.new | ({n.op.left, n.op.right} -
> node.old),
> + node.old | {n},
> + node.next)
> + return node2.expand(node1.expand(node_set))
> +
> +class ImplyOp(BinaryOp):
> + def __init__(self, left, right):
> + super().__init__(left, right)
> +
> + def normalize(self):
> + # P -> Q === !P | Q
> + return OrOp(self.left.negate(), self.right)
> +
> + def _is_temporal(self):
> + return False
> +
> + def negate(self):
> + # !(P -> Q) === !(!P | Q) === P & !Q
> + return AndOp(self.left, self.right.negate())
> +
> +class UnaryOp:
> + def __init__(self, child: ASTNode):
> + self.child = child
> +
> + def __iter__(self):
> + yield from self.child
> +
> + def __hash__(self):
> + return hash(self.child)
> +
> + def normalize(self):
> + raise NotImplementedError
> +
> + def _is_temporal(self):
> + raise NotImplementedError
> +
> + def is_temporal(self):
> + if self.child.op.is_temporal():
> + return True
> + return self._is_temporal()
> +
> + def negate(self):
> + raise NotImplementedError
> +
> +class EventuallyOp(UnaryOp):
> + def __init__(self, child):
> + super().__init__(child)
> +
> + def __str__(self):
> + return "eventually " + str(self.child)
> +
> + def normalize(self):
> + # <>F == true U F
> + return UntilOp(Literal(True), self.right)
> +
> + def _is_temporal(self):
> + return True
> +
> + def negate(self):
> + # !<>F == [](!F)
> + return AlwaysOp(self.right.negate()).normalize()
Shouldn't this be:
+ return AlwaysOp(self.child.negate()).normalize()
> +
> +class AlwaysOp(UnaryOp):
> + def __init__(self, child):
> + super().__init__(child)
> +
> + def normalize(self):
> + # []F === !(true U !F) == false V F
> + new = ASTNode(Literal(False))
> + return VOp(new, self.child)
> +
> + def _is_temporal(self):
> + return True
> +
> + def negate(self):
> + # ![]F == <>(!F)
> + return EventuallyOp(self.left,
> self.right.negate()).normalize()
Shouldn't this be:
+ return EventuallyOp(self.child.negate()).normalize()
> +
> +class NotOp(UnaryOp):
> + def __init__(self, child):
> + super().__init__(child)
> +
> + def __str__(self):
> + return "!" + str(self.child)
> +
> + def normalize(self):
> + return self.child.op.negate()
> +
> + def negate(self):
> + return self.child.op
> +
> + def _is_temporal(self):
> + return False
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + for f in node.old:
> + if n.op.child is f:
> + return node_set
> + node.old |= {n}
> + return node.expand(node_set)
> +
> +class Variable:
> + def __init__(self, name: str):
> + self.name = name
> +
> + def __hash__(self):
> + return hash(self.name)
> +
> + def __iter__(self):
> + yield from ()
> +
> + def negate(self):
> + new = ASTNode(self)
> + return NotOp(new)
> +
> + def normalize(self):
> + return self
> +
> + def is_temporal(self):
> + return False
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + for f in node.old:
> + if isinstance(f, NotOp) and f.op.child is n:
> + return node_set
> + node.old |= {n}
> + return node.expand(node_set)
> +
> +class Literal:
> + def __init__(self, value: bool):
> + self.value = value
> +
> + def __iter__(self):
> + yield from ()
> +
> + def __hash__(self):
> + return hash(self.value)
> +
> + def __str__(self):
> + if self.value:
> + return "true"
> + return "false"
> +
> + def negate(self):
> + self.value = not self.value
> + return self
> +
> + def normalize(self):
> + return self
> +
> + def is_temporal(self):
> + return False
> +
> + @staticmethod
> + def expand(n: ASTNode, node: GraphNode, node_set) ->
> set[GraphNode]:
> + if not n.op.value:
> + return node_set
> + 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]]
> +
> +def p_assign(p):
> + '''
> + assign : VARIABLE ASSIGN ltl
> + '''
> + p[0] = (p[1], p[3])
> +
> +def p_ltl(p):
> + '''
> + ltl : opd
> + | binop
> + | unop
> + '''
> + p[0] = p[1]
> +
> +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 p_unop(p):
> + '''
> + unop : ALWAYS ltl
> + | EVENTUALLY ltl
> + | NOT ltl
> + '''
> + if p[1] == "always":
> + op = AlwaysOp(p[2])
> + if p[1] == "eventually":
> + op = EventuallyOp(p[2])
> + if p[1] == "not":
> + op = NotOp(p[2])
Pylint is complaining about this one, wouldn't it be better changing it to an
if/elif chain and addding:
else:
raise ValueError("Invalid unary operator: %s" % p[1])
> +
> + p[0] = ASTNode(op)
> +
> +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 ValueError("Invalid binary operator: %s" % p[2])
> +
> + p[0] = ASTNode(op)
> +
> +parser = yacc()
> +
> +def parse_ltl(s: str) -> ASTNode:
> + spec = parser.parse(s)
> +
> + subexpr = dict()
> +
> + for assign in spec:
> + if assign[0] == "RULE":
> + rule = assign[1]
> + else:
> + subexpr[assign[0]] = assign[1]
rule may be undefined here (but I guess python will just say variable rule is
undefined and it would be quite clear the ltl didn't start with a rule.
I'm still getting a few more errors but my model may be to blame, still need to
play a bit.
Cheers,
Gabriele
> +
> + for node in rule:
> + if not isinstance(node.op, Variable):
> + continue
> + replace = subexpr.get(node.op.name)
> + if replace is not None:
> + node.op = replace.op
> +
> + return rule
> +
> +def create_graph(s: str):
> + atoms = set()
> +
> + ltl = parse_ltl(s)
> + for c in ltl:
> + c.normalize()
> + if isinstance(c.op, Variable):
> + atoms.add(c.op.name)
> +
> + init = GraphNode(set(), set(), set(), set())
> + head = GraphNode({init}, {ltl}, set(), set())
> + graph = sorted(head.expand(set()))
> +
> + for i, node in enumerate(graph):
> + # The id assignment during graph generation has gaps.
> Reassign them
> + node.id = i
> +
> + for incoming in node.incoming:
> + if incoming is init:
> + node.init = True
> + else:
> + incoming.outgoing.add(node)
> + for o in node.old:
> + if not o.op.is_temporal():
> + node.labels.add(str(o))
> +
> + return sorted(atoms), graph, ltl
> diff --git a/tools/verification/rvgen/rvgen/ltl2k.py
> b/tools/verification/rvgen/rvgen/ltl2k.py
> new file mode 100644
> index 000000000000..b8da9094fb4f
> --- /dev/null
> +++ b/tools/verification/rvgen/rvgen/ltl2k.py
> @@ -0,0 +1,245 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +from pathlib import Path
> +from . import generator
> +from . import ltl2ba
> +
> +COLUMN_LIMIT = 100
> +
> +def line_len(line: str) -> int:
> + tabs = line.count('\t')
> + return tabs * 7 + len(line)
> +
> +def break_long_line(line: str, indent='') -> list[str]:
> + result = []
> + while line_len(line) > COLUMN_LIMIT:
> + i = line[:COLUMN_LIMIT - line_len(line)].rfind(' ')
> + result.append(line[:i])
> + line = indent + line[i + 1:]
> + if line:
> + result.append(line)
> + return result
> +
> +def build_condition_string(node: ltl2ba.GraphNode):
> + if not node.labels:
> + return "(true)"
> +
> + result = "("
> +
> + first = True
> + for label in sorted(node.labels):
> + if not first:
> + result += " && "
> + result += label
> + first = False
> +
> + result += ")"
> +
> + return result
> +
> +def abbreviate_atoms(atoms: list[str]) -> list[str]:
> + def shorten(s: str) -> str:
> + skip = ["is", "by", "or", "and"]
> + return '_'.join([x[:2] for x in s.lower().split('_') if x
> not in skip])
> +
> + abbrs = []
> + for atom in atoms:
> + for i in range(len(atom), -1, -1):
> + if sum(a.startswith(atom[:i]) for a in atoms) > 1:
> + break
> + share = atom[:i]
> + unique = atom[i:]
> + abbrs.append((shorten(share) + shorten(unique)))
> + return abbrs
> +
> +class ltl2k(generator.Monitor):
> + template_dir = "ltl2k"
> +
> + def __init__(self, file_path, MonitorType, extra_params={}):
> + if MonitorType != "per_task":
> + raise NotImplementedError("Only per_task monitor is
> supported for LTL")
> + super().__init__(extra_params)
> + with open(file_path) as f:
> + self.atoms, self.ba, self.ltl =
> ltl2ba.create_graph(f.read())
> + self.atoms_abbr = abbreviate_atoms(self.atoms)
> + self.name = extra_params.get("model_name")
> + if not self.name:
> + self.name = Path(file_path).stem
> +
> + def _fill_states(self) -> str:
> + buf = [
> + "enum ltl_buchi_state {",
> + ]
> +
> + for node in self.ba:
> + buf.append("\tS%i," % node.id)
> + buf.append("\tRV_NUM_BA_STATES")
> + buf.append("};")
> + buf.append("static_assert(RV_NUM_BA_STATES <=
> RV_MAX_BA_STATES);")
> + return buf
> +
> + def _fill_atoms(self):
> + buf = ["enum ltl_atom {"]
> + for a in sorted(self.atoms):
> + buf.append("\tLTL_%s," % a)
> + buf.append("\tLTL_NUM_ATOM")
> + buf.append("};")
> + buf.append("static_assert(LTL_NUM_ATOM <=
> RV_MAX_LTL_ATOM);")
> + return buf
> +
> + def _fill_atoms_to_string(self):
> + buf = [
> + "static const char *ltl_atom_str(enum ltl_atom atom)",
> + "{",
> + "\tstatic const char *const names[] = {"
> + ]
> +
> + for name in self.atoms_abbr:
> + buf.append("\t\t\"%s\"," % name)
> +
> + buf.extend([
> + "\t};",
> + "",
> + "\treturn names[atom];",
> + "}"
> + ])
> + return buf
> +
> + def _fill_atom_values(self):
> + buf = []
> + for node in self.ltl:
> + if node.op.is_temporal():
> + continue
> +
> + if isinstance(node.op, ltl2ba.Variable):
> + buf.append("\tbool %s = test_bit(LTL_%s, mon-
> >atoms);" % (node, node.op.name))
> + elif isinstance(node.op, ltl2ba.AndOp):
> + buf.append("\tbool %s = %s && %s;" % (node,
> node.op.left, node.op.right))
> + elif isinstance(node.op, ltl2ba.OrOp):
> + buf.append("\tbool %s = %s || %s;" % (node,
> node.op.left, node.op.right))
> + elif isinstance(node.op, ltl2ba.NotOp):
> + buf.append("\tbool %s = !%s;" % (node,
> node.op.child))
> + buf.reverse()
> +
> + buf2 = []
> + for line in buf:
> + buf2.extend(break_long_line(line, "\t "))
> + return buf2
> +
> + def _fill_transitions(self):
> + buf = [
> + "static void",
> + "ltl_possible_next_states(struct ltl_monitor *mon,
> unsigned int state, unsigned long *next)",
> + "{"
> + ]
> + buf.extend(self._fill_atom_values())
> + buf.extend([
> + "",
> + "\tswitch (state) {"
> + ])
> +
> + for node in self.ba:
> + buf.append("\tcase S%i:" % node.id)
> +
> + for o in sorted(node.outgoing):
> + line = "\t\tif "
> + indent = "\t\t "
> +
> + line += build_condition_string(o)
> + lines = break_long_line(line, indent)
> + buf.extend(lines)
> +
> + buf.append("\t\t\t__set_bit(S%i, next);" % o.id)
> + buf.append("\t\tbreak;")
> + buf.extend([
> + "\t}",
> + "}"
> + ])
> +
> + return buf
> +
> + def _fill_start(self):
> + buf = [
> + "static void ltl_start(struct task_struct *task, struct
> ltl_monitor *mon)",
> + "{"
> + ]
> + buf.extend(self._fill_atom_values())
> + buf.append("")
> +
> + for node in self.ba:
> + if not node.init:
> + continue
> +
> + line = "\tif "
> + indent = "\t "
> +
> + line += build_condition_string(node)
> + lines = break_long_line(line, indent)
> + buf.extend(lines)
> +
> + buf.append("\t\t__set_bit(S%i, mon->states);" % node.id)
> + buf.append("}")
> + return buf
> +
> + def fill_tracepoint_handlers_skel(self):
> + buff = []
> + buff.append("static void handle_example_event(void *data, /*
> XXX: fill header */)")
> + buff.append("{")
> + buff.append("\tltl_atom_update(task, LTL_%s, true/false);" %
> self.atoms[0])
> + buff.append("}")
> + buff.append("")
> + return '\n'.join(buff)
> +
> + def fill_tracepoint_attach_probe(self):
> + return "\trv_attach_trace_probe(\"%s\", /* XXX: tracepoint
> */, handle_example_event);" \
> + % self.name
> +
> + def fill_tracepoint_detach_helper(self):
> + return "\trv_detach_trace_probe(\"%s\", /* XXX: tracepoint
> */, handle_sample_event);" \
> + % self.name
> +
> + def fill_atoms_init(self):
> + buff = []
> + for a in self.atoms:
> + buff.append("\tltl_atom_set(mon, LTL_%s, true/false);" %
> a)
> + return '\n'.join(buff)
> +
> + def fill_model_h(self):
> + buf = [
> + "/* SPDX-License-Identifier: GPL-2.0 */",
> + "",
> + "#include <linux/rv.h>",
> + "",
> + "#define MONITOR_NAME " + self.name,
> + ""
> + ]
> +
> + buf.extend(self._fill_atoms())
> + buf.append('')
> +
> + buf.extend(self._fill_atoms_to_string())
> + buf.append('')
> +
> + buf.extend(self._fill_states())
> + buf.append('')
> +
> + buf.extend(self._fill_start())
> + buf.append('')
> +
> + buf.extend(self._fill_transitions())
> + buf.append('')
> +
> + return '\n'.join(buf)
> +
> + def fill_monitor_class_type(self):
> + return "LTL_MON_EVENTS_ID"
> +
> + def fill_monitor_class(self):
> + return "ltl_monitor_id"
> +
> + def fill_main_c(self):
> + main_c = super().fill_main_c()
> + main_c = main_c.replace("%%ATOMS_INIT%%",
> self.fill_atoms_init())
> +
> + return main_c
> diff --git a/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
> b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
> new file mode 100644
> index 000000000000..2f3c4d642746
> --- /dev/null
> +++ b/tools/verification/rvgen/rvgen/templates/ltl2k/main.c
> @@ -0,0 +1,102 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <linux/ftrace.h>
> +#include <linux/tracepoint.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/rv.h>
> +#include <rv/instrumentation.h>
> +
> +#define MODULE_NAME "%%MODEL_NAME%%"
> +
> +/*
> + * XXX: include required tracepoint headers, e.g.,
> + * #include <trace/events/sched.h>
> + */
> +#include <rv_trace.h>
> +%%INCLUDE_PARENT%%
> +
> +/*
> + * This is the self-generated part of the monitor. Generally, there
> is no need
> + * to touch this section.
> + */
> +#include "%%MODEL_NAME%%.h"
> +#include <rv/ltl_monitor.h>
> +
> +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.
> + */
> +%%ATOMS_INIT%%
> +}
> +
> +/*
> + * 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.
> + */
> +%%TRACEPOINT_HANDLERS_SKEL%%
> +static int enable_%%MODEL_NAME%%(void)
> +{
> + int retval;
> +
> + retval = ltl_monitor_init();
> + if (retval)
> + return retval;
> +
> +%%TRACEPOINT_ATTACH%%
> +
> + return 0;
> +}
> +
> +static void disable_%%MODEL_NAME%%(void)
> +{
> +%%TRACEPOINT_DETACH%%
> +
> + ltl_monitor_destroy();
> +}
> +
> +/*
> + * This is the monitor register section.
> + */
> +static struct rv_monitor rv_%%MODEL_NAME%% = {
> + .name = "%%MODEL_NAME%%",
> + .description = "%%DESCRIPTION%%",
> + .enable = enable_%%MODEL_NAME%%,
> + .disable = disable_%%MODEL_NAME%%,
> +};
> +
> +static int __init register_%%MODEL_NAME%%(void)
> +{
> + return rv_register_monitor(&rv_%%MODEL_NAME%%, %%PARENT%%);
> +}
> +
> +static void __exit unregister_%%MODEL_NAME%%(void)
> +{
> + rv_unregister_monitor(&rv_%%MODEL_NAME%%);
> +}
> +
> +module_init(register_%%MODEL_NAME%%);
> +module_exit(unregister_%%MODEL_NAME%%);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR(/* TODO */);
> +MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
> diff --git a/tools/verification/rvgen/rvgen/templates/ltl2k/trace.h
> b/tools/verification/rvgen/rvgen/templates/ltl2k/trace.h
> new file mode 100644
> index 000000000000..49394c4b0f1c
> --- /dev/null
> +++ b/tools/verification/rvgen/rvgen/templates/ltl2k/trace.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * Snippet to be included in rv_trace.h
> + */
> +
> +#ifdef CONFIG_RV_MON_%%MODEL_NAME_UP%%
> +DEFINE_EVENT(event_%%MONITOR_CLASS%%, event_%%MODEL_NAME%%,
> + TP_PROTO(struct task_struct *task, char *states, char
> *atoms, char *next),
> + TP_ARGS(task, states, atoms, next));
> +DEFINE_EVENT(error_%%MONITOR_CLASS%%, error_%%MODEL_NAME%%,
> + TP_PROTO(struct task_struct *task),
> + TP_ARGS(task));
> +#endif /* CONFIG_RV_MON_%%MODEL_NAME_UP%% */
Powered by blists - more mailing lists