diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index 661a825460cc9bc3232a038bc617fca189e0875d..066655c80adb7abe16b43fc466f17dcc8fa890bb 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 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/configs.c b/lisa/_assets/kmodules/lisa/configs.c new file mode 100644 index 0000000000000000000000000000000000000000..246b75753beaf2608fc1168e5095a40161e42093 --- /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 0000000000000000000000000000000000000000..f2909471cdb6e16cbe8fe30b38e429c637685df6 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _CONFIGS_H +#define _CONFIGS_H + +#include +#include + +#include "main.h" +#include "features.h" +#include "feature_params.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; +}; + +/** + * 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_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); +} + +static inline void free_lisa_cfg(struct lisa_cfg *cfg) +{ + activate_lisa_cfg(cfg, false, NULL); + // FIXME: should we call dput(cfg->dentry) ? + 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 0000000000000000000000000000000000000000..8193266cb37b63df54908920d73728ba8f3942d3 --- /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 0000000000000000000000000000000000000000..19c2cf1e4a7926f2338b9ad74c6f56b810514d55 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.h @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FEATURE_PARAM_H +#define _FEATURE_PARAM_H + +#include + +#include "features.h" +#include "configs.h" +#include "fs.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 { + // FIXME: remove that union and only keep void *data + 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, \ + // FIXME: the parameter should not need access to its parent feature instance. We should be able to have multiple instances alive at once. + .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); +} + +/** + * 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 // _FEATURE_PARAM_H diff --git a/lisa/_assets/kmodules/lisa/features.c b/lisa/_assets/kmodules/lisa/features.c index 01e5c2c8c93564357588e89bf9beb756cfff2922..7c4ceeac81068ba23fb12f7ce6019ae9fad7a248 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,42 +112,25 @@ 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) { - 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); } -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 enable_single_feature(char *feature_name) { + return __process_features(&feature_name, 1, __enable_feature); } -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) { diff --git a/lisa/_assets/kmodules/lisa/features.h b/lisa/_assets/kmodules/lisa/features.h index b03c5e48c0bcad9c855b2dbe0a29641dea2bb54f..7a60016df1c5b3daacee9dcc06f9e2020b4e4b87 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,17 +38,25 @@ 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; - /* 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. */ 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; }; int __enable_feature(struct feature* feature); @@ -52,43 +68,52 @@ int __disable_feature(struct feature* 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. */ #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, \ .data = NULL, \ .enabled = 0, \ - .__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__) \ }; /** @@ -102,7 +127,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. @@ -112,7 +142,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. @@ -128,7 +159,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 @@ -175,21 +206,34 @@ int __placeholder_deinit(struct feature *feature); }) /** - * 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. + * find_feature() - Find a feature by name. + * @feature_name: Name of the feature. */ -int init_features(char **selected, size_t selected_len); +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; +} /** - * deinit_features() - De-initialize features - * - * De-initialize features initialized with init_features(). - * Return: non-zero in case of errors. + * print_features() - Print available features. + */ +int print_features(void); + +/** + * enable_single_feature() - Enable one feature + * @feature_name: Name of the feature to able. + */ +int enable_single_feature(char *feature_name); + +/** + * disable_single_feature() - disable one feature + * @feature_name: Name of the feature to disable. */ -int deinit_features(void); +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 0000000000000000000000000000000000000000..c4c9ec9ebd553ce8b8ffb5fee46d81a697875681 --- /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 "fs.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/fs.h b/lisa/_assets/kmodules/lisa/fs.h new file mode 100644 index 0000000000000000000000000000000000000000..60685a89588afb86ba760f4d4bac316baf83dd93 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/fs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FS_H +#define _FS_H + +#include +void lisa_fs_remove(struct dentry *dentry); + +#endif // _FS_H diff --git a/lisa/_assets/kmodules/lisa/ftrace_events.h b/lisa/_assets/kmodules/lisa/ftrace_events.h index af991215376ea0a04cf7090dca6aa3341139f0a0..c42de41f72f5798bdd7b0510b195ad209e657aa9 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( u64, value ) + __field( u64, counter_id ) + __field( u32, cpu ) + ), + + TP_fast_assign( + __entry->cpu = cpu; + __entry->counter_id = counter_id; + __entry->value = value; + ), + + TP_printk("cpu=%u counter_id=%llu 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/main.c b/lisa/_assets/kmodules/lisa/main.c index 05ec9b2cfcb167a5af51d3aba0a428d9746caca1..eca9b632ac43aa29ccae73359034131b7cf8da66 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -14,14 +14,11 @@ static char* version = LISA_MODULE_VERSION; module_param(version, charp, 0); MODULE_PARM_DESC(version, "Module version defined as sha1sum of the module sources"); -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"); +int init_lisa_fs(void); +void exit_lisa_fs(void); static void modexit(void) { - if (deinit_features()) - pr_err("Some errors happened while unloading LISA kernel module\n"); + exit_lisa_fs(); } static int __init modinit(void) { @@ -33,6 +30,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}; @@ -40,29 +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(); - return ret; + modexit(); - } + /* Use one of the standard error code */ + return -EINVAL; } return 0; } diff --git a/lisa/_assets/kmodules/lisa/main.h b/lisa/_assets/kmodules/lisa/main.h index e3691166dfbfaeb0b0552aac271fe4625219ae25..41eb9fed3cacb3db7426c53b8f6647eb31b570e5 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 diff --git a/lisa/_assets/kmodules/lisa/perf_counters.c b/lisa/_assets/kmodules/lisa/perf_counters.c new file mode 100644 index 0000000000000000000000000000000000000000..36d3568fafcd928d5a14b13f563d36271297d489 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/perf_counters.c @@ -0,0 +1,660 @@ +// 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_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 { + /* unique name to identify the counter */ + const char *name; + /* counter id (may be generic or raw) */ + u64 id; + enum perf_type_id type; +}; + +#define PERFCTR_DESC(__name, __id, __type) \ + ((struct perfctr_desc) { \ + .name = __name, .id = __id, .type = __type, \ + }) + +#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), + 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), +}; + +/** + * 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. + * @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; + struct hlist_node group_link; + struct perf_event *event; + struct perfctr_event_group *group; + /* PMU specific event counter id */ + u64 event_id; + unsigned int cpu; + refcount_t refcount; + struct rcu_head rcu; +}; + +/** + * 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; + unsigned int generic_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 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) +{ + struct perfctr_event_entry *entry = NULL; + struct perfctr_event_group *group, *n; + struct hlist_node *next; + cpumask_var_t active_mask; + LIST_HEAD(groups); + int cpu; + + if (!zalloc_cpumask_var(&active_mask, GFP_KERNEL)) + 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); + } + + 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); + + 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); + 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); + perfctr_event_release_entry(entry); + continue; + } + + if (attr->type != PERF_TYPE_RAW) { + struct perfctr_event_group *__g; + struct arm_pmu *arm_pmu; + u64 raw_id; + + arm_pmu = to_arm_pmu(entry->event->pmu); + /* There needs to be a better way to do this !!*/ + raw_id = arm_pmu->map_event(entry->event); + + 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); + 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; + } +add_entry: + refcount_set(&entry->refcount, 1); + 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_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)); + free_cpumask_var(active_mask); + return 0; + +activate_failed: + list_for_each_entry_safe(group, n, &groups, node) { + hlist_for_each_entry_safe(entry, next, + &group->entries, group_link) { + perfctr_event_entry_put(entry); + cpumask_clear_cpu(cpu, active_mask); + } + if (hlist_empty(&group->entries)) { + 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; +} + +static inline const struct perfctr_desc +*perfctr_identify_generic_event(struct perfctr_core *perf_data, + const char *perf_event_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(perfctr_generic_lt); ++i) { + if (!strcmp(perf_event_name, perfctr_generic_lt[i].name)) + return &perfctr_generic_lt[i]; + } + return NULL; +} + +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); +} + +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_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) +{ + 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 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)); +} + +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) +{ + 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); + + 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, + 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))); diff --git a/lisa/_assets/kmodules/lisa/pixel6.c b/lisa/_assets/kmodules/lisa/pixel6.c index 00db386483596ef000d28de6c01a4390b9e62c26..b7f87a37abfdf4d5868ea87a54c751e43cb3f436 100644 --- a/lisa/_assets/kmodules/lisa/pixel6.c +++ b/lisa/_assets/kmodules/lisa/pixel6.c @@ -269,4 +269,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 15c5210da5a861fc05896878aa6fcc05a348bd53..ae78528e584ca9ac47e6ac4374f81104fe3e7df0 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)) /** diff --git a/lisa/_cli_tools/lisa_load_kmod.py b/lisa/_cli_tools/lisa_load_kmod.py index 0d4b8a65b60e6a4e3e0f0a97bf5a2b26c8018fd8..c58729224fbbf5d1a2a0ef39c201041f2bccde4f 100755 --- a/lisa/_cli_tools/lisa_load_kmod.py +++ b/lisa/_cli_tools/lisa_load_kmod.py @@ -72,26 +72,25 @@ 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: @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 e48f7867698dbd04dad981211f3162b343a07688..cdd1b48cac97ffb7978716bce3d3220039f982b5 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -2646,14 +2646,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 @@ -2677,9 +2672,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 @@ -2708,15 +2703,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: @@ -2731,7 +2717,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) @@ -2743,18 +2729,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( + subprocess.CalledProcessError, + DmesgCollector(target, output_path=dmesg_out.name) + ) + + try: + with dmesg_coll as dmesg_coll: + target.execute(f'echo 1 > {cfg_path / "activate"}') + + except subprocess.CalledProcessError 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): """ @@ -2929,6 +3021,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( @@ -2937,7 +3037,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 @@ -2961,17 +3061,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( @@ -2990,7 +3086,7 @@ class LISADynamicKmod(FtraceDynamicKmod): yield kmod_path try: - ret = self._install(kmod_cm(), kmod_params=kmod_params) + ret = self._install(kmod_cm()) except (subprocess.CalledProcessError, KmodVersionError) as e: ret = preinstalled_broken(e) else: diff --git a/lisa/trace.py b/lisa/trace.py index 20bea0f8d259b5d4c4c656936d6033f29ae639dd..d7392abd5fc9bd268baab7ce675dfc78359c4343 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -7441,7 +7441,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'): @@ -7531,8 +7531,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. @@ -7541,10 +7550,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: @@ -7575,6 +7585,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 @@ -7640,7 +7651,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) @@ -7655,25 +7666,39 @@ 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, functools.partial( kmod.run, - 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() diff --git a/lisa/wa/plugins/_kmod.py b/lisa/wa/plugins/_kmod.py index 3cac60b23d42a8eca59facf648713efda3ee6a40..a7aa2e30e09a9902b7a4b30180d0e563ac81a490 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): diff --git a/tools/kmodules/lisa/perf_counters.c b/tools/kmodules/lisa/perf_counters.c new file mode 120000 index 0000000000000000000000000000000000000000..8bc3d0ed1c5d78e3f6f4cd6de746b9397f810415 --- /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