From c600cfa8946dcaffb8c07268ccba54b2cc81c0c8 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 18:01:31 +0100 Subject: [PATCH 1/8] lisa._assets.kmodules.lisa: Create VFS for lisa module In order to configure lisa features and their parameters at runtime, add support for a VFS in the lisa module. The lisa VFS can be mounted as: $ mount -t lisa none /sys/fs/lisa It contains the following files/folders: lisa_fs \-activate # Activate this config \-available_features # List of available features \-event__lisa__perf_counter # (E.g.) Dir to configure perf_counter feature \-generic_counters # Generic counter to track \-pmu_raw_counters # PMU raw counters to track \-configs # Allow multiple configurations \-custom_conf_0 # Custom configuration () \- ... # Similar files as in lisa_fs/ \-set_features # Features to enable A feature can have various parameters. For instance, the 'event__lisa__perf_counter' feature has two parameters that can be configured: 'generic_counters', 'pmu_raw_counters'. Multiple configurations can co-exist. The 'main' configuration is shown above, and other custom configurations can be created in the 'configs' directory. Upon activation, the configuration is provided to all features. Each feature is then responsible of merging/aggregating the configurations. Original-author: Beata Michalska Signed-off-by: Pierre Gondois --- lisa/_assets/kmodules/lisa/Makefile | 2 +- lisa/_assets/kmodules/lisa/configs.c | 140 ++++ lisa/_assets/kmodules/lisa/configs.h | 102 +++ lisa/_assets/kmodules/lisa/feature_params.c | 240 +++++++ lisa/_assets/kmodules/lisa/feature_params.h | 289 ++++++++ lisa/_assets/kmodules/lisa/features.c | 45 +- lisa/_assets/kmodules/lisa/features.h | 54 +- lisa/_assets/kmodules/lisa/fs.c | 708 ++++++++++++++++++++ lisa/_assets/kmodules/lisa/main.c | 16 +- lisa/_assets/kmodules/lisa/main.h | 12 + 10 files changed, 1594 insertions(+), 14 deletions(-) create mode 100644 lisa/_assets/kmodules/lisa/configs.c create mode 100644 lisa/_assets/kmodules/lisa/configs.h create mode 100644 lisa/_assets/kmodules/lisa/feature_params.c create mode 100644 lisa/_assets/kmodules/lisa/feature_params.h create mode 100644 lisa/_assets/kmodules/lisa/fs.c diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index 661a82546..bda54790e 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -68,7 +68,7 @@ ifneq ($(KERNELRELEASE),) LISA_KMOD_NAME ?= lisa obj-m := $(LISA_KMOD_NAME).o -$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o pixel6.o introspection_data.o +$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o pixel6.o introspection_data.o configs.o fs.o feature_params.o # -fno-stack-protector is needed to possibly undefined __stack_chk_guard symbol ccflags-y := "-I$(MODULE_SRC)" -std=gnu11 -fno-stack-protector -Wno-declaration-after-statement -Wno-error diff --git a/lisa/_assets/kmodules/lisa/configs.c b/lisa/_assets/kmodules/lisa/configs.c new file mode 100644 index 000000000..246b75753 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.c @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include + +#include "configs.h" +#include "feature_params.h" + +struct feature_param_entry * +find_feature_param_entry_cfg(struct lisa_cfg *cfg, char *param_name) +{ + struct feature_param_entry *entry; + + hlist_for_each_entry(entry, &cfg->list_param, node_cfg) + if (!strcmp(entry->param->name, param_name)) + return entry; + + return NULL; +} + +void print_cfg(struct lisa_cfg *cfg) +{ + struct feature_param_entry *entry; + struct feature_param_value *val; + struct feature_param *param; + + pr_info("Config: %s\n", cfg->name); + pr_info("Activated: %d\n", cfg->activated); + + hlist_for_each_entry(entry, &cfg->list_param, node_cfg) { + if (list_empty(&entry->list_values)) + continue; + + param = entry->param; + pr_info(" Param: %s\n", param->name); + list_for_each_entry(val, &entry->list_values, node) + feature_param_entry_print(param, val, 4); + } +} + +/* Check whether the feature is in the global set_features list. */ +static inline +bool is_feature_set(struct lisa_cfg *cfg, char *name) +{ + struct feature_param_entry *set_feature_entry; + struct feature_param_value *val; + + set_feature_entry = find_feature_param_entry_cfg(cfg, LISA_FEATURE_PARAM_NAME); + + list_for_each_entry(val, &set_feature_entry->list_values, node) + if (lisa_features_param.ops->is_equal(&name, val)) + return true; + return false; +} + +int activate_lisa_cfg(struct lisa_cfg *cfg, bool activate, + struct feature *rollback_feature) +{ + struct feature *feature; + int ret; + + if (cfg->activated == activate) + return 0; + + /* + * All the global values of the feature_param have now been updated. + * Re-configure the features now. + */ + + cfg->activated = activate; + + for_each_feature(feature) { + bool feature_is_set; + + if (feature->__internal) + continue; + + /* Rollback up to this feature. */ + if (feature == rollback_feature) + return 0; + + feature_is_set = is_feature_set(cfg, feature->name); + + /* Nothing to do, the feature is not set for this config. */ + if (!feature_is_set) + continue; + + if (cfg->activated) { + if (feature->enabled && feature->__no_nesting) { + /* + * This config is already enabled and doesn't support + * multiple nested use. + */ + pr_err("Feature:%s already enabled. Skipping for config:%s.", + feature->name, cfg->name); + continue; + } + + ret = enable_single_feature(feature->name); + if (ret && !rollback_feature) + goto rollback; + + if (feature->enable_cfg) { + /* Once the feature is enabled, enabled this config. */ + ret = feature->enable_cfg(feature, cfg); + if (ret && !rollback_feature) { + disable_single_feature(feature->name); + goto rollback; + } + } + + continue; + } else { + if (feature->disable_cfg) { + /* Disable the config first before disabling the whole feature. */ + ret = feature->disable_cfg(feature, cfg); + if (ret && !rollback_feature) + goto rollback; + } + + ret = disable_single_feature(feature->name); + if (ret && !rollback_feature) { + if (feature->enable_cfg) + feature->enable_cfg(feature, cfg); + goto rollback; + } + + continue; + } + } + + print_cfg(cfg); + + return 0; + +rollback: + pr_err("Rolling back up cfg: %s due to feature: %s\n", + cfg->name, feature->name); + activate_lisa_cfg(cfg, !activate, feature); + return ret; +} diff --git a/lisa/_assets/kmodules/lisa/configs.h b/lisa/_assets/kmodules/lisa/configs.h new file mode 100644 index 000000000..55930725b --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _CONFIGS_H +#define _CONFIGS_H + +#include +#include "main.h" + +struct lisa_cfg { + struct dentry *dentry; + + /* Member of cfg_list. */ + struct hlist_node node; + + /* List of (struct feature_param_entry)->node_cfg. */ + struct hlist_head list_param; + + /* This config is currently activated. */ + bool activated; + char *name; +}; + +void lisa_fs_remove(struct dentry *dentry); +static inline +void drain_feature_param_entry_cfg(struct hlist_head *head); +struct feature; + +/** + * find_feature_param_entry_cfg() - Find a (struct feature_param_entry) + * in a config based on the feature_param name. + * @cfg: config to search in. + * @param_name: name of the feature_param to search. + * Return: (struct feature_param_entry*) matching the input param_name if success. + * NULL otherwise. + */ +struct feature_param_entry * +find_feature_param_entry_cfg(struct lisa_cfg *cfg, char *param_name); + +/** + * print_cfg() - Print the config + * @cfg: config to print. + */ +void print_cfg(struct lisa_cfg *cfg); + +/* activate_lisa_cfg - activate a lisa cfg. + * @cfg: the lisa_cfg to activate/deactivate. + * @activate: true to activate the config. + * @rollback_feature: If not NULL, ignore returned error codes and + * rollback up to this feature. +*/ +int activate_lisa_cfg(struct lisa_cfg *cfg, bool activate, + struct feature *rollback_feature); + +static inline +struct lisa_cfg *alloc_lisa_cfg(const char *name) +{ + struct lisa_cfg *cfg; + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return NULL; + + cfg->name = kstrdup(name, GFP_KERNEL); + if (!cfg->name) + goto error; + + return cfg; + +error: + kfree(cfg); + return NULL; +} + +static inline +void init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, + struct dentry *dentry) +{ + cfg->dentry = dget(dentry); + hlist_add_head(&cfg->node, cfg_list); +} + +static inline +void free_lisa_cfg(struct lisa_cfg *cfg) +{ + activate_lisa_cfg(cfg, false, NULL); + drain_feature_param_entry_cfg(&cfg->list_param); + hlist_del(&cfg->node); + kfree(cfg->name); + kfree(cfg); +} + +static inline +void drain_lisa_cfg(struct hlist_head *head) +{ + struct hlist_node *tmp; + struct lisa_cfg *cfg; + + hlist_for_each_entry_safe(cfg, tmp, head, node) + free_lisa_cfg(cfg); +} + +#endif // _CONFIGS_H diff --git a/lisa/_assets/kmodules/lisa/feature_params.c b/lisa/_assets/kmodules/lisa/feature_params.c new file mode 100644 index 000000000..8193266cb --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.c @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include "features.h" + +int lisa_param_feature_create_feature_dir(const char *feature_name, + struct feature_param_value *val, + struct feature_param_entry *entry); + +int feature_param_add_new(struct feature_param_entry *entry, const char *v) +{ + struct feature_param *param = entry->param; + struct feature_param_value *val; + int ret = 0; + + val = param->ops->create(v, entry); + if (IS_ERR_OR_NULL(val)) + return IS_ERR(val) ? PTR_ERR(val) : -EINVAL; + + if (param->validate) { + ret = param->validate(val, entry); + if (ret) { + param->ops->free_value(val); + /* Silently return if a value is already present. */ + if (ret == -EEXIST) + return 0; + return ret; + } + } + + /* Might need to create a dentry if a feature is enabled. */ + if (entry->param->type == FT_PARAM_TYPE_FEATURE) { + ret = lisa_param_feature_create_feature_dir(v, val, entry); + if (ret) { + pr_err("Could not create feature dir %s. ret=%d\n", + v, ret); + param->ops->free_value(val); + return ret; + } + } + + init_feature_param_value(val, entry); + return ret; +} + +struct feature_param * +find_feature_param(struct feature *feature, char *param_name) +{ + struct feature_param *param = NULL, **pparam; + + for_each_feature_param(param, pparam, feature) + if (!strcmp(param_name, param->name)) + break; + return param; +} + +/* + * feature_param validation handlers + */ + +int feature_param_validate_single(struct feature_param_value *val, + struct feature_param_entry *entry) +{ + if (!list_empty(&entry->list_values)) { + pr_err("Single parameter entry, skip value:\n"); + feature_param_entry_print(entry->param, val, 2); + return -EEXIST; + } + return 0; +} + +int feature_param_validate_set(struct feature_param_value *val, + struct feature_param_entry *entry) +{ + struct feature_param *param = entry->param; + struct feature_param_value *i_val; + + list_for_each_entry(i_val, &entry->list_values, node) { + if (param->ops->is_equal(&val->data, i_val)) { + pr_warn("Duplicated parameter, skip value:\n"); + feature_param_entry_print(param, val, 2); + return -EEXIST; + } + } + + return 0; +} + +/* + * feature_param type handlers + */ + +static struct feature_param_value * +feature_param_create_uint(const char *buf, struct feature_param_entry *entry) +{ + struct feature_param_value *val; + unsigned int input_val; + int ret; + + if (!buf) + return ERR_PTR(-EINVAL); + + ret = kstrtouint(buf, 0, &input_val); + if (ret) + return ERR_PTR(ret); + + val = alloc_feature_param_value(); + if (!val) + return ERR_PTR(-ENOMEM); + + val->value = input_val; + return val; +} + +static void feature_param_free_value_uint(struct feature_param_value *val) +{ +} + +static size_t +feature_param_stringify_uint(const struct feature_param_value *val, + char *buffer) +{ + return buffer ? sprintf(buffer, "%u", val->value) : + snprintf(NULL, 0, "%u", val->value); +} + +static int +feature_param_is_equal_uint(const void *data, + const struct feature_param_value *val) +{ + return *(unsigned int *)data == val->value; +} + +static int +feature_param_copy_uint(const struct feature_param_value *src_val, + struct feature_param_value *val) +{ + val->value = src_val->value; + return 0; +} + +static struct feature_param_value * +feature_param_create_string(const char *buf, struct feature_param_entry *entry) +{ + struct feature_param_value *val; + + if (!buf) + return ERR_PTR(-EINVAL); + + val = alloc_feature_param_value(); + if (!val) + return ERR_PTR(-ENOMEM); + + val->data = kstrdup(buf, GFP_KERNEL); + return val; +} + +static void feature_param_free_value_string(struct feature_param_value *val) +{ + kfree(val->data); +} + +static size_t +feature_param_stringify_string(const struct feature_param_value *val, + char *buf) +{ + size_t size = strlen(val->data) + 1; + + if (buf) + memcpy(buf, val->data, size); + return size; +} + +static int +feature_param_is_equal_string(const void *data, + const struct feature_param_value *val) +{ + return !strcmp(*(char **)data, val->data); +} + +static int +feature_param_copy_string(const struct feature_param_value *src_val, + struct feature_param_value *val) +{ + val->data = kstrdup(src_val->data, GFP_KERNEL); + return 0; +} + +const struct feature_param_type_ops feature_param_type_ops_uint = { + .create = feature_param_create_uint, + .free_value = feature_param_free_value_uint, + .stringify = feature_param_stringify_uint, + .is_equal = feature_param_is_equal_uint, + .copy = feature_param_copy_uint, +}; + +const struct feature_param_type_ops feature_param_type_ops_string = { + .create = feature_param_create_string, + .free_value = feature_param_free_value_string, + .stringify = feature_param_stringify_string, + .is_equal = feature_param_is_equal_string, + .copy = feature_param_copy_string, +}; + +/* + * lisa_features_param features + */ + +static int +feature_param_lisa_validate(struct feature_param_value *val, + struct feature_param_entry *entry) +{ + struct feature *feature; + bool valid_feature = false; + + /* Check the name matches an existing feature. */ + for_each_feature(feature) { + if (feature->__internal) + continue; + + if (!strcmp(val->data, feature->name)) { + valid_feature = true; + break; + } + } + + if (valid_feature) + return feature_param_validate_set(val, entry); + return -EINVAL; +} + +/* Handle feature names using the (struct feature_param) logic. */ +struct feature_param lisa_features_param = { + .name = LISA_FEATURE_PARAM_NAME, + .type = FT_PARAM_TYPE_FEATURE, + .perms = S_IFREG | 0666, + .ops = &feature_param_type_ops_string, + .validate = feature_param_lisa_validate, + .param_args = HLIST_HEAD_INIT, +}; diff --git a/lisa/_assets/kmodules/lisa/feature_params.h b/lisa/_assets/kmodules/lisa/feature_params.h new file mode 100644 index 000000000..21151d5b3 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.h @@ -0,0 +1,289 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FEATURE__PARAM_H +#define _FEATURE__PARAM_H + +#include + +#include "features.h" +#include "configs.h" +#include "main.h" +#include "tp.h" + +#define LISA_FEATURE_PARAM_NAME "lisa_features_param" + +/* + * Struct containing one single data value. + * E.g., if a feature can be active on selected CPUs, then each + * config can have its list of selected CPUs. These lists are + * (struct feature_param_entry). Each selected CPU value is stored in a + * (struct feature_param_value), belonging to a + * (struct feature_param_entry). + */ +struct feature_param_value { + /* + * Member of (struct feature_param_entry)->list_values + */ + struct list_head node; + + /* Parent entry. */ + struct feature_param_entry *entry; + + /* Features set in the 'set_feature' entry might require to create + * a dentry to configure the feature. + */ + struct dentry *dentry; + + union { + unsigned int value; + void *data; + }; +}; + +/* + * Struct containing a list of values. + * E.g., if a feature can be active on selected CPUs, then each + * config can have its list of selected CPUs. These lists are + * (struct feature_param_entry). Each selected CPU value is stored in a + * (struct feature_param_value), belonging to a + * (struct feature_param_entry). + */ +struct feature_param_entry { + /* Member of (struct feature_param)->param_args */ + struct hlist_node node; + + /* Member of (struct lisa_cfg)->list_param */ + struct hlist_node node_cfg; + + /* List of (struct feature_param_value)->node. */ + struct list_head list_values; + + /* Parent param. */ + struct feature_param *param; + + /* Parent cfg. */ + struct lisa_cfg *cfg; + + /* Matching dentry. */ + struct dentry *dentry; +}; + +enum feature_param_type { + /* Standard parameter. */ + FT_PARAM_TYPE_STD = 0, + /* 'set_feature' parameter. */ + FT_PARAM_TYPE_FEATURE, +}; + +struct feature_param { + const char *name; + enum feature_param_type type; + umode_t perms; + const struct feature_param_type_ops *ops; + int (*validate)(struct feature_param_value *, struct feature_param_entry *); + + /* List of (struct feature_param_entry)->node. */ + struct hlist_head param_args; + + /* Parent feature. */ + struct feature *feature; +}; + +struct feature_param_type_ops { + struct feature_param_value *(*create)(const char *, struct feature_param_entry *); + void (*free_value)(struct feature_param_value *); + size_t (*stringify)(const struct feature_param_value *, char *); + int (*is_equal)(const void *, const struct feature_param_value *); + int (*copy)(const struct feature_param_value *, struct feature_param_value *); +}; + +extern const struct feature_param_type_ops feature_param_type_ops_uint; +extern const struct feature_param_type_ops feature_param_type_ops_string; + +int feature_param_validate_single(struct feature_param_value *val, + struct feature_param_entry *entry); +int feature_param_validate_set(struct feature_param_value *val, + struct feature_param_entry *entry); + +extern struct feature_param lisa_features_param; + +#define GET_PARAM_HANDLER(type) \ + _Generic((type) {0}, \ + char * : &feature_param_type_ops_string, \ + unsigned int : &feature_param_type_ops_uint \ + ) + +#define __PARAM(__name, __type, __perms, __param_type, __validate_f, __feature) \ + (&(struct feature_param) { \ + .name = __name, \ + .type = __type, \ + .perms = __perms, \ + .ops = GET_PARAM_HANDLER(__param_type), \ + .validate = __validate_f, \ + .param_args = HLIST_HEAD_INIT, \ + .feature = &__FEATURE_NAME(__feature), \ + }) + +#define PARAM_SINGLE(name, perms, param_type, feature) \ + __PARAM(name, FT_PARAM_TYPE_STD, perms, param_type, \ + feature_param_validate_single, __EVENT_FEATURE(feature)) +#define PARAM_SET(name, perms, param_type, feature) \ + __PARAM(name, FT_PARAM_TYPE_STD, perms, param_type, \ + feature_param_validate_set, __EVENT_FEATURE(feature)) + +#define FEATURE_PARAMS(...) \ + .params = (struct feature_param* []){__VA_ARGS__, NULL} \ + +#define EXPAND(...) __VA_ARGS__ +#define DEFINE_FEATURE_PARAMS(...) EXPAND(__VA_ARGS__) + +#define for_each_feature_param(param, pparam, feature) \ + if (feature->params) \ + for (pparam = feature->params, param = *pparam; \ + param != NULL; \ + pparam++, param = *pparam) \ + +static inline void +feature_param_entry_print(struct feature_param *param, + struct feature_param_value *val, + unsigned int indent) +{ + bool success = false; + + if (param && param->ops->stringify) { + size_t size = param->ops->stringify(val, NULL); + char *buf = kzalloc(indent + size + 1, GFP_KERNEL); + + if (buf) { + buf[size] = '\0'; + size = param->ops->stringify(val, &buf[indent]); + + while (indent--) + buf[indent] = ' '; + + pr_info("%s\n", buf); + kfree(buf); + success = true; + } + } + if (!success) + pr_info("Value: failed to print\n"); +} + +static inline +struct feature_param_value *alloc_feature_param_value(void) +{ + struct feature_param_value *val; + + val = kzalloc(sizeof(*val), GFP_KERNEL); + if (!val) + return NULL; + + INIT_LIST_HEAD(&val->node); + return val; +} + +static inline +void init_feature_param_value(struct feature_param_value *val, + struct feature_param_entry *entry) +{ + /* Don't init the refcount for non-global values. */ + list_add_tail(&val->node, &entry->list_values); + val->entry = entry; +} + +static inline +void init_feature_param_value_global(struct feature_param_value *val, + struct feature_param_entry *entry, + struct list_head *head) +{ + list_add_tail(&val->node, head); + val->entry = entry; +} + +void lisa_param_feature_delete_feature_dir(struct feature_param_value *val); +static inline +void free_feature_param_value(struct feature_param_value *val) +{ + struct feature_param *param = val->entry->param; + + /* Might need to create a dentry if a feature is disabled. */ + if (param->type == FT_PARAM_TYPE_FEATURE) + lisa_param_feature_delete_feature_dir(val); + + list_del(&val->node); + param->ops->free_value(val); + kfree(val); +} + +static inline +void drain_feature_param_value(struct list_head *head) +{ + struct feature_param_value *val, *tmp; + + list_for_each_entry_safe(val, tmp, head, node) + free_feature_param_value(val); +} + +static inline +struct feature_param_entry *alloc_feature_param_entry(void) +{ + return kzalloc(sizeof(struct feature_param_entry), GFP_KERNEL); +} + +static inline +void init_feature_param_entry(struct feature_param_entry *entry, struct dentry *dentry, + struct lisa_cfg *cfg, struct feature_param *param) +{ + entry->param = param; + entry->cfg = cfg; + entry->dentry = dentry; + + INIT_LIST_HEAD(&entry->list_values); + hlist_add_head(&entry->node, ¶m->param_args); + hlist_add_head(&entry->node_cfg, &cfg->list_param); +} + +static inline +void free_feature_param_entry(struct feature_param_entry *entry) +{ + drain_feature_param_value(&entry->list_values); + hlist_del(&entry->node); + hlist_del(&entry->node_cfg); + lisa_fs_remove(entry->dentry); + kfree(entry); +} + +static inline +void drain_feature_param_entry_cfg(struct hlist_head *head) +{ + struct feature_param_entry *entry; + struct hlist_node *tmp; + + hlist_for_each_entry_safe(entry, tmp, head, node_cfg) + free_feature_param_entry(entry); +} + +/** + * feature_param_add_new() - Create a (struct feature_param_entry) from + * a string, and add it to the input @entry. + * @entry: Entry to add the new value to. + * @v: String containing the value to parse and add. + * + * Return: 0 if success. + */ +int feature_param_add_new(struct feature_param_entry *entry, const char *v); + +/** + * find_feature_param() - Find the (struct feature_param) of a feature + * matching the input name. + * @feature: Feature to search the @param_name in. + * @param_name: Name of the feature_param to find. + * + * Return: (struct feature_param*) matching the input name if success. + * NULL otherwise. + */ +struct feature_param * +find_feature_param(struct feature *feature, char *param_name); + +#endif diff --git a/lisa/_assets/kmodules/lisa/features.c b/lisa/_assets/kmodules/lisa/features.c index 01e5c2c8c..317a3db33 100644 --- a/lisa/_assets/kmodules/lisa/features.c +++ b/lisa/_assets/kmodules/lisa/features.c @@ -5,13 +5,13 @@ #include "main.h" #include "features.h" -int __enable_feature(struct feature* feature) { +int __enable_feature_locked(struct feature *feature) +{ int ret; if (!feature) return 1; - mutex_lock(feature->lock); if (feature->enabled) { ret = feature->__enable_ret; } else { @@ -25,17 +25,28 @@ int __enable_feature(struct feature* feature) { if (ret) pr_err("Failed to enable feature %s: %i", feature->name, ret); } + feature->enabled++; + return ret; +} + +int __enable_feature(struct feature *feature) +{ + int ret; + + mutex_lock(feature->lock); + ret = __enable_feature_locked(feature); mutex_unlock(feature->lock); + return ret; } -int __disable_feature(struct feature* feature) { +int __disable_feature_locked(struct feature *feature) +{ int ret; if (!feature) return 0; - mutex_lock(feature->lock); if (!feature->enabled) { ret = 0; } else { @@ -52,7 +63,17 @@ int __disable_feature(struct feature* feature) { ret = 0; } } + return ret; +} + +int __disable_feature(struct feature *feature) +{ + int ret; + + mutex_lock(feature->lock); + ret = __disable_feature_locked(feature); mutex_unlock(feature->lock); + return ret; } @@ -80,7 +101,7 @@ static int __process_features(char **selected, size_t selected_len, feature_proc } } else { // User did not ask for any particular feature, so try to enable all non-internal features. - for (struct feature* feature=__lisa_features_start; feature < __lisa_features_stop; feature++) { + for (struct feature *feature=__lisa_features_start; feature < __lisa_features_stop; feature++) { if (!feature->__internal) { ret |= process(feature); } @@ -91,13 +112,13 @@ static int __process_features(char **selected, size_t selected_len, feature_proc } -static int __list_feature(struct feature* feature) { +static int __list_feature(struct feature *feature) { if (!feature->__internal) pr_info(" %s", feature->name); return 0; } -static int __enable_feature_explicitly(struct feature* feature) { +static int __enable_feature_explicitly(struct feature *feature) { mutex_lock(feature->lock); feature->__explicitly_enabled++; mutex_unlock(feature->lock); @@ -112,7 +133,11 @@ int init_features(char **selected, size_t selected_len) { return __process_features(selected, selected_len, __enable_feature_explicitly); } -static int __disable_explicitly_enabled_feature(struct feature* feature) { +int enable_single_feature(char *feature_name) { + return __process_features(&feature_name, 1, __enable_feature); +} + +static int __disable_explicitly_enabled_feature(struct feature *feature) { int ret = 0; mutex_lock(feature->lock); @@ -129,6 +154,10 @@ int deinit_features(void) { return __process_features(NULL, 0, __disable_explicitly_enabled_feature); } +int disable_single_feature(char *feature_name) { + return __process_features(&feature_name, 1, __disable_feature); +} + int __placeholder_init(struct feature *feature) { pr_err("Feature not available: %s\n", feature->name); return 1; diff --git a/lisa/_assets/kmodules/lisa/features.h b/lisa/_assets/kmodules/lisa/features.h index 31e322f41..41c0db292 100644 --- a/lisa/_assets/kmodules/lisa/features.h +++ b/lisa/_assets/kmodules/lisa/features.h @@ -1,13 +1,16 @@ /* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FEATURE_H +#define _FEATURE_H + #include #include #include #include - #include -#ifndef _FEATURE_H -#define _FEATURE_H +#include "feature_params.h" +#include "main.h" /** * struct feature - LISA kernel module feature @@ -18,6 +21,11 @@ * @lock: Lock taken when enabling and disabling the feature. * @enable: Function pointer to the enable function. Return non-zero value in case of error. * @disable: Function pointer to the disable function. Return non-zero value in case of error. + * @enable_cfg: Function pointer to enable a specific config for the feature. + * Return non-zero value in case of error. + * @disable_cfg: Function pointer to disable a specific config for the feature. + * Return non-zero value in case of error. + * @params: Array of pointers to this feature's parameters. * * A struct feature represent an independent feature of the kernel module that * can be enabled and disabled dynamically. Features are ref-counted so that @@ -30,6 +38,8 @@ struct feature { struct mutex *lock; int (*enable)(struct feature*); int (*disable)(struct feature*); + int (*enable_cfg)(struct feature*, struct lisa_cfg *); + int (*disable_cfg)(struct feature*, struct lisa_cfg *); /* Return code of the enable() function */ int __enable_ret; @@ -41,6 +51,14 @@ struct feature { * advantage of reference counting to ensure safe setup/teardown. */ bool __internal; + + /* This feature doesn't support nested usage, i.e. multiple config + * using the feature. + */ + bool __no_nesting; + + /* Array of pointers to this feature's parameters. */ + struct feature_param **params; }; /* Start and stop address of the ELF section containing the struct feature @@ -49,6 +67,9 @@ struct feature { extern struct feature __lisa_features_start[]; extern struct feature __lisa_features_stop[]; +#define for_each_feature(feature) \ + for (feature = __lisa_features_start; feature < __lisa_features_stop; feature++) + /** * MAX_FEATURES - Maximum number of features allowed in this module. */ @@ -173,6 +194,20 @@ int __placeholder_deinit(struct feature *feature); __disable_feature(FEATURE(feature_name)); \ }) +/** + * find_feature() - Find a feature by name. + * @feature_name: Name of the feature. + */ +static inline struct feature * +find_feature(const char *feature_name) { + struct feature *feature; + + for_each_feature(feature) + if (!strcmp(feature_name, feature->name)) + return feature; + return NULL; +} + /** * init_features() - Initialize features * @selected: Array of char * containing feature names to initialize. @@ -184,6 +219,12 @@ int __placeholder_deinit(struct feature *feature); */ int init_features(char **selected, size_t selected_len); +/** + * enable_single_feature() - Enable one feature + * @feature_name: Name of the feature to able. + */ +int enable_single_feature(char *feature_name); + /** * deinit_features() - De-initialize features * @@ -191,4 +232,11 @@ int init_features(char **selected, size_t selected_len); * Return: non-zero in case of errors. */ int deinit_features(void); + +/** + * disable_single_feature() - disable one feature + * @feature_name: Name of the feature to disable. + */ +int disable_single_feature(char *feature_name); + #endif diff --git a/lisa/_assets/kmodules/lisa/fs.c b/lisa/_assets/kmodules/lisa/fs.c new file mode 100644 index 000000000..6089249f4 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/fs.c @@ -0,0 +1,708 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "features.h" +#include "configs.h" + +static int lisa_fs_create_files(struct dentry *dentry, struct lisa_cfg *cfg); +static struct dentry * +lisa_fs_create_single(struct dentry *parent, const char *name, + const struct inode_operations *i_ops, + const struct file_operations *f_ops, umode_t mode, + void *data); + +#define LISA_FS_SUPER_MAGIC 0xcdb11bc9 + +struct lisa_sb_info { + /* Protect the interface. */ + struct mutex interface_lock; + + /* List of configs. */ + struct hlist_head global_cfg_list; +}; + +static inline void lisa_sb_lock(struct super_block *sb) +{ + struct lisa_sb_info *si = sb->s_fs_info; + + mutex_lock(&si->interface_lock); +} + +static inline void lisa_sb_unlock(struct super_block *sb) +{ + struct lisa_sb_info *si = sb->s_fs_info; + + mutex_unlock(&si->interface_lock); +} + +static inline struct hlist_head *lisa_sb_get_cfg_list(struct super_block *sb) +{ + struct lisa_sb_info *si = sb->s_fs_info; + + /* If vfs initialization failed. */ + if (!si) + return NULL; + return &si->global_cfg_list; +} + +static struct inode *lisa_fs_create_inode(struct super_block *sb, int mode) +{ + struct inode *inode = new_inode(sb); + + if (inode) { +/* Cf. commit 077c212f0344 + * ("fs: new accessor methods for atime and mtime") + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) + struct timespec64 ts = current_time(inode); + + inode_set_mtime_to_ts(inode, ts); + inode_set_atime_to_ts(inode, ts); +#else + inode->i_atime = inode->i_mtime = current_time(inode); +#endif + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + } + + return inode; +} + +/* + * available_features handlers + */ + +static int lisa_features_available_show(struct seq_file *s, void *data) +{ + struct feature *feature; + + for_each_feature(feature) + if (!feature->__internal) + seq_printf(s, "%s\n", feature->name); + + return 0; +} + +static int lisa_features_available_open(struct inode *inode, struct file *file) +{ + return single_open(file, lisa_features_available_show, NULL); +} + +static const struct file_operations lisa_available_features_fops = { + .owner = THIS_MODULE, + .open = lisa_features_available_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * activate handlers + */ + +static int lisa_activate_show(struct seq_file *s, void *data) +{ + struct lisa_cfg *cfg = s->private; + + seq_printf(s, "%d\n", cfg->activated); + return 0; +} + +static ssize_t lisa_activate_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + bool value; + int ret; + + if (kstrtobool_from_user(buf, count, &value)) + return -EINVAL; + + lisa_sb_lock(file->f_inode->i_sb); + ret = activate_lisa_cfg((struct lisa_cfg *)s->private, value, NULL); + lisa_sb_unlock(file->f_inode->i_sb); + + return ret < 0 ? ret : count; +} + +static int lisa_activate_open(struct inode *inode, struct file *file) +{ + struct lisa_cfg *cfg = inode->i_private; + + return single_open(file, lisa_activate_show, cfg); +} + +static const struct file_operations lisa_activate_fops = { + .owner = THIS_MODULE, + .open = lisa_activate_open, + .read = seq_read, + .write = lisa_activate_write, + .release = single_release, +}; + +/* + * set_features handlers + * available_features handlers + */ + +static void *lisa_param_feature_seq_start(struct seq_file *s, loff_t *pos) +{ + struct feature_param_entry *entry; + void *ret; + + lisa_sb_lock(s->file->f_inode->i_sb); + + entry = *(struct feature_param_entry **)s->private; + ret = seq_list_start(&entry->list_values, *pos); + + return ret; +} + +static int lisa_param_feature_seq_show(struct seq_file *s, void *v) +{ + struct feature_param_value *val; + struct feature_param_entry *entry; + struct feature_param *param; + + entry = *(struct feature_param_entry **)s->private; + param = entry->param; + + val = hlist_entry(v, struct feature_param_value, node); + + if (param->ops->stringify) { + size_t size = param->ops->stringify(val, NULL); + char *buf = kzalloc(size + 1, GFP_KERNEL); + + if (!buf) + return -ENOMEM; + + buf[size] = '\0'; + size = param->ops->stringify(val, buf); + seq_printf(s, "%s\n", buf); + kfree(buf); + } + + return 0; +} + +static void *lisa_param_feature_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct feature_param_entry *entry; + + entry = *(struct feature_param_entry **)s->private; + return seq_list_next(v, &entry->list_values, pos); +} + +static void lisa_param_feature_seq_stop(struct seq_file *s, void *v) +{ + lisa_sb_unlock(s->file->f_inode->i_sb); +} + +static const struct seq_operations lisa_param_feature_seq_ops = { + .start = lisa_param_feature_seq_start, + .next = lisa_param_feature_seq_next, + .stop = lisa_param_feature_seq_stop, + .show = lisa_param_feature_seq_show, +}; + +static int lisa_param_feature_open(struct inode *inode, struct file *file) +{ + if (file->f_mode & FMODE_READ) { + struct feature_param_entry **entry; + + entry = __seq_open_private(file, &lisa_param_feature_seq_ops, + sizeof(entry)); + if (entry) + *entry = inode->i_private; + + return entry ? 0 : -ENOMEM; + } + return 0; +} + +#define MAX_BUF_SIZE 1024 +static ssize_t lisa_param_feature_write(struct file *file, + const char __user *buf, size_t count, + loff_t *ppos) +{ + struct feature_param_entry *entry = file->f_inode->i_private; + char *kbuf, *s, *sep; + ssize_t done = 0; + int ret; + + /* + * Don't modify the 'set_features' or any parameter value if the + * config is activated. The process is: + * - De-activate + * - Modify + * - Re-activate + */ + if (entry->cfg->activated) { + pr_err("Config must be deactivated before any update.\n"); + return -EBUSY; + } + + lisa_sb_lock(file->f_inode->i_sb); + + if (!(file->f_flags & O_APPEND)) + drain_feature_param_value(&entry->list_values); + + lisa_sb_unlock(file->f_inode->i_sb); + + kbuf = kzalloc(MAX_BUF_SIZE, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + while (done < count) { + ssize_t size = count - done; + + if (size >= MAX_BUF_SIZE) + size = MAX_BUF_SIZE - 1; + + if (copy_from_user(kbuf, buf + done, size)) + goto out; + kbuf[size] = '\0'; + s = sep = kbuf; + do { + sep = strchr(s, ','); + if (sep) { + *sep = '\0'; + ++sep; + ++done; + } + done += size = strlen(s); + /* skip leading whitespaces */ + while (isspace(*s) && *(s++)) + --size; + if (!*s) + goto next; + if (done < count && !sep) { + /* carry over ... */ + done -= strlen(s); + goto next; + } + /* skip trailing whitespaces */ + while (size && isspace(s[--size])); + if (strlen(s) > ++size) + s[size] = '\0'; + + lisa_sb_lock(file->f_inode->i_sb); + ret = feature_param_add_new(entry, s); + lisa_sb_unlock(file->f_inode->i_sb); + if (ret) { + drain_feature_param_value(&entry->list_values); + done = ret; + goto out; + } + + if (ppos) + *ppos += 1; +next: + s = sep; + } while (s); + } + +out: + kfree(kbuf); + return done; +} + +static int +lisa_param_feature_release(struct inode *inode, struct file *file) +{ + return file->f_mode & FMODE_READ ? seq_release_private(inode, file) : 0; +} + +static const struct file_operations lisa_param_feature_fops = { + .owner = THIS_MODULE, + .open = lisa_param_feature_open, + .read = seq_read, + .write = lisa_param_feature_write, + .release = lisa_param_feature_release, +}; + +int lisa_param_feature_create_feature_dir(const char *feature_name, + struct feature_param_value *val, + struct feature_param_entry *entry) +{ + struct dentry *dentry, *cur_dentry, *parent = entry->cfg->dentry; + struct feature_param *param, **pparam; + struct lisa_cfg *cfg = entry->cfg; + struct feature *feature; + + feature = find_feature(feature_name); + if (!feature) { + pr_err("Feature not found: %s.\n", feature_name); + return -EINVAL; + } + + if (!feature->params) + return 0; + + dentry = lisa_fs_create_single(parent, feature->name, + &simple_dir_inode_operations, + &simple_dir_operations, + S_IFDIR | 0666, cfg); + if (!dentry) { + pr_err("Failed to initialize feature's (%s) root node\n", + feature->name); + return -ENOMEM; + } + + for_each_feature_param(param, pparam, feature) { + entry = alloc_feature_param_entry(); + if (!entry) + return -ENOMEM; + + cur_dentry = lisa_fs_create_single(dentry, param->name, NULL, + &lisa_param_feature_fops, + S_IFREG | 0666, + entry); + if (!cur_dentry) + return -ENOMEM; + + init_feature_param_entry(entry, cur_dentry, cfg, param); + } + + val->dentry = dentry; + return 0; +} + +void lisa_param_feature_delete_feature_dir(struct feature_param_value *val) +{ + struct feature_param *param = NULL, **pparam; + struct feature_param_entry *entry; + struct feature *feature; + struct lisa_cfg *cfg; + + /* Nothing to clean. */ + if (!val->dentry) + return; + + feature = find_feature(val->data); + cfg = val->entry->cfg; + + /* Remove feature_param dentries. */ + for_each_feature_param(param, pparam, feature) { + entry = find_feature_param_entry_cfg(cfg, (char *)param->name); + if (entry) + free_feature_param_entry(entry); + } + + lisa_fs_remove(val->dentry); +} + +///////////////////////////////////// +// configs +///////////////////////////////////// + +static int lisa_fs_mkdir(MOUNT_INFO_T mnt_info, struct inode *inode, + struct dentry *dentry, umode_t mode) +{ + struct dentry *my_dentry; + struct hlist_head *cfg_list; + struct super_block *sb; + struct lisa_cfg *cfg; + int ret; + + sb = inode->i_sb; + cfg_list = lisa_sb_get_cfg_list(sb); + + cfg = alloc_lisa_cfg(dentry->d_name.name); + if (!cfg) + return -ENOMEM; + + lisa_sb_lock(sb); + + my_dentry = lisa_fs_create_single(dentry->d_parent, dentry->d_name.name, + &simple_dir_inode_operations, + &simple_dir_operations, + S_IFDIR | mode, cfg); + if (!my_dentry) { + ret = -ENOMEM; + goto error; + } + + init_lisa_cfg(cfg, cfg_list, my_dentry); + + ret = lisa_fs_create_files(my_dentry, cfg); + if (ret) + goto error; + + lisa_sb_unlock(sb); + return 0; + +error: + free_lisa_cfg(cfg); + lisa_sb_unlock(sb); + return ret; +} + +void lisa_fs_remove(struct dentry *dentry) +{ + struct dentry *parent; + + parent = dentry->d_parent; + inode_lock(d_inode(parent)); + if (simple_positive(dentry)) { + pr_err("%d %d\n", __LINE__, d_is_dir(dentry)); + if (d_is_dir(dentry)) + simple_rmdir(d_inode(parent), dentry); + else + simple_unlink(d_inode(parent), dentry); + } + d_drop(dentry); + dput(dentry); + inode_unlock(d_inode(parent)); +} + +static int lisa_fs_rmdir(struct inode *inode, struct dentry *dentry) +{ + struct super_block *sb; + struct lisa_cfg *cfg; + + sb = inode->i_sb; + + lisa_sb_lock(inode->i_sb); + cfg = d_inode(dentry)->i_private; + inode_unlock(d_inode(dentry)); + if (cfg) + free_lisa_cfg(cfg); + inode_lock(d_inode(dentry)); + lisa_sb_unlock(inode->i_sb); + + return 0; +} + +const struct inode_operations lisa_fs_dir_inode_operations = { + .lookup = simple_lookup, + .mkdir = lisa_fs_mkdir, + .rmdir = lisa_fs_rmdir, +}; + +///////////////////////////////////// +// Main files +///////////////////////////////////// + +static struct dentry * +lisa_fs_create_single(struct dentry *parent, const char *name, + const struct inode_operations *i_ops, + const struct file_operations *f_ops, umode_t mode, + void *data) +{ + struct dentry *dentry; + struct inode *inode; + + dentry = d_alloc_name(parent, name); + if (!dentry) + return NULL; + inode = lisa_fs_create_inode(parent->d_sb, mode); + if (!inode) { + dput(dentry); + return NULL; + } + + if (mode & S_IFREG) { + inode->i_fop = f_ops; + } else { + inode->i_op = i_ops; + inode->i_fop = f_ops; + } + inode->i_private = data; + d_add(dentry, inode); + if (mode & S_IFDIR) { + inc_nlink(d_inode(parent)); + inc_nlink(inode); + } + + return dentry; +} + +/* + * Note: Upon failure, the caller must call free_lisa_cfg(), which will go + * over the elements of (struct lisa_cfg)->list_param and free them. + * These elements are of the 'struct feature_param_entry' type. + */ +static int +lisa_fs_create_files(struct dentry *parent, struct lisa_cfg *cfg) +{ + struct feature_param_entry *entry; + struct dentry *dentry; + + entry = alloc_feature_param_entry(); + if (!entry) + return -ENOMEM; + + /* set_features: enable a feature - RW. */ + dentry = lisa_fs_create_single(parent, "set_features", + NULL, &lisa_param_feature_fops, + S_IFREG | 0666, entry); + if (!dentry) + return -ENOMEM; + + init_feature_param_entry(entry, dentry, cfg, &lisa_features_param); + + /* available_features: list available features - RO. */ + if (!lisa_fs_create_single(parent, "available_features", + NULL, &lisa_available_features_fops, + S_IFREG | 0444, &lisa_features_param)) + return -ENOMEM; + + /* activate: activate the selected (and configured) features - RW. */ + if (!lisa_fs_create_single(parent, "activate", + NULL, &lisa_activate_fops, + S_IFREG | 0666, cfg)) + return -ENOMEM; + + /* configs: Dir containing configurations, only setup at the top/root level. */ + if (parent->d_sb->s_root == parent) { + if (!lisa_fs_create_single(parent, "configs", + &lisa_fs_dir_inode_operations, + &simple_dir_operations, + S_IFDIR | 0666, NULL)) + return -ENOMEM; + } + + return 0; +} + +///////////////////////////////////// +// Super block +///////////////////////////////////// + +static struct super_operations lisa_super_ops = { + .statfs = simple_statfs, +}; + +static int lisa_fs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + struct lisa_sb_info *lisa_info; + struct lisa_cfg *cfg; + struct inode *root; + int ret = -ENOMEM; + + sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_blocksize = PAGE_SIZE; + sb->s_blocksize_bits = PAGE_SHIFT; + sb->s_magic = LISA_FS_SUPER_MAGIC; + sb->s_op = &lisa_super_ops; + + lisa_info = kzalloc(sizeof(*lisa_info), GFP_KERNEL); + if (!lisa_info) + return -ENOMEM; + + mutex_init(&lisa_info->interface_lock); + INIT_HLIST_HEAD(&lisa_info->global_cfg_list); + sb->s_fs_info = lisa_info; + + root = lisa_fs_create_inode(sb, S_IFDIR | 0444); + if (!root) + goto error0; + + root->i_op = &simple_dir_inode_operations; + root->i_fop = &simple_dir_operations; + + sb->s_root = d_make_root(root); + if (!sb->s_root) + goto error1; + + cfg = alloc_lisa_cfg("root"); + if (!cfg) + goto error2; + + init_lisa_cfg(cfg, &lisa_info->global_cfg_list, sb->s_root); + + ret = lisa_fs_create_files(sb->s_root, cfg); + if (ret) + goto error3; + + return 0; + +error3: + free_lisa_cfg(cfg); +error2: + dput(sb->s_root); +error1: + iput(root); +error0: + kfree(lisa_info); + sb->s_fs_info = NULL; + + return ret; +} + +static int lisa_fs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, lisa_fs_fill_super); +} + +static const struct fs_context_operations lisa_fs_context_ops = { + .get_tree = lisa_fs_get_tree, +}; + +static int lisa_init_fs_context(struct fs_context *fc) +{ + fc->ops = &lisa_fs_context_ops; + put_user_ns(fc->user_ns); + fc->user_ns = get_user_ns(&init_user_ns); + fc->global = true; + return 0; +} + +static void lisa_fs_kill_sb(struct super_block *sb) +{ + struct hlist_head *cfg_list; + + cfg_list = lisa_sb_get_cfg_list(sb); + if (cfg_list) + drain_lisa_cfg(cfg_list); + + kfree(sb->s_fs_info); + sb->s_root = NULL; + + /* Free the inodes/dentries. */ + kill_litter_super(sb); +} + +static struct file_system_type lisa_fs_type = { + .owner = THIS_MODULE, + .name = "lisa", + .init_fs_context = lisa_init_fs_context, + .kill_sb = lisa_fs_kill_sb, +}; + +int init_lisa_fs(void) +{ + int ret; + + ret = sysfs_create_mount_point(fs_kobj, "lisa"); + if (ret) + goto error0; + + ret = register_filesystem(&lisa_fs_type); + if (ret) + goto error1; + + return ret; + +error1: + sysfs_remove_mount_point(fs_kobj, "lisa"); +error0: + pr_err("Could not install lisa fs.\n"); + return ret; +} + +void exit_lisa_fs(void) +{ + unregister_filesystem(&lisa_fs_type); + sysfs_remove_mount_point(fs_kobj, "lisa"); +} diff --git a/lisa/_assets/kmodules/lisa/main.c b/lisa/_assets/kmodules/lisa/main.c index 05ec9b2cf..29c652d64 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -14,12 +14,16 @@ static char* version = LISA_MODULE_VERSION; module_param(version, charp, 0); MODULE_PARM_DESC(version, "Module version defined as sha1sum of the module sources"); +int init_lisa_fs(void); +void exit_lisa_fs(void); + static char *features[MAX_FEATURES]; unsigned int features_len = 0; module_param_array(features, charp, &features_len, 0); MODULE_PARM_DESC(features, "Comma-separated list of features to enable. Available features are printed when loading the module"); static void modexit(void) { + exit_lisa_fs(); if (deinit_features()) pr_err("Some errors happened while unloading LISA kernel module\n"); } @@ -33,6 +37,12 @@ static int __init modinit(void) { return -EPROTO; } + ret = init_lisa_fs(); + if (ret) { + pr_err("Failed to setup lisa_fs\n"); + return ret; + } + pr_info("Kernel features detected. This will impact the module features that are available:\n"); const char *kernel_feature_names[] = {__KERNEL_FEATURE_NAMES}; const bool kernel_feature_values[] = {__KERNEL_FEATURE_VALUES}; @@ -60,9 +70,11 @@ static int __init modinit(void) { * vanishes. */ modexit(); - return ret; - + } else { + exit_lisa_fs(); } + + return ret; } return 0; } diff --git a/lisa/_assets/kmodules/lisa/main.h b/lisa/_assets/kmodules/lisa/main.h index e3691166d..41eb9fed3 100644 --- a/lisa/_assets/kmodules/lisa/main.h +++ b/lisa/_assets/kmodules/lisa/main.h @@ -3,8 +3,20 @@ #define _MAIN_H #include +#include + +#include "introspection.h" #undef pr_fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +/* Cf. kernel commit c54bd91e9eab + * ("fs: port ->mkdir() to pass mnt_idmap") + */ +#if HAS_TYPE(struct, mnt_idmap) +#define MOUNT_INFO_T struct mnt_idmap * +#else +#define MOUNT_INFO_T struct user_namespace * +#endif + #endif -- GitLab From c508a8967eccc8bab7c593eb3ff0944ce3d4c4d3 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 18:03:31 +0100 Subject: [PATCH 2/8] lisa._assets.kmodules.lisa: Update macros to allow defining feature param Features are declared/defined using macros. Update them to allow declaring feature parameters along features. Original-author: Beata Michalska Signed-off-by: Pierre Gondois --- lisa/_assets/kmodules/lisa/features.h | 32 +++++++++++++++++++-------- lisa/_assets/kmodules/lisa/pixel6.c | 2 +- lisa/_assets/kmodules/lisa/tp.h | 15 +++++++++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/features.h b/lisa/_assets/kmodules/lisa/features.h index 41c0db292..67373f45d 100644 --- a/lisa/_assets/kmodules/lisa/features.h +++ b/lisa/_assets/kmodules/lisa/features.h @@ -75,27 +75,31 @@ extern struct feature __lisa_features_stop[]; */ #define MAX_FEATURES 1024 -int __placeholder_init(struct feature *feature); -int __placeholder_deinit(struct feature *feature); +int __placeholder_enable(struct feature *feature); +int __placeholder_disable(struct feature *feature); +int __placeholder_enable_cfg(struct feature *feature, struct lisa_cfg *cfg); +int __placeholder_disable_cfg(struct feature *feature, struct lisa_cfg *cfg); #define __FEATURE_NAME(name) __lisa_feature_##name /* Weak definition, can be useful to deal with compiled-out features */ -#define __DEFINE_FEATURE_WEAK(feature_name) \ +#define __DEFINE_FEATURE_WEAK(feature_name, ...) \ __attribute__((weak)) DEFINE_MUTEX(__lisa_mutex_feature_##feature_name); \ __attribute__((weak)) struct feature __FEATURE_NAME(feature_name) = { \ .name = #feature_name, \ .data = NULL, \ .enabled = 0, \ .__explicitly_enabled = 0, \ - .enable = __placeholder_init, \ - .disable = __placeholder_deinit, \ + .enable = __placeholder_enable, \ + .disable = __placeholder_disable, \ + .enable_cfg = __placeholder_enable_cfg, \ + .disable_cfg = __placeholder_disable_cfg, \ .lock = &__lisa_mutex_feature_##feature_name, \ .__internal = true, \ .__enable_ret = 0, \ }; -#define __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, internal) \ +#define __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, enable_cfg_f, disable_cfg_f, internal, no_nest, ...) \ DEFINE_MUTEX(__lisa_mutex_feature_##feature_name); \ struct feature __FEATURE_NAME(feature_name) __attribute__((unused,section(".__lisa_features"))) = { \ .name = #feature_name, \ @@ -104,9 +108,13 @@ int __placeholder_deinit(struct feature *feature); .__explicitly_enabled = 0, \ .enable = enable_f, \ .disable = disable_f, \ + .enable_cfg = enable_cfg_f, \ + .disable_cfg = disable_cfg_f, \ .lock = &__lisa_mutex_feature_##feature_name, \ .__internal = internal, \ .__enable_ret = 0, \ + .__no_nesting = no_nest, \ + DEFINE_FEATURE_PARAMS(__VA_ARGS__) \ }; /** @@ -120,7 +128,12 @@ int __placeholder_deinit(struct feature *feature); * DISABLE_FEATURE() on all the features that were enabled by ENABLE_FEATURE() * in enable_f() in order to keep accurate reference-counting. */ -#define DEFINE_FEATURE(feature_name, enable_f, disable_f) __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, false) +#define DEFINE_FEATURE(feature_name, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ...) \ + __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, enable_cfg_f, disable_cfg_f, false, false, ##__VA_ARGS__) + +#define DEFINE_NO_NESTING_FEATURE(feature_name, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ...) \ + __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, enable_cfg_f, disable_cfg_f, false, true, ##__VA_ARGS__) + /** * DEFINE_INTERNAL_FEATURE() - Same as DEFINE_FEATURE() but for internal features. @@ -130,7 +143,8 @@ int __placeholder_deinit(struct feature *feature); * multiple other features, e.g. to initialize and teardown the use of a kernel * API (workqueues, tracepoints etc). */ -#define DEFINE_INTERNAL_FEATURE(feature_name, enable_f, disable_f) __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, true) +#define DEFINE_INTERNAL_FEATURE(feature_name, enable_f, disable_f, ...) \ + __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, NULL, NULL, true, false, ##__VA_ARGS__) /** * DECLARE_FEATURE() - Declare a feature to test for its presence dynamically. @@ -146,7 +160,7 @@ int __placeholder_deinit(struct feature *feature); * Note that because of weak symbols limitations, a given compilation unit * cannot contain both DECLARE_FEATURE() and DEFINE_FEATURE(). */ -#define DECLARE_FEATURE(feature_name) __DEFINE_FEATURE_WEAK(feature_name) +#define DECLARE_FEATURE(feature_name, ...) __DEFINE_FEATURE_WEAK(feature_name, ##__VA_ARGS__) /** * FEATURE() - Pointer the the struct feature diff --git a/lisa/_assets/kmodules/lisa/pixel6.c b/lisa/_assets/kmodules/lisa/pixel6.c index 3fa924c62..6d08f59fe 100644 --- a/lisa/_assets/kmodules/lisa/pixel6.c +++ b/lisa/_assets/kmodules/lisa/pixel6.c @@ -263,4 +263,4 @@ static int disable_p6_emeter(struct feature* feature) { #endif -DEFINE_FEATURE(event__lisa__pixel6_emeter, enable_p6_emeter, disable_p6_emeter); +DEFINE_FEATURE(event__lisa__pixel6_emeter, enable_p6_emeter, disable_p6_emeter, NULL, NULL); diff --git a/lisa/_assets/kmodules/lisa/tp.h b/lisa/_assets/kmodules/lisa/tp.h index 15c5210da..ae78528e5 100644 --- a/lisa/_assets/kmodules/lisa/tp.h +++ b/lisa/_assets/kmodules/lisa/tp.h @@ -106,15 +106,19 @@ struct __tp_probe { * user-defined enable/disable functions. If the tracepoint is not found, the * user functions will not be called. */ -#define DEFINE_EXTENDED_TP_FEATURE(feature_name, probes, enable_f, disable_f) \ +#define DEFINE_EXTENDED_TP_FEATURE(feature_name, probes, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ...) \ DEFINE_TP_ENABLE_DISABLE(feature_name, probes, CONCATENATE(__tp_feature_enable_, feature_name), enable_f, CONCATENATE(__tp_feature_disable_, feature_name), disable_f); \ - DEFINE_FEATURE(feature_name, CONCATENATE(__tp_feature_enable_, feature_name), CONCATENATE(__tp_feature_disable_, feature_name)); + DEFINE_FEATURE(feature_name, CONCATENATE(__tp_feature_enable_, feature_name), CONCATENATE(__tp_feature_disable_, feature_name), enable_cfg_f, disable_cfg_f, ##__VA_ARGS__); + +#define DEFINE_EXTENDED_TP_FEATURE_NO_NESTING(feature_name, probes, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ...) \ + DEFINE_TP_ENABLE_DISABLE(feature_name, probes, CONCATENATE(__tp_feature_enable_, feature_name), enable_f, CONCATENATE(__tp_feature_disable_, feature_name), disable_f); \ + DEFINE_NO_NESTING_FEATURE(feature_name, CONCATENATE(__tp_feature_enable_, feature_name), CONCATENATE(__tp_feature_disable_, feature_name), enable_cfg_f, disable_cfg_f, ##__VA_ARGS__); /** * DEFINE_TP_FEATURE() - Same as DEFINE_EXTENDED_TP_FEATURE() without custom * enable/disable functions. */ -#define DEFINE_TP_FEATURE(feature_name, probes) DEFINE_EXTENDED_TP_FEATURE(feature_name, probes, NULL, NULL) +#define DEFINE_TP_FEATURE(feature_name, probes) DEFINE_EXTENDED_TP_FEATURE(feature_name, probes, NULL, NULL, NULL, NULL) #define __EVENT_FEATURE(event_name) CONCATENATE(event__, event_name) @@ -128,7 +132,10 @@ struct __tp_probe { * DEFINE_EXTENDED_TP_EVENT_FEATURE() - Same as DEFINE_EXTENDED_TP_FEATURE() * with automatic "event__" prefixing of the feature name. */ -#define DEFINE_EXTENDED_TP_EVENT_FEATURE(event_name, probes, enable_f, disable_f) DEFINE_EXTENDED_TP_FEATURE(__EVENT_FEATURE(event_name), probes, enable_f, disable_f) +#define DEFINE_EXTENDED_TP_EVENT_FEATURE(event_name, probes, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ...) \ + DEFINE_EXTENDED_TP_FEATURE(__EVENT_FEATURE(event_name), probes, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ##__VA_ARGS__) +#define DEFINE_EXTENDED_TP_EVENT_FEATURE_NO_NESTING(event_name, probes, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ...) \ + DEFINE_EXTENDED_TP_FEATURE_NO_NESTING(__EVENT_FEATURE(event_name), probes, enable_f, disable_f, enable_cfg_f, disable_cfg_f, ##__VA_ARGS__) #define __DEPRECATED_EVENT_ENABLE(event_name) CONCATENATE(__enable_deprecated_feature_, __EVENT_FEATURE(event_name)) /** -- GitLab From 7ba923c80648c6b7793e1474efdb601b7b1c37d5 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 24 Jun 2024 12:28:06 +0200 Subject: [PATCH 3/8] lisa._kmod: Make use of kmod VFS to configure features A previous patch adds support for a virtual file system (VFS) for the lisa module. This VFS allows to configure the module features at runtime. Make use of this VFS to configure the desired features. Features that have parameters can be configured from a notebook with: """ features = { "lisa__perf_counter": { "generic_counters": ["cpu_cycles", "l1d_cache", "inst_retired"] } } ftrace_coll = FtraceCollector(target, ..., kmod_features=features) """ Original-author: Beata Michalska Signed-off-by: Pierre Gondois --- lisa/_cli_tools/lisa_load_kmod.py | 7 +- lisa/_kmod.py | 114 ++++++++++++++++++++++++++++++ lisa/trace.py | 38 ++++++++-- 3 files changed, 152 insertions(+), 7 deletions(-) diff --git a/lisa/_cli_tools/lisa_load_kmod.py b/lisa/_cli_tools/lisa_load_kmod.py index db914aa19..a4259d7eb 100755 --- a/lisa/_cli_tools/lisa_load_kmod.py +++ b/lisa/_cli_tools/lisa_load_kmod.py @@ -64,17 +64,20 @@ def _main(args, target): logging.info(f'Kernel module provides the following ftrace events: {pretty_events}') _kmod_cm = kmod.run(kmod_params=kmod_params) + _kmod_cfg = kmod.with_features(cfg_name="lisa_cli_tool", features=None) if keep_loaded: @contextlib.contextmanager def cm(): logging.info('Compiling and loading kernel module ...') - yield _kmod_cm.__enter__() + _kmod_cm.__enter__() + _kmod_cfg.__enter__() logging.info(f'Loaded kernel module as "{kmod.mod_name}"') + yield else: @contextlib.contextmanager def cm(): - with _kmod_cm: + with _kmod_cm, _kmod_cfg: logging.info('Compiling and loading kernel module ...') try: yield diff --git a/lisa/_kmod.py b/lisa/_kmod.py index 42c61be00..c2d1c9f2c 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -2675,18 +2675,124 @@ class DynamicKmod(Loggable): else: log_dmesg(dmesg_coll, logger.debug) + self.mount_lisa_fs() + + def mount_lisa_fs(self): + """ + Mount lisa_fs on mount_path. + """ + self.lisa_fs_path = Path("/sys/fs/lisa/") + self.target.execute(f'mount -t lisa none {self.lisa_fs_path}') + def uninstall(self): """ Unload the module from the target. """ mod = quote(self.mod_name) execute = self.target.execute + self.umount_lisa_fs() try: execute(f'rmmod {mod}') except TargetStableError: execute(f'rmmod -f {mod}') + def umount_lisa_fs(self): + """ + Mount lisa_fs on mount_path. + """ + self.target.execute(f'umount {self.lisa_fs_path}') + + def setup_config(self, cfg_name=None, features=None): + """ + config is a dict: { "cfg_name": { "feature": ["asd"] } } + """ + # Avoid circular import + from lisa.trace import DmesgCollector + + # Create the config file + cfg_path = self.lisa_fs_path / "configs" / cfg_name + target = self.target + + target.execute(f'mkdir {cfg_path}') + + # Write the config + if features: + for f in features: + target.execute(f'echo {f} >> {cfg_path / "set_features" }') + + if not features[f]: + continue + + for arg in features[f]: + values = ','.join(map(str, features[f][arg])) + target.execute(f'echo {values} > {cfg_path / f / arg}') + + + def log_dmesg(coll, log): + if coll: + name = self.mod_name + dmesg_entries = [ + entry + for entry in coll.entries + if entry.msg.startswith(name) + ] + if dmesg_entries: + sep = '\n ' + dmesg = sep.join(map(str, dmesg_entries)) + log(f'{name} kernel module dmesg output:{sep}{dmesg}') + + # Enable the config + with tempfile.NamedTemporaryFile() as dmesg_out: + dmesg_coll = ignore_exceps( + TargetStableCalledProcessError, + DmesgCollector(target, output_path=dmesg_out.name) + ) + + try: + with dmesg_coll as dmesg_coll: + target.execute(f'echo 1 > {cfg_path / "activate"}') + + except TargetStableCalledProcessError as e: + name = self.mod_name + dmesg_entries = [ + entry + for entry in dmesg_coll.entries + if entry.msg.startswith(name) + ] + if dmesg_entries: + sep = '\n ' + dmesg = sep.join(map(str, dmesg_entries)) + self.logger.error(f'{name} kernel module dmesg output:{sep}{dmesg}') + raise ValueError(f'Could not activate cfg: {cfg_name}') + + def teardown_config(self, cfg_name=None, features=None): + cfg_path = self.lisa_fs_path / "configs" / cfg_name + + if self.target.execute(f'test -d {cfg_path}'): + return + + self.target.execute(f'rmdir {cfg_path}') + + @destroyablecontextmanager + def with_features(self, **kwargs): + try: + self.teardown_config(**kwargs) + except Exception: + pass + + x = self.setup_config(**kwargs) + try: + yield x + except ContextManagerExit: + self.teardown_config(**kwargs) + + def enable_feature(self, cfg_name, features): + cfg_path = self.lisa_fs_path / cfg_name + self.target.execute(f'mkdir {cfg_path}') + for f in features: + self.target.execute(f'echo {cfg_path}') + @destroyablecontextmanager def run(self, **kwargs): """ @@ -2836,6 +2942,14 @@ class LISADynamicKmod(FtraceDynamicKmod): **kwargs, ) + def _event_features_dict(self, events): + all_events = self.defined_events + return { + event: f'event__{event}' + for pattern in events + for event in fnmatch.filter(all_events, pattern) + } + def _event_features(self, events): all_events = self.defined_events return set( diff --git a/lisa/trace.py b/lisa/trace.py index 90828baa4..7c9db19a3 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -6695,7 +6695,7 @@ class FtraceCollector(CollectorBase, Configurable): TOOLS = ['trace-cmd'] _COMPOSITION_ORDER = 0 - def __init__(self, target, *, events=None, functions=None, buffer_size=10240, output_path=None, autoreport=False, trace_clock=None, saved_cmdlines_nr=8192, tracer=None, kmod_auto_load=True, events_namespaces=('lisa', None), **kwargs): + def __init__(self, target, *, events=None, functions=None, buffer_size=10240, output_path=None, autoreport=False, trace_clock=None, saved_cmdlines_nr=8192, tracer=None, kmod_auto_load=True, events_namespaces=('lisa', None), kmod_features=None, **kwargs): kconfig = target.plat_info['kernel']['config'] if not kconfig.get('FTRACE'): @@ -6787,8 +6787,17 @@ class FtraceCollector(CollectorBase, Configurable): # in custom modules needed_from_kmod = kmod_available_events & events + # Create an empty config if no config was provided. + if not kmod_features: + kmod_features = {} + + missing_features = set(kmod_features.keys()) - set(kmod.possible_events) + if missing_features: + raise ValueError(f"Trying to configure non-existing features: {missing_features}") + kmod_defined_events = set() kmod_cm = None + kmod_feat_cm = None if needed_from_kmod: # If anything wrong happens, we will be restricted to the events # already available. @@ -6797,10 +6806,11 @@ class FtraceCollector(CollectorBase, Configurable): if kmod_auto_load: self.logger.info(f'Building kernel module to try to provide the following events that are not currently available on the target: {", ".join(sorted(needed_from_kmod))}') try: - kmod_defined_events, provided, kmod_cm = self._get_kmod( + kmod_defined_events, provided, kmod_cm, kmod_feat_cm = self._get_kmod( target, target_available_events=target_available_events, needed_events=needed_from_kmod, + kmod_features=kmod_features ) except Exception as e: try: @@ -6831,6 +6841,7 @@ class FtraceCollector(CollectorBase, Configurable): ) self._kmod_cm = kmod_cm + self._kmod_feat_cm = kmod_feat_cm ############################################ # Final checks after we enabled all we could @@ -6896,7 +6907,7 @@ class FtraceCollector(CollectorBase, Configurable): super().__init__(collector, output_path=output_path) @classmethod - def _get_kmod(cls, target, target_available_events, needed_events): + def _get_kmod(cls, target, target_available_events, needed_events, kmod_features): logger = cls.get_logger() kmod = target.get_kmod(LISADynamicKmod) defined_events = set(kmod.defined_events) @@ -6911,6 +6922,19 @@ class FtraceCollector(CollectorBase, Configurable): if overlapping: raise ValueError(f'Events defined in {mod.src.mod_name} ({", ".join(needed)}) are needed but some events overlap with the ones already provided by the kernel: {", ".join(overlapping)}') else: + + # Update the name of the needed features and give them an empty config. + feat_dict = kmod._event_features_dict(needed) + needed_kmod_features = {feat: None for feat in kmod._event_features(needed)} + # If a config is provided, replace the empty one. + needed_kmod_features.update({feat_dict[feat]: kmod_features[feat] for feat in kmod_features.keys()}) + + kmod_feat_config = functools.partial( + kmod.with_features, + cfg_name='lisa_notebook', + features=needed_kmod_features + ) + return ( defined_events, needed, @@ -6919,17 +6943,21 @@ class FtraceCollector(CollectorBase, Configurable): kmod_params={ 'features': sorted(kmod._event_features(needed)) } - ) + ), + kmod_feat_config ) else: - return (defined_events, set(), None) + return (defined_events, set(), None, None) @contextlib.contextmanager def _make_cm(self, record=True): with contextlib.ExitStack() as stack: kmod_cm = self._kmod_cm + kmod_feat_cm = self._kmod_feat_cm if kmod_cm is not None: stack.enter_context(kmod_cm()) + if kmod_feat_cm is not None: + stack.enter_context(kmod_feat_cm()) if record: proxy = super() -- GitLab From 6d0325f7700d7e78f15eea825755aef6d60518ad Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 19:13:24 +0100 Subject: [PATCH 4/8] lisa._kmod: Remove kmod parameters The lisa module was previously configured at load time by providing module parameters. The newly added VFS interface allows to configure kmod features at runtime. Remove the kmod parameters generation. Signed-off-by: Pierre Gondois --- lisa/_assets/kmodules/lisa/features.c | 29 ++--------------------- lisa/_assets/kmodules/lisa/features.h | 22 ++--------------- lisa/_assets/kmodules/lisa/main.c | 34 +++++++-------------------- lisa/_cli_tools/lisa_load_kmod.py | 6 +---- lisa/_kmod.py | 32 ++++++------------------- lisa/trace.py | 3 --- lisa/wa/plugins/_kmod.py | 6 +---- 7 files changed, 21 insertions(+), 111 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/features.c b/lisa/_assets/kmodules/lisa/features.c index 317a3db33..7c4ceeac8 100644 --- a/lisa/_assets/kmodules/lisa/features.c +++ b/lisa/_assets/kmodules/lisa/features.c @@ -118,42 +118,17 @@ static int __list_feature(struct feature *feature) { return 0; } -static int __enable_feature_explicitly(struct feature *feature) { - mutex_lock(feature->lock); - feature->__explicitly_enabled++; - mutex_unlock(feature->lock); - return __enable_feature(feature); -} - -int init_features(char **selected, size_t selected_len) { +int print_features(void) { BUG_ON(MAX_FEATURES < ((__lisa_features_stop - __lisa_features_start) / sizeof(struct feature))); pr_info("Available features:"); - __process_features(NULL, 0, __list_feature); - return __process_features(selected, selected_len, __enable_feature_explicitly); + return __process_features(NULL, 0, __list_feature); } int enable_single_feature(char *feature_name) { return __process_features(&feature_name, 1, __enable_feature); } -static int __disable_explicitly_enabled_feature(struct feature *feature) { - int ret = 0; - - mutex_lock(feature->lock); - int selected = feature->__explicitly_enabled; - mutex_unlock(feature->lock); - while (selected) { - ret |= __disable_feature(feature); - selected--; - } - return ret; -} - -int deinit_features(void) { - return __process_features(NULL, 0, __disable_explicitly_enabled_feature); -} - int disable_single_feature(char *feature_name) { return __process_features(&feature_name, 1, __disable_feature); } diff --git a/lisa/_assets/kmodules/lisa/features.h b/lisa/_assets/kmodules/lisa/features.h index 67373f45d..878773d92 100644 --- a/lisa/_assets/kmodules/lisa/features.h +++ b/lisa/_assets/kmodules/lisa/features.h @@ -44,8 +44,6 @@ struct feature { /* Return code of the enable() function */ int __enable_ret; - /* Count of the times the feature has been explicitly enabled by the user */ - int __explicitly_enabled; /* true if the feature is internal, i.e. not exposed to the user. * Internal features are used to share some code between feature, taking * advantage of reference counting to ensure safe setup/teardown. @@ -89,7 +87,6 @@ int __placeholder_disable_cfg(struct feature *feature, struct lisa_cfg *cfg); .name = #feature_name, \ .data = NULL, \ .enabled = 0, \ - .__explicitly_enabled = 0, \ .enable = __placeholder_enable, \ .disable = __placeholder_disable, \ .enable_cfg = __placeholder_enable_cfg, \ @@ -105,7 +102,6 @@ int __placeholder_disable_cfg(struct feature *feature, struct lisa_cfg *cfg); .name = #feature_name, \ .data = NULL, \ .enabled = 0, \ - .__explicitly_enabled = 0, \ .enable = enable_f, \ .disable = disable_f, \ .enable_cfg = enable_cfg_f, \ @@ -223,15 +219,9 @@ find_feature(const char *feature_name) { } /** - * init_features() - Initialize features - * @selected: Array of char * containing feature names to initialize. - * @selected_len: Length of @selected. - * - * Initialize features listed by name in the provided array. The list of actual - * struct features * is built automatically by DEFINE_FEATURE() and does not - * need to be passed. + * print_features() - Print available features. */ -int init_features(char **selected, size_t selected_len); +int print_features(void); /** * enable_single_feature() - Enable one feature @@ -239,14 +229,6 @@ int init_features(char **selected, size_t selected_len); */ int enable_single_feature(char *feature_name); -/** - * deinit_features() - De-initialize features - * - * De-initialize features initialized with init_features(). - * Return: non-zero in case of errors. - */ -int deinit_features(void); - /** * disable_single_feature() - disable one feature * @feature_name: Name of the feature to disable. diff --git a/lisa/_assets/kmodules/lisa/main.c b/lisa/_assets/kmodules/lisa/main.c index 29c652d64..eca9b632a 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -17,15 +17,8 @@ MODULE_PARM_DESC(version, "Module version defined as sha1sum of the module sourc int init_lisa_fs(void); void exit_lisa_fs(void); -static char *features[MAX_FEATURES]; -unsigned int features_len = 0; -module_param_array(features, charp, &features_len, 0); -MODULE_PARM_DESC(features, "Comma-separated list of features to enable. Available features are printed when loading the module"); - static void modexit(void) { exit_lisa_fs(); - if (deinit_features()) - pr_err("Some errors happened while unloading LISA kernel module\n"); } static int __init modinit(void) { @@ -50,31 +43,20 @@ static int __init modinit(void) { pr_info(" %s: %s\n", kernel_feature_names[i], kernel_feature_values[i] ? "enabled" : "disabled"); } - ret = init_features(features_len ? features : NULL , features_len); + ret = print_features(); if (ret) { pr_err("Some errors happened while loading LISA kernel module\n"); - /* Use one of the standard error code */ - ret = -EINVAL; - - /* If the user selected features manually, make module loading fail so - * that they are aware that things went wrong. Otherwise, just - * keep going as the user just wanted to enable as many features - * as possible. + /* Call modexit() explicitly, since it will not be called when ret != 0. + * Not calling modexit() can (and will) result in kernel panic handlers + * installed by the module are not being de-registered before the module + * code vanishes. */ - if (features_len) { - /* Call modexit() explicitly, since it will not be called when ret != 0. - * Not calling modexit() can (and will) result in kernel panic handlers - * installed by the module are not deregistered before the module code - * vanishes. - */ - modexit(); - } else { - exit_lisa_fs(); - } + modexit(); - return ret; + /* Use one of the standard error code */ + return -EINVAL; } return 0; } diff --git a/lisa/_cli_tools/lisa_load_kmod.py b/lisa/_cli_tools/lisa_load_kmod.py index a4259d7eb..2585c8c31 100755 --- a/lisa/_cli_tools/lisa_load_kmod.py +++ b/lisa/_cli_tools/lisa_load_kmod.py @@ -55,15 +55,11 @@ def _main(args, target): if cmd and cmd[0] == '--': cmd = cmd[1:] - kmod_params = {} - if features is not None: - kmod_params['features'] = list(features) - kmod = target.get_kmod(LISADynamicKmod) pretty_events = ', '.join(kmod.defined_events) logging.info(f'Kernel module provides the following ftrace events: {pretty_events}') - _kmod_cm = kmod.run(kmod_params=kmod_params) + _kmod_cm = kmod.run() _kmod_cfg = kmod.with_features(cfg_name="lisa_cli_tool", features=None) if keep_loaded: diff --git a/lisa/_kmod.py b/lisa/_kmod.py index c2d1c9f2c..d10a5f4b4 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -2578,14 +2578,9 @@ class DynamicKmod(Loggable): return (bin_, kernel_build_env._to_spec()) - def install(self, kmod_params=None): + def install(self): """ Install and load the module on the target. - - :param kmod_params: Parameters to pass to the module via ``insmod``. - Non-string iterable values will be turned into a comma-separated - string following the ``module_param_array()`` kernel API syntax. - :type kmod_params: dict(str, object) or None """ target = self.target @@ -2609,9 +2604,9 @@ class DynamicKmod(Loggable): finally: target.remove(str(target_temp)) - return self._install(kmod_cm(), kmod_params=kmod_params) + return self._install(kmod_cm()) - def _install(self, kmod_cm, kmod_params): + def _install(self, kmod_cm): # Avoid circular import from lisa.trace import DmesgCollector @@ -2640,15 +2635,6 @@ class DynamicKmod(Loggable): logger = self.logger target = self.target - kmod_params = kmod_params or {} - params = ' '.join( - f'{quote(k)}={quote(make_str(v))}' - for k, v in sorted( - kmod_params.items(), - key=itemgetter(0), - ) - ) - try: self.uninstall() except Exception: @@ -2663,7 +2649,7 @@ class DynamicKmod(Loggable): try: with dmesg_coll as dmesg_coll: - target.execute(f'{quote(target.busybox)} insmod {quote(str(ko_path))} {params}', as_root=True) + target.execute(f'{quote(target.busybox)} insmod {quote(str(ko_path))}', as_root=True) except Exception as e: log_dmesg(dmesg_coll, logger.error) @@ -2958,7 +2944,7 @@ class LISADynamicKmod(FtraceDynamicKmod): for event in fnmatch.filter(all_events, pattern) ) - def install(self, kmod_params=None): + def install(self): target = self.target logger = self.logger @@ -2982,17 +2968,13 @@ class LISADynamicKmod(FtraceDynamicKmod): base_path = f"{modules_path_base}/{modules_version}" return (base_path, f"{self.mod_name}.ko") - - kmod_params = kmod_params or {} - kmod_params['version'] = self.src.checksum - base_path, kmod_filename = guess_kmod_path() logger.debug(f'Looking for pre-installed {kmod_filename} module in {base_path}') super_ = super() def preinstalled_broken(e): logger.debug(f'Pre-installed {kmod_filename} is unsuitable, recompiling: {e}') - return super_.install(kmod_params=kmod_params) + return super_.install() try: kmod_path = target.execute( @@ -3011,7 +2993,7 @@ class LISADynamicKmod(FtraceDynamicKmod): yield kmod_path try: - ret = self._install(kmod_cm(), kmod_params=kmod_params) + ret = self._install(kmod_cm()) except (TargetStableCalledProcessError, KmodVersionError) as e: ret = preinstalled_broken(e) else: diff --git a/lisa/trace.py b/lisa/trace.py index 7c9db19a3..778ba4e0a 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -6940,9 +6940,6 @@ class FtraceCollector(CollectorBase, Configurable): needed, functools.partial( kmod.run, - kmod_params={ - 'features': sorted(kmod._event_features(needed)) - } ), kmod_feat_config ) diff --git a/lisa/wa/plugins/_kmod.py b/lisa/wa/plugins/_kmod.py index 547a9765d..447c558c1 100644 --- a/lisa/wa/plugins/_kmod.py +++ b/lisa/wa/plugins/_kmod.py @@ -221,11 +221,7 @@ class LisaKmodInstrument(Instrument): def _run(self): features = sorted(self._features) self.logger.info(f'Enabling LISA kmod features {", ".join(features)}') - return self._kmod.run( - kmod_params={ - 'features': features, - } - ) + return self._kmod.run() @contextmanager def _initialize_cm(self, context): -- GitLab From 10ddf910d181485e9a31d915130dad7112ccf338 Mon Sep 17 00:00:00 2001 From: Beata Michalska Date: Sun, 29 Jan 2023 15:14:24 +0000 Subject: [PATCH 5/8] lisa._assets.kmodules.lisa: Add support for ARM PMU counters FEATURE Add support for tracing ARM PMU event counters by providing a dedicated 'perf_counter' trace event, associated with the sched_switch one. The counters to be traced can be specified through module parameter, as a list of comma-separated values denoting either names of generic perf events or raw PMU counter ids, e.g.: insmod /data/local/sched_tp.ko features=event__perf_counter\ generic_perf_events=cpu_cycles,inst_retired \ pmu_raw_counters=35,36 The counters themselves will get activated on each online core at the time of activation, with currently no support for hotplug. The 'counter_id' filed of the 'perf_counter' trace event always represents the PMU RAW counter id. Signed-off-by: Beata Michalska Validate actual PMUs for created event counters Signed-off-by: Beata Michalska --- lisa/_assets/kmodules/lisa/Makefile | 2 +- lisa/_assets/kmodules/lisa/ftrace_events.h | 19 + lisa/_assets/kmodules/lisa/perf_counters.c | 533 +++++++++++++++++++++ tools/kmodules/lisa/perf_counters.c | 1 + 4 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 lisa/_assets/kmodules/lisa/perf_counters.c create mode 120000 tools/kmodules/lisa/perf_counters.c diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index bda54790e..066655c80 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -68,7 +68,7 @@ ifneq ($(KERNELRELEASE),) LISA_KMOD_NAME ?= lisa obj-m := $(LISA_KMOD_NAME).o -$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o pixel6.o introspection_data.o configs.o fs.o feature_params.o +$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o pixel6.o introspection_data.o configs.o fs.o feature_params.o perf_counters.o # -fno-stack-protector is needed to possibly undefined __stack_chk_guard symbol ccflags-y := "-I$(MODULE_SRC)" -std=gnu11 -fno-stack-protector -Wno-declaration-after-statement -Wno-error diff --git a/lisa/_assets/kmodules/lisa/ftrace_events.h b/lisa/_assets/kmodules/lisa/ftrace_events.h index af9912153..fd62f8560 100644 --- a/lisa/_assets/kmodules/lisa/ftrace_events.h +++ b/lisa/_assets/kmodules/lisa/ftrace_events.h @@ -498,6 +498,25 @@ TRACE_EVENT(lisa__pixel6_emeter, __entry->ts, __entry->device, __entry->chan, __entry->chan_name, __entry->value) ); +TRACE_EVENT(lisa__perf_counter, + TP_PROTO(unsigned int cpu, unsigned int counter_id, u64 value), + TP_ARGS(cpu, counter_id, value), + + TP_STRUCT__entry( + __field( unsigned int, cpu ) + __field( unsigned int, counter_id ) + __field( u64, value ) + ), + + TP_fast_assign( + __entry->cpu = cpu; + __entry->counter_id = counter_id; + __entry->value = value; + ), + + TP_printk("cpu=%u counter_id=%u value=%llu", + __entry->cpu, __entry->counter_id, __entry->value) +); #endif /* _FTRACE_EVENTS_H */ /* This part must be outside protection */ diff --git a/lisa/_assets/kmodules/lisa/perf_counters.c b/lisa/_assets/kmodules/lisa/perf_counters.c new file mode 100644 index 000000000..364f88de4 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/perf_counters.c @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2023-2024 ARM Ltd. +#include +#if defined(CONFIG_HW_PERF_EVENTS) && defined(CONFIG_ARM_PMU) +#include +#endif +#include "main.h" +#include "ftrace_events.h" +#include "tp.h" + +#define MAX_PERF_COUNTERS 6 + +#define __PERFCTR_PARAM(name, param_name, type, param_type, desc) \ + static type param_name[MAX_PERF_COUNTERS]; \ + static unsigned int param_name##_count; \ + module_param_array_named(name, param_name, param_type, \ + ¶m_name##_count, 0644); \ + MODULE_PARM_DESC(name, desc); + +#define PERFCTR_PARAM(name, type, param_type, desc) \ + __PERFCTR_PARAM(perf_counter_##name, name##_param, type, param_type, desc) + +/* Set of perf counters to enable - comma-separated names of events */ +PERFCTR_PARAM(generic_perf_events, char *, charp, + "Comma-separated list of symbolic names for generic perf events"); +/* Set of perf counters to enable - comma-separated PMU raw counter ids */ +PERFCTR_PARAM(pmu_raw_counters, unsigned int, uint, + "Comma-separated list of raw PMU event counter ids"); + +/* Initial set of supported counters to be enabled through module params */ +struct perfctr_desc { + /* unique name to identify the counter */ + const char *name; + /* counter id (may be generic or raw) */ + u64 id; + enum perf_type_id type; + /* enable by default if no counters requested */ + bool default_on; +}; + +#define PERFCTR_DESC(__name, __id, __type, __en) \ + ((struct perfctr_desc) { \ + .name = __name, .id = __id, .type = __type, .default_on = __en, \ + }) + +#define PERFCTR_DESC_COUNT_HW(__name, __id, __en) \ + PERFCTR_DESC(__name, __id, PERF_TYPE_HARDWARE, __en) + +/* Initial set of supported counters to be enabled based on provided event names */ +static const struct perfctr_desc perfctr_generic_lt[] = { + PERFCTR_DESC_COUNT_HW("cpu_cycles", PERF_COUNT_HW_CPU_CYCLES, 1), + PERFCTR_DESC_COUNT_HW("inst_retired", PERF_COUNT_HW_INSTRUCTIONS, 0), + PERFCTR_DESC_COUNT_HW("cache_references", PERF_COUNT_HW_CACHE_REFERENCES, 0), + PERFCTR_DESC_COUNT_HW("cache_misses", PERF_COUNT_HW_CACHE_MISSES, 0), + PERFCTR_DESC_COUNT_HW("branch_retired", PERF_COUNT_HW_BRANCH_INSTRUCTIONS, 0), + PERFCTR_DESC_COUNT_HW("branch_mispred", PERF_COUNT_HW_BRANCH_MISSES, 0), + PERFCTR_DESC_COUNT_HW("bus_cycles", PERF_COUNT_HW_BUS_CYCLES, 0), + PERFCTR_DESC_COUNT_HW("stall_frontend", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, 0), + PERFCTR_DESC_COUNT_HW("stall_backend", PERF_COUNT_HW_STALLED_CYCLES_BACKEND, 0), +}; + +/** + * struct perfctr_event_entry - Event counter context + * @node: Link to a per-CPU list of event counters. + * @group_link: Link to list of event counters within a single group. + * @event: Perf event instance. + * @group: Group this event counter belongs to. + * @event_id: PMU specific event counter id. + */ +struct perfctr_event_entry { + struct hlist_node node; + struct hlist_node group_link; + struct perf_event *event; + struct perfctr_event_group *group; + /* PMU specific event counter id */ + u64 event_id; +}; + +/** + * struct perfctr_event_group - Represents a set of event counters sharing the + * same event id: whether that be generic perf + * event if or a PMU specific, raw one. + * @node: Link to list of active groups + * @entries: List on struct perfctr_event_entry objects representing active event + * counters within the group + * @raw_id: PMU specific event counter id of all events within the group + * @generic_id: Perf generic event id for all events within the group + */ +struct perfctr_event_group { + struct list_head node; + struct hlist_head entries; + u64 raw_id; +}; + +/* Per-CPU list of event counters active on a given CPU */ +struct perfctr_pcpu_data { + struct hlist_head events; + unsigned int pmu_type; +}; + +/** + * struct perfctr_core - Feature-specific data + * @events: List of group of active event counters + * @pcpu_data: Per_CPU list of event counters active on a given CPU + * @nr_events: Number of active event counters groups + * @max_nr_events: Max number of event counters per-CPU + */ +struct perfctr_core { + struct list_head events; + struct perfctr_pcpu_data __percpu *pcpu_data; + unsigned int nr_events; + unsigned int max_nr_events; +}; + +static inline void perfctr_show_supported_generic_events(void) +{ + int i; + + pr_info("Possible (subject to actual support) generic perf events: "); + for (i = 0; i < ARRAY_SIZE(perfctr_generic_lt); ++i) + printk(KERN_CONT "%s, ", perfctr_generic_lt[i].name); +} + +static void perfctr_event_release_entry(struct perfctr_event_entry *entry); + +static int perfctr_event_activate_single(struct perfctr_core *perf_data, + struct perf_event_attr *attr) +{ + struct perfctr_event_entry *entry = NULL; + struct perfctr_event_group *group; + struct hlist_node *next; + cpumask_var_t active_mask; + int cpu; + + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) + return -ENOMEM; + + if (!zalloc_cpumask_var(&active_mask, GFP_KERNEL)) { + kfree(group); + return -ENOMEM; + } + + group->raw_id = PERF_COUNT_HW_MAX; + + for_each_online_cpu(cpu) { + unsigned int pmu_type; + + pmu_type = per_cpu_ptr(perf_data->pcpu_data, cpu)->pmu_type; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + goto activate_failed; + + entry->event = + /* No overflow handler, at least not at this point */ + perf_event_create_kernel_counter(attr, cpu, NULL, + NULL, NULL); + if (IS_ERR(entry->event)) { + pr_err("Failed to create counter id=%llu on cpu%d\n", + attr->config, cpu); + kfree(entry); + continue; + } + + /* Validate the actual pmu for the event */ + if (pmu_type != entry->event->pmu->type) { + pr_err("Event counter=%llu handled by unsupported PMU on cpu=%u\n", + attr->config, cpu); + perf_event_release_kernel(entry->event); + kfree(entry); + continue; + } + perf_event_enable(entry->event); + /* + * the PMU driver might still fail to assign a slot for a given + * counter (@see armpmu_add) which leaves the event ineffective + */ + if (entry->event->state != PERF_EVENT_STATE_ACTIVE) { + pr_err("Failed to enable counter id=%llu on cpu%u\n", + attr->config, cpu); + perf_event_disable(entry->event); + perf_event_release_kernel(entry->event); + kfree(entry); + continue; + } + + hlist_add_head(&entry->group_link, &group->entries); + entry->group = group; + cpumask_set_cpu(cpu, active_mask); + /* One-time only */ + if (group->raw_id != PERF_COUNT_HW_MAX) + continue; + if (attr->type == PERF_TYPE_RAW || !IS_ENABLED(CONFIG_ARM_PMU)) { + group->raw_id = attr->config; + } else { + struct arm_pmu *arm_pmu; + /* arm_pmu only for the time being */ + arm_pmu = to_arm_pmu(entry->event->pmu); + /* There needs to be a better way to do this !!*/ + group->raw_id = arm_pmu->map_event(entry->event); + } + entry->event_id = group->raw_id; + hlist_add_head_rcu(&entry->node, + &per_cpu_ptr(perf_data->pcpu_data, cpu)->events); + } + if (cpumask_empty(active_mask)) + goto activate_failed; + + list_add_tail(&group->node, &perf_data->events); + ++perf_data->nr_events; + + pr_info("%s event counter id=%llu activated on cpus=%*pbl", + attr->type == PERF_TYPE_RAW ? "PMU raw" : "Generic perf", + attr->config, cpumask_pr_args(active_mask)); + free_cpumask_var(active_mask); + return 0; + +activate_failed: + + hlist_for_each_entry(entry, &group->entries, group_link) { + hlist_del_rcu(&entry->node); + } + synchronize_rcu(); + hlist_for_each_entry_safe(entry, next, &group->entries, group_link) { + hlist_del(&entry->group_link); + perfctr_event_release_entry(entry); + } + kfree(group); + free_cpumask_var(active_mask); + return -ENOMEM; +} + +/* Lookup match type */ +enum perfctr_match_type { + PERFCTR_MATCH_NAME, + PERFCTR_MATCH_STATUS +}; + +struct perfctr_match { + union { + char *name; /* generic perf hw event name */ + bool status; /* enable by default */ + }; + enum perfctr_match_type type; +}; + +static int perfctr_event_activate(struct perfctr_core *perf_data, + const struct perfctr_match *match) +{ + int result = -EINVAL; + int i; + + struct perf_event_attr attr = { + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + }; + + for (i = 0; i < ARRAY_SIZE(perfctr_generic_lt); ++i) { + switch (match->type) { + case PERFCTR_MATCH_NAME: + if (strcmp(match->name, perfctr_generic_lt[i].name)) + continue; + break; + case PERFCTR_MATCH_STATUS: + if (match->status != perfctr_generic_lt[i].default_on) + continue; + else + break; + default: + unreachable(); + } + attr.config = perfctr_generic_lt[i].id; + attr.type = perfctr_generic_lt[i].type; + + result = perfctr_event_activate_single(perf_data, &attr); + if (!result || match->type == PERFCTR_MATCH_NAME) + break; + } + return result; +} + +static void perfctr_event_release_entry(struct perfctr_event_entry *entry) +{ + perf_event_disable(entry->event); + perf_event_release_kernel(entry->event); + kfree(entry); +} + +static void perfctr_events_release_group(struct perfctr_core *perf_data, + struct perfctr_event_group *group) +{ + struct perfctr_event_entry *entry; + struct hlist_node *next; + + hlist_for_each_entry(entry, &group->entries, group_link) { + hlist_del_rcu(&entry->node); + } + synchronize_rcu(); + hlist_for_each_entry_safe(entry, next, &group->entries, group_link) { + hlist_del(&entry->group_link); + perfctr_event_release_entry(entry); + } + list_del(&group->node); + kfree(group); + --perf_data->nr_events; +} + +static void perfctr_events_release(struct perfctr_core *perf_data) +{ + struct perfctr_event_group *group, *next; + + list_for_each_entry_safe(group, next, &perf_data->events, node) { + perfctr_events_release_group(perf_data, group); + } +} + +static void perfctr_sched_switch_probe(void *feature, bool preempt, + struct task_struct *prev, + struct task_struct *next, + unsigned int prev_state) +{ + if (trace_lisa__perf_counter_enabled()) { + struct perfctr_core *perf_data = ((struct feature *)feature)->data; + struct perfctr_event_entry *entry; + struct hlist_head *entry_list; + int cpu = smp_processor_id(); + u64 value = 0; + + entry_list = &per_cpu_ptr(perf_data->pcpu_data, cpu)->events; + + rcu_read_lock(); + hlist_for_each_entry_rcu(entry, entry_list, node) { + /* + * The approach taken is a *semi*-safe one as: + * - the execution context is one as of the caller + * (__schedule) with preemption and interrupts being + * disabled + * - the events being traced are per-CPU ones only + * - kernel counter so no inheritance (no child events) + * - counter is being read on/for a local CPU + */ + struct perf_event *event = entry->event; + + event->pmu->read(event); + value = local64_read(&event->count); + trace_lisa__perf_counter(cpu, entry->event_id, value); + } + rcu_read_unlock(); + } +} + +static int perfctr_register_events(struct perfctr_core *perf_data) +{ + struct perfctr_match match; + unsigned int count; + int result = 0; + + count = generic_perf_events_param_count + pmu_raw_counters_param_count; + if (count > perf_data->max_nr_events) { + pr_err("Requested more than max %d counters\n", + perf_data->max_nr_events); + return -EINVAL; + } + + count = generic_perf_events_param_count; + if (count) { + match.type = PERFCTR_MATCH_NAME; + for (; count > 0; --count) { + match.name = generic_perf_events_param[count - 1]; + result = perfctr_event_activate(perf_data, &match); + if (result) { + pr_err("Failed to activate event counter: %s\n", + match.name); + perfctr_show_supported_generic_events(); + goto done; + } + } + } + + count = pmu_raw_counters_param_count; + if (count) { + struct perf_event_attr attr = { + .size = sizeof(struct perf_event_attr), + .type = PERF_TYPE_RAW, + .pinned = 1, + .disabled = 1, + }; + + for (; count > 0; --count) { + struct perfctr_event_group *group; + bool duplicate = false; + + attr.config = pmu_raw_counters_param[count - 1]; + /* Skip duplicates */ + list_for_each_entry(group, &perf_data->events, node) { + if (group->raw_id == attr.config) { + duplicate = true; + break; + } + } + + result = duplicate ? 0 : perfctr_event_activate_single(perf_data, &attr); + if (result) { + pr_err("Failed to activate event counter: %llu\n", + attr.config); + goto done; + }; + } + } +done: + /* All or nothing ..... */ + if (result) + perfctr_events_release(perf_data); + return result; +} + +static void perfctr_pmu_discover(struct perfctr_core *perf_data) +{ + struct perf_event *event; + cpumask_var_t active_mask; + int cpu; + + /* + * This is absolutely loathsome but there seems to be no other way + * to poke relevant PMU driver for details so, there it is .... + */ + struct perf_event_attr attr = { + .type = PERF_TYPE_HARDWARE, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + .config = PERF_COUNT_HW_CPU_CYCLES, + }; + + perf_data->max_nr_events = MAX_PERF_COUNTERS; + + if (!IS_ENABLED(CONFIG_ARM_PMU)) + return; + + if (!zalloc_cpumask_var(&active_mask, GFP_KERNEL)) + return; + + for_each_online_cpu(cpu) { + if (cpumask_test_cpu(cpu, active_mask)) + continue; + + event = perf_event_create_kernel_counter(&attr, cpu, NULL, + NULL, NULL); + + if (IS_ERR(event)) { + pr_err("Failed to create an event (cpu%d) while discovery\n", + cpu); + continue; + } + + if (event->pmu && strstr(event->pmu->name, "armv")) { + struct arm_pmu *pmu = to_arm_pmu(event->pmu); + unsigned int pmu_cpu; + + perf_data->max_nr_events = min_t(unsigned int, + perf_data->max_nr_events, + pmu->num_events); + + cpumask_or(active_mask, active_mask, &pmu->supported_cpus); + + for_each_cpu(pmu_cpu, &pmu->supported_cpus) + per_cpu_ptr(perf_data->pcpu_data, pmu_cpu)->pmu_type = event->pmu->type; + } + perf_event_release_kernel(event); + + if (cpumask_equal(active_mask, cpu_possible_mask)) + break; + } + free_cpumask_var(active_mask); + pr_info("Max of %d PMU counters available on cpus=%*pbl\n", + perf_data->max_nr_events, cpumask_pr_args(cpu_possible_mask)); +} + +static int perfctr_disable(struct feature *feature); + +static int perfctr_enable(struct feature *feature) +{ + struct perfctr_core *perf_data; + + if (!IS_ENABLED(CONFIG_HW_PERF_EVENTS)) { + pr_err("Missing support for HW performance event counters\n"); + return 1; + } + + perf_data = kzalloc(sizeof(*perf_data), GFP_KERNEL); + if (!perf_data) + return 1; + + INIT_LIST_HEAD(&perf_data->events); + + feature->data = perf_data; + + perf_data->pcpu_data = alloc_percpu(struct perfctr_pcpu_data); + if (!perf_data->pcpu_data) + return 1; + + perfctr_pmu_discover(perf_data); + + if (perfctr_register_events(perf_data)) + return 1; + + if (!perf_data->nr_events) + pr_warn("No counters have been activated\n"); + + return 0; +} + +static int perfctr_disable(struct feature *feature) +{ + struct perfctr_core *perf_data = feature->data; + + if (!perf_data) + return 0; + + if (perf_data->pcpu_data) { + perfctr_events_release(perf_data); + free_percpu(perf_data->pcpu_data); + } + kfree(perf_data); + feature->data = NULL; + return 0; +} + +DEFINE_EXTENDED_TP_EVENT_FEATURE(lisa__perf_counter, + TP_PROBES(TP_PROBE("sched_switch", perfctr_sched_switch_probe)), + perfctr_enable, perfctr_disable); diff --git a/tools/kmodules/lisa/perf_counters.c b/tools/kmodules/lisa/perf_counters.c new file mode 120000 index 000000000..8bc3d0ed1 --- /dev/null +++ b/tools/kmodules/lisa/perf_counters.c @@ -0,0 +1 @@ +./../../../lisa/_assets/kmodules/lisa/perf_counters.c \ No newline at end of file -- GitLab From 4f1be722335fadccb623c10a3569e69cad0c35dd Mon Sep 17 00:00:00 2001 From: Beata Michalska Date: Mon, 8 Jul 2024 15:54:24 +0200 Subject: [PATCH 6/8] lisa._assets.kmodules.lisa: Create separate group for unique PMU raw ids There might be cases when single generic perf event is handled by different PMU across available CPUs, and thus might be mapped to different PMU event numbers. To cover those cases, make sure that each group represents event counters with unique pair of ids: generic perf and PMU raw ids. As a consequence, there might be more than a single group representing either. Suggested-by: Pierre Gondois Signed-off-by: Beata Michalska --- lisa/_assets/kmodules/lisa/perf_counters.c | 92 ++++++++++++++-------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/perf_counters.c b/lisa/_assets/kmodules/lisa/perf_counters.c index 364f88de4..807c87b7a 100644 --- a/lisa/_assets/kmodules/lisa/perf_counters.c +++ b/lisa/_assets/kmodules/lisa/perf_counters.c @@ -66,6 +66,7 @@ static const struct perfctr_desc perfctr_generic_lt[] = { * @event: Perf event instance. * @group: Group this event counter belongs to. * @event_id: PMU specific event counter id. + * @cpu: CPU on which this event counter has been activated. */ struct perfctr_event_entry { struct hlist_node node; @@ -74,6 +75,7 @@ struct perfctr_event_entry { struct perfctr_event_group *group; /* PMU specific event counter id */ u64 event_id; + unsigned int cpu; }; /** @@ -90,6 +92,7 @@ struct perfctr_event_group { struct list_head node; struct hlist_head entries; u64 raw_id; + unsigned int generic_id; }; /* Per-CPU list of event counters active on a given CPU */ @@ -127,21 +130,23 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, struct perf_event_attr *attr) { struct perfctr_event_entry *entry = NULL; - struct perfctr_event_group *group; + struct perfctr_event_group *group, *n; struct hlist_node *next; cpumask_var_t active_mask; + LIST_HEAD(groups); int cpu; - group = kzalloc(sizeof(*group), GFP_KERNEL); - if (!group) + if (!zalloc_cpumask_var(&active_mask, GFP_KERNEL)) return -ENOMEM; - if (!zalloc_cpumask_var(&active_mask, GFP_KERNEL)) { - kfree(group); + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) { + free_cpumask_var(active_mask); return -ENOMEM; } - - group->raw_id = PERF_COUNT_HW_MAX; + group->raw_id = attr->config; + group->generic_id = PERF_COUNT_HW_MAX; + list_add(&group->node, &groups); for_each_online_cpu(cpu) { unsigned int pmu_type; @@ -149,6 +154,7 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, pmu_type = per_cpu_ptr(perf_data->pcpu_data, cpu)->pmu_type; entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) goto activate_failed; @@ -167,47 +173,64 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, if (pmu_type != entry->event->pmu->type) { pr_err("Event counter=%llu handled by unsupported PMU on cpu=%u\n", attr->config, cpu); - perf_event_release_kernel(entry->event); - kfree(entry); + perfctr_event_release_entry(entry); continue; } + perf_event_enable(entry->event); + /* * the PMU driver might still fail to assign a slot for a given * counter (@see armpmu_add) which leaves the event ineffective */ if (entry->event->state != PERF_EVENT_STATE_ACTIVE) { pr_err("Failed to enable counter id=%llu on cpu%u\n", - attr->config, cpu); - perf_event_disable(entry->event); - perf_event_release_kernel(entry->event); - kfree(entry); + attr->config, cpu); + perfctr_event_release_entry(entry); continue; } - hlist_add_head(&entry->group_link, &group->entries); - entry->group = group; - cpumask_set_cpu(cpu, active_mask); - /* One-time only */ - if (group->raw_id != PERF_COUNT_HW_MAX) - continue; - if (attr->type == PERF_TYPE_RAW || !IS_ENABLED(CONFIG_ARM_PMU)) { - group->raw_id = attr->config; - } else { + if (attr->type != PERF_TYPE_RAW) { + struct perfctr_event_group *__g; struct arm_pmu *arm_pmu; - /* arm_pmu only for the time being */ + u64 raw_id; + arm_pmu = to_arm_pmu(entry->event->pmu); /* There needs to be a better way to do this !!*/ - group->raw_id = arm_pmu->map_event(entry->event); + raw_id = arm_pmu->map_event(entry->event); + + list_for_each_entry(__g, &groups, node) { + if (__g->raw_id == raw_id) + break; + } + + if (list_entry_is_head(__g, &groups, node)) { + __g = kzalloc(sizeof(*__g), GFP_KERNEL); + if (!__g) { + perfctr_event_release_entry(entry); + goto activate_failed; + } + INIT_LIST_HEAD(&__g->node); + __g->raw_id = raw_id; + __g->generic_id = attr->config; + list_add_tail(&__g->node, &groups); + } + group = __g; } + + entry->cpu = cpu; + entry->group = group; entry->event_id = group->raw_id; + hlist_add_head(&entry->group_link, &group->entries); hlist_add_head_rcu(&entry->node, &per_cpu_ptr(perf_data->pcpu_data, cpu)->events); + cpumask_set_cpu(cpu, active_mask); } if (cpumask_empty(active_mask)) goto activate_failed; - list_add_tail(&group->node, &perf_data->events); + list_splice_tail_init(&groups, &perf_data->events); + ++perf_data->nr_events; pr_info("%s event counter id=%llu activated on cpus=%*pbl", @@ -218,15 +241,18 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, activate_failed: - hlist_for_each_entry(entry, &group->entries, group_link) { - hlist_del_rcu(&entry->node); - } - synchronize_rcu(); - hlist_for_each_entry_safe(entry, next, &group->entries, group_link) { - hlist_del(&entry->group_link); - perfctr_event_release_entry(entry); + list_for_each_entry_safe(group, n, &groups, node) { + hlist_for_each_entry(entry, &group->entries, group_link) { + hlist_del_rcu(&entry->node); + } + synchronize_rcu(); + hlist_for_each_entry_safe(entry, next, &group->entries, group_link) { + hlist_del(&entry->group_link); + perfctr_event_release_entry(entry); + } + list_del(&group->node); + kfree(group); } - kfree(group); free_cpumask_var(active_mask); return -ENOMEM; } -- GitLab From 42f323d69f2170249626d4b1617e209a2cde0025 Mon Sep 17 00:00:00 2001 From: Beata Michalska Date: Thu, 11 Jul 2024 09:59:12 +0200 Subject: [PATCH 7/8] lisa._assets.kmodules.lisa: Allow multiple references to single PMU event counter As a prep work for supporting multiple configurations, allow sharing active PMU event counters across those. Suggested-by:Pierre Gondois Signed-off-by: Beata Michalska --- lisa/_assets/kmodules/lisa/perf_counters.c | 144 ++++++++++++++++----- 1 file changed, 115 insertions(+), 29 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/perf_counters.c b/lisa/_assets/kmodules/lisa/perf_counters.c index 807c87b7a..1bd754cf8 100644 --- a/lisa/_assets/kmodules/lisa/perf_counters.c +++ b/lisa/_assets/kmodules/lisa/perf_counters.c @@ -67,6 +67,8 @@ static const struct perfctr_desc perfctr_generic_lt[] = { * @group: Group this event counter belongs to. * @event_id: PMU specific event counter id. * @cpu: CPU on which this event counter has been activated. + * @refcount: Reference count. + * @rcu: rcu_head struct. */ struct perfctr_event_entry { struct hlist_node node; @@ -76,6 +78,8 @@ struct perfctr_event_entry { /* PMU specific event counter id */ u64 event_id; unsigned int cpu; + refcount_t refcount; + struct rcu_head rcu; }; /** @@ -126,6 +130,56 @@ static inline void perfctr_show_supported_generic_events(void) static void perfctr_event_release_entry(struct perfctr_event_entry *entry); +static inline int perfctr_event_entry_get(struct perfctr_event_entry *entry) +{ + return unlikely(refcount_inc_not_zero(&entry->refcount)) ? -EINVAL : 0; +} + +static void perfctr_event_release_entry_rcu(struct rcu_head *head) +{ + struct perfctr_event_entry *entry; + + entry = container_of(head, struct perfctr_event_entry, rcu); + perfctr_event_release_entry(entry); +} + +static void perfctr_event_entry_put(struct perfctr_event_entry *entry) +{ + if (refcount_dec_and_test(&entry->refcount)) { + hlist_del_rcu(&entry->node); + hlist_del(&entry->group_link); + call_rcu(&entry->rcu, perfctr_event_release_entry_rcu); + } +} + +static void perfctr_match_existing_groups(struct perfctr_core *perf_data, + struct perf_event_attr *attr, + cpumask_var_t active_mask, + bool activate) +{ + struct perfctr_event_group *group; + + list_for_each_entry(group, &perf_data->events, node) { + struct perfctr_event_entry *entry; + u64 group_id = attr->type == PERF_TYPE_RAW + ? group->raw_id + : group->generic_id; + + if (group_id != attr->config) + continue; + + hlist_for_each_entry(entry, &group->entries, group_link) { + if (activate) { + perfctr_event_entry_get(entry); + cpumask_set_cpu(entry->cpu, active_mask); + } else { + perfctr_event_entry_put(entry); + cpumask_clear_cpu(entry->cpu, active_mask); + } + } + } +} + static int perfctr_event_activate_single(struct perfctr_core *perf_data, struct perf_event_attr *attr) { @@ -139,18 +193,43 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, if (!zalloc_cpumask_var(&active_mask, GFP_KERNEL)) return -ENOMEM; - group = kzalloc(sizeof(*group), GFP_KERNEL); - if (!group) { - free_cpumask_var(active_mask); - return -ENOMEM; + perfctr_match_existing_groups(perf_data, attr, active_mask, true); + + if ((attr->type == PERF_TYPE_HARDWARE && !cpumask_empty(active_mask)) || + cpumask_equal(active_mask, cpu_online_mask)) + goto done; + + /* + * When requesting raw PMU events things get bit tricky + * It might be the case that for some CPUs given event has already been + * activated via PERF_TYPE_HARDWARE. + * This should cover the ones that do still need activation. + */ + if (attr->type == PERF_TYPE_RAW || cpumask_empty(active_mask)) { + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) { + free_cpumask_var(active_mask); + return -ENOMEM; + } + + INIT_LIST_HEAD(&group->node); + + if (attr->type == PERF_TYPE_RAW) + group->raw_id = attr->config; + + group->generic_id = attr->type == PERF_TYPE_RAW + ? PERF_COUNT_HW_MAX + : attr->config; + + list_add_tail(&group->node, &groups); } - group->raw_id = attr->config; - group->generic_id = PERF_COUNT_HW_MAX; - list_add(&group->node, &groups); for_each_online_cpu(cpu) { unsigned int pmu_type; + if (cpumask_test_cpu(cpu, active_mask)) + continue; + pmu_type = per_cpu_ptr(perf_data->pcpu_data, cpu)->pmu_type; entry = kzalloc(sizeof(*entry), GFP_KERNEL); @@ -199,10 +278,12 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, /* There needs to be a better way to do this !!*/ raw_id = arm_pmu->map_event(entry->event); - list_for_each_entry(__g, &groups, node) { + if (group->raw_id == raw_id) + goto add_entry; + + list_for_each_entry(__g, &groups, node) if (__g->raw_id == raw_id) break; - } if (list_entry_is_head(__g, &groups, node)) { __g = kzalloc(sizeof(*__g), GFP_KERNEL); @@ -217,7 +298,8 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, } group = __g; } - +add_entry: + refcount_set(&entry->refcount, 1); entry->cpu = cpu; entry->group = group; entry->event_id = group->raw_id; @@ -229,10 +311,10 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, if (cpumask_empty(active_mask)) goto activate_failed; - list_splice_tail_init(&groups, &perf_data->events); + list_splice_tail(&groups, &perf_data->events); ++perf_data->nr_events; - +done: pr_info("%s event counter id=%llu activated on cpus=%*pbl", attr->type == PERF_TYPE_RAW ? "PMU raw" : "Generic perf", attr->config, cpumask_pr_args(active_mask)); @@ -240,19 +322,23 @@ static int perfctr_event_activate_single(struct perfctr_core *perf_data, return 0; activate_failed: - list_for_each_entry_safe(group, n, &groups, node) { - hlist_for_each_entry(entry, &group->entries, group_link) { - hlist_del_rcu(&entry->node); + hlist_for_each_entry_safe(entry, next, + &group->entries, group_link) { + perfctr_event_entry_put(entry); + cpumask_clear_cpu(cpu, active_mask); } - synchronize_rcu(); - hlist_for_each_entry_safe(entry, next, &group->entries, group_link) { - hlist_del(&entry->group_link); - perfctr_event_release_entry(entry); + if (hlist_empty(&group->entries)) { + list_del(&group->node); + kfree(group); } - list_del(&group->node); - kfree(group); } + + if (!cpumask_empty(active_mask)) + perfctr_match_existing_groups(perf_data, attr, active_mask, false); + + WARN_ON(!cpumask_empty(active_mask)); + free_cpumask_var(active_mask); return -ENOMEM; } @@ -309,6 +395,7 @@ static int perfctr_event_activate(struct perfctr_core *perf_data, static void perfctr_event_release_entry(struct perfctr_event_entry *entry) { + WARN_ON(refcount_read(&entry->refcount) != 0); perf_event_disable(entry->event); perf_event_release_kernel(entry->event); kfree(entry); @@ -320,17 +407,16 @@ static void perfctr_events_release_group(struct perfctr_core *perf_data, struct perfctr_event_entry *entry; struct hlist_node *next; - hlist_for_each_entry(entry, &group->entries, group_link) { - hlist_del_rcu(&entry->node); - } - synchronize_rcu(); - hlist_for_each_entry_safe(entry, next, &group->entries, group_link) { - hlist_del(&entry->group_link); - perfctr_event_release_entry(entry); - } + hlist_for_each_entry_safe(entry, next, &group->entries, group_link) + perfctr_event_entry_put(entry); + + if (!hlist_empty(&group->entries)) + return; + list_del(&group->node); kfree(group); --perf_data->nr_events; + } static void perfctr_events_release(struct perfctr_core *perf_data) -- GitLab From ba74553ee9ff030b301f5c4e3421a3a6c9722862 Mon Sep 17 00:00:00 2001 From: Beata Michalska Date: Tue, 16 Jul 2024 16:58:29 +0200 Subject: [PATCH 8/8] lisa._assets.kmodules.lisa: Enroll PMU counters into new config support Make use of the newly added VFS/config handling to configure PMU counters. The counters can be configured as: The counters to be traced can be specified through the lisa VFS: $ mkdir /sys/fs/lisa/configs/test_cfg $ echo event__lisa__perf_counter >> /sys/fs/lisa/configs/test_cfg/set_features $ echo 3 >> /sys/fs/lisa/configs/test_cfg/event__lisa__perf_counter/pmu_raw_counters $ echo 5 >> /sys/fs/lisa/configs/test_cfg/event__lisa__perf_counter/pmu_raw_counters $ echo 1 > /sys/fs/lisa/configs/test_cfg/activate PMU event counters might be requested via either: - generic perf event name (note that this applies to common perf hardware events) - PMU specific event counter id (aka raw id) Note that there might be cases when requested event will not be activated on all online CPUS: subject to actual support and available event counter slots on given CPU. Additionally, enabling the same counter via both generic perf id and PMU raw id may lead to duplicate event counters so it is advised to avoid mixing the ids between different configs. Signed-off-by: Pierre Gondois Co-developed-by:Beata Michalska [BM: reworked bits related to referencing existing event counters as well as cutting down on changes handling generic perf events mapped by different PMUs to different event numbers, as that has been handled upfront. As a consequence, reworded the commit message a bit] Signed-off-by: Beata Michalska --- lisa/_assets/kmodules/lisa/perf_counters.c | 311 +++++++++++---------- 1 file changed, 163 insertions(+), 148 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/perf_counters.c b/lisa/_assets/kmodules/lisa/perf_counters.c index 1bd754cf8..36d3568fa 100644 --- a/lisa/_assets/kmodules/lisa/perf_counters.c +++ b/lisa/_assets/kmodules/lisa/perf_counters.c @@ -10,22 +10,8 @@ #define MAX_PERF_COUNTERS 6 -#define __PERFCTR_PARAM(name, param_name, type, param_type, desc) \ - static type param_name[MAX_PERF_COUNTERS]; \ - static unsigned int param_name##_count; \ - module_param_array_named(name, param_name, param_type, \ - ¶m_name##_count, 0644); \ - MODULE_PARM_DESC(name, desc); - -#define PERFCTR_PARAM(name, type, param_type, desc) \ - __PERFCTR_PARAM(perf_counter_##name, name##_param, type, param_type, desc) - -/* Set of perf counters to enable - comma-separated names of events */ -PERFCTR_PARAM(generic_perf_events, char *, charp, - "Comma-separated list of symbolic names for generic perf events"); -/* Set of perf counters to enable - comma-separated PMU raw counter ids */ -PERFCTR_PARAM(pmu_raw_counters, unsigned int, uint, - "Comma-separated list of raw PMU event counter ids"); +#define PERFCTR_PARAM_GENERIC_PERF_EVENTS "generic_perf_events" +#define PERFCTR_PARAM_PMU_RAW_EVENTS "pmu_raw_events" /* Initial set of supported counters to be enabled through module params */ struct perfctr_desc { @@ -34,29 +20,27 @@ struct perfctr_desc { /* counter id (may be generic or raw) */ u64 id; enum perf_type_id type; - /* enable by default if no counters requested */ - bool default_on; }; -#define PERFCTR_DESC(__name, __id, __type, __en) \ - ((struct perfctr_desc) { \ - .name = __name, .id = __id, .type = __type, .default_on = __en, \ +#define PERFCTR_DESC(__name, __id, __type) \ + ((struct perfctr_desc) { \ + .name = __name, .id = __id, .type = __type, \ }) -#define PERFCTR_DESC_COUNT_HW(__name, __id, __en) \ - PERFCTR_DESC(__name, __id, PERF_TYPE_HARDWARE, __en) +#define PERFCTR_DESC_COUNT_HW(__name, __id) \ + PERFCTR_DESC(__name, __id, PERF_TYPE_HARDWARE) /* Initial set of supported counters to be enabled based on provided event names */ static const struct perfctr_desc perfctr_generic_lt[] = { - PERFCTR_DESC_COUNT_HW("cpu_cycles", PERF_COUNT_HW_CPU_CYCLES, 1), - PERFCTR_DESC_COUNT_HW("inst_retired", PERF_COUNT_HW_INSTRUCTIONS, 0), - PERFCTR_DESC_COUNT_HW("cache_references", PERF_COUNT_HW_CACHE_REFERENCES, 0), - PERFCTR_DESC_COUNT_HW("cache_misses", PERF_COUNT_HW_CACHE_MISSES, 0), - PERFCTR_DESC_COUNT_HW("branch_retired", PERF_COUNT_HW_BRANCH_INSTRUCTIONS, 0), - PERFCTR_DESC_COUNT_HW("branch_mispred", PERF_COUNT_HW_BRANCH_MISSES, 0), - PERFCTR_DESC_COUNT_HW("bus_cycles", PERF_COUNT_HW_BUS_CYCLES, 0), - PERFCTR_DESC_COUNT_HW("stall_frontend", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, 0), - PERFCTR_DESC_COUNT_HW("stall_backend", PERF_COUNT_HW_STALLED_CYCLES_BACKEND, 0), + PERFCTR_DESC_COUNT_HW("cpu_cycles", PERF_COUNT_HW_CPU_CYCLES), + PERFCTR_DESC_COUNT_HW("inst_retired", PERF_COUNT_HW_INSTRUCTIONS), + PERFCTR_DESC_COUNT_HW("cache_references", PERF_COUNT_HW_CACHE_REFERENCES), + PERFCTR_DESC_COUNT_HW("cache_misses", PERF_COUNT_HW_CACHE_MISSES), + PERFCTR_DESC_COUNT_HW("branch_retired", PERF_COUNT_HW_BRANCH_INSTRUCTIONS), + PERFCTR_DESC_COUNT_HW("branch_mispred", PERF_COUNT_HW_BRANCH_MISSES), + PERFCTR_DESC_COUNT_HW("bus_cycles", PERF_COUNT_HW_BUS_CYCLES), + PERFCTR_DESC_COUNT_HW("stall_frontend", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND), + PERFCTR_DESC_COUNT_HW("stall_backend", PERF_COUNT_HW_STALLED_CYCLES_BACKEND), }; /** @@ -343,54 +327,17 @@ activate_failed: return -ENOMEM; } -/* Lookup match type */ -enum perfctr_match_type { - PERFCTR_MATCH_NAME, - PERFCTR_MATCH_STATUS -}; - -struct perfctr_match { - union { - char *name; /* generic perf hw event name */ - bool status; /* enable by default */ - }; - enum perfctr_match_type type; -}; - -static int perfctr_event_activate(struct perfctr_core *perf_data, - const struct perfctr_match *match) +static inline const struct perfctr_desc +*perfctr_identify_generic_event(struct perfctr_core *perf_data, + const char *perf_event_name) { - int result = -EINVAL; int i; - struct perf_event_attr attr = { - .size = sizeof(struct perf_event_attr), - .pinned = 1, - .disabled = 1, - }; - for (i = 0; i < ARRAY_SIZE(perfctr_generic_lt); ++i) { - switch (match->type) { - case PERFCTR_MATCH_NAME: - if (strcmp(match->name, perfctr_generic_lt[i].name)) - continue; - break; - case PERFCTR_MATCH_STATUS: - if (match->status != perfctr_generic_lt[i].default_on) - continue; - else - break; - default: - unreachable(); - } - attr.config = perfctr_generic_lt[i].id; - attr.type = perfctr_generic_lt[i].type; - - result = perfctr_event_activate_single(perf_data, &attr); - if (!result || match->type == PERFCTR_MATCH_NAME) - break; + if (!strcmp(perf_event_name, perfctr_generic_lt[i].name)) + return &perfctr_generic_lt[i]; } - return result; + return NULL; } static void perfctr_event_release_entry(struct perfctr_event_entry *entry) @@ -416,7 +363,6 @@ static void perfctr_events_release_group(struct perfctr_core *perf_data, list_del(&group->node); kfree(group); --perf_data->nr_events; - } static void perfctr_events_release(struct perfctr_core *perf_data) @@ -463,71 +409,6 @@ static void perfctr_sched_switch_probe(void *feature, bool preempt, } } -static int perfctr_register_events(struct perfctr_core *perf_data) -{ - struct perfctr_match match; - unsigned int count; - int result = 0; - - count = generic_perf_events_param_count + pmu_raw_counters_param_count; - if (count > perf_data->max_nr_events) { - pr_err("Requested more than max %d counters\n", - perf_data->max_nr_events); - return -EINVAL; - } - - count = generic_perf_events_param_count; - if (count) { - match.type = PERFCTR_MATCH_NAME; - for (; count > 0; --count) { - match.name = generic_perf_events_param[count - 1]; - result = perfctr_event_activate(perf_data, &match); - if (result) { - pr_err("Failed to activate event counter: %s\n", - match.name); - perfctr_show_supported_generic_events(); - goto done; - } - } - } - - count = pmu_raw_counters_param_count; - if (count) { - struct perf_event_attr attr = { - .size = sizeof(struct perf_event_attr), - .type = PERF_TYPE_RAW, - .pinned = 1, - .disabled = 1, - }; - - for (; count > 0; --count) { - struct perfctr_event_group *group; - bool duplicate = false; - - attr.config = pmu_raw_counters_param[count - 1]; - /* Skip duplicates */ - list_for_each_entry(group, &perf_data->events, node) { - if (group->raw_id == attr.config) { - duplicate = true; - break; - } - } - - result = duplicate ? 0 : perfctr_event_activate_single(perf_data, &attr); - if (result) { - pr_err("Failed to activate event counter: %llu\n", - attr.config); - goto done; - }; - } - } -done: - /* All or nothing ..... */ - if (result) - perfctr_events_release(perf_data); - return result; -} - static void perfctr_pmu_discover(struct perfctr_core *perf_data) { struct perf_event *event; @@ -590,6 +471,138 @@ static void perfctr_pmu_discover(struct perfctr_core *perf_data) perf_data->max_nr_events, cpumask_pr_args(cpu_possible_mask)); } +typedef int (*PROCESS_PERF_EVENT)(struct perfctr_core *perf_data, + const struct perfctr_desc *perfctr_desc); + +static int +perfctr_activate_event_counters(struct perfctr_core *perf_data, + const struct perfctr_desc *perfctr_desc) +{ + struct perf_event_attr attr = { + .type = perfctr_desc->type, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + .config = perfctr_desc->id, + }; + + return perfctr_event_activate_single(perf_data, &attr); +} + +static int +perfctr_deactivate_event_counters(struct perfctr_core *perf_data, + const struct perfctr_desc *perfctr_desc) +{ + struct perfctr_event_group *group, *n; + + list_for_each_entry_safe(group, n, &perf_data->events, node) { + u64 group_id = perfctr_desc->type == PERF_TYPE_RAW + ? group->raw_id + : group->generic_id; + + if (group_id != perfctr_desc->id) + continue; + + perfctr_events_release_group(perf_data, group); + } + return 0; +} + +/** + * perfctr_configure_events() - (De-)Activate requested perf counters + * @perf_data: Data specific to the perf counters feature + * @feature: The perf counters feature. + * @cfg: The config that needs to be (de-)activated, listing requested events + * @activate: True to activate the config. False otherwise. + */ +static int perfctr_configure_events(struct perfctr_core *perf_data, + struct feature *feature, + struct lisa_cfg *cfg, bool activate, + struct feature_param_value *rollback_to) +{ + struct feature_param_entry *param; + struct feature_param_value *val; + PROCESS_PERF_EVENT perfctr_process_events; + int ret; + + perfctr_process_events = activate ? perfctr_activate_event_counters + : perfctr_deactivate_event_counters; + + param = find_feature_param_entry_cfg(cfg, + PERFCTR_PARAM_GENERIC_PERF_EVENTS); + + list_for_each_entry(val, ¶m->list_values, node) { + const struct perfctr_desc *perfctr_desc; + const char *name = (const char *)val->data; + + if (rollback_to && val == rollback_to) + return 0; + + perfctr_desc = perfctr_identify_generic_event(perf_data, name); + if (!perfctr_desc) { + pr_err("Failed to identify event counter: %s\n", name); + perfctr_show_supported_generic_events(); + ret = -EINVAL; + goto done; + } + + ret = perfctr_process_events(perf_data, perfctr_desc); + if (ret) { + pr_err("Failed to %s event counters=%llu\n", + activate ? "activate" : "deactivate", + perfctr_desc->id); + goto done; + } + } + + param = find_feature_param_entry_cfg(cfg, PERFCTR_PARAM_PMU_RAW_EVENTS); + list_for_each_entry(val, ¶m->list_values, node) { + const struct perfctr_desc perfctr_desc = { + .id = val->value, + .type = PERF_TYPE_RAW, + + }; + + if (rollback_to && val == rollback_to) + return 0; + + ret = perfctr_process_events(perf_data, &perfctr_desc); + if (ret) { + pr_err("Failed to %s event counters: %llu\n", + activate ? "activate" : "deactivate", + perfctr_desc.id); + goto done; + } + } +done: + if (ret && !rollback_to) { + pr_err("Rolling back event counters (de-)activation.\n"); + perfctr_configure_events(perf_data, feature, cfg, !activate, val); + } + return ret; +} + +static int perfctr_enable_cfg(struct feature *feature, struct lisa_cfg *cfg) +{ + struct perfctr_core *perf_data = feature->data; + int ret; + + ret = perfctr_configure_events(perf_data, feature, cfg, true, NULL); + if (ret) + return ret; + + if (!perf_data->nr_events) + pr_warn("No counters have been activated\n"); + return 0; +} + +static int perfctr_disable_cfg(struct feature *feature, struct lisa_cfg *cfg) +{ + struct perfctr_core *perf_data = feature->data; + + return perfctr_configure_events(perf_data, feature, cfg, false, NULL); +} + static int perfctr_disable(struct feature *feature); static int perfctr_enable(struct feature *feature) @@ -615,12 +628,6 @@ static int perfctr_enable(struct feature *feature) perfctr_pmu_discover(perf_data); - if (perfctr_register_events(perf_data)) - return 1; - - if (!perf_data->nr_events) - pr_warn("No counters have been activated\n"); - return 0; } @@ -642,4 +649,12 @@ static int perfctr_disable(struct feature *feature) DEFINE_EXTENDED_TP_EVENT_FEATURE(lisa__perf_counter, TP_PROBES(TP_PROBE("sched_switch", perfctr_sched_switch_probe)), - perfctr_enable, perfctr_disable); + perfctr_enable, perfctr_disable, + perfctr_enable_cfg, perfctr_disable_cfg, + FEATURE_PARAMS( + PARAM_SET(PERFCTR_PARAM_GENERIC_PERF_EVENTS, + S_IFREG | 0666, + char *, lisa__perf_counter), + PARAM_SET(PERFCTR_PARAM_PMU_RAW_EVENTS, + S_IFREG | 0666, + unsigned int, lisa__perf_counter))); -- GitLab