745d35a4a3791873a352e3c8144cd48e0a8e1e81
[linux-block.git] / tools / verification / dot2 / dot2k.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0-only
3 #
4 # Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
5 #
6 # dot2k: transform dot files into a monitor for the Linux kernel.
7 #
8 # For further information, see:
9 #   Documentation/trace/rv/da_monitor_synthesis.rst
10
11 from dot2.dot2c import Dot2c
12 import platform
13 import os
14
15 class dot2k(Dot2c):
16     monitor_types = { "global" : 1, "per_cpu" : 2, "per_task" : 3 }
17     monitor_templates_dir = "dot2/dot2k_templates/"
18     rv_dir = "kernel/trace/rv"
19     monitor_type = "per_cpu"
20
21     def __init__(self, file_path, MonitorType, extra_params={}):
22         self.container = extra_params.get("container")
23         self.parent = extra_params.get("parent")
24         self.__fill_rv_templates_dir()
25
26         if self.container:
27             if file_path:
28                 raise ValueError("A container does not require a dot file")
29             if MonitorType:
30                 raise ValueError("A container does not require a monitor type")
31             if self.parent:
32                 raise ValueError("A container cannot have a parent")
33             self.name = extra_params.get("model_name")
34             self.events = []
35             self.states = []
36             self.main_c = self.__read_file(self.monitor_templates_dir + "main_container.c")
37             self.main_h = self.__read_file(self.monitor_templates_dir + "main_container.h")
38         else:
39             super().__init__(file_path, extra_params.get("model_name"))
40
41             self.monitor_type = self.monitor_types.get(MonitorType)
42             if self.monitor_type is None:
43                 raise ValueError("Unknown monitor type: %s" % MonitorType)
44             self.monitor_type = MonitorType
45             self.main_c = self.__read_file(self.monitor_templates_dir + "main.c")
46             self.trace_h = self.__read_file(self.monitor_templates_dir + "trace.h")
47         self.kconfig = self.__read_file(self.monitor_templates_dir + "Kconfig")
48         self.enum_suffix = "_%s" % self.name
49         self.description = extra_params.get("description", self.name) or "auto-generated"
50         self.auto_patch = extra_params.get("auto_patch")
51         if self.auto_patch:
52             self.__fill_rv_kernel_dir()
53
54     def __fill_rv_templates_dir(self):
55
56         if os.path.exists(self.monitor_templates_dir):
57             return
58
59         if platform.system() != "Linux":
60             raise OSError("I can only run on Linux.")
61
62         kernel_path = "/lib/modules/%s/build/tools/verification/dot2/dot2k_templates/" % (platform.release())
63
64         if os.path.exists(kernel_path):
65             self.monitor_templates_dir = kernel_path
66             return
67
68         if os.path.exists("/usr/share/dot2/dot2k_templates/"):
69             self.monitor_templates_dir = "/usr/share/dot2/dot2k_templates/"
70             return
71
72         raise FileNotFoundError("Could not find the template directory, do you have the kernel source installed?")
73
74     def __fill_rv_kernel_dir(self):
75
76         # first try if we are running in the kernel tree root
77         if os.path.exists(self.rv_dir):
78             return
79
80         # offset if we are running inside the kernel tree from verification/dot2
81         kernel_path = os.path.join("../..", self.rv_dir)
82
83         if os.path.exists(kernel_path):
84             self.rv_dir = kernel_path
85             return
86
87         if platform.system() != "Linux":
88             raise OSError("I can only run on Linux.")
89
90         kernel_path = os.path.join("/lib/modules/%s/build" % platform.release(), self.rv_dir)
91
92         # if the current kernel is from a distro this may not be a full kernel tree
93         # verify that one of the files we are going to modify is available
94         if os.path.exists(os.path.join(kernel_path, "rv_trace.h")):
95             self.rv_dir = kernel_path
96             return
97
98         raise FileNotFoundError("Could not find the rv directory, do you have the kernel source installed?")
99
100     def __read_file(self, path):
101         try:
102             fd = open(path, 'r')
103         except OSError:
104             raise Exception("Cannot open the file: %s" % path)
105
106         content = fd.read()
107
108         fd.close()
109         return content
110
111     def __buff_to_string(self, buff):
112         string = ""
113
114         for line in buff:
115             string = string + line + "\n"
116
117         # cut off the last \n
118         return string[:-1]
119
120     def fill_monitor_type(self):
121         return self.monitor_type.upper()
122
123     def fill_parent(self):
124         return "&rv_%s" % self.parent if self.parent else "NULL"
125
126     def fill_include_parent(self):
127         if self.parent:
128             return "#include <monitors/%s/%s.h>\n" % (self.parent, self.parent)
129         return ""
130
131     def fill_tracepoint_handlers_skel(self):
132         buff = []
133         for event in self.events:
134             buff.append("static void handle_%s(void *data, /* XXX: fill header */)" % event)
135             buff.append("{")
136             handle = "handle_event"
137             if self.is_start_event(event):
138                 buff.append("\t/* XXX: validate that this event always leads to the initial state */")
139                 handle = "handle_start_event"
140             elif self.is_start_run_event(event):
141                 buff.append("\t/* XXX: validate that this event is only valid in the initial state */")
142                 handle = "handle_start_run_event"
143             if self.monitor_type == "per_task":
144                 buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;");
145                 buff.append("\tda_%s_%s(p, %s%s);" % (handle, self.name, event, self.enum_suffix));
146             else:
147                 buff.append("\tda_%s_%s(%s%s);" % (handle, self.name, event, self.enum_suffix));
148             buff.append("}")
149             buff.append("")
150         return self.__buff_to_string(buff)
151
152     def fill_tracepoint_attach_probe(self):
153         buff = []
154         for event in self.events:
155             buff.append("\trv_attach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_%s);" % (self.name, event))
156         return self.__buff_to_string(buff)
157
158     def fill_tracepoint_detach_helper(self):
159         buff = []
160         for event in self.events:
161             buff.append("\trv_detach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_%s);" % (self.name, event))
162         return self.__buff_to_string(buff)
163
164     def fill_main_c(self):
165         main_c = self.main_c
166         monitor_type = self.fill_monitor_type()
167         min_type = self.get_minimun_type()
168         nr_events = len(self.events)
169         tracepoint_handlers = self.fill_tracepoint_handlers_skel()
170         tracepoint_attach = self.fill_tracepoint_attach_probe()
171         tracepoint_detach = self.fill_tracepoint_detach_helper()
172         parent = self.fill_parent()
173         parent_include = self.fill_include_parent()
174
175         main_c = main_c.replace("%%MONITOR_TYPE%%", monitor_type)
176         main_c = main_c.replace("%%MIN_TYPE%%", min_type)
177         main_c = main_c.replace("%%MODEL_NAME%%", self.name)
178         main_c = main_c.replace("%%NR_EVENTS%%", str(nr_events))
179         main_c = main_c.replace("%%TRACEPOINT_HANDLERS_SKEL%%", tracepoint_handlers)
180         main_c = main_c.replace("%%TRACEPOINT_ATTACH%%", tracepoint_attach)
181         main_c = main_c.replace("%%TRACEPOINT_DETACH%%", tracepoint_detach)
182         main_c = main_c.replace("%%DESCRIPTION%%", self.description)
183         main_c = main_c.replace("%%PARENT%%", parent)
184         main_c = main_c.replace("%%INCLUDE_PARENT%%", parent_include)
185
186         return main_c
187
188     def fill_model_h_header(self):
189         buff = []
190         buff.append("/* SPDX-License-Identifier: GPL-2.0 */")
191         buff.append("/*")
192         buff.append(" * Automatically generated C representation of %s automaton" % (self.name))
193         buff.append(" * For further information about this format, see kernel documentation:")
194         buff.append(" *   Documentation/trace/rv/deterministic_automata.rst")
195         buff.append(" */")
196         buff.append("")
197
198         return buff
199
200     def fill_model_h(self):
201         #
202         # Adjust the definition names
203         #
204         self.enum_states_def = "states_%s" % self.name
205         self.enum_events_def = "events_%s" % self.name
206         self.struct_automaton_def = "automaton_%s" % self.name
207         self.var_automaton_def = "automaton_%s" % self.name
208
209         buff = self.fill_model_h_header()
210         buff += self.format_model()
211
212         return self.__buff_to_string(buff)
213
214     def fill_monitor_class_type(self):
215         if self.monitor_type == "per_task":
216             return "DA_MON_EVENTS_ID"
217         return "DA_MON_EVENTS_IMPLICIT"
218
219     def fill_monitor_class(self):
220         if self.monitor_type == "per_task":
221             return "da_monitor_id"
222         return "da_monitor"
223
224     def fill_tracepoint_args_skel(self, tp_type):
225         buff = []
226         tp_args_event = [
227                 ("char *", "state"),
228                 ("char *", "event"),
229                 ("char *", "next_state"),
230                 ("bool ",  "final_state"),
231                 ]
232         tp_args_error = [
233                 ("char *", "state"),
234                 ("char *", "event"),
235                 ]
236         tp_args_id = ("int ", "id")
237         tp_args = tp_args_event if tp_type == "event" else tp_args_error
238         if self.monitor_type == "per_task":
239             tp_args.insert(0, tp_args_id)
240         tp_proto_c = ", ".join([a+b for a,b in tp_args])
241         tp_args_c = ", ".join([b for a,b in tp_args])
242         buff.append("        TP_PROTO(%s)," % tp_proto_c)
243         buff.append("        TP_ARGS(%s)" % tp_args_c)
244         return self.__buff_to_string(buff)
245
246     def fill_monitor_deps(self):
247         buff = []
248         buff.append("   # XXX: add dependencies if there")
249         if self.parent:
250             buff.append("       depends on RV_MON_%s" % self.parent.upper())
251             buff.append("       default y")
252         return self.__buff_to_string(buff)
253
254     def fill_trace_h(self):
255         trace_h = self.trace_h
256         monitor_class = self.fill_monitor_class()
257         monitor_class_type = self.fill_monitor_class_type()
258         tracepoint_args_skel_event = self.fill_tracepoint_args_skel("event")
259         tracepoint_args_skel_error = self.fill_tracepoint_args_skel("error")
260         trace_h = trace_h.replace("%%MODEL_NAME%%", self.name)
261         trace_h = trace_h.replace("%%MODEL_NAME_UP%%", self.name.upper())
262         trace_h = trace_h.replace("%%MONITOR_CLASS%%", monitor_class)
263         trace_h = trace_h.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
264         trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_EVENT%%", tracepoint_args_skel_event)
265         trace_h = trace_h.replace("%%TRACEPOINT_ARGS_SKEL_ERROR%%", tracepoint_args_skel_error)
266         return trace_h
267
268     def fill_kconfig(self):
269         kconfig = self.kconfig
270         monitor_class_type = self.fill_monitor_class_type()
271         monitor_deps = self.fill_monitor_deps()
272         kconfig = kconfig.replace("%%MODEL_NAME%%", self.name)
273         kconfig = kconfig.replace("%%MODEL_NAME_UP%%", self.name.upper())
274         kconfig = kconfig.replace("%%MONITOR_CLASS_TYPE%%", monitor_class_type)
275         kconfig = kconfig.replace("%%DESCRIPTION%%", self.description)
276         kconfig = kconfig.replace("%%MONITOR_DEPS%%", monitor_deps)
277         return kconfig
278
279     def fill_main_container_h(self):
280         main_h = self.main_h
281         main_h = main_h.replace("%%MODEL_NAME%%", self.name)
282         return main_h
283
284     def __patch_file(self, file, marker, line):
285         file_to_patch = os.path.join(self.rv_dir, file)
286         content = self.__read_file(file_to_patch)
287         content = content.replace(marker, line + "\n" + marker)
288         self.__write_file(file_to_patch, content)
289
290     def fill_tracepoint_tooltip(self):
291         monitor_class_type = self.fill_monitor_class_type()
292         if self.auto_patch:
293             self.__patch_file("rv_trace.h",
294                             "// Add new monitors based on CONFIG_%s here" % monitor_class_type,
295                             "#include <monitors/%s/%s_trace.h>" % (self.name, self.name))
296             return "  - Patching %s/rv_trace.h, double check the result" % self.rv_dir
297
298         return """  - Edit %s/rv_trace.h:
299 Add this line where other tracepoints are included and %s is defined:
300 #include <monitors/%s/%s_trace.h>
301 """ % (self.rv_dir, monitor_class_type, self.name, self.name)
302
303     def fill_kconfig_tooltip(self):
304         if self.auto_patch:
305             self.__patch_file("Kconfig",
306                             "# Add new monitors here",
307                             "source \"kernel/trace/rv/monitors/%s/Kconfig\"" % (self.name))
308             return "  - Patching %s/Kconfig, double check the result" % self.rv_dir
309
310         return """  - Edit %s/Kconfig:
311 Add this line where other monitors are included:
312 source \"kernel/trace/rv/monitors/%s/Kconfig\"
313 """ % (self.rv_dir, self.name)
314
315     def fill_makefile_tooltip(self):
316         name = self.name
317         name_up = name.upper()
318         if self.auto_patch:
319             self.__patch_file("Makefile",
320                             "# Add new monitors here",
321                             "obj-$(CONFIG_RV_MON_%s) += monitors/%s/%s.o" % (name_up, name, name))
322             return "  - Patching %s/Makefile, double check the result" % self.rv_dir
323
324         return """  - Edit %s/Makefile:
325 Add this line where other monitors are included:
326 obj-$(CONFIG_RV_MON_%s) += monitors/%s/%s.o
327 """ % (self.rv_dir, name_up, name, name)
328
329     def fill_monitor_tooltip(self):
330         if self.auto_patch:
331             return "  - Monitor created in %s/monitors/%s" % (self.rv_dir, self. name)
332         return "  - Move %s/ to the kernel's monitor directory (%s/monitors)" % (self.name, self.rv_dir)
333
334     def __create_directory(self):
335         path = self.name
336         if self.auto_patch:
337             path = os.path.join(self.rv_dir, "monitors", path)
338         try:
339             os.mkdir(path)
340         except FileExistsError:
341             return
342         except:
343             print("Fail creating the output dir: %s" % self.name)
344
345     def __write_file(self, file_name, content):
346         try:
347             file = open(file_name, 'w')
348         except:
349             print("Fail writing to file: %s" % file_name)
350
351         file.write(content)
352
353         file.close()
354
355     def __create_file(self, file_name, content):
356         path = "%s/%s" % (self.name, file_name)
357         if self.auto_patch:
358             path = os.path.join(self.rv_dir, "monitors", path)
359         self.__write_file(path, content)
360
361     def __get_main_name(self):
362         path = "%s/%s" % (self.name, "main.c")
363         if not os.path.exists(path):
364             return "main.c"
365         return "__main.c"
366
367     def print_files(self):
368         main_c = self.fill_main_c()
369
370         self.__create_directory()
371
372         path = "%s.c" % self.name
373         self.__create_file(path, main_c)
374
375         if self.container:
376             main_h = self.fill_main_container_h()
377             path = "%s.h" % self.name
378             self.__create_file(path, main_h)
379         else:
380             model_h = self.fill_model_h()
381             path = "%s.h" % self.name
382             self.__create_file(path, model_h)
383
384             trace_h = self.fill_trace_h()
385             path = "%s_trace.h" % self.name
386             self.__create_file(path, trace_h)
387
388         kconfig = self.fill_kconfig()
389         self.__create_file("Kconfig", kconfig)