Commit | Line | Data |
---|---|---|
24bce201 DBO |
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. | |
d57aff24 DBO |
7 | # |
8 | # For further information, see: | |
9 | # Documentation/trace/rv/da_monitor_synthesis.rst | |
24bce201 DBO |
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 } | |
6c432b56 | 17 | monitor_templates_dir = "dot2/dot2k_templates/" |
9c6cfe80 | 18 | rv_dir = "kernel/trace/rv" |
24bce201 DBO |
19 | monitor_type = "per_cpu" |
20 | ||
64b3e5f0 | 21 | def __init__(self, file_path, MonitorType, extra_params={}): |
2334cf7d GM |
22 | self.container = extra_params.get("container") |
23 | self.parent = extra_params.get("parent") | |
24bce201 | 24 | self.__fill_rv_templates_dir() |
2334cf7d GM |
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") | |
de6f45c2 | 47 | self.kconfig = self.__read_file(self.monitor_templates_dir + "Kconfig") |
24bce201 | 48 | self.enum_suffix = "_%s" % self.name |
64b3e5f0 | 49 | self.description = extra_params.get("description", self.name) or "auto-generated" |
de6f45c2 GM |
50 | self.auto_patch = extra_params.get("auto_patch") |
51 | if self.auto_patch: | |
52 | self.__fill_rv_kernel_dir() | |
24bce201 DBO |
53 | |
54 | def __fill_rv_templates_dir(self): | |
55 | ||
6c432b56 | 56 | if os.path.exists(self.monitor_templates_dir): |
24bce201 DBO |
57 | return |
58 | ||
59 | if platform.system() != "Linux": | |
de6f45c2 | 60 | raise OSError("I can only run on Linux.") |
24bce201 DBO |
61 | |
62 | kernel_path = "/lib/modules/%s/build/tools/verification/dot2/dot2k_templates/" % (platform.release()) | |
63 | ||
6c432b56 | 64 | if os.path.exists(kernel_path): |
24bce201 DBO |
65 | self.monitor_templates_dir = kernel_path |
66 | return | |
67 | ||
6c432b56 | 68 | if os.path.exists("/usr/share/dot2/dot2k_templates/"): |
24bce201 DBO |
69 | self.monitor_templates_dir = "/usr/share/dot2/dot2k_templates/" |
70 | return | |
71 | ||
de6f45c2 | 72 | raise FileNotFoundError("Could not find the template directory, do you have the kernel source installed?") |
24bce201 | 73 | |
de6f45c2 | 74 | def __fill_rv_kernel_dir(self): |
24bce201 | 75 | |
de6f45c2 GM |
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): | |
24bce201 | 101 | try: |
de6f45c2 | 102 | fd = open(path, 'r') |
24bce201 DBO |
103 | except OSError: |
104 | raise Exception("Cannot open the file: %s" % path) | |
105 | ||
106 | content = fd.read() | |
107 | ||
de6f45c2 | 108 | fd.close() |
24bce201 DBO |
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 | ||
ca08e071 GM |
120 | def fill_monitor_type(self): |
121 | return self.monitor_type.upper() | |
122 | ||
2334cf7d GM |
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 | ||
24bce201 DBO |
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("{") | |
87c5d7f5 GM |
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" | |
24bce201 DBO |
143 | if self.monitor_type == "per_task": |
144 | buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;"); | |
87c5d7f5 | 145 | buff.append("\tda_%s_%s(p, %s%s);" % (handle, self.name, event, self.enum_suffix)); |
24bce201 | 146 | else: |
87c5d7f5 | 147 | buff.append("\tda_%s_%s(%s%s);" % (handle, self.name, event, self.enum_suffix)); |
24bce201 DBO |
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 | |
ca08e071 | 166 | monitor_type = self.fill_monitor_type() |
24bce201 | 167 | min_type = self.get_minimun_type() |
6c432b56 | 168 | nr_events = len(self.events) |
24bce201 DBO |
169 | tracepoint_handlers = self.fill_tracepoint_handlers_skel() |
170 | tracepoint_attach = self.fill_tracepoint_attach_probe() | |
171 | tracepoint_detach = self.fill_tracepoint_detach_helper() | |
2334cf7d GM |
172 | parent = self.fill_parent() |
173 | parent_include = self.fill_include_parent() | |
24bce201 | 174 | |
91f3407e GM |
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) | |
64b3e5f0 | 182 | main_c = main_c.replace("%%DESCRIPTION%%", self.description) |
2334cf7d GM |
183 | main_c = main_c.replace("%%PARENT%%", parent) |
184 | main_c = main_c.replace("%%INCLUDE_PARENT%%", parent_include) | |
24bce201 DBO |
185 | |
186 | return main_c | |
187 | ||
188 | def fill_model_h_header(self): | |
189 | buff = [] | |
41a4d2d3 | 190 | buff.append("/* SPDX-License-Identifier: GPL-2.0 */") |
24bce201 DBO |
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 | ||
9c6cfe80 GM |
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 | ||
2334cf7d GM |
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 | ||
9c6cfe80 GM |
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() | |
2334cf7d | 271 | monitor_deps = self.fill_monitor_deps() |
9c6cfe80 GM |
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) | |
2334cf7d | 276 | kconfig = kconfig.replace("%%MONITOR_DEPS%%", monitor_deps) |
9c6cfe80 GM |
277 | return kconfig |
278 | ||
2334cf7d GM |
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 | ||
de6f45c2 GM |
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 | ||
9c6cfe80 GM |
290 | def fill_tracepoint_tooltip(self): |
291 | monitor_class_type = self.fill_monitor_class_type() | |
de6f45c2 GM |
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 | ||
9c6cfe80 GM |
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): | |
de6f45c2 GM |
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 | ||
9c6cfe80 GM |
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() | |
de6f45c2 GM |
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 | ||
9c6cfe80 GM |
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 | ||
de6f45c2 GM |
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 | ||
24bce201 | 334 | def __create_directory(self): |
de6f45c2 GM |
335 | path = self.name |
336 | if self.auto_patch: | |
337 | path = os.path.join(self.rv_dir, "monitors", path) | |
24bce201 | 338 | try: |
de6f45c2 | 339 | os.mkdir(path) |
24bce201 DBO |
340 | except FileExistsError: |
341 | return | |
342 | except: | |
343 | print("Fail creating the output dir: %s" % self.name) | |
344 | ||
de6f45c2 | 345 | def __write_file(self, file_name, content): |
24bce201 | 346 | try: |
de6f45c2 | 347 | file = open(file_name, 'w') |
24bce201 | 348 | except: |
de6f45c2 | 349 | print("Fail writing to file: %s" % file_name) |
24bce201 DBO |
350 | |
351 | file.write(content) | |
352 | ||
353 | file.close() | |
354 | ||
de6f45c2 GM |
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 | ||
24bce201 DBO |
361 | def __get_main_name(self): |
362 | path = "%s/%s" % (self.name, "main.c") | |
6c432b56 GM |
363 | if not os.path.exists(path): |
364 | return "main.c" | |
24bce201 DBO |
365 | return "__main.c" |
366 | ||
367 | def print_files(self): | |
368 | main_c = self.fill_main_c() | |
24bce201 DBO |
369 | |
370 | self.__create_directory() | |
371 | ||
372 | path = "%s.c" % self.name | |
373 | self.__create_file(path, main_c) | |
374 | ||
2334cf7d GM |
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) | |
9c6cfe80 GM |
387 | |
388 | kconfig = self.fill_kconfig() | |
389 | self.__create_file("Kconfig", kconfig) |