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..99499fb174 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include + +#include "configs.h" +#include "feature_params.h" + +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; +} + +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); +} + +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(struct hlist_head *cfg_list, 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..cb2f1fb42f --- /dev/null +++ b/lisa/_assets/kmodules/lisa/configs.h @@ -0,0 +1,30 @@ +/* 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; +}; + +struct lisa_cfg *allocate_lisa_cfg(const char *name); +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(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 new file mode 100644 index 0000000000..f8d29894ec --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.c @@ -0,0 +1,441 @@ +/* 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); + val->entry->param->ops->free_value(val); + 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 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) +{ + 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 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) +{ + 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, + .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_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 new file mode 100644 index 0000000000..26324eb2a3 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/feature_params.h @@ -0,0 +1,184 @@ +/* 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 *); + 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 *); +}; + +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..b8b58c7b24 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. */ @@ -60,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, \ @@ -74,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, \ @@ -86,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__) \ }; /** @@ -99,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. @@ -109,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. @@ -125,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 @@ -184,6 +198,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 +213,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..ec67c9be2c --- /dev/null +++ b/lisa/_assets/kmodules/lisa/fs.c @@ -0,0 +1,668 @@ +/* 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, 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 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) +{ + 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; + + lisa_sb_lock(file->f_inode->i_sb); + ret = activate_lisa_cfg((struct lisa_cfg *)s->private, value); + 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 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_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) +{ + 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_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: + lisa_sb_unlock(file->f_inode->i_sb); + 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_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; + + 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) +{ + d_genocide(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)); + + lisa_sb_lock(inode->i_sb); + + 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); + + lisa_sb_unlock(inode->i_sb); + + 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_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_params 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 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)) + 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/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 | 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)) + 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->cfg_list); + sb->s_fs_info = lisa_info; + + root = lisa_fs_create_inode(sb, S_IFDIR | S_IRUGO); + 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 = allocate_lisa_cfg("root"); + if (!cfg) + goto error2; + + init_lisa_cfg(cfg, &lisa_info->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 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, +}; + +int init_lisa_fs(void) +{ + int ret; + + 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..150c6fb637 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -14,16 +14,35 @@ 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"); } +/* + * 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; @@ -31,6 +50,12 @@ 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; + + init_feature_param(); + 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 +70,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; 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)) /** 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 */ 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 cb0a6c57ab..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) @@ -2631,18 +2617,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 +2849,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( @@ -2800,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 @@ -2824,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( @@ -2853,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 8d3f6610f6..b1f6ca4f2f 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,25 +6116,39 @@ 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, 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 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):