/* Copyright 2017 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * This file does the Hardware Trace Macro command parsing for pdbg * the program. * It will call into libpdbg backend with a target to either a 'nhtm' * or a 'chtm' which will ultimately do the work. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "main.h" #include "path.h" #define HTM_ENUM_TO_STRING(e) ((e == HTM_NEST) ? "nhtm" : "chtm") #define PR_ERROR(x, args...) \ pdbg_log(PDBG_ERROR, x, ##args) enum htm_type { HTM_CORE, HTM_NEST, }; static inline void print_htm_address(enum htm_type type, struct pdbg_target *target) { if (type == HTM_CORE) { printf("p%d:", pdbg_parent_index(target, "pib")); printf("c%d:", pdbg_parent_index(target, "core")); } if (type == HTM_NEST) { printf("p%d\n", pdbg_parent_index(target, "pib")); } } static char *get_htm_dump_filename(struct pdbg_target *target) { char *filename; int rc; rc = asprintf(&filename, "htm-p%d-c%d-t%d.dump", pdbg_parent_index(target, "pib"), pdbg_parent_index(target, "core"), pdbg_target_index(target)); if (rc == -1) return NULL; return filename; } static int run_start(enum htm_type type) { struct pdbg_target *target; int rc = 0; for_each_path_target_class(HTM_ENUM_TO_STRING(type), target) { if (target_is_disabled(target)) continue; printf("Starting with buffer wrapping HTM@"); print_htm_address(type, target); if (htm_start(target) != 1) { printf("Couldn't start HTM@"); print_htm_address(type, target); } rc++; } return rc; } static int run_stop(enum htm_type type) { struct pdbg_target *target; int rc = 0; for_each_path_target_class(HTM_ENUM_TO_STRING(type), target) { if (target_is_disabled(target)) continue; printf("Stopping HTM@"); print_htm_address(type, target); if (htm_stop(target) != 1) { printf("Couldn't stop HTM@"); print_htm_address(type, target); } rc++; } return rc; } static int run_status(enum htm_type type) { struct pdbg_target *target; int rc = 0; for_each_path_target_class(HTM_ENUM_TO_STRING(type), target) { if (target_is_disabled(target)) continue; printf("Status HTM@"); print_htm_address(type, target); if (htm_status(target) != 1) { printf("Couldn't get HTM@"); print_htm_address(type, target); } rc++; } return rc; } static int run_dump(enum htm_type type) { struct pdbg_target *target; char *filename; int rc = 0; for_each_path_target_class(HTM_ENUM_TO_STRING(type), target) { if (target_is_disabled(target)) continue; filename = get_htm_dump_filename(target); if (!filename) return 0; /* size = 0 will dump everything */ printf("Dumping HTM@"); print_htm_address(type, target); if (htm_dump(target, filename) != 1) { printf("Couldn't dump HTM@"); print_htm_address(type, target); } rc++; free(filename); } return rc; } static int run_record(enum htm_type type) { struct pdbg_target *target; char *filename; int rc = 0; for_each_path_target_class(HTM_ENUM_TO_STRING(type), target) { if (target_is_disabled(target)) continue; filename = get_htm_dump_filename(target); if (!filename) return 0; /* size = 0 will dump everything */ printf("Recording till buffer wraps HTM@"); print_htm_address(type, target); if (htm_record(target, filename) != 1) { printf("Couldn't record HTM@"); print_htm_address(type, target); } rc++; free(filename); } return rc; } static struct { const char *name; const char *args; const char *desc; int (*fn)(enum htm_type); } actions[] = { { "start", "", "Start %s HTM", &run_start }, { "stop", "", "Stop %s HTM", &run_stop }, { "status", "", "Get %s HTM status", &run_status }, { "dump", "", "Dump %s HTM buffer to file", &run_dump }, { "record", "", "Start, wait & dump %s HTM", &run_record }, }; static void print_usage(enum htm_type type) { int i; for (i = 0; i < ARRAY_SIZE(actions); i++) { printf("%s %s", actions[i].name, actions[i].args); printf(actions[i].desc, HTM_ENUM_TO_STRING(type)); printf("\n"); } } static bool is_smt1(struct pdbg_target *target) { /* primary thread */ if (pdbg_target_index(target) == 0) { if (((thread_status(target).active)) && (thread_status(target).sleep_state == PDBG_THREAD_STATE_RUN)) return true; goto fail; } /* secondary thread */ if (thread_status(target).quiesced) return true; fail: fprintf(stderr, "Error: core HTM needs to run in SMT1 with no powersave. Try\n"); fprintf(stderr, " ppc64_cpu --smt=1\n"); fprintf(stderr, " for i in /sys/devices/system/cpu/cpu*/cpuidle/state*/disable;do echo 1 > $i;done\n"); return false; } int run_htm(int optind, int argc, char *argv[]) { struct pdbg_target *target; enum htm_type type; struct pdbg_target *core_target = NULL; struct pdbg_target *pib_target = NULL; enum pdbg_target_status status; int i, rc = 0; /* * As the index of the last argument is one less than argc, the difference * between optind and argc will always be at least 1. Here optind is pointing * to the 'htm' arg and we need at least 2 more following arguments, eg: * htm * so argc-optind >= 3 to proceed. */ if (argc - optind < 3) { fprintf(stderr, "Expecting one of 'core' or 'nest' with a command\n"); return 0; } optind++; if (strcmp(argv[optind], "core") == 0) { type = HTM_CORE; } else if (strcmp(argv[optind], "nest") == 0) { type = HTM_NEST; } else { fprintf(stderr, "Expecting one of 'core' or 'nest' not %s\n", argv[optind]); return 0; } if (type == HTM_CORE) { for_each_path_target_class("core", target) { if (pdbg_target_status(target) != PDBG_TARGET_ENABLED) continue; if (!core_target) { core_target = target; } else { fprintf(stderr, "It doesn't make sense to run core trace on multiple cores at once.\n"); return 0; } } if (!core_target) { fprintf(stderr, "No core selected\n"); return 0; } /* Check that powersave is off */ pdbg_for_each_class_target("thread", target) { status = pdbg_target_probe(target); if (status == PDBG_TARGET_NONEXISTENT) continue; if (!is_smt1(target)) return 0; } /* Select the correct chtm target */ pdbg_for_each_target("chtm", core_target, target) { status = pdbg_target_probe(target); if (status == PDBG_TARGET_ENABLED) { assert(path_target_add(target)); break; } } if (!target) { fprintf(stderr, "No CHTM target enabled\n"); return 0; } } if (type == HTM_NEST) { for_each_path_target_class("pib", target) { if (pdbg_target_status(target) != PDBG_TARGET_ENABLED) continue; if (!pib_target) { pib_target = target; } else { fprintf(stderr, "It doesn't make sense to run nest trace on multiple cores at once.\n"); return 0; } } if (!pib_target) { fprintf(stderr, "No pib selected\n"); return 0; } pdbg_for_each_target("nhtm", pib_target, target) { status = pdbg_target_probe(target); if (status == PDBG_TARGET_ENABLED) { assert(path_target_add(target)); break; } } if (!target) { fprintf(stderr, "No NHTM target enabled\n"); return 0; } } optind++; for (i = 0; i < ARRAY_SIZE(actions); i++) { if (strcmp(argv[optind], actions[i].name) == 0) { rc = actions[i].fn(type); break; } } if (i == ARRAY_SIZE(actions)) { PR_ERROR("Unsupported command: %s\n", argv[optind]); print_usage(type); return 0; } else if (rc == 0) { fprintf(stderr, "Couldn't run the HTM command.\n"); fprintf(stderr, "Double check that your kernel has debugfs mounted and the memtrace patches\n"); } return rc; }