summaryrefslogtreecommitdiff
path: root/tools/verification
diff options
context:
space:
mode:
Diffstat (limited to 'tools/verification')
-rw-r--r--tools/verification/dot2/Makefile26
-rw-r--r--tools/verification/dot2/automata.py206
-rw-r--r--tools/verification/dot2/dot2c.py254
-rw-r--r--tools/verification/dot2/dot2k44
-rw-r--r--tools/verification/dot2/dot2k.py341
-rw-r--r--tools/verification/models/deadline/nomiss.dot41
-rw-r--r--tools/verification/models/rtapp/pagefault.ltl1
-rw-r--r--tools/verification/models/rtapp/sleep.ltl23
-rw-r--r--tools/verification/models/sched/nrp.dot29
-rw-r--r--tools/verification/models/sched/opid.dot13
-rw-r--r--tools/verification/models/sched/sco.dot18
-rw-r--r--tools/verification/models/sched/scpd.dot18
-rw-r--r--tools/verification/models/sched/snep.dot18
-rw-r--r--tools/verification/models/sched/snroc.dot18
-rw-r--r--tools/verification/models/sched/sssw.dot30
-rw-r--r--tools/verification/models/sched/sts.dot38
-rw-r--r--tools/verification/models/stall.dot22
-rw-r--r--tools/verification/rv/Makefile6
-rw-r--r--tools/verification/rv/Makefile.rv2
-rw-r--r--tools/verification/rv/include/in_kernel.h2
-rw-r--r--tools/verification/rv/include/rv.h3
-rw-r--r--tools/verification/rv/src/in_kernel.c260
-rw-r--r--tools/verification/rv/src/rv.c39
-rw-r--r--tools/verification/rvgen/.gitignore3
-rw-r--r--tools/verification/rvgen/Makefile27
-rw-r--r--tools/verification/rvgen/__main__.py70
-rw-r--r--tools/verification/rvgen/dot2c (renamed from tools/verification/dot2/dot2c)3
-rw-r--r--tools/verification/rvgen/rvgen/automata.py368
-rw-r--r--tools/verification/rvgen/rvgen/container.py32
-rw-r--r--tools/verification/rvgen/rvgen/dot2c.py268
-rw-r--r--tools/verification/rvgen/rvgen/dot2k.py611
-rw-r--r--tools/verification/rvgen/rvgen/generator.py251
-rw-r--r--tools/verification/rvgen/rvgen/ltl2ba.py567
-rw-r--r--tools/verification/rvgen/rvgen/ltl2k.py277
-rw-r--r--tools/verification/rvgen/rvgen/templates/Kconfig9
-rw-r--r--tools/verification/rvgen/rvgen/templates/container/Kconfig (renamed from tools/verification/dot2/dot2k_templates/Kconfig)1
-rw-r--r--tools/verification/rvgen/rvgen/templates/container/main.c35
-rw-r--r--tools/verification/rvgen/rvgen/templates/container/main.h3
-rw-r--r--tools/verification/rvgen/rvgen/templates/dot2k/main.c (renamed from tools/verification/dot2/dot2k_templates/main.c)28
-rw-r--r--tools/verification/rvgen/rvgen/templates/dot2k/trace.h (renamed from tools/verification/dot2/dot2k_templates/trace.h)0
-rw-r--r--tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h16
-rw-r--r--tools/verification/rvgen/rvgen/templates/ltl2k/main.c102
-rw-r--r--tools/verification/rvgen/rvgen/templates/ltl2k/trace.h14
43 files changed, 3167 insertions, 970 deletions
diff --git a/tools/verification/dot2/Makefile b/tools/verification/dot2/Makefile
deleted file mode 100644
index 021beb07a521..000000000000
--- a/tools/verification/dot2/Makefile
+++ /dev/null
@@ -1,26 +0,0 @@
-INSTALL=install
-
-prefix ?= /usr
-bindir ?= $(prefix)/bin
-mandir ?= $(prefix)/share/man
-miscdir ?= $(prefix)/share/dot2
-srcdir ?= $(prefix)/src
-
-PYLIB ?= $(shell python3 -c 'import sysconfig; print (sysconfig.get_path("purelib"))')
-
-.PHONY: all
-all:
-
-.PHONY: clean
-clean:
-
-.PHONY: install
-install:
- $(INSTALL) automata.py -D -m 644 $(DESTDIR)$(PYLIB)/dot2/automata.py
- $(INSTALL) dot2c.py -D -m 644 $(DESTDIR)$(PYLIB)/dot2/dot2c.py
- $(INSTALL) dot2c -D -m 755 $(DESTDIR)$(bindir)/
- $(INSTALL) dot2k.py -D -m 644 $(DESTDIR)$(PYLIB)/dot2/dot2k.py
- $(INSTALL) dot2k -D -m 755 $(DESTDIR)$(bindir)/
-
- mkdir -p ${miscdir}/
- cp -rp dot2k_templates $(DESTDIR)$(miscdir)/
diff --git a/tools/verification/dot2/automata.py b/tools/verification/dot2/automata.py
deleted file mode 100644
index d9a3fe2b74bf..000000000000
--- a/tools/verification/dot2/automata.py
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
-#
-# Automata object: parse an automata in dot file digraph format into a python object
-#
-# For further information, see:
-# Documentation/trace/rv/deterministic_automata.rst
-
-import ntpath
-
-class Automata:
- """Automata class: Reads a dot file and part it as an automata.
-
- Attributes:
- dot_file: A dot file with an state_automaton definition.
- """
-
- invalid_state_str = "INVALID_STATE"
-
- 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.states, self.initial_state, self.final_states = self.__get_state_variables()
- self.events = self.__get_event_variables()
- self.function = self.__create_matrix()
- self.events_start, self.events_start_run = self.__store_init_events()
-
- def __get_model_name(self):
- basename = ntpath.basename(self.__dot_path)
- if not basename.endswith(".dot") and not basename.endswith(".gv"):
- print("not a dot file")
- raise Exception("not a dot file: %s" % self.__dot_path)
-
- model_name = ntpath.splitext(basename)[0]
- if model_name.__len__() == 0:
- raise Exception("not a dot file: %s" % self.__dot_path)
-
- return model_name
-
- def __open_dot(self):
- cursor = 0
- dot_lines = []
- try:
- dot_file = open(self.__dot_path)
- except:
- raise Exception("Cannot open the file: %s" % self.__dot_path)
-
- dot_lines = dot_file.read().splitlines()
- dot_file.close()
-
- # checking the first line:
- line = dot_lines[cursor].split()
-
- if (line[0] != "digraph") and (line[1] != "state_automaton"):
- raise Exception("Not a valid .dot format: %s" % self.__dot_path)
- else:
- cursor += 1
- return dot_lines
-
- def __get_cursor_begin_states(self):
- cursor = 0
- while self.__dot_lines[cursor].split()[0] != "{node":
- cursor += 1
- return cursor
-
- def __get_cursor_begin_events(self):
- cursor = 0
- while self.__dot_lines[cursor].split()[0] != "{node":
- cursor += 1
- while self.__dot_lines[cursor].split()[0] == "{node":
- cursor += 1
- # skip initial state transition
- cursor += 1
- return cursor
-
- def __get_state_variables(self):
- # wait for node declaration
- states = []
- final_states = []
-
- has_final_states = False
- cursor = self.__get_cursor_begin_states()
-
- # process nodes
- while self.__dot_lines[cursor].split()[0] == "{node":
- line = self.__dot_lines[cursor].split()
- raw_state = line[-1]
-
- # "enabled_fired"}; -> enabled_fired
- state = raw_state.replace('"', '').replace('};', '').replace(',','_')
- if state[0:7] == "__init_":
- initial_state = state[7:]
- else:
- states.append(state)
- if "doublecircle" in self.__dot_lines[cursor]:
- final_states.append(state)
- has_final_states = True
-
- if "ellipse" in self.__dot_lines[cursor]:
- final_states.append(state)
- has_final_states = True
-
- cursor += 1
-
- states = sorted(set(states))
- states.remove(initial_state)
-
- # Insert the initial state at the bein og 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):
- # here we are at the begin of transitions, take a note, we will return later.
- cursor = self.__get_cursor_begin_events()
-
- events = []
- while self.__dot_lines[cursor].lstrip()[0] == '"':
- # transitions have the format:
- # "all_fired" -> "both_fired" [ label = "disable_irq" ];
- # ------------ event is here ------------^^^^^
- if self.__dot_lines[cursor].split()[1] == "->":
- line = self.__dot_lines[cursor].split()
- event = line[-2].replace('"','')
-
- # when a transition has more than one lables, they are like this
- # "local_irq_enable\nhw_local_irq_enable_n"
- # so split them.
-
- event = event.replace("\\n", " ")
- for i in event.split():
- events.append(i)
- cursor += 1
-
- return sorted(set(events))
-
- def __create_matrix(self):
- # transform the array into a dictionary
- events = self.events
- states = self.states
- events_dict = {}
- states_dict = {}
- nr_event = 0
- for event in events:
- events_dict[event] = nr_event
- nr_event += 1
-
- nr_state = 0
- for state in states:
- states_dict[state] = nr_state
- nr_state += 1
-
- # declare the matrix....
- matrix = [[ self.invalid_state_str for x in range(nr_event)] for y in range(nr_state)]
-
- # and we are back! Let's fill the matrix
- cursor = self.__get_cursor_begin_events()
-
- while self.__dot_lines[cursor].lstrip()[0] == '"':
- if self.__dot_lines[cursor].split()[1] == "->":
- line = self.__dot_lines[cursor].split()
- origin_state = line[0].replace('"','').replace(',','_')
- dest_state = line[2].replace('"','').replace(',','_')
- possible_events = line[-2].replace('"','').replace("\\n", " ")
- for event in possible_events.split():
- matrix[states_dict[origin_state]][events_dict[event]] = dest_state
- cursor += 1
-
- return matrix
-
- def __store_init_events(self):
- events_start = [False] * len(self.events)
- events_start_run = [False] * len(self.events)
- for i, _ in enumerate(self.events):
- curr_event_will_init = 0
- curr_event_from_init = False
- curr_event_used = 0
- for j, _ in enumerate(self.states):
- if self.function[j][i] != self.invalid_state_str:
- curr_event_used += 1
- if self.function[j][i] == self.initial_state:
- curr_event_will_init += 1
- if self.function[0][i] != self.invalid_state_str:
- curr_event_from_init = True
- # this event always leads to init
- if curr_event_will_init and curr_event_used == curr_event_will_init:
- events_start[i] = True
- # this event is only called from init
- if curr_event_from_init and curr_event_used == 1:
- events_start_run[i] = True
- return events_start, events_start_run
-
- def is_start_event(self, event):
- return self.events_start[self.events.index(event)]
-
- def is_start_run_event(self, event):
- # prefer handle_start_event if there
- if any(self.events_start):
- return False
- return self.events_start_run[self.events.index(event)]
diff --git a/tools/verification/dot2/dot2c.py b/tools/verification/dot2/dot2c.py
deleted file mode 100644
index fa2816ac7b61..000000000000
--- a/tools/verification/dot2/dot2c.py
+++ /dev/null
@@ -1,254 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
-#
-# dot2c: parse an automata in dot file digraph format into a C
-#
-# This program was written in the development of this paper:
-# de Oliveira, D. B. and Cucinotta, T. and de Oliveira, R. S.
-# "Efficient Formal Verification for the Linux Kernel." International
-# Conference on Software Engineering and Formal Methods. Springer, Cham, 2019.
-#
-# For further information, see:
-# Documentation/trace/rv/deterministic_automata.rst
-
-from dot2.automata import Automata
-
-class Dot2c(Automata):
- enum_suffix = ""
- enum_states_def = "states"
- enum_events_def = "events"
- struct_automaton_def = "automaton"
- var_automaton_def = "aut"
-
- def __init__(self, file_path, model_name=None):
- super().__init__(file_path, model_name)
- self.line_length = 100
-
- def __buff_to_string(self, buff):
- string = ""
-
- for line in buff:
- string = string + line + "\n"
-
- # cut off the last \n
- return string[:-1]
-
- def __get_enum_states_content(self):
- buff = []
- buff.append("\t%s%s = 0," % (self.initial_state, self.enum_suffix))
- for state in self.states:
- if state != self.initial_state:
- buff.append("\t%s%s," % (state, self.enum_suffix))
- buff.append("\tstate_max%s" % (self.enum_suffix))
-
- return buff
-
- def get_enum_states_string(self):
- buff = self.__get_enum_states_content()
- return self.__buff_to_string(buff)
-
- def format_states_enum(self):
- buff = []
- buff.append("enum %s {" % self.enum_states_def)
- buff.append(self.get_enum_states_string())
- buff.append("};\n")
-
- return buff
-
- def __get_enum_events_content(self):
- buff = []
- first = True
- for event in self.events:
- if first:
- buff.append("\t%s%s = 0," % (event, self.enum_suffix))
- first = False
- else:
- buff.append("\t%s%s," % (event, self.enum_suffix))
-
- buff.append("\tevent_max%s" % self.enum_suffix)
-
- return buff
-
- def get_enum_events_string(self):
- buff = self.__get_enum_events_content()
- return self.__buff_to_string(buff)
-
- def format_events_enum(self):
- buff = []
- buff.append("enum %s {" % self.enum_events_def)
- buff.append(self.get_enum_events_string())
- buff.append("};\n")
-
- return buff
-
- def get_minimun_type(self):
- min_type = "unsigned char"
-
- if self.states.__len__() > 255:
- min_type = "unsigned short"
-
- if self.states.__len__() > 65535:
- min_type = "unsigned int"
-
- if self.states.__len__() > 1000000:
- raise Exception("Too many states: %d" % self.states.__len__())
-
- return min_type
-
- def format_automaton_definition(self):
- min_type = self.get_minimun_type()
- buff = []
- buff.append("struct %s {" % self.struct_automaton_def)
- buff.append("\tchar *state_names[state_max%s];" % (self.enum_suffix))
- buff.append("\tchar *event_names[event_max%s];" % (self.enum_suffix))
- buff.append("\t%s function[state_max%s][event_max%s];" % (min_type, self.enum_suffix, self.enum_suffix))
- buff.append("\t%s initial_state;" % min_type)
- buff.append("\tbool final_states[state_max%s];" % (self.enum_suffix))
- buff.append("};\n")
- return buff
-
- def format_aut_init_header(self):
- buff = []
- buff.append("static const struct %s %s = {" % (self.struct_automaton_def, self.var_automaton_def))
- return buff
-
- def __get_string_vector_per_line_content(self, buff):
- first = True
- string = ""
- for entry in buff:
- if first:
- string = string + "\t\t\"" + entry
- first = False;
- else:
- string = string + "\",\n\t\t\"" + entry
- string = string + "\""
-
- return string
-
- def get_aut_init_events_string(self):
- return self.__get_string_vector_per_line_content(self.events)
-
- def get_aut_init_states_string(self):
- return self.__get_string_vector_per_line_content(self.states)
-
- def format_aut_init_events_string(self):
- buff = []
- buff.append("\t.event_names = {")
- buff.append(self.get_aut_init_events_string())
- buff.append("\t},")
- return buff
-
- def format_aut_init_states_string(self):
- buff = []
- buff.append("\t.state_names = {")
- buff.append(self.get_aut_init_states_string())
- buff.append("\t},")
-
- return buff
-
- def __get_max_strlen_of_states(self):
- max_state_name = max(self.states, key = len).__len__()
- return max(max_state_name, self.invalid_state_str.__len__())
-
- def __get_state_string_length(self):
- maxlen = self.__get_max_strlen_of_states() + self.enum_suffix.__len__()
- return "%" + str(maxlen) + "s"
-
- def get_aut_init_function(self):
- nr_states = self.states.__len__()
- nr_events = self.events.__len__()
- buff = []
-
- strformat = self.__get_state_string_length()
-
- for x in range(nr_states):
- line = "\t\t{ "
- for y in range(nr_events):
- next_state = self.function[x][y]
- if next_state != self.invalid_state_str:
- next_state = self.function[x][y] + self.enum_suffix
-
- if y != nr_events-1:
- line = line + strformat % next_state + ", "
- else:
- line = line + strformat % next_state + " },"
- buff.append(line)
-
- return self.__buff_to_string(buff)
-
- def format_aut_init_function(self):
- buff = []
- buff.append("\t.function = {")
- buff.append(self.get_aut_init_function())
- buff.append("\t},")
-
- return buff
-
- def get_aut_init_initial_state(self):
- return self.initial_state
-
- def format_aut_init_initial_state(self):
- buff = []
- initial_state = self.get_aut_init_initial_state()
- buff.append("\t.initial_state = " + initial_state + self.enum_suffix + ",")
-
- return buff
-
- def get_aut_init_final_states(self):
- line = ""
- first = True
- for state in self.states:
- if first == False:
- line = line + ', '
- else:
- first = False
-
- if self.final_states.__contains__(state):
- line = line + '1'
- else:
- line = line + '0'
- return line
-
- def format_aut_init_final_states(self):
- buff = []
- buff.append("\t.final_states = { %s }," % self.get_aut_init_final_states())
-
- return buff
-
- def __get_automaton_initialization_footer_string(self):
- footer = "};\n"
- return footer
-
- def format_aut_init_footer(self):
- buff = []
- buff.append(self.__get_automaton_initialization_footer_string())
-
- return buff
-
- def format_invalid_state(self):
- buff = []
- buff.append("#define %s state_max%s\n" % (self.invalid_state_str, self.enum_suffix))
-
- return buff
-
- def format_model(self):
- buff = []
- buff += self.format_states_enum()
- buff += self.format_invalid_state()
- buff += self.format_events_enum()
- buff += self.format_automaton_definition()
- buff += self.format_aut_init_header()
- buff += self.format_aut_init_states_string()
- buff += self.format_aut_init_events_string()
- buff += self.format_aut_init_function()
- buff += self.format_aut_init_initial_state()
- buff += self.format_aut_init_final_states()
- buff += self.format_aut_init_footer()
-
- return buff
-
- def print_model_classic(self):
- buff = self.format_model()
- print(self.__buff_to_string(buff))
diff --git a/tools/verification/dot2/dot2k b/tools/verification/dot2/dot2k
deleted file mode 100644
index 559ba191a1f6..000000000000
--- a/tools/verification/dot2/dot2k
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
-#
-# dot2k: transform dot files into a monitor for the Linux kernel.
-#
-# For further information, see:
-# Documentation/trace/rv/da_monitor_synthesis.rst
-
-if __name__ == '__main__':
- from dot2.dot2k import dot2k
- import argparse
- import ntpath
- import os
- import platform
- import sys
-
- parser = argparse.ArgumentParser(description='transform .dot file into kernel rv monitor')
- parser.add_argument('-d', "--dot", dest="dot_file", required=True)
- parser.add_argument('-t', "--monitor_type", dest="monitor_type", required=True)
- parser.add_argument('-n', "--model_name", dest="model_name", required=False)
- parser.add_argument("-D", "--description", dest="description", required=False)
- parser.add_argument("-a", "--auto_patch", dest="auto_patch",
- action="store_true", required=False,
- help="Patch the kernel in place")
- params = parser.parse_args()
-
- print("Opening and parsing the dot file %s" % params.dot_file)
- try:
- monitor=dot2k(params.dot_file, params.monitor_type, vars(params))
- except Exception as e:
- print('Error: '+ str(e))
- print("Sorry : :-(")
- sys.exit(1)
-
- print("Writing the monitor into the directory %s" % monitor.name)
- monitor.print_files()
- print("Almost done, checklist")
- print(" - Edit the %s/%s.c to add the instrumentation" % (monitor.name, monitor.name))
- print(monitor.fill_tracepoint_tooltip())
- print(monitor.fill_makefile_tooltip())
- print(monitor.fill_kconfig_tooltip())
- print(monitor.fill_monitor_tooltip())
diff --git a/tools/verification/dot2/dot2k.py b/tools/verification/dot2/dot2k.py
deleted file mode 100644
index 7547eb290b7d..000000000000
--- a/tools/verification/dot2/dot2k.py
+++ /dev/null
@@ -1,341 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-only
-#
-# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
-#
-# dot2k: transform dot files into a monitor for the Linux kernel.
-#
-# For further information, see:
-# Documentation/trace/rv/da_monitor_synthesis.rst
-
-from dot2.dot2c import Dot2c
-import platform
-import os
-
-class dot2k(Dot2c):
- monitor_types = { "global" : 1, "per_cpu" : 2, "per_task" : 3 }
- monitor_templates_dir = "dot2/dot2k_templates/"
- rv_dir = "kernel/trace/rv"
- monitor_type = "per_cpu"
-
- def __init__(self, file_path, MonitorType, extra_params={}):
- super().__init__(file_path, extra_params.get("model_name"))
-
- self.monitor_type = self.monitor_types.get(MonitorType)
- if self.monitor_type is None:
- raise ValueError("Unknown monitor type: %s" % MonitorType)
-
- self.monitor_type = MonitorType
- self.__fill_rv_templates_dir()
- self.main_c = self.__read_file(self.monitor_templates_dir + "main.c")
- self.trace_h = self.__read_file(self.monitor_templates_dir + "trace.h")
- self.kconfig = self.__read_file(self.monitor_templates_dir + "Kconfig")
- self.enum_suffix = "_%s" % self.name
- 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()
-
- def __fill_rv_templates_dir(self):
-
- if os.path.exists(self.monitor_templates_dir):
- return
-
- if platform.system() != "Linux":
- raise OSError("I can only run on Linux.")
-
- kernel_path = "/lib/modules/%s/build/tools/verification/dot2/dot2k_templates/" % (platform.release())
-
- if os.path.exists(kernel_path):
- self.monitor_templates_dir = kernel_path
- return
-
- if os.path.exists("/usr/share/dot2/dot2k_templates/"):
- self.monitor_templates_dir = "/usr/share/dot2/dot2k_templates/"
- return
-
- raise FileNotFoundError("Could not find the template directory, do you have the kernel source installed?")
-
- def __fill_rv_kernel_dir(self):
-
- # first try if we are running in the kernel tree root
- if os.path.exists(self.rv_dir):
- 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
- return
-
- if platform.system() != "Linux":
- raise OSError("I can only run on Linux.")
-
- kernel_path = os.path.join("/lib/modules/%s/build" % platform.release(), 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
- return
-
- raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?")
-
- def __read_file(self, path):
- try:
- fd = open(path, 'r')
- except OSError:
- raise Exception("Cannot open the file: %s" % path)
-
- content = fd.read()
-
- fd.close()
- return content
-
- def __buff_to_string(self, buff):
- string = ""
-
- for line in buff:
- string = string + line + "\n"
-
- # cut off the last \n
- return string[:-1]
-
- def fill_monitor_type(self):
- return self.monitor_type.upper()
-
- def fill_tracepoint_handlers_skel(self):
- buff = []
- for event in self.events:
- buff.append("static void handle_%s(void *data, /* XXX: fill header */)" % event)
- buff.append("{")
- handle = "handle_event"
- if self.is_start_event(event):
- buff.append("\t/* XXX: validate that this event always leads to the initial state */")
- handle = "handle_start_event"
- elif self.is_start_run_event(event):
- buff.append("\t/* XXX: validate that this event is only valid in the initial state */")
- handle = "handle_start_run_event"
- if self.monitor_type == "per_task":
- buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;");
- buff.append("\tda_%s_%s(p, %s%s);" % (handle, self.name, event, self.enum_suffix));
- else:
- buff.append("\tda_%s_%s(%s%s);" % (handle, self.name, event, self.enum_suffix));
- buff.append("}")
- buff.append("")
- return self.__buff_to_string(buff)
-
- def fill_tracepoint_attach_probe(self):
- buff = []
- for event in self.events:
- buff.append("\trv_attach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_%s);" % (self.name, event))
- return self.__buff_to_string(buff)
-
- def fill_tracepoint_detach_helper(self):
- buff = []
- for event in self.events:
- buff.append("\trv_detach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_%s);" % (self.name, event))
- return self.__buff_to_string(buff)
-
- def fill_main_c(self):
- main_c = self.main_c
- monitor_type = self.fill_monitor_type()
- min_type = self.get_minimun_type()
- nr_events = len(self.events)
- tracepoint_handlers = self.fill_tracepoint_handlers_skel()
- tracepoint_attach = self.fill_tracepoint_attach_probe()
- tracepoint_detach = self.fill_tracepoint_detach_helper()
-
- main_c = main_c.replace("%%MONITOR_TYPE%%", monitor_type)
- main_c = main_c.replace("%%MIN_TYPE%%", min_type)
- main_c = main_c.replace("%%MODEL_NAME%%", self.name)
- main_c = main_c.replace("%%NR_EVENTS%%", str(nr_events))
- main_c = main_c.replace("%%TRACEPOINT_HANDLERS_SKEL%%", tracepoint_handlers)
- main_c = main_c.replace("%%TRACEPOINT_ATTACH%%", tracepoint_attach)
- main_c = main_c.replace("%%TRACEPOINT_DETACH%%", tracepoint_detach)
- main_c = main_c.replace("%%DESCRIPTION%%", self.description)
-
- return main_c
-
- def fill_model_h_header(self):
- buff = []
- buff.append("/*")
- buff.append(" * Automatically generated C representation of %s automaton" % (self.name))
- buff.append(" * For further information about this format, see kernel documentation:")
- buff.append(" * Documentation/trace/rv/deterministic_automata.rst")
- buff.append(" */")
- buff.append("")
-
- return buff
-
- def fill_model_h(self):
- #
- # Adjust the definition names
- #
- self.enum_states_def = "states_%s" % self.name
- self.enum_events_def = "events_%s" % self.name
- self.struct_automaton_def = "automaton_%s" % self.name
- self.var_automaton_def = "automaton_%s" % self.name
-
- buff = self.fill_model_h_header()
- buff += self.format_model()
-
- return self.__buff_to_string(buff)
-
- def fill_monitor_class_type(self):
- if self.monitor_type == "per_task":
- return "DA_MON_EVENTS_ID"
- return "DA_MON_EVENTS_IMPLICIT"
-
- def fill_monitor_class(self):
- if self.monitor_type == "per_task":
- return "da_monitor_id"
- return "da_monitor"
-
- def fill_tracepoint_args_skel(self, tp_type):
- buff = []
- tp_args_event = [
- ("char *", "state"),
- ("char *", "event"),
- ("char *", "next_state"),
- ("bool ", "final_state"),
- ]
- tp_args_error = [
- ("char *", "state"),
- ("char *", "event"),
- ]
- tp_args_id = ("int ", "id")
- tp_args = tp_args_event if tp_type == "event" else tp_args_error
- if self.monitor_type == "per_task":
- tp_args.insert(0, tp_args_id)
- tp_proto_c = ", ".join([a+b for a,b in tp_args])
- tp_args_c = ", ".join([b for a,b in tp_args])
- buff.append(" TP_PROTO(%s)," % tp_proto_c)
- buff.append(" TP_ARGS(%s)" % tp_args_c)
- return self.__buff_to_string(buff)
-
- def fill_trace_h(self):
- trace_h = self.trace_h
- monitor_class = self.fill_monitor_class()
- monitor_class_type = self.fill_monitor_class_type()
- tracepoint_args_skel_event = self.fill_tracepoint_args_skel("event")
- tracepoint_args_skel_error = self.fill_tracepoint_args_skel("error")
- trace_h = trace_h.replace("%%MODEL_NAME%%", self.name)
- trace_h = trace_h.replace("%%MODEL_NAME_UP%%", self.name.upper())
- trace_h = trace_h.replace("%%MONITOR_CLASS%%", monitor_class)
- trace_h = trace_h.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
- trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_EVENT%%", tracepoint_args_skel_event)
- trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR%%", tracepoint_args_skel_error)
- return trace_h
-
- def fill_kconfig(self):
- kconfig = self.kconfig
- monitor_class_type = self.fill_monitor_class_type()
- kconfig = kconfig.replace("%%MODEL_NAME%%", self.name)
- kconfig = kconfig.replace("%%MODEL_NAME_UP%%", self.name.upper())
- kconfig = kconfig.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
- kconfig = kconfig.replace("%%DESCRIPTION%%", self.description)
- return kconfig
-
- def __patch_file(self, file, marker, line):
- file_to_patch = os.path.join(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)
-
- def fill_tracepoint_tooltip(self):
- monitor_class_type = self.fill_monitor_class_type()
- if self.auto_patch:
- self.__patch_file("rv_trace.h",
- "// Add new monitors based on CONFIG_%s here" % monitor_class_type,
- "#include <monitors/%s/%s_trace.h>" % (self.name, self.name))
- return " - Patching %s/rv_trace.h, double check the result" % self.rv_dir
-
- return """ - Edit %s/rv_trace.h:
-Add this line where other tracepoints are included and %s is defined:
-#include <monitors/%s/%s_trace.h>
-""" % (self.rv_dir, monitor_class_type, self.name, self.name)
-
- def fill_kconfig_tooltip(self):
- if self.auto_patch:
- self.__patch_file("Kconfig",
- "# Add new monitors here",
- "source \"kernel/trace/rv/monitors/%s/Kconfig\"" % (self.name))
- return " - Patching %s/Kconfig, double check the result" % self.rv_dir
-
- return """ - Edit %s/Kconfig:
-Add this line where other monitors are included:
-source \"kernel/trace/rv/monitors/%s/Kconfig\"
-""" % (self.rv_dir, self.name)
-
- def fill_makefile_tooltip(self):
- name = self.name
- name_up = name.upper()
- if self.auto_patch:
- self.__patch_file("Makefile",
- "# Add new monitors here",
- "obj-$(CONFIG_RV_MON_%s) += monitors/%s/%s.o" % (name_up, name, name))
- return " - Patching %s/Makefile, double check the result" % self.rv_dir
-
- return """ - Edit %s/Makefile:
-Add this line where other monitors are included:
-obj-$(CONFIG_RV_MON_%s) += monitors/%s/%s.o
-""" % (self.rv_dir, name_up, name, name)
-
- def fill_monitor_tooltip(self):
- if self.auto_patch:
- return " - Monitor created in %s/monitors/%s" % (self.rv_dir, self. name)
- return " - Move %s/ to the kernel's monitor directory (%s/monitors)" % (self.name, self.rv_dir)
-
- def __create_directory(self):
- path = self.name
- if self.auto_patch:
- path = os.path.join(self.rv_dir, "monitors", path)
- try:
- os.mkdir(path)
- except FileExistsError:
- return
- except:
- print("Fail creating the output dir: %s" % self.name)
-
- def __write_file(self, file_name, content):
- try:
- file = open(file_name, 'w')
- except:
- print("Fail writing to file: %s" % file_name)
-
- file.write(content)
-
- file.close()
-
- def __create_file(self, file_name, content):
- path = "%s/%s" % (self.name, file_name)
- if self.auto_patch:
- path = os.path.join(self.rv_dir, "monitors", path)
- self.__write_file(path, content)
-
- def __get_main_name(self):
- path = "%s/%s" % (self.name, "main.c")
- if not os.path.exists(path):
- return "main.c"
- return "__main.c"
-
- def print_files(self):
- main_c = self.fill_main_c()
- model_h = self.fill_model_h()
-
- self.__create_directory()
-
- path = "%s.c" % self.name
- self.__create_file(path, main_c)
-
- path = "%s.h" % self.name
- self.__create_file(path, model_h)
-
- trace_h = self.fill_trace_h()
- path = "%s_trace.h" % self.name
- self.__create_file(path, trace_h)
-
- kconfig = self.fill_kconfig()
- self.__create_file("Kconfig", kconfig)
diff --git a/tools/verification/models/deadline/nomiss.dot b/tools/verification/models/deadline/nomiss.dot
new file mode 100644
index 000000000000..fd1ea6bf2509
--- /dev/null
+++ b/tools/verification/models/deadline/nomiss.dot
@@ -0,0 +1,41 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = circle] "idle"};
+ {node [shape = plaintext, style=invis, label=""] "__init_ready"};
+ {node [shape = doublecircle] "ready"};
+ {node [shape = circle] "ready"};
+ {node [shape = circle] "running"};
+ {node [shape = circle] "sleeping"};
+ {node [shape = circle] "throttled"};
+ "__init_ready" -> "ready";
+ "idle" [label = "idle"];
+ "idle" -> "idle" [ label = "dl_server_idle" ];
+ "idle" -> "ready" [ label = "dl_replenish;reset(clk)" ];
+ "idle" -> "running" [ label = "sched_switch_in" ];
+ "idle" -> "sleeping" [ label = "dl_server_stop" ];
+ "idle" -> "throttled" [ label = "dl_throttle" ];
+ "ready" [label = "ready\nclk < DEADLINE_NS()", color = green3];
+ "ready" -> "idle" [ label = "dl_server_idle" ];
+ "ready" -> "ready" [ label = "sched_wakeup\ndl_replenish;reset(clk)" ];
+ "ready" -> "running" [ label = "sched_switch_in" ];
+ "ready" -> "sleeping" [ label = "dl_server_stop" ];
+ "ready" -> "throttled" [ label = "dl_throttle;is_defer == 1" ];
+ "running" [label = "running\nclk < DEADLINE_NS()"];
+ "running" -> "idle" [ label = "dl_server_idle" ];
+ "running" -> "running" [ label = "dl_replenish;reset(clk)\nsched_switch_in\nsched_wakeup" ];
+ "running" -> "sleeping" [ label = "sched_switch_suspend\ndl_server_stop" ];
+ "running" -> "throttled" [ label = "dl_throttle" ];
+ "sleeping" [label = "sleeping"];
+ "sleeping" -> "ready" [ label = "sched_wakeup\ndl_replenish;reset(clk)" ];
+ "sleeping" -> "running" [ label = "sched_switch_in" ];
+ "sleeping" -> "sleeping" [ label = "dl_server_stop\ndl_server_idle" ];
+ "sleeping" -> "throttled" [ label = "dl_throttle;is_constr_dl == 1 || is_defer == 1" ];
+ "throttled" [label = "throttled"];
+ "throttled" -> "ready" [ label = "dl_replenish;reset(clk)" ];
+ "throttled" -> "throttled" [ label = "sched_switch_suspend\nsched_wakeup\ndl_server_idle\ndl_throttle" ];
+ { rank = min ;
+ "__init_ready";
+ "ready";
+ }
+}
diff --git a/tools/verification/models/rtapp/pagefault.ltl b/tools/verification/models/rtapp/pagefault.ltl
new file mode 100644
index 000000000000..d7ce62102733
--- /dev/null
+++ b/tools/verification/models/rtapp/pagefault.ltl
@@ -0,0 +1 @@
+RULE = always (RT imply not PAGEFAULT)
diff --git a/tools/verification/models/rtapp/sleep.ltl b/tools/verification/models/rtapp/sleep.ltl
new file mode 100644
index 000000000000..6f26c4810f78
--- /dev/null
+++ b/tools/verification/models/rtapp/sleep.ltl
@@ -0,0 +1,23 @@
+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)
+
+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 or NANOSLEEP_CLOCK_TAI)
+
+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
diff --git a/tools/verification/models/sched/nrp.dot b/tools/verification/models/sched/nrp.dot
new file mode 100644
index 000000000000..77bb64669416
--- /dev/null
+++ b/tools/verification/models/sched/nrp.dot
@@ -0,0 +1,29 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = doublecircle] "any_thread_running"};
+ {node [shape = circle] "any_thread_running"};
+ {node [shape = circle] "nested_preempt"};
+ {node [shape = plaintext, style=invis, label=""] "__init_preempt_irq"};
+ {node [shape = circle] "preempt_irq"};
+ {node [shape = circle] "rescheduling"};
+ "__init_preempt_irq" -> "preempt_irq";
+ "any_thread_running" [label = "any_thread_running", color = green3];
+ "any_thread_running" -> "any_thread_running" [ label = "schedule_entry\nirq_entry" ];
+ "any_thread_running" -> "rescheduling" [ label = "sched_need_resched" ];
+ "nested_preempt" [label = "nested_preempt"];
+ "nested_preempt" -> "any_thread_running" [ label = "schedule_entry_preempt\nschedule_entry" ];
+ "nested_preempt" -> "nested_preempt" [ label = "irq_entry" ];
+ "nested_preempt" -> "preempt_irq" [ label = "sched_need_resched" ];
+ "preempt_irq" [label = "preempt_irq"];
+ "preempt_irq" -> "nested_preempt" [ label = "schedule_entry_preempt\nschedule_entry" ];
+ "preempt_irq" -> "preempt_irq" [ label = "irq_entry\nsched_need_resched" ];
+ "rescheduling" [label = "rescheduling"];
+ "rescheduling" -> "any_thread_running" [ label = "schedule_entry_preempt\nschedule_entry" ];
+ "rescheduling" -> "preempt_irq" [ label = "irq_entry" ];
+ "rescheduling" -> "rescheduling" [ label = "sched_need_resched" ];
+ { rank = min ;
+ "__init_preempt_irq";
+ "preempt_irq";
+ }
+}
diff --git a/tools/verification/models/sched/opid.dot b/tools/verification/models/sched/opid.dot
new file mode 100644
index 000000000000..511051fce430
--- /dev/null
+++ b/tools/verification/models/sched/opid.dot
@@ -0,0 +1,13 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext, style=invis, label=""] "__init_any"};
+ {node [shape = doublecircle] "any"};
+ "__init_any" -> "any";
+ "any" [label = "any", color = green3];
+ "any" -> "any" [ label = "sched_need_resched;irq_off == 1\nsched_waking;irq_off == 1 && preempt_off == 1" ];
+ { rank = min ;
+ "__init_any";
+ "any";
+ }
+}
diff --git a/tools/verification/models/sched/sco.dot b/tools/verification/models/sched/sco.dot
new file mode 100644
index 000000000000..20b0e3b449a6
--- /dev/null
+++ b/tools/verification/models/sched/sco.dot
@@ -0,0 +1,18 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext] "scheduling_context"};
+ {node [shape = plaintext, style=invis, label=""] "__init_thread_context"};
+ {node [shape = ellipse] "thread_context"};
+ {node [shape = plaintext] "thread_context"};
+ "__init_thread_context" -> "thread_context";
+ "scheduling_context" [label = "scheduling_context"];
+ "scheduling_context" -> "thread_context" [ label = "schedule_exit" ];
+ "thread_context" [label = "thread_context", color = green3];
+ "thread_context" -> "scheduling_context" [ label = "schedule_entry" ];
+ "thread_context" -> "thread_context" [ label = "sched_set_state" ];
+ { rank = min ;
+ "__init_thread_context";
+ "thread_context";
+ }
+}
diff --git a/tools/verification/models/sched/scpd.dot b/tools/verification/models/sched/scpd.dot
new file mode 100644
index 000000000000..340413896765
--- /dev/null
+++ b/tools/verification/models/sched/scpd.dot
@@ -0,0 +1,18 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext] "can_sched"};
+ {node [shape = plaintext, style=invis, label=""] "__init_cant_sched"};
+ {node [shape = ellipse] "cant_sched"};
+ {node [shape = plaintext] "cant_sched"};
+ "__init_cant_sched" -> "cant_sched";
+ "can_sched" [label = "can_sched"];
+ "can_sched" -> "can_sched" [ label = "schedule_entry\nschedule_exit" ];
+ "can_sched" -> "cant_sched" [ label = "preempt_enable" ];
+ "cant_sched" [label = "cant_sched", color = green3];
+ "cant_sched" -> "can_sched" [ label = "preempt_disable" ];
+ { rank = min ;
+ "__init_cant_sched";
+ "cant_sched";
+ }
+}
diff --git a/tools/verification/models/sched/snep.dot b/tools/verification/models/sched/snep.dot
new file mode 100644
index 000000000000..fe1300e93f21
--- /dev/null
+++ b/tools/verification/models/sched/snep.dot
@@ -0,0 +1,18 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext, style=invis, label=""] "__init_non_scheduling_context"};
+ {node [shape = ellipse] "non_scheduling_context"};
+ {node [shape = plaintext] "non_scheduling_context"};
+ {node [shape = plaintext] "scheduling_contex"};
+ "__init_non_scheduling_context" -> "non_scheduling_context";
+ "non_scheduling_context" [label = "non_scheduling_context", color = green3];
+ "non_scheduling_context" -> "non_scheduling_context" [ label = "preempt_disable\npreempt_enable" ];
+ "non_scheduling_context" -> "scheduling_contex" [ label = "schedule_entry" ];
+ "scheduling_contex" [label = "scheduling_contex"];
+ "scheduling_contex" -> "non_scheduling_context" [ label = "schedule_exit" ];
+ { rank = min ;
+ "__init_non_scheduling_context";
+ "non_scheduling_context";
+ }
+}
diff --git a/tools/verification/models/sched/snroc.dot b/tools/verification/models/sched/snroc.dot
new file mode 100644
index 000000000000..8b71c32d4dca
--- /dev/null
+++ b/tools/verification/models/sched/snroc.dot
@@ -0,0 +1,18 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext, style=invis, label=""] "__init_other_context"};
+ {node [shape = ellipse] "other_context"};
+ {node [shape = plaintext] "other_context"};
+ {node [shape = plaintext] "own_context"};
+ "__init_other_context" -> "other_context";
+ "other_context" [label = "other_context", color = green3];
+ "other_context" -> "own_context" [ label = "sched_switch_in" ];
+ "own_context" [label = "own_context"];
+ "own_context" -> "other_context" [ label = "sched_switch_out" ];
+ "own_context" -> "own_context" [ label = "sched_set_state" ];
+ { rank = min ;
+ "__init_other_context";
+ "other_context";
+ }
+}
diff --git a/tools/verification/models/sched/sssw.dot b/tools/verification/models/sched/sssw.dot
new file mode 100644
index 000000000000..4994c3e876be
--- /dev/null
+++ b/tools/verification/models/sched/sssw.dot
@@ -0,0 +1,30 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext, style=invis, label=""] "__init_runnable"};
+ {node [shape = doublecircle] "runnable"};
+ {node [shape = circle] "runnable"};
+ {node [shape = circle] "signal_wakeup"};
+ {node [shape = circle] "sleepable"};
+ {node [shape = circle] "sleeping"};
+ "__init_runnable" -> "runnable";
+ "runnable" [label = "runnable", color = green3];
+ "runnable" -> "runnable" [ label = "sched_set_state_runnable\nsched_wakeup\nsched_switch_in\nsched_switch_yield\nsched_switch_preempt\nsignal_deliver" ];
+ "runnable" -> "sleepable" [ label = "sched_set_state_sleepable" ];
+ "runnable" -> "sleeping" [ label = "sched_switch_blocking" ];
+ "signal_wakeup" [label = "signal_wakeup"];
+ "signal_wakeup" -> "runnable" [ label = "signal_deliver" ];
+ "signal_wakeup" -> "signal_wakeup" [ label = "sched_switch_in\nsched_switch_preempt\nsched_switch_yield\nsched_wakeup" ];
+ "signal_wakeup" -> "sleepable" [ label = "sched_set_state_sleepable" ];
+ "sleepable" [label = "sleepable"];
+ "sleepable" -> "runnable" [ label = "sched_set_state_runnable\nsched_wakeup" ];
+ "sleepable" -> "signal_wakeup" [ label = "sched_switch_yield" ];
+ "sleepable" -> "sleepable" [ label = "sched_set_state_sleepable\nsched_switch_in\nsched_switch_preempt\nsignal_deliver" ];
+ "sleepable" -> "sleeping" [ label = "sched_switch_suspend\nsched_switch_blocking" ];
+ "sleeping" [label = "sleeping"];
+ "sleeping" -> "runnable" [ label = "sched_wakeup" ];
+ { rank = min ;
+ "__init_runnable";
+ "runnable";
+ }
+}
diff --git a/tools/verification/models/sched/sts.dot b/tools/verification/models/sched/sts.dot
new file mode 100644
index 000000000000..8f5f38be04d5
--- /dev/null
+++ b/tools/verification/models/sched/sts.dot
@@ -0,0 +1,38 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = plaintext, style=invis, label=""] "__init_can_sched"};
+ {node [shape = doublecircle] "can_sched"};
+ {node [shape = circle] "can_sched"};
+ {node [shape = circle] "cant_sched"};
+ {node [shape = circle] "disable_to_switch"};
+ {node [shape = circle] "enable_to_exit"};
+ {node [shape = circle] "in_irq"};
+ {node [shape = circle] "scheduling"};
+ {node [shape = circle] "switching"};
+ "__init_can_sched" -> "can_sched";
+ "can_sched" [label = "can_sched", color = green3];
+ "can_sched" -> "cant_sched" [ label = "irq_disable" ];
+ "can_sched" -> "scheduling" [ label = "schedule_entry" ];
+ "cant_sched" [label = "cant_sched"];
+ "cant_sched" -> "can_sched" [ label = "irq_enable" ];
+ "cant_sched" -> "cant_sched" [ label = "irq_entry" ];
+ "disable_to_switch" [label = "disable_to_switch"];
+ "disable_to_switch" -> "enable_to_exit" [ label = "irq_enable" ];
+ "disable_to_switch" -> "in_irq" [ label = "irq_entry" ];
+ "disable_to_switch" -> "switching" [ label = "sched_switch" ];
+ "enable_to_exit" [label = "enable_to_exit"];
+ "enable_to_exit" -> "can_sched" [ label = "schedule_exit" ];
+ "enable_to_exit" -> "enable_to_exit" [ label = "irq_disable\nirq_entry\nirq_enable" ];
+ "in_irq" [label = "in_irq"];
+ "in_irq" -> "in_irq" [ label = "irq_entry" ];
+ "in_irq" -> "scheduling" [ label = "irq_enable" ];
+ "scheduling" [label = "scheduling"];
+ "scheduling" -> "disable_to_switch" [ label = "irq_disable" ];
+ "switching" [label = "switching"];
+ "switching" -> "enable_to_exit" [ label = "irq_enable" ];
+ { rank = min ;
+ "__init_can_sched";
+ "can_sched";
+ }
+}
diff --git a/tools/verification/models/stall.dot b/tools/verification/models/stall.dot
new file mode 100644
index 000000000000..50077d1dff74
--- /dev/null
+++ b/tools/verification/models/stall.dot
@@ -0,0 +1,22 @@
+digraph state_automaton {
+ center = true;
+ size = "7,11";
+ {node [shape = circle] "enqueued"};
+ {node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
+ {node [shape = doublecircle] "dequeued"};
+ {node [shape = circle] "running"};
+ "__init_dequeued" -> "dequeued";
+ "enqueued" [label = "enqueued\nclk < threshold_jiffies"];
+ "running" [label = "running"];
+ "dequeued" [label = "dequeued", color = green3];
+ "running" -> "running" [ label = "sched_switch_in\nsched_wakeup" ];
+ "enqueued" -> "enqueued" [ label = "sched_wakeup" ];
+ "enqueued" -> "running" [ label = "sched_switch_in" ];
+ "running" -> "dequeued" [ label = "sched_switch_wait" ];
+ "dequeued" -> "enqueued" [ label = "sched_wakeup;reset(clk)" ];
+ "running" -> "enqueued" [ label = "sched_switch_preempt;reset(clk)" ];
+ { rank = min ;
+ "__init_dequeued";
+ "dequeued";
+ }
+}
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 411d62b3d8eb..5b898360ba48 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -35,12 +35,6 @@ FEATURE_TESTS += libtracefs
FEATURE_DISPLAY := libtraceevent
FEATURE_DISPLAY += libtracefs
-ifeq ($(V),1)
- Q =
-else
- Q = @
-endif
-
all: $(RV)
include $(srctree)/tools/build/Makefile.include
diff --git a/tools/verification/rv/Makefile.rv b/tools/verification/rv/Makefile.rv
index 161baa29eb86..2497fb96c83d 100644
--- a/tools/verification/rv/Makefile.rv
+++ b/tools/verification/rv/Makefile.rv
@@ -27,7 +27,7 @@ endif
INCLUDE := -Iinclude/
CFLAGS := -g -DVERSION=\"$(VERSION)\" $(FOPTS) $(WOPTS) $(EXTRA_CFLAGS) $(INCLUDE)
-LDFLAGS := -ggdb $(EXTRA_LDFLAGS)
+LDFLAGS := -ggdb $(LDFLAGS) $(EXTRA_LDFLAGS)
INSTALL := install
MKDIR := mkdir
diff --git a/tools/verification/rv/include/in_kernel.h b/tools/verification/rv/include/in_kernel.h
index 3090638c8d71..f3bfd3b9895f 100644
--- a/tools/verification/rv/include/in_kernel.h
+++ b/tools/verification/rv/include/in_kernel.h
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0
-int ikm_list_monitors(void);
+int ikm_list_monitors(char *container);
int ikm_run_monitor(char *monitor, int argc, char **argv);
diff --git a/tools/verification/rv/include/rv.h b/tools/verification/rv/include/rv.h
index 770fd6da3610..6f668eb266cb 100644
--- a/tools/verification/rv/include/rv.h
+++ b/tools/verification/rv/include/rv.h
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
#define MAX_DESCRIPTION 1024
-#define MAX_DA_NAME_LEN 24
+#define MAX_DA_NAME_LEN 32
struct monitor {
char name[MAX_DA_NAME_LEN];
char desc[MAX_DESCRIPTION];
int enabled;
+ int nested;
};
int should_stop(void);
diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c
index f2bbc75a76f4..4bb746ea6e17 100644
--- a/tools/verification/rv/src/in_kernel.c
+++ b/tools/verification/rv/src/in_kernel.c
@@ -6,15 +6,18 @@
*/
#include <getopt.h>
#include <stdlib.h>
+#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
+#include <dirent.h>
#include <trace.h>
#include <utils.h>
#include <rv.h>
static int config_has_id;
+static int config_is_container;
static int config_my_pid;
static int config_trace;
@@ -45,6 +48,51 @@ static int __ikm_read_enable(char *monitor_name)
}
/*
+ * __ikm_find_monitor - find the full name of a possibly nested module
+ *
+ * __does not log errors.
+ *
+ * Returns 1 if we found the monitor, -1 on error and 0 if it does not exist.
+ * The string out_name is populated with the full name, which can be
+ * equal to monitor_name or container/monitor_name if nested
+ */
+static int __ikm_find_monitor_name(char *monitor_name, char *out_name)
+{
+ char *available_monitors, container[MAX_DA_NAME_LEN+1], *cursor, *end;
+ int retval = 1;
+
+ available_monitors = tracefs_instance_file_read(NULL, "rv/available_monitors", NULL);
+ if (!available_monitors)
+ return -1;
+
+ cursor = strstr(available_monitors, monitor_name);
+ if (!cursor) {
+ retval = 0;
+ goto out_free;
+ }
+
+ for (; cursor > available_monitors; cursor--)
+ if (*(cursor-1) == '\n')
+ break;
+ end = strstr(cursor, "\n");
+ memcpy(out_name, cursor, end-cursor);
+ out_name[end-cursor] = '\0';
+
+ cursor = strstr(out_name, ":");
+ if (cursor)
+ *cursor = '/';
+ else {
+ sprintf(container, "%s:", monitor_name);
+ if (strstr(available_monitors, container))
+ config_is_container = 1;
+ }
+
+out_free:
+ free(available_monitors);
+ return retval;
+}
+
+/*
* ikm_read_enable - reads monitor's enable status
*
* Returns the current status, or -1 on error.
@@ -132,12 +180,28 @@ static char *ikm_read_desc(char *monitor_name)
/*
* ikm_fill_monitor_definition - fill monitor's definition
*
- * Returns -1 on error, 0 otherwise.
+ * Returns -1 on error, 1 if the monitor does not belong in the container, 0 otherwise.
+ * container can be NULL
*/
-static int ikm_fill_monitor_definition(char *name, struct monitor *ikm)
+static int ikm_fill_monitor_definition(char *name, struct monitor *ikm, char *container)
{
int enabled;
- char *desc;
+ char *desc, *nested_name;
+
+ nested_name = strstr(name, ":");
+ if (nested_name) {
+ /* it belongs in container if it starts with "container:" */
+ if (container && strstr(name, container) != name)
+ return 1;
+ *nested_name = '/';
+ ++nested_name;
+ ikm->nested = 1;
+ } else {
+ if (container)
+ return 1;
+ nested_name = name;
+ ikm->nested = 0;
+ }
enabled = ikm_read_enable(name);
if (enabled < 0) {
@@ -151,7 +215,7 @@ static int ikm_fill_monitor_definition(char *name, struct monitor *ikm)
return -1;
}
- strncpy(ikm->name, name, MAX_DA_NAME_LEN);
+ strncpy(ikm->name, nested_name, MAX_DA_NAME_LEN);
ikm->enabled = enabled;
strncpy(ikm->desc, desc, MAX_DESCRIPTION);
@@ -270,12 +334,12 @@ static int ikm_has_id(char *monitor_name)
*
* Returns 0 on success, -1 otherwise.
*/
-int ikm_list_monitors(void)
+int ikm_list_monitors(char *container)
{
char *available_monitors;
- struct monitor ikm;
+ struct monitor ikm = {0};
char *curr, *next;
- int retval;
+ int retval, list_monitor = 0;
available_monitors = tracefs_instance_file_read(NULL, "rv/available_monitors", NULL);
@@ -289,15 +353,29 @@ int ikm_list_monitors(void)
next = strstr(curr, "\n");
*next = '\0';
- retval = ikm_fill_monitor_definition(curr, &ikm);
- if (retval)
+ retval = ikm_fill_monitor_definition(curr, &ikm, container);
+ if (retval < 0)
err_msg("ikm: error reading %d in kernel monitor, skipping\n", curr);
- printf("%-24s %s %s\n", ikm.name, ikm.desc, ikm.enabled ? "[ON]" : "[OFF]");
+ if (!retval) {
+ int indent = ikm.nested && !container;
+
+ list_monitor = 1;
+ printf("%s%-*s %s %s\n", indent ? " - " : "",
+ indent ? MAX_DA_NAME_LEN - 3 : MAX_DA_NAME_LEN,
+ ikm.name, ikm.desc, ikm.enabled ? "[ON]" : "[OFF]");
+ }
curr = ++next;
} while (strlen(curr));
+ if (!list_monitor) {
+ if (container)
+ printf("-- No monitor found in container %s --\n", container);
+ else
+ printf("-- No monitor found --\n");
+ }
+
free(available_monitors);
return 0;
@@ -343,25 +421,34 @@ ikm_event_handler(struct trace_seq *s, struct tep_record *record,
unsigned long long final_state;
unsigned long long pid;
unsigned long long id;
- int cpu = record->cpu;
int val;
+ bool missing_id;
if (config_has_id)
- tep_get_field_val(s, trace_event, "id", record, &id, 1);
+ missing_id = tep_get_field_val(s, trace_event, "id", record, &id, 1);
tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1);
if (config_has_id && (config_my_pid == id))
return 0;
- else if (config_my_pid && (config_my_pid == pid))
+ else if (config_my_pid == pid)
return 0;
- tep_print_event(trace_event->tep, s, record, "%16s-%-8d ", TEP_PRINT_COMM, TEP_PRINT_PID);
+ tep_print_event(trace_event->tep, s, record, "%16s-%-8d [%.3d] ",
+ TEP_PRINT_COMM, TEP_PRINT_PID, TEP_PRINT_CPU);
- trace_seq_printf(s, "[%.3d] event ", cpu);
+ if (config_is_container)
+ tep_print_event(trace_event->tep, s, record, "%s ", TEP_PRINT_NAME);
+ else
+ trace_seq_printf(s, "event ");
- if (config_has_id)
- trace_seq_printf(s, "%8llu ", id);
+ if (config_has_id) {
+ if (missing_id)
+ /* placeholder if we are dealing with a mixed-type container*/
+ trace_seq_printf(s, " ");
+ else
+ trace_seq_printf(s, "%8llu ", id);
+ }
state = tep_get_field_raw(s, trace_event, "state", record, &val, 0);
event = tep_get_field_raw(s, trace_event, "event", record, &val, 0);
@@ -394,9 +481,10 @@ ikm_error_handler(struct trace_seq *s, struct tep_record *record,
int cpu = record->cpu;
char *state, *event;
int val;
+ bool missing_id;
if (config_has_id)
- tep_get_field_val(s, trace_event, "id", record, &id, 1);
+ missing_id = tep_get_field_val(s, trace_event, "id", record, &id, 1);
tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1);
@@ -405,10 +493,20 @@ ikm_error_handler(struct trace_seq *s, struct tep_record *record,
else if (config_my_pid == pid)
return 0;
- trace_seq_printf(s, "%8lld [%03d] error ", pid, cpu);
+ trace_seq_printf(s, "%8lld [%03d] ", pid, cpu);
- if (config_has_id)
- trace_seq_printf(s, "%8llu ", id);
+ if (config_is_container)
+ tep_print_event(trace_event->tep, s, record, "%s ", TEP_PRINT_NAME);
+ else
+ trace_seq_printf(s, "error ");
+
+ if (config_has_id) {
+ if (missing_id)
+ /* placeholder if we are dealing with a mixed-type container*/
+ trace_seq_printf(s, " ");
+ else
+ trace_seq_printf(s, "%8llu ", id);
+ }
state = tep_get_field_raw(s, trace_event, "state", record, &val, 0);
event = tep_get_field_raw(s, trace_event, "event", record, &val, 0);
@@ -421,6 +519,64 @@ ikm_error_handler(struct trace_seq *s, struct tep_record *record,
return 0;
}
+static int ikm_enable_trace_events(char *monitor_name, struct trace_instance *inst)
+{
+ char event[MAX_DA_NAME_LEN + 7]; /* max(error_,event_) + '0' = 7 */
+ int retval;
+
+ snprintf(event, sizeof(event), "event_%s", monitor_name);
+ retval = tracefs_event_enable(inst->inst, "rv", event);
+ if (retval)
+ return -1;
+
+ tep_register_event_handler(inst->tep, -1, "rv", event,
+ ikm_event_handler, NULL);
+
+ snprintf(event, sizeof(event), "error_%s", monitor_name);
+ retval = tracefs_event_enable(inst->inst, "rv", event);
+ if (retval)
+ return -1;
+
+ tep_register_event_handler(inst->tep, -1, "rv", event,
+ ikm_error_handler, NULL);
+
+ /* set if at least 1 monitor has id in case of a container */
+ config_has_id = ikm_has_id(monitor_name);
+ if (config_has_id < 0)
+ return -1;
+
+
+ return 0;
+}
+
+static int ikm_enable_trace_container(char *monitor_name,
+ struct trace_instance *inst)
+{
+ DIR *dp;
+ char *abs_path, rv_path[MAX_PATH];
+ struct dirent *ep;
+ int retval = 0;
+
+ snprintf(rv_path, MAX_PATH, "rv/monitors/%s", monitor_name);
+ abs_path = tracefs_instance_get_file(NULL, rv_path);
+ if (!abs_path)
+ return -1;
+ dp = opendir(abs_path);
+ if (!dp)
+ goto out_free;
+
+ while (!retval && (ep = readdir(dp))) {
+ if (ep->d_type != DT_DIR || ep->d_name[0] == '.')
+ continue;
+ retval = ikm_enable_trace_events(ep->d_name, inst);
+ }
+
+ closedir(dp);
+out_free:
+ free(abs_path);
+ return retval;
+}
+
/*
* ikm_setup_trace_instance - set up a tracing instance to collect data
*
@@ -430,19 +586,12 @@ ikm_error_handler(struct trace_seq *s, struct tep_record *record,
*/
static struct trace_instance *ikm_setup_trace_instance(char *monitor_name)
{
- char event[MAX_DA_NAME_LEN + 7]; /* max(error_,event_) + '0' = 7 */
struct trace_instance *inst;
int retval;
if (!config_trace)
return NULL;
- config_has_id = ikm_has_id(monitor_name);
- if (config_has_id < 0) {
- err_msg("ikm: failed to read monitor %s event format\n", monitor_name);
- goto out_err;
- }
-
/* alloc data */
inst = calloc(1, sizeof(*inst));
if (!inst) {
@@ -454,23 +603,13 @@ static struct trace_instance *ikm_setup_trace_instance(char *monitor_name)
if (retval)
goto out_free;
- /* enable events */
- snprintf(event, sizeof(event), "event_%s", monitor_name);
- retval = tracefs_event_enable(inst->inst, "rv", event);
+ if (config_is_container)
+ retval = ikm_enable_trace_container(monitor_name, inst);
+ else
+ retval = ikm_enable_trace_events(monitor_name, inst);
if (retval)
goto out_inst;
- tep_register_event_handler(inst->tep, -1, "rv", event,
- ikm_event_handler, NULL);
-
- snprintf(event, sizeof(event), "error_%s", monitor_name);
- retval = tracefs_event_enable(inst->inst, "rv", event);
- if (retval)
- goto out_inst;
-
- tep_register_event_handler(inst->tep, -1, "rv", event,
- ikm_error_handler, NULL);
-
/* ready to enable */
tracefs_trace_on(inst->inst);
@@ -595,7 +734,7 @@ static int parse_arguments(char *monitor_name, int argc, char **argv)
config_reactor = optarg;
break;
case 's':
- config_my_pid = 0;
+ config_my_pid = -1;
break;
case 't':
config_trace = 1;
@@ -633,32 +772,41 @@ static int parse_arguments(char *monitor_name, int argc, char **argv)
int ikm_run_monitor(char *monitor_name, int argc, char **argv)
{
struct trace_instance *inst = NULL;
+ char *nested_name, full_name[2*MAX_DA_NAME_LEN];
int retval;
- /*
- * Check if monitor exists by seeing it is enabled.
- */
- retval = __ikm_read_enable(monitor_name);
- if (retval < 0)
+ nested_name = strstr(monitor_name, ":");
+ if (nested_name)
+ ++nested_name;
+ else
+ nested_name = monitor_name;
+
+ retval = __ikm_find_monitor_name(monitor_name, full_name);
+ if (!retval)
return 0;
+ if (retval < 0) {
+ err_msg("ikm: error finding monitor %s\n", nested_name);
+ return -1;
+ }
+ retval = __ikm_read_enable(full_name);
if (retval) {
- err_msg("ikm: monitor %s (in-kernel) is already enabled\n", monitor_name);
+ err_msg("ikm: monitor %s (in-kernel) is already enabled\n", nested_name);
return -1;
}
/* we should be good to go */
- retval = parse_arguments(monitor_name, argc, argv);
+ retval = parse_arguments(full_name, argc, argv);
if (retval)
- ikm_usage(1, monitor_name, "ikm: failed parsing arguments");
+ ikm_usage(1, nested_name, "ikm: failed parsing arguments");
if (config_trace) {
- inst = ikm_setup_trace_instance(monitor_name);
+ inst = ikm_setup_trace_instance(nested_name);
if (!inst)
return -1;
}
- retval = ikm_enable(monitor_name);
+ retval = ikm_enable(full_name);
if (retval < 0)
goto out_free_instance;
@@ -682,17 +830,17 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
sleep(1);
}
- ikm_disable(monitor_name);
+ ikm_disable(full_name);
ikm_destroy_trace_instance(inst);
if (config_reactor && config_initial_reactor)
- ikm_write_reactor(monitor_name, config_initial_reactor);
+ ikm_write_reactor(full_name, config_initial_reactor);
return 1;
out_free_instance:
ikm_destroy_trace_instance(inst);
if (config_reactor && config_initial_reactor)
- ikm_write_reactor(monitor_name, config_initial_reactor);
+ ikm_write_reactor(full_name, config_initial_reactor);
return -1;
}
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index 1ddb85532816..b8fe24a87d97 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -41,30 +41,42 @@ static void rv_list(int argc, char **argv)
{
static const char *const usage[] = {
"",
- " usage: rv list [-h]",
+ " usage: rv list [-h] [container]",
"",
" list all available monitors",
"",
" -h/--help: print this menu",
+ "",
+ " [container]: list only monitors in this container",
NULL,
};
- int i;
-
- if (argc > 1) {
+ int i, print_help = 0, retval = 0;
+ char *container = NULL;
+
+ if (argc == 2) {
+ if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+ print_help = 1;
+ retval = 0;
+ } else if (argv[1][0] == '-') {
+ /* assume invalid option */
+ print_help = 1;
+ retval = 1;
+ } else
+ container = argv[1];
+ } else if (argc > 2) {
+ /* more than 2 is always usage */
+ print_help = 1;
+ retval = 1;
+ }
+ if (print_help) {
fprintf(stderr, "rv version %s\n", VERSION);
-
- /* more than 1 is always usage */
for (i = 0; usage[i]; i++)
fprintf(stderr, "%s\n", usage[i]);
-
- /* but only -h is valid */
- if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
- exit(0);
- else
- exit(1);
+ exit(retval);
}
- ikm_list_monitors();
+ ikm_list_monitors(container);
+
exit(0);
}
@@ -179,6 +191,7 @@ int main(int argc, char **argv)
* and exit.
*/
signal(SIGINT, stop_rv);
+ signal(SIGTERM, stop_rv);
rv_mon(argc - 1, &argv[1]);
}
diff --git a/tools/verification/rvgen/.gitignore b/tools/verification/rvgen/.gitignore
new file mode 100644
index 000000000000..1e288a076560
--- /dev/null
+++ b/tools/verification/rvgen/.gitignore
@@ -0,0 +1,3 @@
+__pycache__/
+parser.out
+parsetab.py
diff --git a/tools/verification/rvgen/Makefile b/tools/verification/rvgen/Makefile
new file mode 100644
index 000000000000..cfc4056c1e87
--- /dev/null
+++ b/tools/verification/rvgen/Makefile
@@ -0,0 +1,27 @@
+INSTALL=install
+
+prefix ?= /usr
+bindir ?= $(prefix)/bin
+mandir ?= $(prefix)/share/man
+srcdir ?= $(prefix)/src
+
+PYLIB ?= $(shell python3 -c 'import sysconfig; print (sysconfig.get_path("purelib"))')
+
+.PHONY: all
+all:
+
+.PHONY: clean
+clean:
+
+.PHONY: install
+install:
+ $(INSTALL) rvgen/automata.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/automata.py
+ $(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/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
+ $(INSTALL) rvgen/ltl2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2k.py
+ $(INSTALL) __main__.py -D -m 755 $(DESTDIR)$(bindir)/rvgen
+ cp -rp rvgen/templates $(DESTDIR)$(PYLIB)/rvgen/
diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
new file mode 100644
index 000000000000..3be7f85fe37b
--- /dev/null
+++ b/tools/verification/rvgen/__main__.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
+#
+# dot2k: transform dot files into a monitor for the Linux kernel.
+#
+# For further information, see:
+# Documentation/trace/rv/da_monitor_synthesis.rst
+
+if __name__ == '__main__':
+ from rvgen.dot2k import da2k, ha2k
+ from rvgen.generator import Monitor
+ from rvgen.container import Container
+ from rvgen.ltl2k import ltl2k
+ from rvgen.automata import AutomataError
+ import argparse
+ import sys
+
+ parser = argparse.ArgumentParser(description='Generate kernel rv monitor')
+ parser.add_argument("-D", "--description", dest="description", required=False)
+ parser.add_argument("-a", "--auto_patch", dest="auto_patch",
+ action="store_true", required=False,
+ help="Patch the kernel in place")
+
+ subparsers = parser.add_subparsers(dest="subcmd", required=True)
+
+ monitor_parser = subparsers.add_parser("monitor")
+ monitor_parser.add_argument('-n', "--model_name", dest="model_name")
+ monitor_parser.add_argument("-p", "--parent", dest="parent",
+ required=False, help="Create a monitor nested to parent")
+ monitor_parser.add_argument('-c', "--class", dest="monitor_class", required=True,
+ help="Monitor class, either \"da\", \"ha\" or \"ltl\"")
+ monitor_parser.add_argument('-s', "--spec", dest="spec", required=True,
+ help="Monitor specification file")
+ monitor_parser.add_argument('-t', "--monitor_type", dest="monitor_type", required=True,
+ help=f"Available options: {', '.join(Monitor.monitor_types.keys())}")
+
+ container_parser = subparsers.add_parser("container")
+ container_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
+
+ params = parser.parse_args()
+
+ try:
+ if params.subcmd == "monitor":
+ print(f"Opening and parsing the specification file {params.spec}")
+ if params.monitor_class == "da":
+ monitor = da2k(params.spec, params.monitor_type, vars(params))
+ elif params.monitor_class == "ha":
+ monitor = ha2k(params.spec, params.monitor_type, vars(params))
+ elif params.monitor_class == "ltl":
+ monitor = ltl2k(params.spec, params.monitor_type, vars(params))
+ else:
+ print("Unknown monitor class:", params.monitor_class)
+ 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)
+ sys.exit(1)
+
+ print(f"Writing the monitor into the directory {monitor.name}")
+ monitor.print_files()
+ print("Almost done, checklist")
+ if params.subcmd == "monitor":
+ print(f" - Edit the {monitor.name}/{monitor.name}.c to add the instrumentation")
+ print(monitor.fill_tracepoint_tooltip())
+ print(monitor.fill_makefile_tooltip())
+ print(monitor.fill_kconfig_tooltip())
+ print(monitor.fill_monitor_tooltip())
diff --git a/tools/verification/dot2/dot2c b/tools/verification/rvgen/dot2c
index 3fe89ab88b65..1012becc7fab 100644
--- a/tools/verification/dot2/dot2c
+++ b/tools/verification/rvgen/dot2c
@@ -14,9 +14,8 @@
# Documentation/trace/rv/deterministic_automata.rst
if __name__ == '__main__':
- from dot2 import dot2c
+ from rvgen import dot2c
import argparse
- import sys
parser = argparse.ArgumentParser(description='dot2c: converts a .dot file into a C structure')
parser.add_argument('dot_file', help='The dot file to be converted')
diff --git a/tools/verification/rvgen/rvgen/automata.py b/tools/verification/rvgen/rvgen/automata.py
new file mode 100644
index 000000000000..b9f8149f7118
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/automata.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
+#
+# Automata class: parse an automaton in dot file digraph format into a python object
+#
+# For further information, see:
+# Documentation/trace/rv/deterministic_automata.rst
+
+import ntpath
+import re
+from typing import Iterator
+from itertools import islice
+
+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.
+
+ Raised when DOT file processing fails due to invalid format, I/O errors,
+ or malformed automaton definitions.
+ """
+
+class Automata:
+ """Automata class: Reads a dot file and parses it as an automaton.
+
+ It supports both deterministic and hybrid automata.
+
+ Attributes:
+ dot_file: A dot file with an state_automaton definition.
+ """
+
+ 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<env>[a-zA-Z_][a-zA-Z0-9_]+) # C-like identifier for the env var
+ (?P<op>[!<=>]{1,2}) # operator
+ (?P<val>
+ [0-9]+ | # numerical value
+ [A-Z_]+\(\) | # macro
+ [A-Z_]+ | # constant
+ [a-z_]+\(\) | # function
+ [a-z_]+ # parameter
+ )
+ (?P<unit>[a-z]{1,2})? # optional unit for numerical values
+ """, re.VERBOSE)
+ constraint_reset = re.compile(r"^reset\((?P<env>[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.states, self.initial_state, self.final_states = self.__get_state_variables()
+ self.env_types = {}
+ self.env_stored = set()
+ 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.events_start, self.events_start_run = self.__store_init_events()
+ self.env_stored = sorted(self.env_stored)
+ self.constraint_vars = sorted(self.constraint_vars)
+ self.self_loop_reset_events = sorted(self.self_loop_reset_events)
+
+ def __get_model_name(self) -> str:
+ basename = ntpath.basename(self.__dot_path)
+ if not basename.endswith(".dot") and not basename.endswith(".gv"):
+ print("not a dot file")
+ raise AutomataError(f"not a dot file: {self.__dot_path}")
+
+ model_name = ntpath.splitext(basename)[0]
+ if not model_name:
+ raise AutomataError(f"not a dot file: {self.__dot_path}")
+
+ 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 __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] = []
+ # 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
+
+ # 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('"', '')
+
+ # 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(" ", "")])
+
+ 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: 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 __create_matrix(self) -> tuple[list[list[str]], dict[_ConstraintKey, list[str]]]:
+ # transform the array into a dictionary
+ events = self.events
+ states = self.states
+ events_dict = {}
+ states_dict = {}
+ nr_event = 0
+ for event in events:
+ events_dict[event] = nr_event
+ nr_event += 1
+
+ nr_state = 0
+ for state in states:
+ states_dict[state] = nr_state
+ nr_state += 1
+
+ # 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 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
+
+ def __store_init_events(self) -> tuple[list[bool], list[bool]]:
+ events_start = [False] * len(self.events)
+ events_start_run = [False] * len(self.events)
+ for i in range(len(self.events)):
+ curr_event_will_init = 0
+ curr_event_from_init = False
+ curr_event_used = 0
+ 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:
+ curr_event_will_init += 1
+ if self.function[0][i] != self.invalid_state_str:
+ curr_event_from_init = True
+ # this event always leads to init
+ if curr_event_will_init and curr_event_used == curr_event_will_init:
+ events_start[i] = True
+ # this event is only called from init
+ if curr_event_from_init and curr_event_used == 1:
+ events_start_run[i] = True
+ return events_start, events_start_run
+
+ def is_start_event(self, event: str) -> bool:
+ return self.events_start[self.events.index(event)]
+
+ def is_start_run_event(self, event: str) -> bool:
+ # prefer handle_start_event if there
+ if any(self.events_start):
+ return False
+ return self.events_start_run[self.events.index(event)]
+
+ 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/container.py b/tools/verification/rvgen/rvgen/container.py
new file mode 100644
index 000000000000..51f188530b4d
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/container.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
+#
+# Generator for runtime verification monitor container
+
+from . import generator
+
+
+class Container(generator.RVGenerator):
+ template_dir = "container"
+
+ def __init__(self, extra_params={}):
+ super().__init__(extra_params)
+ self.name = extra_params.get("model_name")
+ self.main_h = self._read_template_file("main.h")
+
+ def fill_model_h(self):
+ main_h = self.main_h
+ main_h = main_h.replace("%%MODEL_NAME%%", self.name)
+ return main_h
+
+ def fill_kconfig_tooltip(self):
+ """Override to produce a marker for this container in the Kconfig"""
+ container_marker = self._kconfig_marker(self.name) + "\n"
+ result = super().fill_kconfig_tooltip()
+ if self.auto_patch:
+ self._patch_file("Kconfig",
+ self._kconfig_marker(), container_marker)
+ return result
+ return result + container_marker
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
new file mode 100644
index 000000000000..fc85ba1f649e
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
+#
+# dot2c: parse an automaton in dot file digraph format into a C
+#
+# This program was written in the development of this paper:
+# de Oliveira, D. B. and Cucinotta, T. and de Oliveira, R. S.
+# "Efficient Formal Verification for the Linux Kernel." International
+# Conference on Software Engineering and Formal Methods. Springer, Cham, 2019.
+#
+# For further information, see:
+# Documentation/trace/rv/deterministic_automata.rst
+
+from .automata import Automata, AutomataError
+
+class Dot2c(Automata):
+ enum_suffix = ""
+ enum_states_def = "states"
+ enum_events_def = "events"
+ enum_envs_def = "envs"
+ struct_automaton_def = "automaton"
+ var_automaton_def = "aut"
+
+ def __init__(self, file_path, model_name=None):
+ super().__init__(file_path, model_name)
+ self.line_length = 100
+
+ def __get_enum_states_content(self) -> list[str]:
+ buff = []
+ buff.append(f"\t{self.initial_state}{self.enum_suffix},")
+ for state in self.states:
+ if state != self.initial_state:
+ buff.append(f"\t{state}{self.enum_suffix},")
+ buff.append(f"\tstate_max{self.enum_suffix},")
+
+ return buff
+
+ def format_states_enum(self) -> list[str]:
+ buff = []
+ buff.append(f"enum {self.enum_states_def} {{")
+ buff += self.__get_enum_states_content()
+ buff.append("};\n")
+
+ return buff
+
+ def __get_enum_events_content(self) -> list[str]:
+ buff = []
+ for event in self.events:
+ buff.append(f"\t{event}{self.enum_suffix},")
+
+ buff.append(f"\tevent_max{self.enum_suffix},")
+
+ return buff
+
+ def format_events_enum(self) -> list[str]:
+ buff = []
+ buff.append(f"enum {self.enum_events_def} {{")
+ buff += self.__get_enum_events_content()
+ buff.append("};\n")
+
+ return buff
+
+ def __get_non_stored_envs(self) -> list[str]:
+ return [e for e in self.envs if e not in self.env_stored]
+
+ def __get_enum_envs_content(self) -> list[str]:
+ buff = []
+ # We first place env variables that have a u64 storage.
+ # Those are limited by MAX_HA_ENV_LEN, other variables
+ # are read only and don't require a storage.
+ unstored = self.__get_non_stored_envs()
+ for env in list(self.env_stored) + unstored:
+ buff.append(f"\t{env}{self.enum_suffix},")
+
+ buff.append(f"\tenv_max{self.enum_suffix},")
+ max_stored = unstored[0] if len(unstored) else "env_max"
+ buff.append(f"\tenv_max_stored{self.enum_suffix} = {max_stored}{self.enum_suffix},")
+
+ return buff
+
+ def format_envs_enum(self) -> list[str]:
+ buff = []
+ if self.is_hybrid_automata():
+ buff.append(f"enum {self.enum_envs_def} {{")
+ buff += self.__get_enum_envs_content()
+ buff.append("};\n")
+ buff.append(f"_Static_assert(env_max_stored{self.enum_suffix} <= MAX_HA_ENV_LEN,"
+ ' "Not enough slots");')
+ if {"ns", "us", "ms", "s"}.intersection(self.env_types.values()):
+ buff.append("#define HA_CLK_NS")
+ buff.append("")
+ return buff
+
+ def get_minimun_type(self) -> str:
+ min_type = "unsigned char"
+
+ if len(self.states) > 255:
+ min_type = "unsigned short"
+
+ if len(self.states) > 65535:
+ min_type = "unsigned int"
+
+ if len(self.states) > 1000000:
+ raise AutomataError(f"Too many states: {len(self.states)}")
+
+ return min_type
+
+ def format_automaton_definition(self) -> list[str]:
+ min_type = self.get_minimun_type()
+ buff = []
+ buff.append(f"struct {self.struct_automaton_def} {{")
+ buff.append(f"\tchar *state_names[state_max{self.enum_suffix}];")
+ buff.append(f"\tchar *event_names[event_max{self.enum_suffix}];")
+ if self.is_hybrid_automata():
+ buff.append(f"\tchar *env_names[env_max{self.enum_suffix}];")
+ buff.append(f"\t{min_type} function[state_max{self.enum_suffix}][event_max{self.enum_suffix}];")
+ buff.append(f"\t{min_type} initial_state;")
+ buff.append(f"\tbool final_states[state_max{self.enum_suffix}];")
+ buff.append("};\n")
+ return buff
+
+ def format_aut_init_header(self) -> list[str]:
+ buff = []
+ buff.append(f"static const struct {self.struct_automaton_def} {self.var_automaton_def} = {{")
+ return buff
+
+ def __get_string_vector_per_line_content(self, entries: list[str]) -> str:
+ buff = []
+ for entry in entries:
+ buff.append(f"\t\t\"{entry}\",")
+ return "\n".join(buff)
+
+ def format_aut_init_events_string(self) -> list[str]:
+ buff = []
+ buff.append("\t.event_names = {")
+ buff.append(self.__get_string_vector_per_line_content(self.events))
+ buff.append("\t},")
+ return buff
+
+ 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("\t},")
+
+ return buff
+
+ def format_aut_init_envs_string(self) -> list[str]:
+ buff = []
+ if self.is_hybrid_automata():
+ buff.append("\t.env_names = {")
+ # maintain consistent order with the enum
+ ordered_envs = list(self.env_stored) + self.__get_non_stored_envs()
+ buff.append(self.__get_string_vector_per_line_content(ordered_envs))
+ buff.append("\t},")
+
+ return buff
+
+ def __get_max_strlen_of_states(self) -> int:
+ max_state_name = len(max(self.states, key=len))
+ return max(max_state_name, len(self.invalid_state_str))
+
+ def get_aut_init_function(self) -> str:
+ nr_states = len(self.states)
+ nr_events = len(self.events)
+ buff = []
+
+ maxlen = self.__get_max_strlen_of_states() + len(self.enum_suffix)
+ tab_braces = 2 * 8 + 2 + 1 # "\t\t{ " ... "}"
+ comma_space = 2 # ", " count last comma here
+ linetoolong = tab_braces + (maxlen + comma_space) * nr_events > self.line_length
+ for x in range(nr_states):
+ line = "\t\t{\n" if linetoolong else "\t\t{ "
+ for y in range(nr_events):
+ next_state = self.function[x][y]
+ if next_state != self.invalid_state_str:
+ next_state = self.function[x][y] + self.enum_suffix
+
+ if linetoolong:
+ line += f"\t\t\t{next_state}"
+ else:
+ line += f"{next_state:>{maxlen}}"
+ if y != nr_events - 1:
+ line += ",\n" if linetoolong else ", "
+ else:
+ line += ",\n\t\t}," if linetoolong else " },"
+ buff.append(line)
+
+ return '\n'.join(buff)
+
+ def format_aut_init_function(self) -> list[str]:
+ buff = []
+ buff.append("\t.function = {")
+ buff.append(self.get_aut_init_function())
+ buff.append("\t},")
+
+ return buff
+
+ def get_aut_init_initial_state(self) -> str:
+ return self.initial_state
+
+ def format_aut_init_initial_state(self) -> list[str]:
+ buff = []
+ initial_state = self.get_aut_init_initial_state()
+ buff.append("\t.initial_state = " + initial_state + self.enum_suffix + ",")
+
+ return buff
+
+ def get_aut_init_final_states(self) -> str:
+ line = ""
+ first = True
+ for state in self.states:
+ if not first:
+ line = line + ', '
+ else:
+ first = False
+
+ if state in self.final_states:
+ line = line + '1'
+ else:
+ line = line + '0'
+ return line
+
+ def format_aut_init_final_states(self) -> list[str]:
+ buff = []
+ buff.append(f"\t.final_states = {{ {self.get_aut_init_final_states()} }},")
+
+ return buff
+
+ def __get_automaton_initialization_footer_string(self) -> str:
+ footer = "};\n"
+ return footer
+
+ def format_aut_init_footer(self) -> list[str]:
+ buff = []
+ buff.append(self.__get_automaton_initialization_footer_string())
+
+ return buff
+
+ def format_invalid_state(self) -> list[str]:
+ buff = []
+ buff.append(f"#define {self.invalid_state_str} state_max{self.enum_suffix}\n")
+
+ return buff
+
+ def format_model(self) -> list[str]:
+ buff = []
+ buff += self.format_states_enum()
+ buff += self.format_invalid_state()
+ buff += self.format_events_enum()
+ buff += self.format_envs_enum()
+ buff += self.format_automaton_definition()
+ buff += self.format_aut_init_header()
+ buff += self.format_aut_init_states_string()
+ buff += self.format_aut_init_events_string()
+ buff += self.format_aut_init_envs_string()
+ buff += self.format_aut_init_function()
+ buff += self.format_aut_init_initial_state()
+ buff += self.format_aut_init_final_states()
+ buff += self.format_aut_init_footer()
+
+ return buff
+
+ def print_model_classic(self):
+ buff = self.format_model()
+ print('\n'.join(buff))
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
new file mode 100644
index 000000000000..e6f476b903b0
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -0,0 +1,611 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
+#
+# dot2k: transform dot files into a monitor for the Linux kernel.
+#
+# 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
+
+
+class dot2k(Monitor, Dot2c):
+ template_dir = "dot2k"
+
+ def __init__(self, file_path, MonitorType, extra_params={}):
+ self.monitor_type = MonitorType
+ Monitor.__init__(self, extra_params)
+ Dot2c.__init__(self, file_path, extra_params.get("model_name"))
+ self.enum_suffix = f"_{self.name}"
+ self.enum_suffix = f"_{self.name}"
+ self.monitor_class = extra_params["monitor_class"]
+
+ def fill_monitor_type(self) -> str:
+ buff = [ self.monitor_type.upper() ]
+ buff += self._fill_timer_type()
+ if self.monitor_type == "per_obj":
+ buff.append("typedef /* XXX: define the target type */ *monitor_target;")
+ return "\n".join(buff)
+
+ def fill_tracepoint_handlers_skel(self) -> str:
+ buff = []
+ buff += self._fill_hybrid_definitions()
+ for event in self.events:
+ buff.append(f"static void handle_{event}(void *data, /* XXX: fill header */)")
+ buff.append("{")
+ handle = "handle_event"
+ if self.is_start_event(event):
+ buff.append("\t/* XXX: validate that this event always leads to the initial state */")
+ handle = "handle_start_event"
+ elif self.is_start_run_event(event):
+ buff.append("\t/* XXX: validate that this event is only valid in the initial state */")
+ handle = "handle_start_run_event"
+ if self.monitor_type == "per_task":
+ buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;")
+ buff.append(f"\tda_{handle}(p, {event}{self.enum_suffix});")
+ elif self.monitor_type == "per_obj":
+ buff.append("\tint id = /* XXX: how do I get the id? */;")
+ buff.append("\tmonitor_target t = /* XXX: how do I get t? */;")
+ buff.append(f"\tda_{handle}(id, t, {event}{self.enum_suffix});")
+ else:
+ buff.append(f"\tda_{handle}({event}{self.enum_suffix});")
+ 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});")
+ 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});")
+ return '\n'.join(buff)
+
+ def fill_model_h_header(self) -> list[str]:
+ buff = []
+ buff.append("/* SPDX-License-Identifier: GPL-2.0 */")
+ buff.append("/*")
+ buff.append(f" * Automatically generated C representation of {self.name} automaton")
+ buff.append(" * For further information about this format, see kernel documentation:")
+ buff.append(" * Documentation/trace/rv/deterministic_automata.rst")
+ buff.append(" */")
+ buff.append("")
+ buff.append(f"#define MONITOR_NAME {self.name}")
+ buff.append("")
+
+ return buff
+
+ def fill_model_h(self) -> str:
+ #
+ # Adjust the definition names
+ #
+ self.enum_states_def = f"states_{self.name}"
+ self.enum_events_def = f"events_{self.name}"
+ self.enum_envs_def = f"envs_{self.name}"
+ self.struct_automaton_def = f"automaton_{self.name}"
+ self.var_automaton_def = f"automaton_{self.name}"
+
+ buff = self.fill_model_h_header()
+ buff += self.format_model()
+
+ return '\n'.join(buff)
+
+ def _is_id_monitor(self) -> bool:
+ return self.monitor_type in ("per_task", "per_obj")
+
+ def fill_monitor_class_type(self) -> str:
+ if self._is_id_monitor():
+ return "DA_MON_EVENTS_ID"
+ return "DA_MON_EVENTS_IMPLICIT"
+
+ def fill_monitor_class(self) -> str:
+ if self._is_id_monitor():
+ return "da_monitor_id"
+ return "da_monitor"
+
+ def fill_tracepoint_args_skel(self, tp_type: str) -> str:
+ buff = []
+ tp_args_event = [
+ ("char *", "state"),
+ ("char *", "event"),
+ ("char *", "next_state"),
+ ("bool ", "final_state"),
+ ]
+ tp_args_error = [
+ ("char *", "state"),
+ ("char *", "event"),
+ ]
+ tp_args_error_env = tp_args_error + [("char *", "env")]
+ tp_args_dict = {
+ "event": tp_args_event,
+ "error": tp_args_error,
+ "error_env": tp_args_error_env
+ }
+ tp_args_id = ("int ", "id")
+ tp_args = tp_args_dict[tp_type]
+ if self._is_id_monitor():
+ tp_args.insert(0, tp_args_id)
+ tp_proto_c = ", ".join([a + b for a, b in tp_args])
+ tp_args_c = ", ".join([b for a, b in tp_args])
+ buff.append(f" TP_PROTO({tp_proto_c}),")
+ buff.append(f" TP_ARGS({tp_args_c})")
+ return '\n'.join(buff)
+
+ def _fill_hybrid_definitions(self) -> list:
+ """Stub, not valid for deterministic automata"""
+ return []
+
+ def _fill_timer_type(self) -> list:
+ """Stub, not valid for deterministic automata"""
+ return []
+
+ def fill_main_c(self) -> str:
+ main_c = super().fill_main_c()
+
+ min_type = self.get_minimun_type()
+ nr_events = len(self.events)
+ monitor_type = self.fill_monitor_type()
+
+ main_c = main_c.replace("%%MIN_TYPE%%", min_type)
+ main_c = main_c.replace("%%NR_EVENTS%%", str(nr_events))
+ main_c = main_c.replace("%%MONITOR_TYPE%%", monitor_type)
+ main_c = main_c.replace("%%MONITOR_CLASS%%", self.monitor_class)
+
+ return main_c
+
+class da2k(dot2k):
+ """Deterministic automata only"""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.is_hybrid_automata():
+ raise AutomataError("Detected hybrid automaton, use the 'ha' class")
+
+class ha2k(dot2k):
+ """Hybrid automata only"""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ 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()
+
+ def fill_monitor_class_type(self) -> str:
+ if self._is_id_monitor():
+ return "HA_MON_EVENTS_ID"
+ return "HA_MON_EVENTS_IMPLICIT"
+
+ def fill_monitor_class(self) -> str:
+ """
+ Used for tracepoint classes, since they are shared we keep da
+ instead of ha (also for the ha specific tracepoints).
+ The tracepoint class is not visible to the tools.
+ """
+ return super().fill_monitor_class()
+
+ def __adjust_value(self, value: str | int, unit: str | None) -> str:
+ """Adjust the value in ns"""
+ try:
+ value = int(value)
+ except ValueError:
+ # it's a constant, a parameter or a function
+ if value.endswith("()"):
+ return value.replace("()", "(ha_mon)")
+ return value
+ match unit:
+ case "us":
+ value *= 10**3
+ case "ms":
+ value *= 10**6
+ case "s":
+ 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 __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.rstrip(f"_{self.name}") in self.envs
+ return env
+
+ def __start_to_invariant_check(self, constr: str) -> str:
+ # by default assume the timer has ns expiration
+ env = self.__get_constraint_env(constr)
+ clock_type = "ns"
+ if self.env_types.get(env.rstrip(f"_{self.name}")) == "j":
+ clock_type = "jiffy"
+
+ return f"return ha_check_invariant_{clock_type}(ha_mon, {env}, time_ns)"
+
+ 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 __format_guard_rules(self, rules: list[str]) -> list[str]:
+ """
+ Merge guard constraints as a single C return statement.
+ If the rules include a stored env, also check its validity.
+ Break lines in a best effort way that tries to keep readability.
+ """
+ if not rules:
+ return []
+
+ invalid_checks = [f"ha_monitor_env_invalid(ha_mon, {env}{self.enum_suffix}) ||"
+ for env in self.env_stored if any(env in rule for rule in rules)]
+ if invalid_checks and len(rules) > 1:
+ rules[0] = "(" + rules[0]
+ rules[-1] = rules[-1] + ")"
+ rules = invalid_checks + rules
+
+ 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<N)
+ if self.is_event_constraint(key):
+ if not rule and not reset:
+ raise AutomataError("Unrecognised event constraint "
+ f"({self.states[key[0]]}/{self.events[key[1]]}: {constr})")
+ if rule and (rule["env"] in self.env_types and
+ rule["env"] not in self.env_stored):
+ raise AutomataError("Clocks in hybrid automata always require a storage"
+ f" ({rule["env"]})")
+ else:
+ if not rule:
+ raise AutomataError("Unrecognised state constraint "
+ f"({self.states[key]}: {constr})")
+ if rule["env"] not in self.env_stored:
+ raise AutomataError("State constraints always require a storage "
+ f"({rule["env"]})")
+ if rule["op"] not in ["<", "<="]:
+ raise AutomataError("State constraints must be clock expirations like"
+ f" clk<N ({rule.string})")
+
+ def __parse_constraints(self) -> 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]:
+ buff = []
+ if not self.invariants:
+ return []
+
+ buff.append(
+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})")
+ buff.append(f"\t\t{check_str};")
+ _else = "else "
+
+ 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 = []
+ if not self.guards:
+ return []
+
+ buff.append(
+f"""static inline bool ha_verify_guards(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)
+{{
+\tbool res = true;
+""")
+
+ _else = ""
+ for edge, constr in sorted(self.guards.items()):
+ 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:
+ buff[-1] += " {"
+ buff += [f"\t\t{c};" for c in constr.split(";")]
+ if constr.count(";") > 0:
+ _else = "} else "
+ else:
+ _else = "else "
+ if _else[0] == "}":
+ buff.append("\t}")
+ 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]:
+ buff = []
+ if not self.invariants:
+ return []
+
+ buff.append(
+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}"
+ for e in self.self_loop_reset_events]
+ condition_str = " && ".join(conditions)
+ 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};")
+ _else = "else "
+
+ for state in self.invariants:
+ buff.append(f"\telse if (curr_state == {self.states[state]}{self.enum_suffix})")
+ buff.append("\t\tha_cancel_timer(ha_mon);")
+
+ buff.append("}\n")
+ return buff
+
+ def __fill_constr_func(self) -> list[str]:
+ buff = []
+ if not self.constraints:
+ return []
+
+ buff.append(
+"""/*
+ * 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.
+ * 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()
+
+ buff.append(
+f"""static bool ha_verify_constraint(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)
+{{""")
+
+ 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, "
+ "next_state, time_ns))\n\t\treturn false;\n")
+
+ if self.invariants:
+ buff.append("\tha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);\n")
+
+ buff.append("\treturn true;\n}\n")
+ return buff
+
+ def __fill_env_getter(self, env: str) -> str:
+ if env in self.env_types:
+ match self.env_types[env]:
+ case "ns" | "us" | "ms" | "s":
+ return "ha_get_clk_ns(ha_mon, env, time_ns);"
+ case "j":
+ return "ha_get_clk_jiffy(ha_mon, env);"
+ return f"/* XXX: how do I read {env}? */"
+
+ def __fill_env_resetter(self, env: str) -> str:
+ if env in self.env_types:
+ match self.env_types[env]:
+ case "ns" | "us" | "ms" | "s":
+ return "ha_reset_clk_ns(ha_mon, env, time_ns);"
+ case "j":
+ return "ha_reset_clk_jiffy(ha_mon, env);"
+ return f"/* XXX: how do I reset {env}? */"
+
+ def __fill_hybrid_get_reset_functions(self) -> list[str]:
+ buff = []
+ if self.is_hybrid_automata():
+ for var in self.constraint_vars:
+ if var.endswith("()"):
+ func_name = var.replace("()", "")
+ if func_name.isupper():
+ buff.append(f"#define {func_name}(ha_mon) "
+ f"/* XXX: what is {func_name}(ha_mon)? */\n")
+ else:
+ buff.append(f"static inline u64 {func_name}(struct ha_monitor *ha_mon)\n{{")
+ buff.append(f"\treturn /* XXX: what is {func_name}(ha_mon)? */;")
+ buff.append("}\n")
+ elif var.isupper():
+ buff.append(f"#define {var} /* XXX: what is {var}? */\n")
+ else:
+ buff.append(f"static u64 {var} = /* XXX: default value */;")
+ buff.append(f"module_param({var}, ullong, 0644);\n")
+ buff.append("""/*
+ * 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.
+ */""")
+ buff.append("static u64 ha_get_env(struct ha_monitor *ha_mon, "
+ f"enum envs{self.enum_suffix} env, u64 time_ns)\n{{")
+ _else = ""
+ for env in self.envs:
+ buff.append(f"\t{_else}if (env == {env}{self.enum_suffix})")
+ buff.append(f"\t\treturn {self.__fill_env_getter(env)}")
+ _else = "else "
+ buff.append("\treturn ENV_INVALID_VALUE;\n}\n")
+ if len(self.env_stored):
+ buff.append("static void ha_reset_env(struct ha_monitor *ha_mon, "
+ f"enum envs{self.enum_suffix} env, u64 time_ns)\n{{")
+ _else = ""
+ for env in self.env_stored:
+ buff.append(f"\t{_else}if (env == {env}{self.enum_suffix})")
+ buff.append(f"\t\t{self.__fill_env_resetter(env)}")
+ _else = "else "
+ buff.append("}\n")
+ return buff
+
+ def _fill_hybrid_definitions(self) -> list[str]:
+ return self.__fill_hybrid_get_reset_functions() + self.__fill_constr_func()
+
+ def _fill_timer_type(self) -> list:
+ if self.invariants:
+ return [
+ "/* XXX: If the monitor has several instances, consider HA_TIMER_WHEEL */",
+ "#define HA_TIMER_TYPE HA_TIMER_HRTIMER"
+ ]
+ return []
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
new file mode 100644
index 000000000000..56f3bd8db850
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
+#
+# Abstract class for generating kernel runtime verification monitors from specification file
+
+import platform
+import os
+
+
+class RVGenerator:
+ rv_dir = "kernel/trace/rv"
+
+ def __init__(self, extra_params={}):
+ 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)
+ 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"
+ self.auto_patch = extra_params.get("auto_patch")
+ if self.auto_patch:
+ self.__fill_rv_kernel_dir()
+
+ def __fill_rv_kernel_dir(self):
+
+ # first try if we are running in the kernel tree root
+ if os.path.exists(self.rv_dir):
+ 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
+ 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)
+
+ # 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
+ return
+
+ raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?")
+
+ def _read_file(self, path):
+ with open(path, 'r') as fd:
+ content = fd.read()
+ return content
+
+ def _read_template_file(self, file):
+ try:
+ path = os.path.join(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)
+ return self._read_file(path)
+
+ def fill_parent(self):
+ return f"&rv_{self.parent}" if self.parent else "NULL"
+
+ def fill_include_parent(self):
+ if self.parent:
+ return f"#include <monitors/{self.parent}/{self.parent}.h>\n"
+ return ""
+
+ def fill_tracepoint_handlers_skel(self):
+ return "NotImplemented"
+
+ def fill_tracepoint_attach_probe(self):
+ return "NotImplemented"
+
+ def fill_tracepoint_detach_helper(self):
+ return "NotImplemented"
+
+ def fill_main_c(self):
+ main_c = self.main_c
+ tracepoint_handlers = self.fill_tracepoint_handlers_skel()
+ tracepoint_attach = self.fill_tracepoint_attach_probe()
+ tracepoint_detach = self.fill_tracepoint_detach_helper()
+ parent = self.fill_parent()
+ parent_include = self.fill_include_parent()
+
+ main_c = main_c.replace("%%MODEL_NAME%%", self.name)
+ main_c = main_c.replace("%%TRACEPOINT_HANDLERS_SKEL%%", tracepoint_handlers)
+ main_c = main_c.replace("%%TRACEPOINT_ATTACH%%", tracepoint_attach)
+ main_c = main_c.replace("%%TRACEPOINT_DETACH%%", tracepoint_detach)
+ main_c = main_c.replace("%%DESCRIPTION%%", self.description)
+ main_c = main_c.replace("%%PARENT%%", parent)
+ main_c = main_c.replace("%%INCLUDE_PARENT%%", parent_include)
+
+ return main_c
+
+ def fill_model_h(self):
+ return "NotImplemented"
+
+ def fill_monitor_class_type(self):
+ return "NotImplemented"
+
+ def fill_monitor_class(self):
+ return "NotImplemented"
+
+ def fill_tracepoint_args_skel(self, tp_type):
+ return "NotImplemented"
+
+ def fill_monitor_deps(self):
+ buff = []
+ buff.append(" # XXX: add dependencies if there")
+ if self.parent:
+ buff.append(f" depends on RV_MON_{self.parent.upper()}")
+ buff.append(" default y")
+ return '\n'.join(buff)
+
+ def fill_kconfig(self):
+ kconfig = self.kconfig
+ monitor_class_type = self.fill_monitor_class_type()
+ monitor_deps = self.fill_monitor_deps()
+ kconfig = kconfig.replace("%%MODEL_NAME%%", self.name)
+ kconfig = kconfig.replace("%%MODEL_NAME_UP%%", self.name.upper())
+ kconfig = kconfig.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
+ kconfig = kconfig.replace("%%DESCRIPTION%%", self.description)
+ kconfig = kconfig.replace("%%MONITOR_DEPS%%", monitor_deps)
+ return kconfig
+
+ def _patch_file(self, file, marker, line):
+ assert self.auto_patch
+ file_to_patch = os.path.join(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)
+
+ def fill_tracepoint_tooltip(self):
+ monitor_class_type = self.fill_monitor_class_type()
+ if self.auto_patch:
+ self._patch_file("rv_trace.h",
+ f"// Add new monitors based on CONFIG_{monitor_class_type} here",
+ f"#include <monitors/{self.name}/{self.name}_trace.h>")
+ return f" - Patching {self.rv_dir}/rv_trace.h, double check the result"
+
+ return f""" - Edit {self.rv_dir}/rv_trace.h:
+Add this line where other tracepoints are included and {monitor_class_type} is defined:
+#include <monitors/{self.name}/{self.name}_trace.h>
+"""
+
+ def _kconfig_marker(self, container=None) -> str:
+ return f"# Add new {container + ' ' if container else ''}monitors here"
+
+ def fill_kconfig_tooltip(self):
+ if self.auto_patch:
+ # monitors with a container should stay together in the Kconfig
+ self._patch_file("Kconfig",
+ self._kconfig_marker(self.parent),
+ f"source \"kernel/trace/rv/monitors/{self.name}/Kconfig\"")
+ return f" - Patching {self.rv_dir}/Kconfig, double check the result"
+
+ return f""" - Edit {self.rv_dir}/Kconfig:
+Add this line where other monitors are included:
+source \"kernel/trace/rv/monitors/{self.name}/Kconfig\"
+"""
+
+ def fill_makefile_tooltip(self):
+ name = self.name
+ name_up = name.upper()
+ if self.auto_patch:
+ self._patch_file("Makefile",
+ "# Add new monitors here",
+ f"obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o")
+ return f" - Patching {self.rv_dir}/Makefile, double check the result"
+
+ return f""" - Edit {self.rv_dir}/Makefile:
+Add this line where other monitors are included:
+obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
+"""
+
+ def fill_monitor_tooltip(self):
+ if self.auto_patch:
+ return f" - Monitor created in {self.rv_dir}/monitors/{self.name}"
+ return f" - Move {self.name}/ to the kernel's monitor directory ({self.rv_dir}/monitors)"
+
+ def __create_directory(self):
+ path = self.name
+ if self.auto_patch:
+ path = os.path.join(self.rv_dir, "monitors", path)
+ try:
+ os.mkdir(path)
+ except FileExistsError:
+ return
+
+ 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}"
+ if self.auto_patch:
+ path = os.path.join(self.rv_dir, "monitors", path)
+ self.__write_file(path, content)
+
+ def print_files(self):
+ main_c = self.fill_main_c()
+
+ self.__create_directory()
+
+ path = f"{self.name}.c"
+ self._create_file(path, main_c)
+
+ model_h = self.fill_model_h()
+ path = f"{self.name}.h"
+ self._create_file(path, model_h)
+
+ kconfig = self.fill_kconfig()
+ self._create_file("Kconfig", kconfig)
+
+
+class Monitor(RVGenerator):
+ monitor_types = {"global": 1, "per_cpu": 2, "per_task": 3, "per_obj": 4}
+
+ def __init__(self, extra_params={}):
+ super().__init__(extra_params)
+ self.trace_h = self._read_template_file("trace.h")
+
+ def fill_trace_h(self):
+ trace_h = self.trace_h
+ monitor_class = self.fill_monitor_class()
+ monitor_class_type = self.fill_monitor_class_type()
+ tracepoint_args_skel_event = self.fill_tracepoint_args_skel("event")
+ tracepoint_args_skel_error = self.fill_tracepoint_args_skel("error")
+ tracepoint_args_skel_error_env = self.fill_tracepoint_args_skel("error_env")
+ trace_h = trace_h.replace("%%MODEL_NAME%%", self.name)
+ trace_h = trace_h.replace("%%MODEL_NAME_UP%%", self.name.upper())
+ trace_h = trace_h.replace("%%MONITOR_CLASS%%", monitor_class)
+ trace_h = trace_h.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
+ trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_EVENT%%", tracepoint_args_skel_event)
+ trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR%%", tracepoint_args_skel_error)
+ trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR_ENV%%", tracepoint_args_skel_error_env)
+ return trace_h
+
+ def print_files(self):
+ super().print_files()
+ trace_h = self.fill_trace_h()
+ path = f"{self.name}_trace.h"
+ self._create_file(path, trace_h)
diff --git a/tools/verification/rvgen/rvgen/ltl2ba.py b/tools/verification/rvgen/rvgen/ltl2ba.py
new file mode 100644
index 000000000000..7f538598a868
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/ltl2ba.py
@@ -0,0 +1,567 @@
+#!/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
+from .automata import AutomataError
+
+# Grammar:
+# ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl
+#
+# Operands (opd):
+# true, false, user-defined names
+#
+# Unary Operators (unop):
+# always
+# eventually
+# next
+# not
+#
+# Binary Operators (binop):
+# until
+# and
+# or
+# imply
+# equivalent
+
+tokens = (
+ 'AND',
+ 'OR',
+ 'IMPLY',
+ 'UNTIL',
+ 'ALWAYS',
+ 'EVENTUALLY',
+ 'NEXT',
+ '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_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'
+
+def t_error(t):
+ raise AutomataError(f"Illegal character '{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)
+ if 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 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 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 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 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 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 __str__(self):
+ return "eventually " + str(self.child)
+
+ def normalize(self):
+ # <>F == true U F
+ return UntilOp(ASTNode(Literal(True)), self.child)
+
+ def _is_temporal(self):
+ return True
+
+ def negate(self):
+ # !<>F == [](!F)
+ return AlwaysOp(self.child.negate()).normalize()
+
+class AlwaysOp(UnaryOp):
+ 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.child.negate()).normalize()
+
+class NextOp(UnaryOp):
+ def normalize(self):
+ return self
+
+ def _is_temporal(self):
+ return True
+
+ def negate(self):
+ # not (next A) == next (not A)
+ self.child = self.child.negate()
+ return self
+
+ @staticmethod
+ def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
+ tmp = GraphNode(node.incoming,
+ node.new,
+ node.old | {n},
+ node.next | {n.op.child})
+ return tmp.expand(node_set)
+
+class NotOp(UnaryOp):
+ 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.op, 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
+ | 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]}")
+
+ 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 AutomataError(f"Invalid binary operator {p[2]}")
+
+ p[0] = ASTNode(op)
+
+parser = yacc()
+
+def parse_ltl(s: str) -> ASTNode:
+ spec = parser.parse(s)
+
+ rule = None
+ subexpr = {}
+
+ for assign in spec:
+ if assign[0] == "RULE":
+ rule = assign[1]
+ else:
+ subexpr[assign[0]] = assign[1]
+
+ if rule is None:
+ raise AutomataError("Please define your specification in the \"RULE = <LTL spec>\" format")
+
+ 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..81fd1f5ea5ea
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/ltl2k.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+
+from pathlib import Path
+from . import generator
+from . import ltl2ba
+from .automata import AutomataError
+
+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])
+
+ def find_share_length(atom: str) -> int:
+ for i in range(len(atom), -1, -1):
+ if sum(a.startswith(atom[:i]) for a in atoms) > 1:
+ return i
+ return 0
+
+ abbrs = []
+ for atom in atoms:
+ share_len = find_share_length(atom)
+ share = atom[:share_len]
+ unique = atom[share_len:]
+ 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)
+ try:
+ with open(file_path) as f:
+ self.atoms, self.ba, self.ltl = ltl2ba.create_graph(f.read())
+ except OSError as exc:
+ raise AutomataError(exc.strerror) from exc
+ 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) -> list[str]:
+ buf = [
+ "enum ltl_buchi_state {",
+ ]
+
+ for node in self.ba:
+ buf.append(f"\tS{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(f"\tLTL_{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(f"\t\t\"{name}\",")
+
+ buf.extend([
+ "\t};",
+ "",
+ "\treturn names[atom];",
+ "}"
+ ])
+ return buf
+
+ def _fill_atom_values(self, required_values):
+ buf = []
+ for node in self.ltl:
+ if str(node) not in required_values:
+ continue
+
+ if isinstance(node.op, ltl2ba.AndOp):
+ buf.append(f"\tbool {node} = {node.op.left} && {node.op.right};")
+ required_values |= {str(node.op.left), str(node.op.right)}
+ elif isinstance(node.op, ltl2ba.OrOp):
+ buf.append(f"\tbool {node} = {node.op.left} || {node.op.right};")
+ required_values |= {str(node.op.left), str(node.op.right)}
+ elif isinstance(node.op, ltl2ba.NotOp):
+ buf.append(f"\tbool {node} = !{node.op.child};")
+ required_values.add(str(node.op.child))
+
+ for atom in self.atoms:
+ if atom.lower() not in required_values:
+ continue
+ buf.append(f"\tbool {atom.lower()} = test_bit(LTL_{atom}, mon->atoms);")
+
+ 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)",
+ "{"
+ ]
+
+ required_values = set()
+ for node in self.ba:
+ for o in sorted(node.outgoing):
+ required_values |= o.labels
+
+ buf.extend(self._fill_atom_values(required_values))
+ buf.extend([
+ "",
+ "\tswitch (state) {"
+ ])
+
+ for node in self.ba:
+ buf.append(f"\tcase S{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(f"\t\t\t__set_bit(S{o.id}, next);")
+ 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)",
+ "{"
+ ]
+
+ required_values = set()
+ for node in self.ba:
+ if node.init:
+ required_values |= node.labels
+
+ buf.extend(self._fill_atom_values(required_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(f"\t\t__set_bit(S{node.id}, mon->states);")
+ 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(f"\tltl_atom_update(task, LTL_{self.atoms[0]}, true/false);")
+ buff.append("}")
+ buff.append("")
+ return '\n'.join(buff)
+
+ def fill_tracepoint_attach_probe(self):
+ 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);"
+
+ def fill_atoms_init(self):
+ buff = []
+ for a in self.atoms:
+ buff.append(f"\tltl_atom_set(mon, LTL_{a}, true/false);")
+ return '\n'.join(buff)
+
+ def fill_model_h(self):
+ buf = [
+ "/* 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 <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/Kconfig b/tools/verification/rvgen/rvgen/templates/Kconfig
new file mode 100644
index 000000000000..291b29ea28db
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_%%MODEL_NAME_UP%%
+ depends on RV
+%%MONITOR_DEPS%%
+ select %%MONITOR_CLASS_TYPE%%
+ bool "%%MODEL_NAME%% monitor"
+ help
+ %%DESCRIPTION%%
diff --git a/tools/verification/dot2/dot2k_templates/Kconfig b/tools/verification/rvgen/rvgen/templates/container/Kconfig
index 90cdc1e9379e..a606111949c2 100644
--- a/tools/verification/dot2/dot2k_templates/Kconfig
+++ b/tools/verification/rvgen/rvgen/templates/container/Kconfig
@@ -1,6 +1,5 @@
config RV_MON_%%MODEL_NAME_UP%%
depends on RV
- select %%MONITOR_CLASS_TYPE%%
bool "%%MODEL_NAME%% monitor"
help
%%DESCRIPTION%%
diff --git a/tools/verification/rvgen/rvgen/templates/container/main.c b/tools/verification/rvgen/rvgen/templates/container/main.c
new file mode 100644
index 000000000000..5fc89b46f279
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/container/main.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rv.h>
+
+#define MODULE_NAME "%%MODEL_NAME%%"
+
+#include "%%MODEL_NAME%%.h"
+
+struct rv_monitor rv_%%MODEL_NAME%% = {
+ .name = "%%MODEL_NAME%%",
+ .description = "%%DESCRIPTION%%",
+ .enable = NULL,
+ .disable = NULL,
+ .reset = NULL,
+ .enabled = 0,
+};
+
+static int __init register_%%MODEL_NAME%%(void)
+{
+ return rv_register_monitor(&rv_%%MODEL_NAME%%, NULL);
+}
+
+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("dot2k: auto-generated");
+MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
diff --git a/tools/verification/rvgen/rvgen/templates/container/main.h b/tools/verification/rvgen/rvgen/templates/container/main.h
new file mode 100644
index 000000000000..0f6883ab4bcc
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/container/main.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+extern struct rv_monitor rv_%%MODEL_NAME%%;
diff --git a/tools/verification/dot2/dot2k_templates/main.c b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
index 9605ca994416..bf0999f6657a 100644
--- a/tools/verification/dot2/dot2k_templates/main.c
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main.c
@@ -6,7 +6,6 @@
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
-#include <rv/da_monitor.h>
#define MODULE_NAME "%%MODEL_NAME%%"
@@ -15,20 +14,14 @@
* #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.
*/
+#define RV_MON_TYPE RV_MON_%%MONITOR_TYPE%%
#include "%%MODEL_NAME%%.h"
-
-/*
- * Declare the deterministic automata monitor.
- *
- * The rv monitor reference is needed for the monitor declaration.
- */
-static struct rv_monitor rv_%%MODEL_NAME%%;
-DECLARE_DA_MON_%%MONITOR_TYPE%%(%%MODEL_NAME%%, %%MIN_TYPE%%);
+#include <rv/%%MONITOR_CLASS%%_monitor.h>
/*
* This is the instrumentation part of the monitor.
@@ -42,7 +35,7 @@ static int enable_%%MODEL_NAME%%(void)
{
int retval;
- retval = da_monitor_init_%%MODEL_NAME%%();
+ retval = da_monitor_init();
if (retval)
return retval;
@@ -53,34 +46,33 @@ static int enable_%%MODEL_NAME%%(void)
static void disable_%%MODEL_NAME%%(void)
{
- rv_%%MODEL_NAME%%.enabled = 0;
+ rv_this.enabled = 0;
%%TRACEPOINT_DETACH%%
- da_monitor_destroy_%%MODEL_NAME%%();
+ da_monitor_destroy();
}
/*
* 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%%,
.disable = disable_%%MODEL_NAME%%,
- .reset = da_monitor_reset_all_%%MODEL_NAME%%,
+ .reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_%%MODEL_NAME%%(void)
{
- rv_register_monitor(&rv_%%MODEL_NAME%%);
- return 0;
+ 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%%);
diff --git a/tools/verification/dot2/dot2k_templates/trace.h b/tools/verification/rvgen/rvgen/templates/dot2k/trace.h
index 87d3a1308926..87d3a1308926 100644
--- a/tools/verification/dot2/dot2k_templates/trace.h
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/trace.h
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h b/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h
new file mode 100644
index 000000000000..c8290e9ba2f4
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/trace_hybrid.h
@@ -0,0 +1,16 @@
+/* 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%%,
+%%TRACEPOINT_ARGS_SKEL_EVENT%%);
+
+DEFINE_EVENT(error_%%MONITOR_CLASS%%, error_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_ERROR%%);
+
+DEFINE_EVENT(error_env_%%MONITOR_CLASS%%, error_env_%%MODEL_NAME%%,
+%%TRACEPOINT_ARGS_SKEL_ERROR_ENV%%);
+#endif /* CONFIG_RV_MON_%%MODEL_NAME_UP%% */
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..f85d076fbf78
--- /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%% */