From 17369911b88d23a12f6954eed910e8e6791419c5 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 17:57:12 +0100 Subject: [PATCH 1/5] lisa._assets.kmodules.lisa: Add list_count_elements() utility function Add list_count_elements() utility function. Original-author: Beata Michalska Signed-off-by: Pierre Gondois --- lisa/_assets/kmodules/lisa/utils.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lisa/_assets/kmodules/lisa/utils.h b/lisa/_assets/kmodules/lisa/utils.h index c7ab03cea1..c9776d6d9b 100644 --- a/lisa/_assets/kmodules/lisa/utils.h +++ b/lisa/_assets/kmodules/lisa/utils.h @@ -12,4 +12,15 @@ #include "linux/kernel.h" +static inline size_t list_count_elements(struct list_head *head) +{ + struct list_head *pos; + size_t count = 0; + + list_for_each (pos, head) + count++; + + return count; +} + #endif /* _UTILS_H */ From 6c3ca7cac1a0a72f135e2c8e6f4c360216e912e6 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 18:01:31 +0100 Subject: [PATCH 2/5] 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: $ mkdir /lisa $ mount -t lisa none /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. If multiple configurations are active simultaneously, the VFS merges them into one global configuration. For instance, if: - feat_0 is set in custom_conf_0 - feat_1 is set in custom_conf_1 and both configurations are active, the resulting configuration will have feat_0 and feat_1 active. Original-author: Beata Michalska Signed-off-by: Pierre Gondois --- lisa/_assets/kmodules/lisa/Makefile | 2 +- lisa/_assets/kmodules/lisa/configs.c | 168 +++++ lisa/_assets/kmodules/lisa/configs.h | 32 + lisa/_assets/kmodules/lisa/feature_params.c | 428 +++++++++++++ lisa/_assets/kmodules/lisa/feature_params.h | 183 ++++++ lisa/_assets/kmodules/lisa/features.c | 36 +- lisa/_assets/kmodules/lisa/features.h | 52 +- lisa/_assets/kmodules/lisa/fs.c | 639 ++++++++++++++++++++ lisa/_assets/kmodules/lisa/main.c | 12 + 9 files changed, 1546 insertions(+), 6 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 0b68c78431..f0377464df 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 0000000000..707d25eae9 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include + +#include "configs.h" +#include "feature_params.h" + +/* List of configs. */ +HLIST_HEAD(cfg_list); + +void lisa_fs_remove(struct dentry *dentry); + +struct lisa_cfg *allocate_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; +} + +int init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, + struct dentry *dentry) +{ + cfg->dentry = dentry; + hlist_add_head(&cfg->node, cfg_list); + return 0; +} + +void free_lisa_cfg(struct lisa_cfg *cfg) +{ + /* De-activate the config. */ + activate_lisa_cfg(cfg, false); + drain_feature_param_entry_cfg(&cfg->list_param); + + /* Remove its dentries. */ + if (cfg->dentry) + lisa_fs_remove(cfg->dentry); + + hlist_del(&cfg->node); + kfree(cfg->name); + kfree(cfg); +} + +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); +} + +struct lisa_cfg *find_lisa_cfg(const char *name) +{ + struct lisa_cfg *cfg; + hlist_for_each_entry(cfg, &cfg_list, node) { + if (!strcmp(cfg->name, name)) + return cfg; + } + return NULL; +} + +static int update_global_value(struct lisa_cfg *cfg, int new_value) +{ + struct feature_param_entry *entry, *rollback_entry; + int ret = 0; + + /* For each parameter of the config. */ + hlist_for_each_entry(entry, &cfg->list_param, node_cfg) { + if (!list_empty(&entry->list_values)) { + /* For each value of this entry. */ + if (new_value) + ret = feature_param_merge_common(entry); + else + ret = feature_param_remove_config_common(entry); + if (ret) { + rollback_entry = entry; + goto rollback; + } + } + } + + return ret; + +rollback: + hlist_for_each_entry(entry, &cfg->list_param, node_cfg) { + if (entry == rollback_entry) + break; + + if (!list_empty(&entry->list_values)) { + /* For each value of this entry. */ + if (new_value) + ret = feature_param_remove_config_common(entry); + else + ret = feature_param_merge_common(entry); + if (ret) { + pr_err("Could not rollback config values\n"); + return ret; + } + } + } + + return ret; +} + +static bool is_feature_set(char *name) +{ + struct feature_param_entry_value *val; + + /* Check whether the feature is in the global set_features list. */ + list_for_each_entry(val, &lisa_features_param.global_value, node) + if (lisa_features_param.ops->is_equal(&name, val)) + return true; + return false; +} + +int activate_lisa_cfg(struct lisa_cfg *cfg, bool value) +{ + struct feature *feature; + int ret; + + if (cfg->activated == value) + return 0; + + /* All the global values have now been updated. Time to enable them. */ + + ret = update_global_value(cfg, value); + if (ret) + return ret; + + cfg->activated = value; + + for_each_feature(feature) { + if (!is_feature_set(feature->name)) { + /* + * Feature was enabled, and de-activating this config + * disabled the feature. + */ + if (feature->__explicitly_enabled && !cfg->activated) + deinit_single_features(feature->name); + continue; + } + + if (cfg->activated) { + /* + * Feature was enabled. By default, de-init before re-init the feature + * to catch potential modifications. + */ + if (feature->__explicitly_enabled) + deinit_single_features(feature->name); + init_single_feature(feature->name); + continue; + } + } + + return 0; +} \ No newline at end of file diff --git a/lisa/_assets/kmodules/lisa/configs.h b/lisa/_assets/kmodules/lisa/configs.h new file mode 100644 index 0000000000..16c27bd09c --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _CONFIGS_H +#define _CONFIGS_H + +#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; +}; + +extern struct hlist_head cfg_list; + +struct lisa_cfg *allocate_lisa_cfg(const char *name); +int init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, + struct dentry *dentry); +void free_lisa_cfg(struct lisa_cfg *cfg); +void drain_lisa_cfg(struct hlist_head *head); +struct lisa_cfg *find_lisa_cfg(const char *name); +int activate_lisa_cfg(struct lisa_cfg *cfg, bool value); + +#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 0000000000..da29361b95 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.c @@ -0,0 +1,428 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include "features.h" + +struct feature_param_entry_value *allocate_feature_param_entry_value(void) +{ + struct feature_param_entry_value *val; + + val = kmalloc(sizeof(*val), GFP_KERNEL); + if (!val) + return NULL; + + INIT_LIST_HEAD(&val->node); + return val; +} + +void init_feature_param_entry_value(struct feature_param_entry_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; +} + +void init_feature_param_entry_value_global(struct feature_param_entry_value *val, + struct feature_param_entry *entry, + struct list_head *head) +{ + refcount_set(&val->refcnt, 1); + list_add_tail(&val->node, head); + val->entry = entry; +} + +void free_feature_param_entry_value(struct feature_param_entry_value *val) +{ + list_del(&val->node); + kfree(val); +} + +void drain_feature_param_entry_value(struct list_head *head) +{ + struct feature_param_entry_value *val, *tmp; + + list_for_each_entry_safe(val, tmp, head, node) + free_feature_param_entry_value(val); +} + +struct feature_param_entry *allocate_feature_param_entry(void) +{ + struct feature_param_entry *entry; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + return entry; +} + +void init_feature_param_entry(struct feature_param_entry *entry, + struct lisa_cfg *cfg, struct feature_param *param) +{ + entry->param = param; + entry->cfg = cfg; + + INIT_LIST_HEAD(&entry->list_values); + hlist_add_head(&entry->node, ¶m->param_args); + hlist_add_head(&entry->node_cfg, &cfg->list_param); +} + +void free_feature_param_entry(struct feature_param_entry *entry) +{ + drain_feature_param_entry_value(&entry->list_values); + hlist_del(&entry->node); + hlist_del(&entry->node_cfg); + kfree(entry); +} + +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); +} + +int feature_param_add_new(struct feature_param_entry *entry, const char *v) +{ + struct feature_param *param = entry->param; + struct feature_param_entry_value *val; + int ret = 0; + + val = param->ops->set(v, entry); + if (IS_ERR_OR_NULL(val)) + return IS_ERR(val) ? PTR_ERR(val) : -EINVAL; + + if (param->validate) { + ret = param->validate(val); + if (ret) + goto error; + } + + init_feature_param_entry_value(val, entry); + + return ret; + +error: + free_feature_param_entry_value(val); + return ret; +} + +int feature_param_merge_common(struct feature_param_entry *added_entry) +{ + struct feature_param_entry_value *added_val, *merged_val, *new_val; + struct feature_param *param = added_entry->param; + struct list_head *head; + int ret = 0; + + /* Should have been checked already. */ + if (list_empty(&added_entry->list_values)) + return -EINVAL; + + head = ¶m->global_value; + + switch (param->mode) { + case FT_PARAM_MODE_SINGLE: + added_val = list_first_entry(&added_entry->list_values, + struct feature_param_entry_value, node); + + if (list_empty(head)) { + /* No global value set yet. Allocate the single value. */ + + new_val = allocate_feature_param_entry_value(); + if (!new_val) { + ret = -ENOMEM; + break; + } + + init_feature_param_entry_value_global( + new_val, added_val->entry, head); + ret = param->ops->copy(added_val, new_val); + if (ret) + goto free; + + break; + } + + /* Otherwise check added_val has the same value as the global config. */ + + merged_val = list_first_entry( + head, struct feature_param_entry_value, node); + if (!param->ops->is_equal(&added_val->data, merged_val)) { + pr_err("Single value must be set across configs for %s\n", + added_entry->param->name); + feature_param_entry_print(param, added_val); + feature_param_entry_print(param, merged_val); + ret = -EEXIST; + goto error; + } + + break; + + case FT_PARAM_MODE_SET: + list_for_each_entry(added_val, &added_entry->list_values, node) { + bool found = false; + + /* Check the value doesn't already exist. */ + list_for_each_entry(merged_val, head, node) { + if (param->ops->is_equal(&added_val->data, merged_val)) { + /* If the value exists, increase the refcnt. */ + refcount_inc(&merged_val->refcnt); + found = true; + break; + } + } + if (found) + continue; + + /* Else allocate a new value */ + new_val = allocate_feature_param_entry_value(); + if (!new_val) { + ret = -ENOMEM; + break; + } + + init_feature_param_entry_value_global( + new_val, added_val->entry, head); + ret = param->ops->copy(added_val, new_val); + if (ret) + goto free; + } + + break; + + default: + ret = -EINVAL; + break; + } + + return ret; + +free: + free_feature_param_entry_value(new_val); +error: + return ret; +} + +int feature_param_remove_config_common(struct feature_param_entry *removed_entry) +{ + struct feature_param_entry_value *removed_val, *merged_val; + struct feature_param *param = removed_entry->param; + struct list_head *head; + int ret = 0; + + /* Should have been checked already. */ + if (list_empty(&removed_entry->list_values)) + return -EINVAL; + + head = ¶m->global_value; + + list_for_each_entry(removed_val, &removed_entry->list_values, node) { + bool found = false; + + /* Check for an existing value. */ + list_for_each_entry(merged_val, head, node) { + if (!param->ops->is_equal(&removed_val->data, merged_val)) + continue; + + found = true; + + /* This was the last reference. Free. */ + if (refcount_dec_and_test(&merged_val->refcnt)) { + free_feature_param_entry_value(merged_val); + break; + } + } + + if (!found) { + pr_err("Value not found while deactivating config.\n"); + feature_param_entry_print(param, removed_val); + ret = -EINVAL; + break; + } + } + + return ret; +} + +///////////////////////////////////// +// lisa_features_param features +///////////////////////////////////// + +int feature_param_lisa_validate(struct feature_param_entry_value *val) +{ + struct feature *feature; + + for_each_feature(feature) { + if (!strcmp(feature->name, val->data)) + return 0; + } + return -EINVAL; +} + +/* Handle feature names using the (struct feature_param) logic. */ +struct feature_param lisa_features_param = { + .name = "lisa_features_param", + .mode = FT_PARAM_MODE_SET, + .type = FT_PARAM_TYPE_AVAILABLE_FT, + .perms = S_IFREG | S_IRUGO | S_IWUGO, + .ops = &feature_param_ops_string, + .validate = feature_param_lisa_validate, + .param_args = HLIST_HEAD_INIT, + .global_value = LIST_HEAD_INIT(lisa_features_param.global_value), +}; + +///////////////////////////////////// +// feature_param type handlers +///////////////////////////////////// + +static int +feature_param_set_common(struct feature_param_entry *entry, void *data) +{ + struct feature_param_entry_value *val; + int ret = 0; + + switch (entry->param->mode) { + case FT_PARAM_MODE_SINGLE: + /* Single parameter, replace the pre-existing value. */ + /* + * TODO This might not be a good idea. The value is replaced + * even when the user thinks the value is appended. + * I.e. 'echo 1 >> file' will replace the pre-existing value. + */ + val = list_first_entry(&entry->list_values, + struct feature_param_entry_value, node); + free_feature_param_entry_value(val); + break; + case FT_PARAM_MODE_SET: + /* Don't allow duplicated values. */ + list_for_each_entry(val, &entry->list_values, node) + if (entry->param->ops->is_equal(data, val)) { + pr_err("Value already set.\n"); + ret = -EEXIST; + break; + } + break; + default: + ret = -EINVAL; + break; + } + + return 0; +} + +struct feature_param_entry_value * +feature_param_set_uint(const char *buf, struct feature_param_entry *entry) +{ + struct feature_param_entry_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); + + if (list_empty(&entry->list_values)) + goto new_val; + + ret = feature_param_set_common(entry, &input_val); + if (ret) + return ERR_PTR(ret); + +new_val: + val = allocate_feature_param_entry_value(); + if (!val) + return ERR_PTR(-ENOMEM); + + val->value = input_val; + return val; +} + +static size_t +feature_param_stringify_uint(const struct feature_param_entry_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_entry_value *val) +{ + return *(unsigned int *)data == val->value; +} + +static int +feature_param_copy_uint(const struct feature_param_entry_value *src_val, + struct feature_param_entry_value *val) +{ + val->value = src_val->value; + return 0; +} + +static struct feature_param_entry_value * +feature_param_set_string(const char *buf, struct feature_param_entry *entry) +{ + struct feature_param_entry_value *val; + int ret; + + if (!buf) + return ERR_PTR(-EINVAL); + + if (list_empty(&entry->list_values)) + goto new_val; + + ret = feature_param_set_common(entry, &buf); + if (ret) + return ERR_PTR(ret); + +new_val: + val = allocate_feature_param_entry_value(); + if (!val) + return ERR_PTR(-ENOMEM); + + val->data = kstrdup(buf, GFP_KERNEL); + return val; +} + +static size_t +feature_param_stringify_string(const struct feature_param_entry_value *val, + char *buf) +{ + size_t size = strlen(val->data); + if (buf) + memcpy(buf, val->data, size); + return size; +} + +static int +feature_param_is_equal_string(const void *data, + const struct feature_param_entry_value *val) +{ + return !strcmp(*(char **)data, val->data); +} + +static int +feature_param_copy_string(const struct feature_param_entry_value *src_val, + struct feature_param_entry_value *val) +{ + val->data = kstrdup(src_val->data, GFP_KERNEL); + return 0; +} + +const struct feature_param_ops feature_param_ops_uint = { + .set = feature_param_set_uint, + .stringify = feature_param_stringify_uint, + .is_equal = feature_param_is_equal_uint, + .copy = feature_param_copy_uint, +}; + +const struct feature_param_ops feature_param_ops_string = { + .set = feature_param_set_string, + .stringify = feature_param_stringify_string, + .is_equal = feature_param_is_equal_string, + .copy = feature_param_copy_string, +}; diff --git a/lisa/_assets/kmodules/lisa/feature_params.h b/lisa/_assets/kmodules/lisa/feature_params.h new file mode 100644 index 0000000000..900686774e --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FEATURE__PARAM_H +#define _FEATURE__PARAM_H + +#include + +#include "configs.h" +#include "features.h" +#include "main.h" +#include "tp.h" + +/* + * Struct containing one single data value. + * E.g., if the [0, 1, 2] pmu_raw_counters are set, + * each value is stored in a (struct feature_param_entry_value). + */ +struct feature_param_entry_value { + /* + * Member of a either: + * - (struct feature_param_entry)->list_values + * - (struct feature_param)->global_value + */ + struct list_head node; + + /* Parent entry. */ + struct feature_param_entry *entry; + + /* + * Refcount of the struct. + * Only meaningful for the (struct feature_param)->global_value, when values + * of multiple configs are merged. + */ + refcount_t refcnt; + + union { + unsigned int value; + void *data; + }; +}; + +/* + * Struct containing a list of values. + * E.g., if the [0, 1, 2] pmu_raw_counters are set for a 'config1', + * and [2, 3, 4] are set for a 'config2', each set of values will be + * referenced by 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_entry_value)->node. */ + struct list_head list_values; + + /* Parent param. */ + struct feature_param *param; + + /* Parent cfg. */ + struct lisa_cfg *cfg; +}; + +enum feature_param_mode { + /* + * Among all configs, at most one value is allowed. + * I.e. for all the configs where a value is set, + * this value must be the same. + */ + FT_PARAM_MODE_SINGLE = 0, + /* + * Merge values of all configs by creating a set. + * E.g. pmu_raw_counters can have different counters enabled in + * different configs. The resulting value is a set of all the + * values of the different configs. + */ + FT_PARAM_MODE_SET = 1, +}; + +enum feature_param_type { + /* Standard parameter. */ + FT_PARAM_TYPE_STD = 0, + /* Specific to the 'lisa_features_param' parameter handling. */ + FT_PARAM_TYPE_AVAILABLE_FT, +}; + +struct feature_param { + const char *name; + enum feature_param_mode mode; + enum feature_param_type type; + struct dentry *dentry; + umode_t perms; + const struct feature_param_ops *ops; + int (*validate)(struct feature_param_entry_value *); + + /* List of (struct feature_param_entry)->node. */ + struct hlist_head param_args; + + /* List of (struct feature_param_entry_value)->node. */ + struct list_head global_value; + + /* Parent feature. */ + struct feature *feature; +}; + +struct feature_param_ops { + struct feature_param_entry_value *(*set) (const char *, struct feature_param_entry *); + size_t (*stringify) (const struct feature_param_entry_value *, char *); + int (*is_equal) (const void *, const struct feature_param_entry_value *); + int (*copy) (const struct feature_param_entry_value *, struct feature_param_entry_value *); +}; + +extern struct feature_param lisa_features_param; +extern const struct feature_param_ops feature_param_ops_uint; +extern const struct feature_param_ops feature_param_ops_string; + +#define GET_PARAM_HANDLER(type) \ + __builtin_choose_expr( \ + __builtin_types_compatible_p(type, char *), \ + &feature_param_ops_string, \ + __builtin_choose_expr( \ + __builtin_types_compatible_p(type, unsigned int), \ + &feature_param_ops_uint, NULL)) + +#define __PARAM(__name, __mode, __type, __perms, __param_type, __feature) \ + (&(struct feature_param) { \ + .name = __name, \ + .mode = __mode, \ + .type = __type, \ + .perms = __perms, \ + .ops = GET_PARAM_HANDLER(__param_type), \ + .param_args = HLIST_HEAD_INIT, \ + .feature = &__FEATURE_NAME(__feature), \ + }) + +#define PARAM_SINGLE(name, perms, param_type, feature) \ + __PARAM(name, FT_PARAM_MODE_SINGLE, FT_PARAM_TYPE_STD, perms, param_type, __EVENT_FEATURE(feature)) +#define PARAM_SET(name, perms, param_type, feature) \ + __PARAM(name, FT_PARAM_MODE_SET, FT_PARAM_TYPE_STD, perms, param_type, __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) + +#define feature_param_entry_print(param, val) { \ + bool success = false; \ + if (param->ops->stringify) { \ + size_t size = param->ops->stringify(val, NULL); \ + char *buf = kmalloc(size +1, GFP_KERNEL); \ + if (buf) { \ + buf[size] = '\0'; \ + size = param->ops->stringify(val, buf); \ + pr_err("Value: %s\n", buf); \ + kfree(buf); \ + success = true; \ + } \ + } \ + if (!success) \ + pr_err("Value: failed to print\n"); \ +} + +struct feature_param_entry_value *allocate_feature_param_entry_value(void); +void init_feature_param_entry_value(struct feature_param_entry_value *val, struct feature_param_entry *entry); +void free_feature_param_entry_value(struct feature_param_entry_value *val); +void drain_feature_param_entry_value(struct list_head *head); + +struct feature_param_entry *allocate_feature_param_entry(void); +void init_feature_param_entry(struct feature_param_entry *entry, struct lisa_cfg *cfg, struct feature_param *param); +void free_feature_param_entry(struct feature_param_entry *entry); +void drain_feature_param_entry_cfg(struct hlist_head *head); + +int feature_param_add_new(struct feature_param_entry *entry, const char *v); +int feature_param_merge_common(struct feature_param_entry *added_entry); +int feature_param_remove_config_common(struct feature_param_entry *removed_entry); + +#endif diff --git a/lisa/_assets/kmodules/lisa/features.c b/lisa/_assets/kmodules/lisa/features.c index 01e5c2c8c9..308b991599 100644 --- a/lisa/_assets/kmodules/lisa/features.c +++ b/lisa/_assets/kmodules/lisa/features.c @@ -90,7 +90,6 @@ static int __process_features(char **selected, size_t selected_len, feature_proc return ret; } - static int __list_feature(struct feature* feature) { if (!feature->__internal) pr_info(" %s", feature->name); @@ -109,7 +108,16 @@ int init_features(char **selected, size_t selected_len) { pr_info("Available features:"); __process_features(NULL, 0, __list_feature); - return __process_features(selected, selected_len, __enable_feature_explicitly); + + // TODO: features are now only initialized if the event is requested. + // __process_features(selected, selected_len, __enable_feature_explicitly); + + return 0; +} + +int init_single_feature(char *selected) +{ + return __process_features(&selected, 1, __enable_feature_explicitly); } static int __disable_explicitly_enabled_feature(struct feature* feature) { @@ -125,6 +133,10 @@ static int __disable_explicitly_enabled_feature(struct feature* feature) { return ret; } +int deinit_single_features(char *selected) { + return __process_features(&selected, 1, __disable_explicitly_enabled_feature); +} + int deinit_features(void) { return __process_features(NULL, 0, __disable_explicitly_enabled_feature); } @@ -137,3 +149,23 @@ int __placeholder_init(struct feature *feature) { int __placeholder_deinit(struct feature *feature) { return 0; } + +struct feature *find_feature(char *name) +{ + struct feature *feature; + + for_each_feature(feature) + if (!strcmp(name, feature->name)) + return feature; + return NULL; +} + +struct feature_param *find_feature_param(char *name, struct feature *feature) +{ + struct feature_param *param = NULL, **pparam; + + for_each_feature_param(param, pparam, feature) + if (!strcmp(name, param->name)) + break; + return param; +} diff --git a/lisa/_assets/kmodules/lisa/features.h b/lisa/_assets/kmodules/lisa/features.h index 31e322f41f..96f57493d8 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,7 @@ * @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. + * @params: Array of pointer 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 @@ -41,6 +45,9 @@ struct feature { * advantage of reference counting to ensure safe setup/teardown. */ bool __internal; + + /* Array of pointer to this feature's parameters. */ + struct feature_param **params; }; /* Start and stop address of the ELF section containing the struct feature @@ -49,6 +56,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. */ @@ -184,6 +194,14 @@ int __placeholder_deinit(struct feature *feature); */ int init_features(char **selected, size_t selected_len); +/** + * init_single_feature() - Initialize one feature + * @selected: Name of the feature to initialize. + * + * Cf. init_features() + */ +int init_single_feature(char *selected); + /** * deinit_features() - De-initialize features * @@ -191,4 +209,32 @@ int init_features(char **selected, size_t selected_len); * Return: non-zero in case of errors. */ int deinit_features(void); + +/** + * deinit_single_features() - De-initialize one feature + * + * Cf. deinit_features() + */ +int deinit_single_features(char *selected); + +/** + * find_feature() - Find the (struct feature) matching the input name. + * @name: Name of the feature to find. + * + * Return: (struct feature*) matching the input name if success. + * NULL otherwise. + */ +struct feature *find_feature(char *name); + +/** + * find_feature_param() - Find the (struct feature_param) of a feature + * matching the input name. + * @name: Name of the feature to find. + * @feature: Feature to search the (struct feature_param) from. + * + * Return: (struct feature_param*) matching the input name if success. + * NULL otherwise. + */ +struct feature_param *find_feature_param(char *name, struct feature *feature); + #endif diff --git a/lisa/_assets/kmodules/lisa/fs.c b/lisa/_assets/kmodules/lisa/fs.c new file mode 100644 index 0000000000..31edb80a0f --- /dev/null +++ b/lisa/_assets/kmodules/lisa/fs.c @@ -0,0 +1,639 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include +#include +#include + +#include "features.h" +#include "configs.h" + +static int lisa_fs_create_files(struct dentry *dentry, bool is_top_level, 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 + +/* Protect the interface. */ +static struct mutex interface_lock; + +static struct inode *lisa_fs_create_inode(struct super_block *sb, int mode) +{ + struct inode *inode = new_inode(sb); + + if (inode) { + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = current_time(inode); + } + + 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 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; + + mutex_lock(&interface_lock); + ret = activate_lisa_cfg((struct lisa_cfg *)s->private, value); + mutex_unlock(&interface_lock); + + 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 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; + + mutex_lock(&interface_lock); + + 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_entry_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_entry_value, node); + + if (param->ops->stringify) { + size_t size = param->ops->stringify(val, NULL); + char *buf = kmalloc(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) +{ + mutex_unlock(&interface_lock); +} + +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; + } + + mutex_lock(&interface_lock); + + if (!(file->f_flags & O_APPEND)) + drain_feature_param_entry_value(&entry->list_values); + + kbuf = kmalloc(MAX_BUF_SIZE, GFP_KERNEL); + if (!kbuf) { + done = -ENOMEM; + goto leave; + } + + 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)) { + kfree(kbuf); + goto done; + } + 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'; + + ret = feature_param_add_new(entry, s); + if (ret) { + done = ret; + goto done; + } + + if (ppos) + *ppos += 1; + next: + s = sep; + } while (s); + } +done: + kfree(kbuf); +leave: + mutex_unlock(&interface_lock); + return done; +} + +int lisa_param_feature_release(struct inode *inode, struct file *file) +{ + return file->f_mode & FMODE_READ ? seq_release_private(inode, file) : 0; +} + +static 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, +}; + +///////////////////////////////////// +// configs +///////////////////////////////////// + +/* TODO: + * linux: + * int (*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *, + * umode_t); + * android: + * int (*mkdir) (struct user_namespace *, struct inode *,struct dentry *, + * umode_t); + */ + +static int lisa_fs_syscall_mkdir(struct mnt_idmap *idmap, + struct inode *inode, struct dentry *dentry, + umode_t mode) +{ + struct dentry *my_dentry; + struct lisa_cfg *cfg; + int ret; + + cfg = allocate_lisa_cfg(dentry->d_name.name); + if (!cfg) + return -ENOMEM; + + mutex_lock(&interface_lock); + + 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; + } + + ret = init_lisa_cfg(cfg, &cfg_list, my_dentry); + if (ret) { + ret = -ENOMEM; + goto error; + } + + lisa_fs_create_files(my_dentry, false, cfg); + mutex_unlock(&interface_lock); + return 0; + +error: + free_lisa_cfg(cfg); + mutex_unlock(&interface_lock); + return ret; +} + +void lisa_fs_remove(struct dentry *dentry) +{ + simple_recursive_removal(dentry, NULL); +} + +static int lisa_fs_syscall_rmdir(struct inode *inode, struct dentry *dentry) +{ + struct lisa_cfg *cfg; + cfg = inode->i_private; + + inode_unlock(inode); + inode_unlock(d_inode(dentry)); + + mutex_lock(&interface_lock); + + cfg = find_lisa_cfg(dentry->d_name.name); + if (!cfg) + pr_err("Failed to find config: %s\n", dentry->d_name.name); + else + free_lisa_cfg(cfg); + + mutex_unlock(&interface_lock); + + inode_lock_nested(inode, I_MUTEX_PARENT); + inode_lock(d_inode(dentry)); + + return 0; +} + +const struct inode_operations lisa_fs_dir_inode_operations = { + .lookup = simple_lookup, + .mkdir = lisa_fs_syscall_mkdir, + .rmdir = lisa_fs_syscall_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; +} + +/* Called with interface_lock */ +static int +lisa_fs_create_files(struct dentry *parent, bool is_top_level, struct lisa_cfg *cfg) +{ + struct feature_param_entry *entry; + struct feature *feature; + + entry = allocate_feature_param_entry(); + if (!entry) + return -ENOMEM; + + init_feature_param_entry(entry, cfg, &lisa_features_param); + + /* set_features: enable a feature - RW. */ + if (!lisa_fs_create_single(parent, "set_features", + NULL, + &lisa_param_feature_fops, + S_IFREG | S_IRUGO | S_IWUGO, entry)) { + free_feature_param_entry(entry); + return -ENOMEM; + } + + /* available_features: list available features - RO. */ + if (!lisa_fs_create_single(parent, "available_features", + NULL, + &lisa_available_features_fops, + S_IFREG | S_IRUGO, &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 | S_IRUGO | S_IWUGO, cfg)) + return -ENOMEM; + + /* configs: Dir containing configurations, only setup at the top level. */ + if (is_top_level) { + if (!lisa_fs_create_single(parent, "configs", + &lisa_fs_dir_inode_operations, + &simple_dir_operations, + S_IFDIR | S_IRUGO, NULL)) + return -ENOMEM; + } + + /* Create a dir for features having parameters. */ + for_each_feature(feature) { + struct feature_param *param, **pparam; + struct dentry *dentry; + + if (!feature->params) + continue; + + dentry = lisa_fs_create_single(parent, feature->name, + &simple_dir_inode_operations, + &simple_dir_operations, + S_IFDIR | S_IRUGO, 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 = allocate_feature_param_entry(); + if (!entry) + return -ENOMEM; + + init_feature_param_entry(entry, cfg, param); + + if (!lisa_fs_create_single(dentry, param->name, NULL, + &lisa_param_feature_fops, + S_IFREG | S_IRUGO, entry)) { + free_feature_param_entry(entry); + 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_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; + + root = lisa_fs_create_inode(sb, S_IFDIR | S_IRUGO); + if (!root) + return -ENOMEM; + + 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 = allocate_lisa_cfg("root"); + if (!cfg) + goto error2; + + mutex_lock(&interface_lock); + + ret = lisa_fs_create_files(sb->s_root, true, cfg); + if (ret) + goto error3; + + ret = init_lisa_cfg(cfg, &cfg_list, NULL); + if (ret) + goto error4; + + mutex_unlock(&interface_lock); + + return 0; + +error4: + free_lisa_cfg(cfg); +error3: + mutex_unlock(&interface_lock); +error2: + dput(sb->s_root); +error1: + iput(root); + + 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) +{ + drain_lisa_cfg(&cfg_list); + + /* + * Free the lisa_features_param param, + * which is not bound to any feature. + */ + drain_feature_param_entry_value(&lisa_features_param.global_value); + + /* 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, +}; + +/* + * Note: Cannot initialize global_value list of an unnamed struct in __PARAM + * using LIST_HEAD_INIT. Need to have a function to do this. + */ +void init_feature_param(void) +{ + struct feature_param *param, **pparam; + struct feature *feature; + + for_each_feature(feature) + for_each_feature_param(param, pparam, feature) + INIT_LIST_HEAD(¶m->global_value); +} + +int init_lisa_fs(void) +{ + int ret; + + init_feature_param(); + mutex_init(&interface_lock); + + ret = sysfs_create_mount_point(fs_kobj, "lisa"); + if (ret) + goto error; + + ret = register_filesystem(&lisa_fs_type); + if (ret) { + sysfs_remove_mount_point(fs_kobj, "lisa"); + goto error; + } + + return ret; + +error: + 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 05ec9b2cfc..ff4e3f1914 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -14,12 +14,17 @@ 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"); } @@ -31,6 +36,11 @@ static int __init modinit(void) { if (strcmp(version, LISA_MODULE_VERSION)) { pr_err("Lisa module version check failed. Got %s, expected %s\n", version, LISA_MODULE_VERSION); 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"); @@ -45,6 +55,8 @@ static int __init modinit(void) { if (ret) { pr_err("Some errors happened while loading LISA kernel module\n"); + exit_lisa_fs(); + /* Use one of the standard error code */ ret = -EINVAL; From aeef76d45ba736da30250a1d14dfef6d7e654498 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 18:03:31 +0100 Subject: [PATCH 3/5] 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 | 14 +++++++++----- lisa/_assets/kmodules/lisa/tp.h | 7 ++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/features.h b/lisa/_assets/kmodules/lisa/features.h index 96f57493d8..b8b58c7b24 100644 --- a/lisa/_assets/kmodules/lisa/features.h +++ b/lisa/_assets/kmodules/lisa/features.h @@ -70,7 +70,7 @@ int __placeholder_deinit(struct feature *feature); #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, \ @@ -84,7 +84,7 @@ int __placeholder_deinit(struct feature *feature); .__enable_ret = 0, \ }; -#define __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, internal) \ +#define __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, internal, ...) \ DEFINE_MUTEX(__lisa_mutex_feature_##feature_name); \ struct feature __FEATURE_NAME(feature_name) __attribute__((unused,section(".__lisa_features"))) = { \ .name = #feature_name, \ @@ -96,6 +96,7 @@ int __placeholder_deinit(struct feature *feature); .lock = &__lisa_mutex_feature_##feature_name, \ .__internal = internal, \ .__enable_ret = 0, \ + DEFINE_FEATURE_PARAMS(__VA_ARGS__) \ }; /** @@ -109,7 +110,9 @@ 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, ...) \ + __DEFINE_FEATURE_STRONG(feature_name, enable_f, disable_f, false, ##__VA_ARGS__) + /** * DEFINE_INTERNAL_FEATURE() - Same as DEFINE_FEATURE() but for internal features. @@ -119,7 +122,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, true, ##__VA_ARGS__) /** * DECLARE_FEATURE() - Declare a feature to test for its presence dynamically. @@ -135,7 +139,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/tp.h b/lisa/_assets/kmodules/lisa/tp.h index 15c5210da5..68bd1d913c 100644 --- a/lisa/_assets/kmodules/lisa/tp.h +++ b/lisa/_assets/kmodules/lisa/tp.h @@ -106,9 +106,9 @@ 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, ...) \ 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), ##__VA_ARGS__); /** * DEFINE_TP_FEATURE() - Same as DEFINE_EXTENDED_TP_FEATURE() without custom @@ -128,7 +128,8 @@ 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, ...) \ + DEFINE_EXTENDED_TP_FEATURE(__EVENT_FEATURE(event_name), probes, enable_f, disable_f, ##__VA_ARGS__) #define __DEPRECATED_EVENT_ENABLE(event_name) CONCATENATE(__enable_deprecated_feature_, __EVENT_FEATURE(event_name)) /** From 9ee25dabb738ddc754e66d429c19c2c74c845be0 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 11:34:28 +0100 Subject: [PATCH 4/5] 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/_assets/kmodules/lisa/configs.c | 11 +- lisa/_assets/kmodules/lisa/configs.h | 6 +- lisa/_assets/kmodules/lisa/feature_params.c | 13 ++ lisa/_assets/kmodules/lisa/feature_params.h | 1 + lisa/_assets/kmodules/lisa/fs.c | 163 ++++++++++++-------- lisa/_assets/kmodules/lisa/main.c | 15 ++ lisa/_kmod.py | 79 ++++++++++ lisa/trace.py | 38 ++++- 8 files changed, 243 insertions(+), 83 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/configs.c b/lisa/_assets/kmodules/lisa/configs.c index 707d25eae9..99499fb174 100644 --- a/lisa/_assets/kmodules/lisa/configs.c +++ b/lisa/_assets/kmodules/lisa/configs.c @@ -5,9 +5,6 @@ #include "configs.h" #include "feature_params.h" -/* List of configs. */ -HLIST_HEAD(cfg_list); - void lisa_fs_remove(struct dentry *dentry); struct lisa_cfg *allocate_lisa_cfg(const char *name) @@ -29,12 +26,11 @@ struct lisa_cfg *allocate_lisa_cfg(const char *name) return NULL; } -int init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, +void init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, struct dentry *dentry) { cfg->dentry = dentry; hlist_add_head(&cfg->node, cfg_list); - return 0; } void free_lisa_cfg(struct lisa_cfg *cfg) @@ -61,10 +57,11 @@ void drain_lisa_cfg(struct hlist_head *head) free_lisa_cfg(cfg); } -struct lisa_cfg *find_lisa_cfg(const char *name) +struct lisa_cfg *find_lisa_cfg(struct hlist_head *cfg_list, const char *name) { struct lisa_cfg *cfg; - hlist_for_each_entry(cfg, &cfg_list, node) { + + hlist_for_each_entry(cfg, cfg_list, node) { if (!strcmp(cfg->name, name)) return cfg; } diff --git a/lisa/_assets/kmodules/lisa/configs.h b/lisa/_assets/kmodules/lisa/configs.h index 16c27bd09c..cb2f1fb42f 100644 --- a/lisa/_assets/kmodules/lisa/configs.h +++ b/lisa/_assets/kmodules/lisa/configs.h @@ -19,14 +19,12 @@ struct lisa_cfg { char *name; }; -extern struct hlist_head cfg_list; - struct lisa_cfg *allocate_lisa_cfg(const char *name); -int init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, +void init_lisa_cfg(struct lisa_cfg *cfg, struct hlist_head *cfg_list, struct dentry *dentry); void free_lisa_cfg(struct lisa_cfg *cfg); void drain_lisa_cfg(struct hlist_head *head); -struct lisa_cfg *find_lisa_cfg(const char *name); +struct lisa_cfg *find_lisa_cfg(struct hlist_head *cfg_list, const char *name); int activate_lisa_cfg(struct lisa_cfg *cfg, bool value); #endif // _CONFIGS_H diff --git a/lisa/_assets/kmodules/lisa/feature_params.c b/lisa/_assets/kmodules/lisa/feature_params.c index da29361b95..f8d29894ec 100644 --- a/lisa/_assets/kmodules/lisa/feature_params.c +++ b/lisa/_assets/kmodules/lisa/feature_params.c @@ -35,6 +35,7 @@ void init_feature_param_entry_value_global(struct feature_param_entry_value *val void free_feature_param_entry_value(struct feature_param_entry_value *val) { list_del(&val->node); + val->entry->param->ops->free_value(val); kfree(val); } @@ -340,6 +341,11 @@ feature_param_set_uint(const char *buf, struct feature_param_entry *entry) return val; } +static void feature_param_free_value_uint(struct feature_param_entry_value *val) +{ + return; +} + static size_t feature_param_stringify_uint(const struct feature_param_entry_value *val, char *buffer) @@ -388,6 +394,11 @@ feature_param_set_string(const char *buf, struct feature_param_entry *entry) return val; } +static void feature_param_free_value_string(struct feature_param_entry_value *val) +{ + kfree(val->data); +} + static size_t feature_param_stringify_string(const struct feature_param_entry_value *val, char *buf) @@ -415,6 +426,7 @@ feature_param_copy_string(const struct feature_param_entry_value *src_val, const struct feature_param_ops feature_param_ops_uint = { .set = feature_param_set_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, @@ -422,6 +434,7 @@ const struct feature_param_ops feature_param_ops_uint = { const struct feature_param_ops feature_param_ops_string = { .set = feature_param_set_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, diff --git a/lisa/_assets/kmodules/lisa/feature_params.h b/lisa/_assets/kmodules/lisa/feature_params.h index 900686774e..26324eb2a3 100644 --- a/lisa/_assets/kmodules/lisa/feature_params.h +++ b/lisa/_assets/kmodules/lisa/feature_params.h @@ -106,6 +106,7 @@ struct feature_param { struct feature_param_ops { struct feature_param_entry_value *(*set) (const char *, struct feature_param_entry *); + void (*free_value) (struct feature_param_entry_value *); size_t (*stringify) (const struct feature_param_entry_value *, char *); int (*is_equal) (const void *, const struct feature_param_entry_value *); int (*copy) (const struct feature_param_entry_value *, struct feature_param_entry_value *); diff --git a/lisa/_assets/kmodules/lisa/fs.c b/lisa/_assets/kmodules/lisa/fs.c index 31edb80a0f..ec67c9be2c 100644 --- a/lisa/_assets/kmodules/lisa/fs.c +++ b/lisa/_assets/kmodules/lisa/fs.c @@ -10,7 +10,7 @@ #include "features.h" #include "configs.h" -static int lisa_fs_create_files(struct dentry *dentry, bool is_top_level, struct lisa_cfg *cfg); +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, @@ -19,8 +19,35 @@ lisa_fs_create_single(struct dentry *parent, const char *name, #define LISA_FS_SUPER_MAGIC 0xcdb11bc9 -/* Protect the interface. */ -static struct mutex interface_lock; +struct lisa_sb_info { + /* Protect the interface. */ + struct mutex interface_lock; + + /* List of configs. */ + struct hlist_head 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->cfg_list; +} static struct inode *lisa_fs_create_inode(struct super_block *sb, int mode) { @@ -85,9 +112,9 @@ static ssize_t lisa_activate_write(struct file *file, const char __user *buf, if (kstrtobool_from_user(buf, count, &value)) return -EINVAL; - mutex_lock(&interface_lock); + lisa_sb_lock(file->f_inode->i_sb); ret = activate_lisa_cfg((struct lisa_cfg *)s->private, value); - mutex_unlock(&interface_lock); + lisa_sb_unlock(file->f_inode->i_sb); return ret < 0 ? ret : count; } @@ -117,7 +144,7 @@ static void *lisa_param_feature_seq_start(struct seq_file *s, loff_t *pos) struct feature_param_entry *entry; void *ret; - mutex_lock(&interface_lock); + lisa_sb_lock(s->file->f_inode->i_sb); entry = *(struct feature_param_entry **)s->private; ret = seq_list_start(&entry->list_values, *pos); @@ -162,7 +189,7 @@ static void *lisa_param_feature_seq_next(struct seq_file *s, void *v, loff_t *po static void lisa_param_feature_seq_stop(struct seq_file *s, void *v) { - mutex_unlock(&interface_lock); + lisa_sb_unlock(s->file->f_inode->i_sb); } static const struct seq_operations lisa_param_feature_seq_ops = { @@ -209,7 +236,7 @@ static ssize_t lisa_param_feature_write(struct file *file, return -EBUSY; } - mutex_lock(&interface_lock); + lisa_sb_lock(file->f_inode->i_sb); if (!(file->f_flags & O_APPEND)) drain_feature_param_entry_value(&entry->list_values); @@ -270,7 +297,7 @@ static ssize_t lisa_param_feature_write(struct file *file, done: kfree(kbuf); leave: - mutex_unlock(&interface_lock); + lisa_sb_unlock(file->f_inode->i_sb); return done; } @@ -300,19 +327,23 @@ static struct file_operations lisa_param_feature_fops = { * umode_t); */ -static int lisa_fs_syscall_mkdir(struct mnt_idmap *idmap, - struct inode *inode, struct dentry *dentry, - umode_t mode) +static int lisa_fs_mkdir(struct mnt_idmap *idmap, 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 = allocate_lisa_cfg(dentry->d_name.name); if (!cfg) return -ENOMEM; - mutex_lock(&interface_lock); + lisa_sb_lock(sb); my_dentry = lisa_fs_create_single(dentry->d_parent, dentry->d_name.name, &simple_dir_inode_operations, @@ -323,44 +354,48 @@ static int lisa_fs_syscall_mkdir(struct mnt_idmap *idmap, goto error; } - ret = init_lisa_cfg(cfg, &cfg_list, my_dentry); - if (ret) { - ret = -ENOMEM; + init_lisa_cfg(cfg, cfg_list, my_dentry); + + ret = lisa_fs_create_files(my_dentry, cfg); + if (ret) goto error; - } - lisa_fs_create_files(my_dentry, false, cfg); - mutex_unlock(&interface_lock); + lisa_sb_unlock(sb); return 0; error: free_lisa_cfg(cfg); - mutex_unlock(&interface_lock); + lisa_sb_unlock(sb); return ret; } void lisa_fs_remove(struct dentry *dentry) { - simple_recursive_removal(dentry, NULL); + d_genocide(dentry); } -static int lisa_fs_syscall_rmdir(struct inode *inode, struct dentry *dentry) +static int lisa_fs_rmdir(struct inode *inode, struct dentry *dentry) { + struct hlist_head *cfg_list; + struct super_block *sb; struct lisa_cfg *cfg; + + sb = inode->i_sb; cfg = inode->i_private; inode_unlock(inode); inode_unlock(d_inode(dentry)); - mutex_lock(&interface_lock); + lisa_sb_lock(inode->i_sb); - cfg = find_lisa_cfg(dentry->d_name.name); + cfg_list = lisa_sb_get_cfg_list(sb); + cfg = find_lisa_cfg(cfg_list, dentry->d_name.name); if (!cfg) pr_err("Failed to find config: %s\n", dentry->d_name.name); else free_lisa_cfg(cfg); - mutex_unlock(&interface_lock); + lisa_sb_unlock(inode->i_sb); inode_lock_nested(inode, I_MUTEX_PARENT); inode_lock(d_inode(dentry)); @@ -370,8 +405,8 @@ static int lisa_fs_syscall_rmdir(struct inode *inode, struct dentry *dentry) const struct inode_operations lisa_fs_dir_inode_operations = { .lookup = simple_lookup, - .mkdir = lisa_fs_syscall_mkdir, - .rmdir = lisa_fs_syscall_rmdir, + .mkdir = lisa_fs_mkdir, + .rmdir = lisa_fs_rmdir, }; ///////////////////////////////////// @@ -412,9 +447,13 @@ lisa_fs_create_single(struct dentry *parent, const char *name, return dentry; } -/* Called with interface_lock */ +/* + * Note: Upon failure, the caller must call free_lisa_cfg(), which will go + * over the elements of (struct lisa_cfg)->list_params and free them. + * These elements are of the 'struct feature_param_entry' type. + */ static int -lisa_fs_create_files(struct dentry *parent, bool is_top_level, struct lisa_cfg *cfg) +lisa_fs_create_files(struct dentry *parent, struct lisa_cfg *cfg) { struct feature_param_entry *entry; struct feature *feature; @@ -429,10 +468,8 @@ lisa_fs_create_files(struct dentry *parent, bool is_top_level, struct lisa_cfg * if (!lisa_fs_create_single(parent, "set_features", NULL, &lisa_param_feature_fops, - S_IFREG | S_IRUGO | S_IWUGO, entry)) { - free_feature_param_entry(entry); + S_IFREG | S_IRUGO | S_IWUGO, entry)) return -ENOMEM; - } /* available_features: list available features - RO. */ if (!lisa_fs_create_single(parent, "available_features", @@ -448,8 +485,8 @@ lisa_fs_create_files(struct dentry *parent, bool is_top_level, struct lisa_cfg * S_IFREG | S_IRUGO | S_IWUGO, cfg)) return -ENOMEM; - /* configs: Dir containing configurations, only setup at the top level. */ - if (is_top_level) { + /* 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, @@ -484,10 +521,8 @@ lisa_fs_create_files(struct dentry *parent, bool is_top_level, struct lisa_cfg * if (!lisa_fs_create_single(dentry, param->name, NULL, &lisa_param_feature_fops, - S_IFREG | S_IRUGO, entry)) { - free_feature_param_entry(entry); + S_IFREG | S_IRUGO, entry)) return -ENOMEM; - } } } @@ -504,6 +539,7 @@ static struct super_operations lisa_super_ops = { 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; @@ -514,9 +550,17 @@ static int lisa_fs_fill_super(struct super_block *sb, struct fs_context *fc) 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->cfg_list); + sb->s_fs_info = lisa_info; + root = lisa_fs_create_inode(sb, S_IFDIR | S_IRUGO); if (!root) - return -ENOMEM; + goto error0; root->i_op = &simple_dir_inode_operations; root->i_fop = &simple_dir_operations; @@ -529,28 +573,23 @@ static int lisa_fs_fill_super(struct super_block *sb, struct fs_context *fc) if (!cfg) goto error2; - mutex_lock(&interface_lock); + init_lisa_cfg(cfg, &lisa_info->cfg_list, sb->s_root); - ret = lisa_fs_create_files(sb->s_root, true, cfg); + ret = lisa_fs_create_files(sb->s_root, cfg); if (ret) goto error3; - ret = init_lisa_cfg(cfg, &cfg_list, NULL); - if (ret) - goto error4; - - mutex_unlock(&interface_lock); - return 0; -error4: - free_lisa_cfg(cfg); error3: - mutex_unlock(&interface_lock); + free_lisa_cfg(cfg); error2: dput(sb->s_root); error1: iput(root); +error0: + kfree(lisa_info); + sb->s_fs_info = NULL; return ret; } @@ -575,7 +614,14 @@ static int lisa_init_fs_context(struct fs_context *fc) static void lisa_fs_kill_sb(struct super_block *sb) { - drain_lisa_cfg(&cfg_list); + 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 lisa_features_param param, @@ -594,27 +640,10 @@ static struct file_system_type lisa_fs_type = { .kill_sb = lisa_fs_kill_sb, }; -/* - * Note: Cannot initialize global_value list of an unnamed struct in __PARAM - * using LIST_HEAD_INIT. Need to have a function to do this. - */ -void init_feature_param(void) -{ - struct feature_param *param, **pparam; - struct feature *feature; - - for_each_feature(feature) - for_each_feature_param(param, pparam, feature) - INIT_LIST_HEAD(¶m->global_value); -} - int init_lisa_fs(void) { int ret; - init_feature_param(); - mutex_init(&interface_lock); - ret = sysfs_create_mount_point(fs_kobj, "lisa"); if (ret) goto error; diff --git a/lisa/_assets/kmodules/lisa/main.c b/lisa/_assets/kmodules/lisa/main.c index ff4e3f1914..150c6fb637 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -29,6 +29,20 @@ static void modexit(void) { pr_err("Some errors happened while unloading LISA kernel module\n"); } +/* + * Note: Cannot initialize global_value list of an unnamed struct in __PARAM + * using LIST_HEAD_INIT. Need to have a function to do this. + */ +void init_feature_param(void) +{ + struct feature_param *param, **pparam; + struct feature *feature; + + for_each_feature(feature) + for_each_feature_param(param, pparam, feature) + INIT_LIST_HEAD(¶m->global_value); +} + static int __init modinit(void) { int ret; @@ -37,6 +51,7 @@ static int __init modinit(void) { pr_err("Lisa module version check failed. Got %s, expected %s\n", version, LISA_MODULE_VERSION); return -EPROTO; + init_feature_param(); ret = init_lisa_fs(); if (ret) { pr_err("Failed to setup lisa_fs\n"); diff --git a/lisa/_kmod.py b/lisa/_kmod.py index cb0a6c57ab..a9ba4880af 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -2631,18 +2631,89 @@ def log_dmesg(coll, log): else: log_dmesg(dmesg_coll, logger.debug) + self.mount_lisa_fs() + + def mount_lisa_fs(self): + """ + Mount lisa_fs on mount_path. + """ + # TODO android: + self.lisa_fs_path = Path("/data/local/lisa") + # TODO: mainline: + # self.lisa_fs_path = Path("/lisa") + self.target.execute(f'mkdir -p {self.lisa_fs_path}') + 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}') + self.target.execute(f'rmdir {self.lisa_fs_path}') + + def setup_config(self, cfg_name=None, features=None): + """ + config is a dict: { "cfg_name": { "feature": ["asd"] } } + """ + # Create the config file + cfg_path = self.lisa_fs_path / "configs" / cfg_name + self.target.execute(f'mkdir {cfg_path}') + + # Write the config + if features: + for f in features: + self.target.execute(f'echo {f} >> {cfg_path / "set_features" }') + + if not features[f]: + continue + + for arg in features[f]: + for val in features[f][arg]: + self.target.execute(f'echo {val} >> {cfg_path / f / arg}') + + # Enable the config + self.target.execute(f'echo 1 > {cfg_path / "activate"}') + + 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): """ @@ -2792,6 +2863,14 @@ def from_target(cls, target, **kwargs): **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 8d3f6610f6..9d2192775e 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -5889,7 +5889,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'): @@ -5949,9 +5949,12 @@ def wildcard(checker): } events_checker = events_checker.map(rewrite) + events_checker = events_checker.expand_namespaces(namespaces=events_namespaces) + # Expand the wildcards after having expanded the namespaces. events_checker = events_checker.map(wildcard) + self.logger.debug(f'Will try to collect events: {events_checker}') # Select the events, after having expanded the namespaces @@ -5981,8 +5984,14 @@ def wildcard(checker): # in custom modules needed_from_kmod = kmod_available_events & events + # Create an empty config if no config was provided. + # TODO: 'perf_counter' won't work, need to provide 'lisa__perf_counter' + if not kmod_features: + kmod_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. @@ -5991,10 +6000,11 @@ def wildcard(checker): 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: @@ -6025,6 +6035,7 @@ def wildcard(checker): ) self._kmod_cm = kmod_cm + self._kmod_feat_cm = kmod_feat_cm ############################################ # Final checks after we enabled all we could @@ -6090,7 +6101,7 @@ def wildcard(checker): 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) @@ -6105,6 +6116,19 @@ def _get_kmod(cls, target, target_available_events, needed_events): 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, @@ -6113,17 +6137,21 @@ def _get_kmod(cls, target, target_available_events, needed_events): 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() From 777a78cc6bcb8f189c45925c3f89dd9e0cc81845 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Mon, 18 Dec 2023 19:13:24 +0100 Subject: [PATCH 5/5] 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. Original-author: Beata Michalska Signed-off-by: Pierre Gondois --- lisa/_cli_tools/lisa_load_kmod.py | 6 +----- lisa/_kmod.py | 32 +++++++------------------------ lisa/trace.py | 3 --- lisa/wa/plugins/_kmod.py | 6 +----- 4 files changed, 9 insertions(+), 38 deletions(-) diff --git a/lisa/_cli_tools/lisa_load_kmod.py b/lisa/_cli_tools/lisa_load_kmod.py index db914aa190..183762d010 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() if keep_loaded: @contextlib.contextmanager diff --git a/lisa/_kmod.py b/lisa/_kmod.py index a9ba4880af..746975d749 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -2534,14 +2534,9 @@ def populate(key, path): 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 @@ -2565,9 +2560,9 @@ def kmod_cm(): 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 @@ -2596,15 +2591,6 @@ def log_dmesg(coll, log): 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: @@ -2619,7 +2605,7 @@ def log_dmesg(coll, log): 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) @@ -2879,7 +2865,7 @@ def _event_features(self, events): for event in fnmatch.filter(all_events, pattern) ) - def install(self, kmod_params=None): + def install(self): target = self.target logger = self.logger @@ -2903,17 +2889,13 @@ def guess_kmod_path(): 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( @@ -2932,7 +2914,7 @@ def kmod_cm(): 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 9d2192775e..b1f6ca4f2f 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -6134,9 +6134,6 @@ def _get_kmod(cls, target, target_available_events, needed_events, kmod_features 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 547a9765d5..447c558c1a 100644 --- a/lisa/wa/plugins/_kmod.py +++ b/lisa/wa/plugins/_kmod.py @@ -221,11 +221,7 @@ def _all_ftrace_events(self, context): 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):